[머신러닝] #7 합성곱 신경망 (CNN) |
"Deep Learning from Scratch” 책을 보고 제가 필요한 내용만 정리한 내용입니다.
자세한 내용은 책을 구매해서 보세요~
-
합성곱 신경망(Convolutional Neutral Network, CNN) 은 이미지 인식과 음성 인식 등 다양한 곳에 사용된다.
특히 이미지 인식 분야에서 딥러닝을 활용한 기법은 거의 다 CNN 을 기초로 한다.
7.1. 전체 구조
-
CNN 도 기존 신경망과 같이 레고 블록처럼 계층을 조합하여 만들 수 있다.
이곳에 새로운 개념이 등장한다. 합성곱 계층(convolutional layer)과 폴링 계층(pooling layer)이다.
-
지금까지 본 신경망은 인접하는 계층의 모든 뉴런과 결합되어 있었다.
이를 완전 연결(fully connected, 전결합)이라고 한다.
완전히 연결된 계층을 Affine 계층이라는 이름으로 구현했다.
완전연결 신경망은 Affine 계층 뒤에 활성화 함수를 갖는 ReLU 계층(혹은 SIgmoid 계층)이 이어진다.
-
CNN 계층은 합성곱 계층(conv) 와 풀링 계층(pooling)이 추가된다.
Conv - ReLU - (Pooling) 흐름으로 연결된다. (풀링계층은 생략되기도 한다.)
CNN 에서 주목할 또 다른 점은 출력에 가까운 층에서는 지금까지의 Affine-ReLU 구성을 사용할 수 있다는 것이고,
마지막 출력 계층에서는 Affine-Softmax 조합을 그대로 사용한다.
( Conv - ReLu - Pooling -- Conv - ReLU - Pooling -- Conv - ReLU -- Affine - ReLU -- Affine - Softmax 느낌 )
7.2. 합성곱 계층
-
CNN 에서는 패딩(padding), 스트라이드(stride) 등 CNN 고유 용어가 등장한다.
각 계층 사이에는 3차원 데이터같이 입체적인 데이터가 흐른다는 점에서 완전연결 신경망과 다르다.
7.2.1. 완전연결 계층의 문제점
-
완전연결 신경망에서는 완전연결 계층(Affine 계층)을 사용했다.
완전연결 계층에서는 인접하는 계층의 뉴런이 모두 연결되고 출력의 수는 임의로 정할 수 있다.
-
완전연결 계층의 문제점은 “데이터의 형상이 무시” 된다는 사실이다.
예를 들어 입력 데이터가 이미지인 경우, 이미지는 통상 세로, 가로, 채널(색상)로 구성된 3차원 데이터이다.
그러나 완전연결 계층에 입력할 때는 3차원 데이터를 평평한 1차원 데이터로 평탄화해줘야 한다.
이미지는 3차원 형상이며, 이 형상에는 소중한 공간적 정보가 담겨 있다.
예를 들어 공간적으로 가까운 픽셀은 값이 비슷하거나, RGB 의 각 채널은 서로 밀접하게 관련되어 있거나, 거리가 먼 픽셀끼리는 별 연관이 없는 등, 3차원 속에서 의미를 갖는 본질적인 패턴이 있다.
완전연결 계층은 형상을 무시하고 모든 입력 데이터를 동등한 뉴런(같은 차원의 뉴런)으로 취급하여 형상에 담긴 정보를 살릴 수 없다.
-
합성곱 계층은 형상을 유지한다. 이미지도 3차원 데이터로 입력받으며, 마찬가지로 다음 계층에도 3차원 데이터로 전달한다.
CNN 에서는 이미지처럼 형상을 가진 데이터를 제대로 이해할 (가능성이 있는) 것이다.
-
CNN 에서는 합성곱 계층의 입출력 데이터를 특징 맵(feature map)이라고도 한다.
합성곱 계층의 입력 데이터를 입력 특징 맵(input feature map), 출력 데이터를 출력 특징 맵(output feature map)이라고 한다.
7.2.2. 합성곱 연산
-
합성곱 계층에서는 합성곱 연산을 처리한다.
합성곱 연산은 이미지 처리에서 말하는 “필터 연산”에 해당한다.
문헌에 따라 필터를 “커널”이라 칭하기도 한다.
-
합성곱 연산은 필터의 윈도우(window)를 일정 간격으로 이동해가며 입력 데이터에 적용한다.
입력과 필터에서 대응하는 원소끼리 곱한 후 그 총합을 구한다. (이 계산을 단일 곱셈-누산(fused multiply-add, FMA)라고 한다,)
그리고 그 결과를 출력의 해당 장소에 저장한다.
이 과정을 모든 장소에서 수행하면 합성곱 연산의 출력이 완성된다.
-
완전연결 신경망에는 가중치 매개변수와 편향이 존재했다.
CNN 에서는 필터의 매개변수가 그동안의 ‘가중치’에 해당한다.
그리고 CNN 에도 편향이 존재한다.
7.2.3. 패딩
-
합성곱 연산을 수행하기 전에 입력 데이터 주변을 특정 값(예컨대 0)으로 채우기도 한다.
이를 패딩(padding)이라 하며, 합성곱 연산에서 자주 이용하는 기법이다.
폭 1짜리 패딩이라 하면 입력 데이터 사방 1픽셀을 특정 값으로 채우는 것이다.
-
패딩은 주로 출력 크기를 조정할 목적으로 사용한다.
예를 들어 (4,4) 입력 데이터에 (3,3) 필터를 적용하면 출력은 (2,2)가 되어 입력보다 2만큼 줄어든다.
이는 합성곱 연산을 몇 번이나 되풀이하는 심층 신경망에서는 문제가 될 수 있다.
합성곱 연산을 거칠 때마다 크기가 작아지면 어느 시점에서는 출력 크기가 1이 되어버린다.
더 이상은 합성곱 연산을 적용할 수 없다는 뜻이다.
이런 사태를 막기 위해 패딩을 사용한다.
7.2.4. 스트라이드
-
필터를 적용하는 위치의 간격을 스트라이드(stride)라고 한다.
예를 들어 스트라이드를 2로 하면 필터를 적용하는 윈도우가 두 칸씩 이동한다.
스트라이드를 키우면 출력 크기가 작아진다.
( 패딩을 크게 하면 출력 크기가 커진다. )
-
입력 크기를 (H,W), 필터 크기를 (FH, FW), 출력 크기를 (OH, OW), 패딩을 P, 스트라이드를 S 라 하면, 출력 크기는 다음 식으로 계산한다.
OH = (H + 2P - FH) / S + 1
OW = (W + 2P - FW) / S + 1
이 값은 정수가 되어야 한다는 점에 주의해야 한다.
( 정수가 아니면 오류를 내는 등의 대응을 해줘야 한다.
딥러닝 프레임워크 중에는 값이 딱 나눠떨어지지 않을 때는 가장 가까운 정수로 반올림하는 등, 특별히 에러를 내지 않고 진행하도록 구현하는 경우도 있다. )
7.2.5. 3차원 데이터의 합성곱 연산
-
3차원 합성곱 연산에서 주의할 점은 입력 데이터의 채널 수와 필터의 채널 수가 같아야 한다는 것이다.
필터 자체의 크기는 원하는 값으로 설정할 수 있지만, 모든 채널의 필터가 같은 크기여야 한다.
7.2.6. 블록으로 생각하기
-
3차원의 합성곱 연산은 데이터와 필터를 3차원 직육면체 블록이라고 생각하면 쉽다.
3차원 데이터를 다차원 배열로 나타낼 때는 (Channel, Height, Width) 로 표현할 수 있다. ( C, H, W )
-
(CHW 가 있는 3차원의 합성곱 연산의 결과는 Matrix(2차원) 이 된다. 그래서..)
합성곱 연산의 출력으로 다수의 채널을 내보내려면(3차원을 유지시켜주려면) 필터 가중치를 다수 사용하는 것이다.
필터를 N 개 사용하면 출력 맵도 N 개가 된다. (3차원이면서 Chnanel 의 수가 N 이 된다.)
-
합성곱 연산에서는 필터의 수도 고려해야 한다.
필터의 가중치 데이터는 4차원 데이터이다. (출력 채널 수(위에서 말한 N), 입력 채널 수, 높이, 너비)
예를 들어 채널 수 3, 크기 5 X 5 인 필터가 20개 있다면 (20, 3, 5, 5) 로 쓴다.
-
합성곱 연산에도 완전연결 계층과 마찬가지로 편향이 쓰인다.
편향의 형상은 (FN, 1, 1) 이다.
7.2.7. 배치 처리
-
합성곱 연산도 마찬가지로 배치 처리를 지원할 수 있다.
계층을 흐르는 데이터의 차원을 하나 늘려 4차원 데이터로 저장한다. (데이터 수, 채널 수, 높이, 너비)
신경망에 4차원 데이터가 하나 흐를 때마다 데이터 N 개에 대한 합성곱 연산이 이뤄진다.
즉 N 회 분의 처리를 한 번에 수행하는 것이다.
7.3. 풀링 계층
-
풀링은 세로, 가로 방향의 공간을 줄이는 연산이다.
-
풀링은 최대 풀링 외에도 평균 풀링(average pooling)등이 있다.
최대 풀링은 대상 영역에서 최댓값을 취하는 연산인 반면, 평균 풀링은 대상 영역의 평균을 계산한다.
이미지 인식 분야에서는 주로 최대 풀링을 사용한다.
7.3.1. 풀링 계층의 특징
-
학습해야 할 매개변수가 없다.
풀링 계층은 합성곱 계층과 달리 학습해야 할 매개변수가 없다. 풀링은 대상 영역에서 최댓값이나 평균을 취하는 명확한 처리이므로 특별히 학습할 것이 없다.
채널 수가 변하지 않는다.
풀링 연산은 입력 데이터의 채널 수 그대로 출력 데이터로 내보낸다. 채널마다 독립적으로 계산하기 때문이다.
입력의 변화에 영향을 적게 받는다. (강건하다)
입력 데이터가 조금 변해도 풀링의 결과는 잘 변하지 않는다.
7.4. 합성곱/풀링 계층 구현하기
7.4.1. 4차원 배열
-
데이터의 형상이 (10, 1, 28, 28) 이라면 높이 28, 너비 28, 채널 1개인 데이터가 10개라는 이야기이다.
x = np.random.rand(10, 1, 28, 28)
7.4.2. im2col 로 데이터 전개하기
-
합성곱 연산을 곧이곧대로 구현하려면 for 문을 겹겹이 써야한다.
성능 문제가 있기 때문에 이 대신 im2col 이라는 편의 함수를 사용하면 간단하다.
-
im2col 은 입력 데이터를 필터링(가중치 계산)하기 좋게 전개하는(펼치는) 함수이다.
3차원 입력 데이터에 im2col 을 적용하면 2차원 행렬로 바뀐다. (정확히는 배치 안의 데이터 수까지 포함한 4차원 데이터를 2차원으로 변환한다.)
입력데이터에서 "필터를 적용하는 영역"을 한 줄로 늘어놓는다.
이 전개를 필터를 적용하는 모든 영역에서 수행하는 게 im2col 이다.
-
실제 상황에서는 스트라이드가 크지 않다면 필터의 적용 영역이 겹치는 경우가 대부분이다.
필터 적용 영역이 겹치게 되면 im2col 로 전개한 후의 원소 수가 원래 블록의 원소 수보다 많아진다.
그래서 im2col 을 사용해 구현하면 메모리를 더 많이 소비하는 단점이 있다.
하지만 컴퓨터는 큰 행렬을 묶어서 계산하는 데 탁월하다.
그래서 문제를 행렬 계산으로 만들면 선형 대수 라이브러리를 활용해 효율을 높일 수 있다.
-
im2col 은 image to column 즉 이미지에서 행렬로라는 뜻이다.
카페(Caffe)나 체이너(Chainer) 등의 딥러닝 프레임워크는 im2col 이라는 이름의 함수를 만들어 합성곱 계층을 구현할 때 이용하고 있다.
-
im2col 로 입력 데이터를 전개한 다음에는 합성곱 계층의 필터(가중치)를 1열로 전개하고 두 행렬의 내적을 계산하면 된다.
완전연결 Affine 계층에서 한 것과 거의 같다.
-
CNN 은 데이터를 4차원 배열로 저장하므로 2차원인 출력 데이터를 4차원으로 변형(reshape)해야 한다.
7.4.3. 합성곱 계층 구현하기
-
im2col interface 는 아래와 같다.
im2col(input_data, filter_h, filter_w, stride=1, pad=0)
이 함수는 필터 크기, 스트라이드, 패딩을 고려하여 입력 데이터를 2차원 배열로 전개한다.
-
x1 = np.random.rand(1, 3, 7, 7)
col1 = im2col(x1, 5, 5, stride=1, pad=0) # shape = (9, 75) = ( 3^2, 5 * 5 * 3 )
x2 = np.random.rand(10, 3, 7, 7) # 배치
col2 = im2col(x2, 5, 5, stride=1, pad=0) # shape = (90, 75) = (3^2 * 10, 5 * 5 * 3)
-
class Convolution:
def __init__(self, W, b, stride=1, pad=0):
self.W = W
self.b = b
self.stride = stride
self.pad = pad
def forward(self, x):
FN, C, FH, FW = self.W.shape
N, C, H, W = x.shape
out_h = int(1 + (H + 2*self.pad - FH) / self.stride)
out_w = int(1 + (W + 2*self.pad - FW) / self.stride)
col = im2col(x, FH, FW, self.stride, self.pad)
col_W = self.W.reshape(FN, -1).T # 필터 전개, -1 편의 기능으로 FN 갯수로 행을 만들었을 때 열 수는 원소 갯수 변화없도록 알아서 적용되는 방식의 param 값이다.
out = np.dot(col, col_W) + self.b
out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2) # 축의 순서를 바꾸어준다.
return out
-
합성곱 계층의 역전파는 Affine 계층과 공통점이 많다
주의할 점은 합성곱 계층의 역전파에서는 im2col 을 역으로 처리해야 한다.
이는 col2im 함수로 보통 구현되어 있다
7.4.4. 풀링 계층 구현하기
-
풀링 계층 구현도 합성곱 계층과 마찬가지로 im2col 을 사용해 입력 데이터를 전개한다.
단, 풀링의 경우엔 채널 쪽이 독립적이라는 점이 합성곱 계층 때와 다르다.
전개한 후 전개한 행렬에서 행별 최댓값을 구하고 적절한 형상으로 성형하기만 하면 된다.
-
class Pooling:
def __init__(self, pool_h, pool_w, stride=1, pad=0):
self.pool_h = pool_h
self.pool_w = pool_w
self.stride = stride
self.pad = pad
def forward(self, x):
N, C, H, W = x.shape
out_h = int(1 + (H - self.pool_h) / self.stride)
out_w = int(1 + (W - self.pool_w) / self.stride)
#전개
col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
col = col.reshape(-1, self.pool_h * self.pool_w)
#최대값
out = np.max(col, axis=1)
#성형
out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)
return out
-
풀링 계층 구현은 다음의 세 단계로 진행한다.
1. 입력 데이터를 전개한다.
2. 행별 최댓값을 구한다.
3. 적절한 모양으로 성형한다.
-
최댓값 계산에는 넘파이의 np.max 메서드를 사용할 수 있다.
np.max 는 인수로 축(axis)을 지정할 수 있는데 이 인수로 지정한 축마다 최댓값을 구할 수 있다. 가령 np.max(x, axis=1)과 같이 쓰면 입력 x 의 첫번째 차원의 축마다 최댓값을 구한다.
7.5.5. CNN 구현하기
-
CNN 네트워크는 Convolution - ReLU - Pooling - Affine - ReLU - Affine - Softmax 순으로 흐른다.
-
class SimpleConvNet:
def __init__(self, input_dim=(1, 28,28), conv_param={‘filter_num’:30, ‘filter_size’:5, ‘pad’:0, ‘stride’:1}, hidden_size=100, output_size=10, weight_init_std=0.01):
filter_num = conv_param[‘filter_num’]
filter_size = conv_param[‘filter_size’]
filter_pad = conv_param[‘pad’]
filter_stride = conv_param[‘stride’]
input_size = input_dim[1]
conv_output_size = (input_size - filter_size + 2 * filter_pad) / filter_stride + 1
pool_output_size = int(filter_num * (conv_output_size/2) * (conv_output_size/2))
self.params = { }
self.params[‘W1’] = weight_init_std * np.random.randn(filter_num, input_dim[0], filter_size, filter_size)
self.params[‘b1’] = np.zeros(filter_num)
self.params[‘W2’] = weight_init_std * np.random.randn(pool_output_size, hidden_size)
self.params[‘b2’] = np.zeros(hidden_size)
self.params[‘W3’] = weight_init_std * np.random.randn(hidden_size, output_size)
self.params[‘b3’] = np.zeros(output_size)
self.layers = OrderedDict()
self.layers[‘Conv1’] = Convolution(self.params[‘W1’], self.params[‘b1’], conv_param[‘stride’], conv_param[‘pad’])
self.layers[‘Relu1’] = Relu()
self.layers[‘Pool1’] = Pooling(pool_h=2, pool_w=2, stride=2)
self.layers[‘Affine1’] = Affine(self.params[‘W2’], self.params[‘b2’])
self.layers[‘Relu2’] = Relu()
self.layers[‘Affine2’] = Affine(self.params[‘W3’], self.params[‘b3’])
self.last_layer = SoftmaxWithLoss()
def predict(self, x):
for layer in self.layers.values():
x = layer.forward(x)
return x
def loss(self, x, t):
y = self.predict(x)
return self.last_layer.forward(y, t)
def gradient(self, x, t):
# 순전파
self.loss(x, t)
# 역전파
dout = 1
dout = self.last_layer.backward(dout)
layers = list(self.layers.values())
layers.reverse()
for layer in layers:
dout = layer.backward(dout)
grads = { }
grads[‘W1’] = self.layer[‘Conv1’].dW
grads[‘b1’] = self.layers[‘Conv1’].db
grads[‘W2’] = self.layers[‘Affine1’].dW
grads[‘b2’] = self.layers[‘Affine1’].db
grads[‘W3’] = self.layers[‘Affine2].dW
grads[‘b3’] = self.layers[‘Affine2’].db
return grads
7.6. CNN 시각화하기
7.6.1. 1번째 층의 가중치 시각화하기
-
학습전 필터는 무작위로 초기화되어 있어 흑백의 정도에 규칙성을 찾기 어렵다.
한편, 학습을 마친 필터는 규칙성 있는 이미지가 되어있다.
흰색에서 검은색으로 점차 변화하는 필터와 덩어리(블롭, blob)가 진 필터 등, 규칙을 띄는 필터로 바뀌었다.
-
왼쪽 절반이 흰색이고 오른쪽 절반이 검은색인 필터는 세로 방향의 에지에 반응하는 필터이다
7.6.2. 층 깊이에 따른 추출 정보 변화
-
딥러닝 시각화에 관한 연구에 따르면, 계층이 깊어질수록 추출되는 정보(정확히는 강하게 반응하는 뉴런)는 더 추상화된다.
딥러닝의 흥미로운 점은 합성곱 계층을 여러 겹 쌓으면, 층이 깊어지면서 더 복잡하고 추상화된 정보가 추출된다.
처음 층은 단순한 에지에 반응하고, 이어서 텍스처에 반응하고, 더 복잡한 사물의 일부에 반응하도록 변화한다.
즉 층이 깊어지면서 뉴런이 반응하는 대상이 단순한 모양에서 “고급" 정보로 변화해간다.
다시 말하면 사물의 “의미” 를 이해하도록 변화하는 것이다.
7.7. 대표적인 CNN
7.7.1. LeNet
-
LeNet 은 손글씨 숫자를 인식하는 네트워크로, 1998년에 제안되었다.
합성곱 계층과 풀링 계층(정확히는 단순히 “원소를 줄이기”만 하는 서브샘플링 계층)을 반복하고, 마지막으로 완전연결 계층을 거치면서 결과를 출력한다.
LeNet 과 앞서 본 CNN 을 비교하면 몇 가지 면에서 차이가 있다..
LeNet 은 활성화 함수로 시그모이드 함수를 사용하는 데 반해, CNN 은 주로 ReLU 를 사용한다.
LeNet 은 서브샘플링을 하여 중간 데이터의 크기가 작아지지만 CNN 은 최대 풀링이 주류이다.
7.7.2. AlexNet
-
LeNet 과 비교해 훨씬 최근인 2012년에 발표된 AlexNet 은 딥러닝 열풍을 일으키는 데 큰 역할을 했다.
기본 구성은 LeNet 과 크게 다르지 않다.
-
AlexNet 은 합성곱 계층과 풀링 계층을 거듭하며 마지막으로 완전연결 계층을 거쳐 결과를 출력한다.
LetNet 에서 큰 구조는 바뀌지 않았지만, AlexNet 에서는 다음과 같은 변화를 주었다.
1. 활성화 함수로 ReLU 를 이용한다.
2. LRN( Local response Normalization ) 이라는 국소적 정규화를 실시하는 계층을 이용한다.
3. 드롭아웃을 사용한다.
-
LaNet 과 AlexNet 에는 큰 차이가 없다.
그러나 컴퓨터와 관련된 환경이 진보를 이루었다.
누구나 대량의 데이터를 얻을 수 있게 되었고, 병렬 계산에 특화된 GPU 가 보급되면서 대량의 연산을 고속으로 수행할 수 있게 되었다.
빅 데이터와 GPU, 이것이 딥러닝 발전의 큰 원동력이다.
-
딥러닝(심층 신경망)에는 대부분 수많은 매개변수가 쓰인다.
그래서 학습하려면 엄청난 양의 계산을 해야만 한다.
또한 그 매개변수를 피팅(fitting) 시키는 데이터도 대량으로 필요하다.
GPU 와 빅 데이터는 이런 문제에 해결책을 던졌다고 말할 수 있다.
7.8 정리
-
CNN 은 지금까지의 완전연결 계층 네트워크에 합성곱 계층과 풀링 계층을 새로 추가한다.
합성곱 계층과 풀링 계층은 im2col(이미지를 행렬로 전개하는 함수)을 이용하면 간단하고 효율적으로 구현할 수 있다.
CNN 을 시각화해보면 계층이 깊어질수록 고급 정보가 추출되는 모습을 확인할 수 있다.
대표적인 CNN 에는 LeNet 과 AlexNet 이 있다.
딥러닝의 발전에는 빅 데이터와 GPU 가 크게 기여했다.
'프로그래밍 놀이터 > 머신러닝, AI, 딥러닝' 카테고리의 다른 글
[머신러닝] #8 딥러닝 (0) | 2018.07.10 |
---|---|
[머신러닝] #6 학습 관련 기술들 (0) | 2018.07.08 |
[머신러닝] #5 오차역전파법 ( Back propagation ) (0) | 2018.07.07 |
[머신러닝] #4 신경망 학습 #2 (0) | 2018.07.06 |
[머신러닝] #4 신경망 학습 #1 (0) | 2018.07.05 |
댓글