8. 멀티채널 Conv와 im2col - 인공지능 고급(시각) 강의 복습

안상훈·2024년 6월 5일
0

인공지능-시각

목록 보기
8/54
post-thumbnail

개요

본 블로그 포스팅은 수도권 ICT 이노베이션 스퀘어에서 진행하는 인공지능 고급-시각 강의의 CNN알고리즘 강좌 내용을 필자가 다시 복기한 내용에 관한 것입니다.


1. 3차원 합성곱

위 gif에서 수행하는 3차원 합성곱 연산을 코드로 구현하고자 한다.

import numpy as np

input_data = np.array([
    [[1, 2, 3, 0],
     [0, 1, 2, 3],
     [3, 0, 1, 2],
     [2, 3, 0, 1]],
     
    [[1, 3, 2, 0],
     [0, 1, 3, 2],
     [2, 0, 3, 1],
     [3, 2, 1, 0]],
     
    [[2, 3, 1, 0],
     [0, 2, 3, 1],
     [1, 0, 2, 3],
     [3, 1, 0, 2]]
])

kernel = np.array([
    [[1, -1, 1],
     [0, 1, -1],
     [-1, 0, 1]],
     
    [[-1, 1, 0],
     [1, -1, 0],
     [0, 1, -1]],
     
    [[0, -1, 1],
     [1, 0, -1],
     [-1, 1, 0]]
])

먼저 gif의 input_datakernel을 구현한 뒤

#합성곱 연산
for y in range(out_data.shape[0]): #높이
    for x in range(out_data.shape[1]): #너비
        roi = input_data[:, y:y+kernel.shape[1], x:x+kernel.shape[2]]
        filtered = roi * kernel #3x3x3의 값이 나온 filtered

        conv_value = np.sum(filtered) #3x3x3의 모든 값을 SUM
        out_data[y, x] = np.int16(conv_value) #이게 높이x너비 만큼 반복

print("Feature Map:\n", out_data)

3차원 합성곱 연산을 위한 코드를 작성하면 된다.
이때 np.sum 메서드에 '곱'연산이 완료된 filtered만을 인자로 넣으면 filtered의 [3x3x3]에 해당하는 모든 원소합을 수행하기에
2중 for문으로 Feature Map 연산을 수행할 수 있다.


2. im2col

im2col(image to column)은 Conv연산을 수행할 때 위 3채널에 해당하는 이미지를 가지고 멀티 채널 Conv을 수행했지만,
딥러닝에서 데이터를 처리할 때는
아래의 그림처럼 Batch_size라는 항목이 하나 붙어서 4차원 데이터인 Tensor자료형을 기반으로 Conv연산을 수행한다.

이때 위 멀티 채널 Conv에서 선보인 2중 for문을 넘어선 다중 for문 구현으로 [배치사이즈, 멀티채널 Conv]을 수행해야 하기에
코드 난이도도 높아지고, 다중 for문으로 인한 연산속도의 저하도 심해진다.

이를 해결하기 위해 아래의 그림처럼 4차원에 속하는 필터(Kernel)입력 이미지차원 축소+행렬 형상변환(Reshape)으로
2차원 행렬 간 곱셈으로 연산을 처리하는 과정을 의미한다.

이렇게 위 사진처럼
1) 4차원 Tensor ->
2) 2차원 행렬로 전환 ->
3) 행렬 곱 수행 ->
4) 4차원 Tensor 로 전환하는 연산 과정이

다중 for문을 사용한 [배치사이즈, 멀티채널 Conv]보다 훨씬 빠르게 연산을 수행하기에 위 함수를 구현하고 이를 Conv 연산 과정에 활용한다.

이를 python 코드로 구현하면 아래의 함수와 같다

def im2col(img, kh, kw, stride=1, pad=0): #이미지, 커널사이즈(높이/너비)
    N, C, H, W = img.shape      #배치크기, 채널, 이미지 높이너비

    out_h = (H + 2*pad - kh)//stride + 1
    out_w = (W + 2*pad - kw)//stride + 1
    #stride, padding을 통해 얻은 output 이미지 크기


    pad_img = np.pad(array=img,
                     pad_width=[(0,0), (0,0), (pad, pad), (pad, pad)])
    #입력 이미지의 N, C는 건너뛰고 크기, 너비에 각각 padding을 입힌 pad_img를 만들기
    
    col = np.zeros((N, C, kh, kw, out_h, out_w)) #6차원 데이터를 일단 만든다

    for y in range(kh):
        y_max = y + stride*out_h

        for x in range(kw):
            x_max = x + stride*out_w

            col[:, :, y, x, :, :] = pad_img[:, :, y:y_max:stride,
                                                x:x_max:stride]
            #6차원 빵 데이터에 pad_img를 입력시켜준다.
            
    col = col.transpose(0,4,5, 1,2,3).reshape(N*out_h*out_w, -1)
    #N, C, kh, kw, out_h, out_w 이거를
    #N, out_h, out_w,  C, kh, kw 로 순번 바꾸기

    #그리고 순번 바꾼 데이터를 [N * out_h * out_w, C * kw * kh] 형태의 2차원으로 reshape
    #[배치 * 출력이미지, 채널 * 커널이미지] 이게 나온다.

    return col

입력 이미지의 im2col 진행 과정을 설명하자면

1) Stride, Padding 인자를 고려하여 Out Feature Map [H, W]계산
1-1) 입력 이미지에 Padding을 적용

2) 중간 연산 수행을 위해 6차원 np.array자료형 초기화

3) 2중 for문을 통해 4차원 입력 이미지를 6차원 자료형에 붙여넣기

4) 6차원 자료형의 축 재배치

5) 6차원 자료형을 2차원으로 차원 축소 및 행렬 변환
[배치 크기 * 출력이미지(높이,너비), 채널수 * 커널사이즈(높이,너비]
순으로 2차원 데이터로 변환한다.

설계한 im2col 함수에 예시 이미지 x1, x2를 삽입해보자

x1 = np.random.rand(8, 3, 5, 5) #배치사이즈, 채널, 이미지 높이너비
col1 = im2col(x1, kh=3, kw=3, stride=1, pad=0) #x1 , 커널 사이즈, stride, pad
print(col1.shape) #[배치 * 출력이미지, 채널 * 커널이미지]

x1의 [8, 3, 5, 5] 4차원 데이터는 kernel_size = [3,3]
그리고 stride=1, padding=0일 시

출력 이미지 크기 = [3,3]

im2col결과 : [8X3X3 = 72, 3X3X3 = 27]
으로 차원축소가 진행된다.



그 다음 입력 이미지와 Conv 연산을 수행할 Kernel도 4차원 Tensor 자료형이니 이를 2차원으로 형상변환을 수행한다

def fil2col(kernel):
    filter_num, channel_num, filter_height, filter_width = kernel.shape
    #커널의 개수, 이미지 채널, 커널 사이즈(높이 너비)
    col = kernel.reshape(filter_num, 
                         channel_num * filter_height * filter_width)
    #4차원 커널[N, C, H, W]를
    #2차원 [N, C*H*W]로 차원축소 및 행렬 형상 변환
    return col

위 함수에 4차원 커널을 넣어 차원 축소 과정을 알아보자

filters = np.random.rand(6, 3, 5, 5)
fil_col = fil2col(filters)
print(fil_col.shape) #[6, 3*5*5=75]

[6, 3, 5, 5] 4차원 커널이 [6, 75] 2차원으로 차원축소가 진행되었다.



입력이미지, 커널에 대한 im2col과정을 수행해주는 2개의 함수를 설계했으니 위 함수로 행렬 연산을 수행해보자

N, C, H, W = 4, 3, 5, 5
#[배치사이즈, 채널, 이미지 높이너비]
F, kh, kw, stride, pad = 6, 3, 3, 1, 1
#[커널 개수, 커널사이즈(높이너비), stride, padding]

input_data = np.random.rand(N, C, H, W)
filters = np.random.rand(F, C, kh, kw)

첫번째로 4차원의 입력이미지, 커널, 그리고 stride, padding 인자값을 결정하고

img_col = im2col(input_data, kh, kw, stride, pad)
print("im2col 결과:\n", img_col.shape)

fil_col = fil2col(filters)
print("fil2col 결과:\n", fil_col.shape)

각각 im2col을 수행한다.

위 과정으로 얻은 입력이미지, 커널을 행렬 곱연산을 수행한다.

conv_out = np.dot(img_col, fil_col.T) 
#필터를 행렬곱 해야 하니 Transpose -> (6,27)이 (27,6)이 됨

#(100, 27) X (27,6) =  (100, 6)
print("합성곱연산결과:\n", conv_out.shape)

얻어낸 Conv의 연산결과(2차원) 데이터를
[배치 사이즈, 출력 이미지(높이, 너비), 커널 개수] 순으로 4차원 자료형 변환을 진행한다.

out_h = (H + 2*pad - kh) // stride + 1
out_w = (W + 2*pad - kw) // stride + 1

conv_out = conv_out.reshape(N, out_h, out_w, F)
#합성곱의 연산 결과를 [배치 사이즈, 출력이미지(높이너비), 커널의 개수] 로 다시 차원분해

print(conv_out.shape) #(4, 5, 5, 6)
#합성곱 연산 결관는 
#[배치사이즈, 출력이미지(높이너비), 커널 개수]

이렇게 Conv의 연산에는 중간에 im2col함수가 도입되어
4차원 데이터 간 합성곱 연산을 원활하게 해주는 작업이 수행된다.

이 과정에 대해 개념을 알아가자는 차원에서 해당 코드를 작성하였다.

profile
자율차 공부중

0개의 댓글