[밑바닥 딥러닝] im2col 구현 이해하기

Jaden Kim·2021년 7월 16일
1

밑바닥 딥러닝 책의 구현 코드들을 보면서, 넘파이를 통해 데이터 다루는 스킬이 많이 부족하다는 걸 느꼈다.
im2col 구현 부분은 책에도 설명이 제대로 안 되어 있어서, 차근차근 코드를 뜯어보며 알아보자!

def im2col(input_data, filter_h, filter_w, stride=1, pad=0):

im2col의 매개변수는 총 5개이다.
(데이터 수, 채널 수, 높이, 너비)의 4차원 배열로 이루어진 input_data,
필터 높이, 필터 너비, stride, pad 순서이다.

N, C, H, W = input_data.shape
out_h = (H + 2*pad - filter_h)//stride + 1
out_w = (W + 2*pad - filter_w)//stride + 1

먼저 input_data.shape를 통해 데이터 수, 채널 수, 높이, 너비를 각각의 변수에 저장한다
또한 공식을 통해서 output의 높이, 너비를 구한다

img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))

지정한 패딩사이즈에 맞게 input_data에 패딩을 추가하고,
input_data를 행렬로 나타내기 위한 그릇인 col을 선언한다

col은 특정 데이터(N)에 대해서,
필터의 특정 채널(C)의 특정 원소(filter_h, filter_w)가
곱해지는 값들의 모음(out_h, out_w)를 나타낸다.

예를 들어, col[1, 2, 3, 4, 5, 6]은 0번째 데이터에 대해서,
필터의 2번째 채널의 [3, 4]위치의 원소가
곱해지는 값들의 모음 중 [5, 6]위치에 있는 원소를 나타낸다!

for y in range(filter_h):
    y_max = y + stride*out_h
    for x in range(filter_w):  
        x_max = x + stride*out_w
        col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]

여기서 이중 for문으로 (y, x)를 통해 (filter_h, filter_w)를 순회하게 된다.
이는 채널은 전체 채널을 포함해서,
(filter_h, filter_w)의 2차원의 필터를 순회하고 있다고 생각하면 된다
예를 들어, (y=1, x=2)는 필터의 모든 채널의 (1, 2)위치를 순회중인 것이다.

그리고 각각의 y, x에 대해 y_max, x_max를 정의한다.
이는 (y, x)에서 시작하여 stride를 통해 도달할 수 있는 끝점을 의미한다.

이제 최종적으로 이해해야 하는 것은
col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]
부분이다.
[y : y_max : stride, x : x_max : stride]는 (y, x)에서 시작하여
y방향 또는 x방향으로 각각 stride씩 증가하며 순회한다
이를 통해 필터의 (y, x) 위치의 원소가 곱해지는 input_data의 원소를 모두 순회하게 된다!

col은 6차원의 배열이고, img는 4차원의 배열이므로 broadcating이 이루어진다.
이 때, 모든 데이터의 모든 채널에 대해서(앞의 두개의 :),
필터의 (y, x)의 위치 원소가 곱해지는 부분(y:y_max:stride, x:x_max:stride)의 모든 원소값이 col[:, :, y, x, :, :]에 들어간다.
모든 (y, x)를 순회하면서 col에 값을 채우게 된다.

col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)

col을 최종적으로 2차원 배열 형태로 바꿔준다
이때, 먼저 transpose를 통해 col의 축을 변경한다.

차원은 쉽게 말해서 데이터를 분할하는 순서라고 볼 수 있다.
0번째 차원으로 가장 크게 데이터를 분할하고,
그 다음으로 1, 2, 3,.. 번째 차원 순서로 데이터를 분할해나간다

col의 4번째 차원은 (y, x) 위치의 필터 원소에 대해서
y 위치가 stride만큼 커지면서 해당 필터의 원소가 곱해지는 원소들을 나타내고,
5번째 차원은 x가 stride만큼 커지면서 곱해지는 원소들을 나타낸다.

이들이 각각 1번째, 2번째 차원으로 바뀌게 되면,
필터의 특정 원소가 곱해지는 영역에 따라 데이터가 분할된다.
최종적으로는 하위 차원에서 각각의 필터단위가 곱해지는 영역으로 데이터가 모이게 된다

reshape는 간단하게 생각해서 데이터를 형태에 맞게 쪼개는 것이라고 생각하자.
예를 들어,

>>> np.arange(24).reshape(2, 3, 4)
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]],
       [[12, 13, 14, 15],
        [16, 17, 18, 19],
        [20, 21, 22, 23]]])

와 같이, 0~23까지의 숫자가 담긴 벡터를 먼저 2등분하고, 3등분하고, 4등분해서
결과를 낸다고 생각할 수 있다.

im2col의 목적은 convolution을 행렬곱으로 변경하는 것이다.
이를 위해 입력 데이터는 필터가 적용되는 단위로 행으로 바뀌고,
각각의 필터는 각각의 열로 바뀌게 된다.

이 때 한 데이터에 대한 필터의 총 순회 횟수는 out_h * out_w, 데이터는 총 N개이므로,
입력데이터는 최종적으로 N * out_h * out_w개의 행으로 바뀌어야 한다.

앞서 우리는 필터의 각 원소가 곱해지는 데이터의 원소를 val에 넣었었다.
reshape(N\*out_h\*out_w, -1)를 통해서
최종적으로 데이터를 N*out_h*out_w등분하게 되는데
이렇게 되면 각각의 행에는 필터가 특정 순회위치에 있을 때 곱해지는 입력 데이터의 값들이 위치하게 된다!

return col

최종적으로 만들어진 col을 반환하면서 im2col의 실행이 종료된다!

0개의 댓글