0%

15. im2col의 원리

해당 시리즈는 프로그래밍 언어 중 하나인 줄리아(Julia)로 딥러닝(Deep learning)을 구현하면서 원리를 설명합니다.


이전 글에서 CNN의 원리를 살펴보고 줄리아로 구현하였다. CNN은 4차원 데이터를 다루기 때문에 시간이 매우 오래걸린다. 지난 글에서 학습했던 모델은 그나마 간단한 형태라서 학습을 진행했지만, 더 복잡하고 깊은 신경망에서는 1에폭 학습하는 데 하루 종일 학습해야 할 것이다. 이런 문제로 인해 우리는 좀 더 빠르게 연산처리를 할 수 있는 방법을 구상해야 했고, 그 결과가 im2col()col2im()이다. 이번 글에서는 CNN 모델의 학습 속도를 높여줄 im2col()의 원리를 살펴보고 줄리아로 구현할 것이다.

im2col 의 원리

im2col()은 4차원의 데이터를 2차원의 데이터로 변경해준다. 해당 설명이 im2col()의 역할 전부이지만, 차원에 익숙하지 않은 사람은 데이터가 어떻게 변화하는지 이해하기 어려울 수 있다. im2col()의 원리를 그림으로 살펴보자.

im2col

im2col()은 합성곱하기 하기 위해 입력 데이터의 형태를 살짝 변형한다. 위 그림을 보면 $5 \times 5 \times 3$ 입력 데이터 2개가 있으며, 이를 $3 \times 3 \times 3 \times 1$ 필터로 합성곱을 진행하기 위해 데이터의 차원을 변경해준다. 즉, im2col()은 입력 데이터가 사칙연산되는 것이 아니라 그냥 데이터의 형태만 바꿔주는 것이다.

위 그림을 살펴보면 입력 데이터의 차원은 옆으로 추가되며, 배치 데이터의 개수는 밑으로 추가되는 것을 확인할 수 있다. 그렇다면 이런 im2col을 어떻게 코드로 구현할 수 있을까? im2col() 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function im2col(input, filter_r,filter_c, stride, pad)

input_r, input_c,input_d,input_num = size(input)

out_r = Int(((input_r + 2*pad - filter_r) ÷ stride) + 1)
out_c = Int(((input_c + 2*pad - filter_c) ÷ stride) + 1)

img = padding(input, pad)
col = zeros(out_r, out_c,filter_r,filter_c,input_d,input_num)


for i in 1:filter_r
r_max = (i + stride *out_r) -1
for j in 1:filter_c
c_max = (j + stride *out_c) -1

col[:, :, j, i, :, :] = img[i:stride:r_max, j:stride:c_max,:,:]
end
end

위 코드에서 이해하기 어려운 개념이 등장한다. 바로 6차원 배열이다. col이라는 배열은 6차원이며, 이는 합성곱을 진행할 때 필요한 데이터들을 모두 저장한다. 참고로 6차원은 인간이 이미지화할 수 없다. 따라서 데이터가 어떻게 저장되는지 완벽하게 표현할 수는 없지만, 최선을 다하여 그림으로 표현하였다. 아래의 그림을 참고하자.

6-D array

위 그림은 im2col() 그림에서 사용했던 예시를 그대로 가져왔다. 6차원 배열에서 out_r, out_c은 결과값이다. 위 예시에서는 $3 \times 3$이며, 요소는 총 9개이다. 각 요소는 필터와 입력데이터 해당 위치의 곱들의 합이다. 이를 좀 더 편하게 계산하기 위해서 필터의 크기에 맞춰서 입력데이터들을 쪼개어 저장해두는 것이다. 즉, filter_r,filter_c는 필터의 요소에 따라서 곱해져야 하는 입력데이터들을 저장한다. 해당 배열에 데이터를 다 쪼갠 후에는 차원 변경을 통해 첫 번째 배치데이터의 첫 번째 합성곱 대상이 나올 수 있도록 수정한다. 그리고 reshape()을 사용하여 이를 $1 \times n$ 의 형태로 변환하여 반환한다.

WARNING
참고로 위 코드를 파이썬에서 구현한 것과 줄리아에서 구현한 것은 차이가 있다. 그 이유는 배열의 인덱스 순서 때문이다. 파이썬의 경우 인덱스를 가로방향을 기준으로 잡아 모양을 변형하지만, 줄리아는 인덱스를 세로로 잡는다. 다만 이는 기술적인 문제라서 계층의 결과값을 다르게 도출하지는 않는다.

im2col을 사용한 layers

CNN에서 im2col()을 사용하는 계층은 합성곱층과 풀링층이다. 사실 이전 글에서 사용했던 합성곱층과 풀링층은 사람이 이해하는 방식과 같이 인덱스를 잡아 일일이 곱하고 합한 후 할당하는 방식이었다. 이 방식은 컴퓨터가 인덱스에 따른 데이터를 계속 호출해야 하기에 느려질 수밖에 없다. 하지만 im2col()을 사용하면 한번에 데이터를 호출하여 연산을 끝낼 수 있다.

합성곱 (Convolution)

im2col()이 사용된 합성곱의 원리는 다음과 같다.

conv

먼저 입력 데이터를 im2col 연산을 통해 2차원으로 만들어준 후, 필터 또한 $1 \times n$ 형태로 펴준다. 그 다음 두 개를 행렬곱 한다. 그러면 2차원 입력데이터의 가로줄과 필터의 곱의 합이 결과값으로 도출된다. 이것이 합성곱이 완료된 결과의 요소이다. 행렬곱이 끝난 후에는 다시 4차원으로 reshape 하면 합성곱은 끝난다.

이제 위의 원리를 코드로 구현해보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function convolution2D_forward(dense ,input, filter, bias, stride, pad)

input_r, input_c,input_d,input_num = size(input)
filter_r, filter_c, filter_d, filter_num = size(filter)

out_r = Int(((input_r + 2*pad - filter_r) ÷ stride) + 1)
out_c = Int(((input_c + 2*pad - filter_c) ÷ stride) + 1)

col = im2col(input, filter_r, filter_c, stride, pad)
col_w = reshape(filter,filter_r*filter_c*filter_d,filter_num)

out = col * col_w .+ bias
temp = Array(out')
temp2 = reshape(temp,filter_num,out_r,out_c,input_num)
result = permutedims(temp2,(3,2,1,4))

dense.x = input
dense.w = filter
dense.col = col
dense.col_w = col_w

return result
end

최대값 풀링 (Max Pooling)

풀링은 입력 데이터를 축소하기 위해서 사용한다. 풀링 중에서도 최대값 풀링은 해당 범위 내에서 가장 큰 수를 결과값으로 도출한다. 이를 im2col 연산을 사용하여 계산하는 과정은 다음과 같다.

pool

풀링도 마찬가지로 먼저 입력 데이터를 im2col 연산을 통해 2차원으로 만들어준 후, 아래의 있는 다른 데이터의 행렬도 옆으로 붙여준다. 이후 역전파에서 사용하기 위해 최대값 인덱스를 뽑아 mask로 저장하고, 뽑은 최대값은 결과로 반환한다.

최대값 풀링을 줄리아로 구현하면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function Maxpooling_forward(pool, input, pool_h, pool_w, stride, pad)

input_r, input_c,input_d,input_num = size(input)

out_r = Int(1 + (input_r +2*pad - pool_h) ÷ stride)
out_c = Int(1 + (input_c +2*pad - pool_w) ÷ stride)

## 1234 순서로 인덱스를 뽑아야 한다.
col_ex = im2col(input, pool_h, pool_w, stride, pad)
col= zeros(size(col_ex));
order = reshape(Vector(1:size(col_ex)[2]),pool_w,pool_h,input_d)
count = []

for i in 1:size(order)[3]
temp = reshape(Array(order[:,:,i]'),1,:)
append!(count, temp)
end

for i in 1:size(col_ex)[2]
col[:,count[i]] = col_ex[:,i]
end

col = Array(col') #1234
coll = reshape(col, pool_h * pool_w, out_r*out_c*input_num*input_d)
arg_max = argmax(coll, dims = 1)
result = maximum(coll,dims = 1)
out = reshape(result,input_d,out_c,out_r,input_num)
out = permutedims(out,(3,2,1,4))

pool.x = input
pool.count = count
pool.mask = arg_max

return out
end

지금까지 im2col()을 사용하여 구성한 계층들을 살펴보았다. 다음 글에서는 CNN의 역전파와 그때 사용되는 col2im에 대해서 알아보자.