일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 동적프로그래밍
- BFS
- 동적 프로그래밍
- 구글 킥스타트
- 리눅스
- 프로그래밍
- DFS
- kick start
- nlp
- 코딩 테스트
- AI
- 순열
- google coding competition
- 네트워크
- OS
- 운영체제
- 그래프
- 프로그래머스
- 브루트포스
- 딥러닝
- linux
- PYTHON
- 코딩
- 코딩테스트
- dp
- 킥스타트
- 백준
- 알고리즘
- 파이썬
- CSS
- Today
- Total
오뚝이개발자
[CH7] 합성곱 신경망(CNN) 본문
합성곱 신경망 CNN은 Convolutional Neural Network의 약자이다. 이미지 인식과 음성 인식 등 다양한 곳에서 활용되는데, 특히 이미지 인식 분야에서 딥러닝을 활용한 기법은 거의 CNN을 기초로 한다.
CNN의 구조
- 완전연결 신경망(fully-conected) : 인접하는 계층의 모든 뉴련과 결합
- 완전히 연결된 계층을 Affine 계층이라는 이름으로 구현
- CNN : 합성곱 계층(Conv)과 풀링 계층(Pooling)이 추가됨.
- Conv -> ReLU -> (Pooling) 흐름으로 연결(Pooling은 생략되기도 함)
- 지금까지의 Affine -> ReLU 연결이 Conv -> ReLU -> Pooling으로 바뀌었다고 생각하면 쉽다.
- 마지막 출력 계층에선 Affine -> Softmax 조합을 그대로 사용
완전연결 계층의 문제점
- 데이터의 형상이 무시됨.
- 이미지는 통상 3차원(채널, 세로, 가로)으로 3차원 속에서 의미를 갖는 패턴이 有
- 공간적으로 가까운 픽셀은 값이 비슷하거나, 거리가 먼 픽셀끼리는 관련성이 떨어진다거나 등.
- BUT 완전연결 계층에 입력할 땐 평평한 1차원으로 바꿔준다.
- MNIST 손글씨 사례에서도 형상이 (1, 28, 28)인 이미지를 1줄로 세운 784개의 데이터 입력해줬음.
- CNN은 형상을 유지
- 입력도 3차원으로 받고, 다음 계층에도 3차원으로 전달
- CNN에선 입출력 데이터를 특징 맵(feature map)이라 부름.
합성곱 연산
- 합성곱 연산은 이미지 처리에서 말하는 필터 연산에 해당
- 필터를 커널이라 칭하기도 함.
- 합성곱 연산은 필터의 윈도우(window)를 일정 간격 이동해가며 입력 데이터에 적용
- 아래 그림과 같이 입력과 필터에서 대응하는 원소끼리 곱한 후 그 총합을 구함
- CNN에선 필터의 매개변수가 지금까지의 가중치에 해당
- 편향(bias)는 항상 하나만 존재(1 x 1)
패딩
- 패딩(padding)이란 합성곱 연산 수행 전 입력 데이터 주변을 0과 같은 특정 값으로 채우는 것
- 주로 출력 데이터의 크기를 조정할 목적으로 사용
- 예컨대, (4,4) 입력 데이터에 (3,3) 필터를 적용하면 출력은 (2,2)가 되어 입력보다 줄어든다. 이는 합성곱 연산을 되풀이해 야 하는 심층 신경망에서 문제가 된다. 연산을 거칠 때마가 크기가 작아져 어느 시점에서는 출력 크기가 1이 되어 더 이상 합성곱 연산을 적용할 수 없게 된다.
스트라이드(stride)
- 스트라이드 : 필터를 적용하는 위치의 간격
- 입력크기 (H,W), 필터크기 (FH,FW), 출력크기 (OH,OW), 패딩 P, 스트라이드 S일 때, 출력크기 계산(출력크기는 정수로 나눠 떨어지는 값이어야 한다!)
3차원 데이터의 합성곱 연산
- 3차원 데이터는 기존 2차원과 비교해 채널 방향으로 특징 맵이 늘어난 형태
- 입력 데이터와 필터의 합성곱 연산을 채널마다 수행하고, 그 결과를 더해서 하나의 출력을 얻어냄
- 고로, 입력 데이터와 필터의 채널 수 같아야 함.
블록으로 생각하기
- 3차원의 합성곱 연산은 데이터와 필터를 직육면체 블록이라고 생각하면 쉽다.
- 3차원 데이터를 다차원 배열로 나타낼 때는 (C, H, W)=(채널, 높이, 너비) 순으로 쓴다.
- 위의 예는 출력으로 1장의 특징 맵을 내보냄.
- 여러 장의 특징 맵을 출력으로 하려면? 필터를 FN개 쓰면 출력 맵도 FN개 생성됨.
- 이 때, 필터의 가중치 데이터는 4차원
- 편향은 채널 하나에 값 하나씩 대응
배치 처리
- 합성곱 연산에서 배치처리를 하려면, 데이터를 4차원으로 저장하면 됨.
- (N, C, H, W) = (데이터 수, 채널 수, 높이, 너비)
- 신경망에 4차원 데이터가 하나 흐를 때마다 데이터 N개에 대한 합성곱 연산이 이루어지는 것
풀링 계층
- 풀링 : 가로, 세로 방향의 공간을 줄이는 연산(2x2이상되는 크기의 영역을 원소 하나로 집약)
- 풀링 연산은 전체 매개변수의 수를 크게 줄인다.
- 풀링의 이론적 측면은 계산된 특징이 이미지 내의 위치에 대한 변화에 영항을 덜 받기 때문이다. 예를 들어 이미지의 우측 상단에서 눈을 찾는 특징은, 눈이 이미지의 중앙에 위치하더라도 크게 영향을 받지 않아야 한다. 그렇기 때문에 풀링을 이용하여 불변성(invariance)을 찾아내서 공간적 변화를 극복할 수 있다.
- max pooling : 대상 영역에서 최댓값을 취함.
- average pooling : 대상 영역의 평균값을 취함.
- 일반적으로, 풀링의 윈도우 크기 = 스트라이드
- ex) 윈도우가 3x3이면 스트라이드는 3
풀링 계층의 특징
- 학습해야 할 매개변수가 없다 : 대상영역에서 최댓값이나 평균을 취하는 명확한 처리이므로
- 채널 수가 변하지 않는다 : 채널마다 독립적으로 계산하므로
- 입력의 변화에 영향을 적게 받는다(강건하다) : 입력데이터가 조금 변해도 풀링이 이를 흡수해 사라지게 함.
im2col로 데이터 전개하기
- 합성곱 연산을 그대로 구현하려면 다중 for문 써야함(매우 귀찬...성능 저조...) 그래서 im2col을 쓴다.
- im2col이란 입력 데이터를 필터링(가중치 계산0하기 좋게 전개하는 함수
- im2col => image to column으로 '이미지에서 행렬로'라는 뜻
- 아래 그림과 같이 입력 데이터에서 필터를 적용하는 영역(3차원 블록)을 한 줄로 늘어놓음. 이 전개를 모든 영역에서 수행.
- 합성곱 연산의 필터 처리 과정 : 필터를 세로로 1열로 전개하고, im2col이 전개한 데이터와 행렬 내적 계산
- 마지막으로 출력 데이터를 reshape(2차원->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)
# 전개 (1)
col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
col = col.reshape(-1, self.pool_h*self.pool_w)
# 최댓값 (2)
# axis=0 은 열방향, axis=1은 행방향
out = np.max(col, axis=1)
# 성형 (3)
out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)
return out
CNN 구현하기
1. SimpleConvNet
# coding: utf-8
import sys, os
sys.path.append(os.pardir) # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import pickle
import numpy as np
from collections import OrderedDict
from common.layers import *
from common.gradient import numerical_gradient
class SimpleConvNet:
"""단순한 합성곱 신경망
conv - relu - pool - affine - relu - affine - softmax
Parameters
----------
input_size : 입력 크기(MNIST의 경우엔 784)
hidden_size_list : 각 은닉층의 뉴런 수를 담은 리스트(e.g. [100, 100, 100])
output_size : 출력 크기(MNIST의 경우엔 10)
activation : 활성화 함수 - 'relu' 혹은 'sigmoid'
weight_init_std : 가중치의 표준편차 지정(e.g. 0.01)
'relu'나 'he'로 지정하면 'He 초깃값'으로 설정
'sigmoid'나 'xavier'로 지정하면 'Xavier 초깃값'으로 설정
"""
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):
"""손실 함수를 구한다.
Parameters
----------
x : 입력 데이터
t : 정답 레이블
"""
y = self.predict(x)
return self.last_layer.forward(y, t)
def accuracy(self, x, t, batch_size=100):
if t.ndim != 1 : t = np.argmax(t, axis=1)
acc = 0.0
for i in range(int(x.shape[0] / batch_size)):
tx = x[i*batch_size:(i+1)*batch_size]
tt = t[i*batch_size:(i+1)*batch_size]
y = self.predict(tx)
y = np.argmax(y, axis=1)
acc += np.sum(y == tt)
return acc / x.shape[0]
def numerical_gradient(self, x, t):
"""기울기를 구한다(수치미분).
Parameters
----------
x : 입력 데이터
t : 정답 레이블
Returns
-------
각 층의 기울기를 담은 사전(dictionary) 변수
grads['W1']、grads['W2']、... 각 층의 가중치
grads['b1']、grads['b2']、... 각 층의 편향
"""
loss_w = lambda w: self.loss(x, t)
grads = {}
for idx in (1, 2, 3):
grads['W' + str(idx)] = numerical_gradient(loss_w, self.params['W' + str(idx)])
grads['b' + str(idx)] = numerical_gradient(loss_w, self.params['b' + str(idx)])
return grads
def gradient(self, x, t):
"""기울기를 구한다(오차역전파법).
Parameters
----------
x : 입력 데이터
t : 정답 레이블
Returns
-------
각 층의 기울기를 담은 사전(dictionary) 변수
grads['W1']、grads['W2']、... 각 층의 가중치
grads['b1']、grads['b2']、... 각 층의 편향
"""
# forward
self.loss(x, t)
# backward
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'], grads['b1'] = self.layers['Conv1'].dW, self.layers['Conv1'].db
grads['W2'], grads['b2'] = self.layers['Affine1'].dW, self.layers['Affine1'].db
grads['W3'], grads['b3'] = self.layers['Affine2'].dW, self.layers['Affine2'].db
return grads
def save_params(self, file_name="params.pkl"):
params = {}
for key, val in self.params.items():
params[key] = val
with open(file_name, 'wb') as f:
pickle.dump(params, f)
def load_params(self, file_name="params.pkl"):
with open(file_name, 'rb') as f:
params = pickle.load(f)
for key, val in params.items():
self.params[key] = val
for i, key in enumerate(['Conv1', 'Affine1', 'Affine2']):
self.layers[key].W = self.params['W' + str(i+1)]
self.layers[key].b = self.params['b' + str(i+1)]
2. SimpleConvNet 학습시키기
# coding: utf-8
import sys, os
sys.path.append(os.pardir) # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from simple_convnet import SimpleConvNet
from common.trainer import Trainer
# 데이터 읽기
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=False)
# 시간이 오래 걸릴 경우 데이터를 줄인다.
#x_train, t_train = x_train[:5000], t_train[:5000]
#x_test, t_test = x_test[:1000], t_test[:1000]
max_epochs = 20
network = SimpleConvNet(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)
trainer = Trainer(network, x_train, t_train, x_test, t_test,
epochs=max_epochs, mini_batch_size=100,
optimizer='Adam', optimizer_param={'lr': 0.001},
evaluate_sample_num_per_epoch=1000)
trainer.train()
# 매개변수 보존
network.save_params("params.pkl")
print("Saved Network Parameters!")
# 그래프 그리기
markers = {'train': 'o', 'test': 's'}
x = np.arange(max_epochs)
plt.plot(x, trainer.train_acc_list, marker='o', label='train', markevery=2)
plt.plot(x, trainer.test_acc_list, marker='s', label='test', markevery=2)
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()
CNN 시각화하기
- 위 사진은 MNIST 데이터셋으로 학습한 CNN의 1번째 층을 시각화하여 나타낸 것.
- 학습 전 필터는 무작위로 초기화되고 있어 흑백의 정도에 규칙성 無
- 학습 마친 필터는 검은색으로 점차 변화하는 필터와 덩어리(블롭 ; blob)가 진 필터 등 규칙을 띄는 필터로 변화
- 규칙성 있는 필터는 에지(색상이 바뀐 경계선)와 블롭(국소적으로 덩어리진 영역) 등을 보는 것
- 합성곱 계층의 필터는 이처럼 에지나 블롭 등의 원시적인 정보 추출해 뒷단에 전달
층 깊이에 따른 추출 정보 변화
- 위의 이미지와 같이 층이 깊어지면서 뉴런이 반응하는 대상이 단순한 모양에서 '고급' 정보로 변화
- 사물의 '의미'를 이해하도록 변화
대표적인 CNN
1. LeNet(1998)
현재의 CNN과의 차이
- LeNet은 시그모이드 함수를 활성화 함수로 사용, 현재는 주로 ReLU 사용
- LeNet은 서브샘플링을 하여 중간 데이터의 크기가 작아지지만 현재는 최대 풀링이 주류
- 거의 20년 전에 제안된 '첫 CNN'
2. AlexNet(2012)
LeNet과 비교한 AlexNet 차이점
- 활성화 함수로 ReLU 사용
- 드롭아웃 사용
- LRN(Local Response Normalization)이라는 국소적 정규화를 실시하는 계층 사용
LeNet과 AlexNet 간에는 네트워크 구성 면에서 큰 차이가 없다. 그러나 이를 둘러싼 환경과 컴퓨터 기술이 큰 진보를 이룬 것. 대량의 데이터를 누구나 얻을 수 있게 되었고, 병렬 계산에 특화된 GPU가 보급되면서 대량의 연산을 고속으로 수행할 수 있게 됨. 즉, 빅 데이터와 GPU가 딥러닝 발전의 큰 원동력.
'AI > 밑바닥딥러닝1' 카테고리의 다른 글
오버피팅(overfitting) 방지법 정리 (0) | 2020.06.27 |
---|---|
[CH8] 딥러닝 (0) | 2020.06.23 |
[CH6] 학습 관련 기술들 (0) | 2020.06.21 |
[CH5] 오차역전파법 (0) | 2020.06.20 |
[CH4] 신경망 학습 (0) | 2020.06.19 |