이번 장은 코드 없이 개념 설명 위주로, 합성곱 신경망의 구성요소인 필터, 특성 맵, 패딩, 스트라이드, 풀링에 관해 배웠습니다. 이번 장의 발표를 위해 추가로 필터와 특성 맵은 어떤 의미인지, 왜 합성곱 신경망을 사용하는지에 관해 공부한 내용을 함께 적겠습니다.
책에서는 합성곱을 이미지 위에 도장을 찍는 것으로 표현하였습니다.
이처럼 4 x 4 이미지 위에 3 x 3 도장을 찍는다면 네 번을 찍을 수 있을 것입니다. 비유적으로 표현된 도장을 찍는다는 행위는 우리가 그동안 해왔던 가중치를 곱하고 절편을 더하였던 것과 같이 이미지의 픽셀과 각 가중치 배열을 곱하는 것과 같습니다. 이때의 도장, 즉 가중치 배열은 필터라고 불립니다.
1. 필터
4 x 4 이미지 내부의 픽셀 값이 다음과 같다고 합시다.
위의 3 x 3 도장, 필터는 이런 생김새를 가지고 있을 겁니다.
이제 도장을 찍어 봅시다. 편의상 위 그림에는 절편을 넣지 않았지만, 절편이 존재한다는 사실은 잊지 맙시다.
\[x_1w_1+x_2w_2+x_3w_3+x_5w_4+x_6w_5+x_7w_6+x_9w_7+x_10w_8+x_11w_9+b\]찍힌 부분의 필터 원소와 이미지 픽셀 원소를 곱한 단순한 형태입니다. 도장을 찍을 수 있는 나머지 세 경우도 같은 방법으로 도장을 찍을 수 있습니다. 이렇게 계산한 값은 어떻게 되는 걸까요?
2. 특성 맵
위 수식의 값을 편의상 $y_1$이라 하고, 나머지 세 경우를 각 $y_2, y_3, y_4$라 합시다.
이 값들을 2차원 배열의 각 원소로 할당합니다.
이것이 바로 특성 맵입니다. 아니, 정확히는 여기에 각각 활성화 함수를 적용한 후의 값이 특성 맵이라고 합니다.
실제로 학습을 할 때는 필터를 여러 개 사용하며, 한 이미지 당 필터 개수 만큼의 특성 맵이 만들어집니다. 이때 필터의 가중치는 랜덤하게 초기화되어 시작하며, 학습되는 파라미터입니다. 저는 이미지를 잘 판별할 수 있는 어떠한 특성을 담은 가중치가 학습된다고 생각하였습니다만, 직관적으로는 잘 이해가 가지 않아 영상을 찾아보던 중 https://www.youtube.com/watch?v=YRhxdVk_sIs&t=434s에서 mnist data를 가지고 합성곱을 시각화한 것을 책에서 사용했던 fashion mnist data로 따라해 보았습니다.
영상에서 사용한 필터는 총 네 가지입니다.
1
2
3
4
5
6
import numpy as np
filter_1 = np.array([[-1,-1,-1],[1,1,1],[0,0,0]])
filter_2 = np.array([[-1,1,0],[-1,1,0],[-1,1,0]])
filter_3 = np.array([[0,0,0],[1,1,1],[-1,-1,-1]])
filter_4 = np.array([[0,1,-1],[0,1,-1],[0,1,-1]])
필터 1은 위쪽 수평 경계, 필터 2는 왼쪽 수직 경계, 필터 3은 아래쪽 수평 경계, 필터 4는 오른쪽 수직 경계를 검출합니다.
이제 데이터를 사용해 정말 그런지, 어떻게 그럴 수 있는지 확인해 봅시다.
1
2
3
4
5
6
7
8
import tensorflow as tf
from tensorflow import keras
(train_input, train_target), (test_input, test_target) = \
keras.datasets.fashion_mnist.load_data()
ex = train_input[1]
ex_scaled = ex / 255.0
패션 mnist 데이터를 불러오고 scale하는 것은 저번 시간에 이미 다 배웠습니다. 아무 숫자나 골라서 데이터 한 개를 꺼냈는데, 뭐가 나왔나 확인해 봅시다.
1
2
3
4
import matplotlib.pyplot as plt
plt.imshow(ex_scaled, cmap='gray_r')
plt.show()
티셔츠입니다. 상하좌우 경계가 뚜렷한 편이니 그대로 사용하기로 했습니다.
1
2
3
4
5
6
7
8
9
10
def conv(X, filter):
out = np.zeros((26, 26))
for h in range(26):
h_start = h
h_end = h_start + 3
for w in range(26):
w_start = w
w_end = w_start + 3
out[h, w] = np.sum(X[h_start:h_end, w_start:w_end] * filter)
return np.maximum(0, out)
학습시켜 필터를 적합한 필터를 찾는 것이 아닌 정해진 필터를 사용해야 하므로 직접 conversion을 해주는 함수를 간단하게 만들었습니다. 함수를 적용해 결과를 봅시다.
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
result_1 = conv(ex_scaled, filter_1)
result_2 = conv(ex_scaled, filter_2)
result_3 = conv(ex_scaled, filter_3)
result_4 = conv(ex_scaled, filter_4)
plt.subplot(1, 5, 1)
plt.imshow(result_1, cmap='gray')
plt.title('filter 1')
plt.subplot(1, 5, 2)
plt.imshow(result_2, cmap='gray')
plt.title('filter 2')
plt.subplot(1, 5, 3)
plt.imshow(result_3, cmap='gray')
plt.title('filter 3')
plt.subplot(1, 5, 4)
plt.imshow(result_4, cmap='gray')
plt.title('filter 4')
plt.subplot(1, 5, 5)
plt.imshow(ex_scaled, cmap='gray_r')
plt.title('original')
plt.tight_layout()
plt.show()
밝은 색 = 큰 수이니 각 필터는 검출하고자 하는 윤곽을 제외한 나머지 부분을 거의 0에 가깝게 만들고 필터가 추출하고자 하는 특성이 있는 부분을 눈에 띄게 하였습니다. 어떻게 이런 일이 가능할까 생각해 보면 사실 숫자 놀음이라는 것을 알 수 있습니다.
기본적으로 각 필터는 배경이 비슷한 값을 갖는 숫자라는 가정 하에 도장이 배경을 찍을 때는 1과 -1이 상쇄되어 0에 가까운 값이 됩니다. 이 경우에는 배경이 모두 동일하므로 완전히 0이 되었을 겁니다. 반면, 필터가 배경과 물체 사이에 있을 때는 1과 -1 사이에 균형이 깨져 0에서 멀어지게 됩니다.
이것은 아주 간단한 필터이지만, 이를 통해 필터가 어떻게 동작하는지 직관적으로 이해할 수 있었습니다.
그런데, 이렇게 획기적인 것 같은 특성 맵에는 문제가 있습니다. 조금 위로 올라가 보면, 우리는 4 x 4 이미지를 넣어 2 x 2의 결과를 얻었습니다. 또, 바로 위의 경우에도 28 x 28 이미지가 25 x 25로 축소되었습니다. 네, 이미지가 축소되고 있습니다. 이 작업을 여러 번 반복할수록 이미지가 계속해서 축소되고, 합성곱 층을 여러 번 지난 이미지는 어쩌면 1 x 1까지 축소될 수도 있습니다.
이 뿐만이 아닙니다.
위 이미지는 각 픽셀이 몇 번 도장 찍혔는지를 표시한 것입니다. 가장자리는 단 한 번만 찍혔지만, 가운데 있는 픽셀들은 4번이나 찍혔습니다. 우리는 가장자리의 데이터를 거의 사용하지 않고 있습니다.
3. 패딩
이 두 가지 문제를 한 번에 해결할 수 있는 방법이 있습니다.
이미지의 주위를 가상의 원소로 채우는 것입니다. 이때 계산에 영향이 가지 않도록 0으로 채워줍니다. 그림처럼 한 층만 가능한 것이 아니라, 여러 층을 덧씌울 수 있습니다.
이미지가 축소되는 것이 문제였으니, 이미지의 크기가 그대로 유지되는 same 패딩을 하기 위해서는 몇 개의 층을 패딩해야 할까요? 그것은 다음 이미지의 크기 n, 패딩 층의 수 p, 필터의 크기 f에 관한 식을 계산하여 얻을 수 있습니다.
\[n + 2p - f + 1 = n\]우리는 어떤 이미지에 합성곱을 하였을 때 $n + 2p - f + 1$ 크기의 배열을 얻습니다. 따라서, 그 결과가 원래 이미지의 크기였던 n과 같으면 문제가 해결되는 것입니다. 식을 정리해보면 다음을 얻습니다.
\[p = (f-1)/2\]즉, same 패딩에서 패딩을 얼마나 할지는 필터의 크기 f에 의해 결정된다는 것입니다. 책에서는 주로 3 x 3 크기의 필터와 5 x 5 크기의 필터를 사용한다고 하였으니 p = 1 or p = 2가 되겠습니다.
또한, 위의 식으로부터 한 가지 알 수 있는 사실은 필터의 크기가 짝수일 경우 p가 정수로 나누어 떨어지지 않아 same 패딩이 불가능하다는 것입니다. 아마 3 x 3 필터와 5 x 5 필터를 주로 사용하면서 4 x 4 필터는 사용하지 않는 이유는 여기에 있을 것이라고 추측됩니다.
이렇게 패딩을 하면 할수록 우리가 사용해야 할 픽셀들은 모서리에서 점점 중심부에 가까운 쪽으로 들어가게 되어 본래 모서리였던 부분과 중심부였던 부분의 사용 빈도 차이가 크게 줄어들 것입니다.
패딩은 크게 same과 valid로 나뉘는데, valid를 사용하면 그냥 패딩 없이 합성곱을 수행하는 층이 만들어지게 됩니다.
4. 스트라이드
책에서는 스트라이드를 이동의 크기라고 말했습니다. 일반적으로 앞에서 설명한 합성곱을 할 때는 도장이 한 칸씩 이동합니다. 이 때 스트라이드가 1입니다. 하지만 사실 여러 칸씩 이동할 수도, 거꾸로 이동할 수도(스트라이드가 음수), 가로와 세로의 칸 수가 다르게 이동(스트라이드를 튜플로 지정)할 수도 있습니다.
일반적이지 않은 이야기를 굳이 하는 까닭은, 이 다음에는 지금까지 배웠던 합성곱 층이 아닌 Pooling 층에 관해 말할 것이기 때문입니다.
5. 풀링
풀링은 앞서 배웠던 특성맵이 모두 완성되고 난 후에 나오는 층입니다. 특성맵의 크기를 줄여 계산 속도를 빠르게 하고, 특성을 더 잘 검출하도록 한다고 합니다.
풀링에는 도장에 가중치가 없습니다. 대신, 도장의 크기와 스트라이드가 같습니다. 이렇게 말하면 무슨 말인지 단번에 알기가 어렵지만 그냥 겹지지 않게 도장을 찍는다는 이야기와 같습니다.
가중치가 없는데 결과값을 어떻게 계산하는 걸까요? 풀링의 종류에 따라 다릅니다. 평균(Average) 풀링에서는 도장 안의 값들 전부의 평균을 결과값으로 하고, 최대(Max) 풀링에서는 최대값을 결과값으로 합니다. 더 자주 쓰이는 것은 Max 풀링입니다.
- 왜 그럴까요?
책에서는 평균 풀링이 특성 맵의 정보를 희석시킬 수 있기 때문이라고 했습니다. 조금만 더 부연설명 하자면, 합성곱 층의 어떤 필터를 거친 값이 크다는 것은 필터가 검출하고자 했던 특성을 확실하게 가지고 있다고 해석할 수 있습니다. 따라서, 우리는 큰 값에 집중해야 하며, Max 풀링은 중요한 값을 덜 잃을 수 있는 좋은 풀링 방법입니다.
풀링 층을 거친 특성 맵은 다차원 배열을 펼치는 Flatten 층을 통과해 마지막으로 Dense 층을 통과하는 것이 일반적인 합성곱 신경망 모델의 구조입니다. 그렇다면 이런 과정을 거쳐 결과를 내는 것이 저번 시간에 배웠던 간단한 신경망 모델에 비해 무슨 이점이 있을까요?
6. 왜 합성곱 신경망을 사용할까?
이 부분에 관해 꽤 많은 설명들이 있었지만, 저는 https://youtu.be/ay3zYUeuyhU에서 설명한 것이 잘 이해되었습니다.
(1) 과대적합 방지
- 파라미터 수를 크게 줄일 수 있다
밀집층을 이용할 경우 이미지의 크기가 커지거나 깊이가 깊어질수록 지나치케 많은 파라미터를 사용하게 됩니다. 각 픽셀에 관해 뉴런 개수만큼의 파라미터를 사용해야 하기 때문입니다.
하지만, 합성곱 층에서는 이미지 한 장이 겨우 3 x 3 또는 5 x 5 필터 몇 개, 또는 몇십 개를 공유하여 사용하기 때문에 사용하는 파라미터의 수가 크게 줄어듭니다.
- 오직 f x f 개의 픽셀들만이 각 결과값에 관여한다
우리는 합성곱을 계산할 때에 필터의 크기 f x f 만큼의 데이터만을 가중치와 곱하므로 전체 데이터를 계속해서 사용하는 것보다 일반적이라고 볼 수 있습니다. 또한, 전체 데이터를 사용하는 것보다 계산이 쉽기도 합니다.
(2) 픽셀 이동에 강하다
당연하게도, 합성곱 층은 픽셀 단위로 필터가 이동하기 때문에 같은 물체가 조금 더 오른쪽, 또는 왼쪽, 위쪽, 아래쪽에 가있는 픽셀 이동에 무척 강합니다. 이런 이미지가 많은 경우에 용이하게 사용할 수 있습니다.