합성곱/풀링 계층 구현하기
4차원 배열
앞선 포스팅에서 설명한대로 CNN에서 계층 사이를 흐르는 데이터는 4차원이다. 파이썬으로 구현해보자.
import numpy as np
x=np.random.rand(10,1,28,28)
x.shape
(10, 1, 28, 28)
*첫번째 데이터에 접근하려면 단순히 x[0]이라고 쓴다(파이썬의 인덱스는 0부터 시작한다).
x[0].shape #(1,28,28)
x[1].shape #(1,28,28)
*또, 첫 번째 데이터의 첫 채널의 공간 데이터에 접근하려면 다음과 같이 구현한다.
x[0,0].shape #또는 x[0][0]
im2col로 데이터 전개하기
합성곱 연산을 곧이곧대로 구현하려면 for 문을 겹겹이 써야한다. 하지만 넘파이에 for문을 사용하면 성능이 떨어지는 단점이 있다.(넘파이에서는 원소에 접근할 때 for문을 사용하지 않는 것이 바람직하다).
그렇기에 for 문 대신 im2col이라는 편의 함수를 사용해 간단하게 구현해보자.
im2col은 입력 데이터를 필터링(가중치 계산)하기 좋게 전개하는 함수이다. 위 그림과 같이 3차원 입력 데이터에 im2col 을 적용하면 2차원 행렬로 바뀐다.
정확히는 배치 안의 데이터 수까지 포함한 4차원 데이터를 2차원으로 변환한다.
im2col은 필터링하기 좋게 입력 데이터를 전개한다. 구체적으로는 위 그림과 같이 입력 데이터에서 필터를 적용하는 영역(3차원 블록)을 한줄로 늘어놓는다. 이 전개를 필터를 적용하는 모든 영역에서 수행하는게 im2col이다.
위 그림에서는 보기에 좋게끔 스트라이드를 크게 잡아 필터의 적용 영역이 겹치지 않도록 했지만, 실제 상황에서는 영역이 겹치는 경우가 대부분이다.
필터 적용 영역이 겹치면 im2col로 전개한 후의 원소 수가 블록의 원소 수보다 많아진다.(이에 메모리를 더 소비하는 단점도 있다)
[Note]
im2col은 'image to column' 즉 '이미지에서 행렬로'라는 뜻이다. 카페와 체이너등의 딥러닝 프레임워크는 im2col이라는 이름의 함수를 만들어 합성곱 계층을 구현할 때 이용하고 있다.
im2col 함수
def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
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
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))
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]
col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N * out_h * out_w, -1)
return col
x1=np.random.rand(1,3,7,7) #데이터의 수,채널 수, 높이, 너비
col1=im2col(x1,5,5,stride=1,pad=0)
print(col1.shape)
(9, 75)
위 예는 batchsize=1(data=1), channel=3, height*width=7*7
x2=np.random.rand(10,3,7,7)
col2=im2col(x2,5,5,stride=1,pad=0)
print(col2.shape)
(90, 75)
위 예는 batchsize=10(data=10),channel=3,height*width=7*7
위 두 예 모두 2번째 차원의 원소는 75개이다. 이 값은 필터의 원소 수와 같다(channel=3, 5*5 data).
또한, batchsize=1일 때는 im2col의 결과의 크기가 (9,75)이고, 10일 때는 그 10배인 (90,75) 크기의 데이터가 저장된다.
다음은 Convolution class를 구현해보자.
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 #필터 전개
out=np.dot(col,col_W)+self.b
out=out.reshape(N,out_h,out_w,-1).transpose(0,3,1,2)
return out
합성곱 계층은 필터(가중치), 편향, 스트라이드, 패딩을 인수로 받아 초기화한다.
필터는 (FN,C,FH,FW)의 4차원 형상이다. 여기서 FN은 필터 개수, C는 채널, FH는 필터 높이, FW는 필터 너비이다.
col=im2col(x,FH,FW,self.stride,self.pad)
col_W=self.W.reshape(FN,-1).T #필터 전개
out=np.dot(col,col_W)+self.b
위 코드 3줄이 가장 중요하다. 위 코드는 input_data를 im2col로 전개하고 필터도 reshape하여 2차원 배열로 전개한다. 그 후 내적을 진행한다.
forward 구현의 마지막에는 넘파이의 transpose 함수를 사용하는데, 이는 다차원 배열의 축 순서를 바꿔주는 함수이다.
풀링 계층 구현하기
풀링 계층 구현도 함성곱 계층과 마찬가지로 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*self.pool_w)
#최댓값(2)
out=np.max(col,axis=1)
#성형(3)
out=out.reshape(N,out_h,out_w,C).transpose(0,3,1,2)
return out
[Note]
최댓값 계산에는 넘파이의 np.max 메서드를 사용할 수 있다. np.max는 인수로 축(axis)을 지정할 수 있는데, 이 인수로 지정한 축마다 최댓값을 구할 수 있다. 가령 np.max(x,axis=1)과 같이 쓰면 입력 x의 1번째 축마다 최댓값을 구한다.
CNN 구현하기
합성곱 계층과 풀링 계층을 구현했으니, 조합하여 손글자 인식 CNN을 조립해보자.
초기화 때 받는 인수
-input_dim :입력 데이터(채널 수, 높이,너비)의 차원
-conv_param: 함성곱 계층의 하이퍼파라미터(딕셔너리). 딕셔너리 키는 다음과 같다.
filter_num: 필터의 수
filter_size: 필터 크기
stride: 스트라이드
pad: 패딩
hidden_size: 은닉층(fully-connected)의 뉴런 수
output_size: 출력층(fully_connected)의 뉴런 수
weight_init_std: 초기화 때의 가중치 표준편차
여기서 CNN 계층의 하이퍼파라미터는 딕셔너리 형태로 주어진다(conv_param).
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.parmas['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.layers['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
이상으로 DeepLearning from Scratch 밑바닥부터 시작하는 딥러닝1을 전부 공부하며 리뷰했다. 다음은
딥러닝 2를 리뷰하겠다.
'Deep Learning > 밑바닥부터 시작하는 딥러닝(1)' 카테고리의 다른 글
Chapter 7. 합성곱 신경망(CNN)(1) (0) | 2021.03.09 |
---|---|
Chapter 6. 학습 관련 기술들(3) (0) | 2021.03.04 |
Chapter 6. 학습 관련 기술들(2) (0) | 2021.03.03 |
Chapter 6. 학습 관련 기술들(1) (0) | 2021.03.02 |
Chapter 5. 오차역전파법 (0) | 2021.01.25 |