im2col 코드
출처: https://github.com/youbeebee/deeplearning_from_scratch/blob/master/common/util.py
import numpy as np
def smooth_curve(x):
"""손실 함수의 그래프를 매끄럽게 하기 위해 사용
참고:http://glowingpython.blogspot.jp/2012/02/convolution-with-numpy.html
"""
window_len = 11
s = np.r_[x[window_len-1:0:-1], x, x[-1:-window_len:-1]]
w = np.kaiser(window_len, 2)
y = np.convolve(w/w.sum(), s, mode='valid')
return y[5:len(y)-5]
def shuffle_dataset(x, t):
"""데이터셋을 뒤섞는다.
Parameters
----------
x : 훈련 데이터
t : 정답 레이블
Returns
-------
x, t : 뒤섞은 훈련 데이터와 정답 레이블
"""
permutation = np.random.permutation(x.shape[0])
x = x[permutation, :] if x.ndim == 2 else x[permutation, :, :, :]
t = t[permutation]
return x, t
def conv_output_size(input_size, filter_size, stride=1, pad=0):
return (input_size + 2*pad - filter_size) / stride + 1
def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
"""다수의 이미지를 입력받아 2차원 배열로 변환한다(평탄화).
Parameters
----------
input_data : 4차원 배열 형태의 입력 데이터(이미지 수, 채널 수, 높이, 너비)
filter_h : 필터의 높이
filter_w : 필터의 너비
stride : 스트라이드
pad : 패딩
Returns
-------
col : 2차원 배열
"""
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
def col2im(col, input_shape, filter_h, filter_w, stride=1, pad=0):
"""(im2col과 반대) 2차원 배열을 입력받아 다수의 이미지 묶음으로 변환한다.
Parameters
----------
col : 2차원 배열(입력 데이터)
input_shape : 원래 이미지 데이터의 형상(예:(10, 1, 28, 28))
filter_h : 필터의 높이
filter_w : 필터의 너비
stride : 스트라이드
pad : 패딩
Returns
-------
img : 변환된 이미지들
"""
N, C, H, W = input_shape
out_h = (H + 2*pad - filter_h)//stride + 1
out_w = (W + 2*pad - filter_w)//stride + 1
col = col.reshape(N, out_h, out_w, C, filter_h, filter_w).transpose(0, 3, 4, 5, 1, 2)
img = np.zeros((N, C, H + 2*pad + stride - 1, W + 2*pad + stride - 1))
for y in range(filter_h):
y_max = y + stride*out_h
for x in range(filter_w):
x_max = x + stride*out_w
img[:, :, y:y_max:stride, x:x_max:stride] += col[:, :, y, x, :, :]
return img[:, :, pad:H + pad, pad:W + pad]
im2col(input_data, filter_h, filter_w, stride=1, pad=0)
input_data: (데이터 수, 채널 수, 높이, 너비) 4차원 배열로 이뤄진 입력 데이터
filter_h - 필터의 높이
filter_w - 필터의 너비
stride - 스트라이드
pad - 패딩
import sys, oys
sys.path.append(os.pardir)
from common.util import im2col
x1 = np.random.rand(1,3,7,7)#(데이터수(배치사이즈),채널 수, 높이 ,너비)
col = im2col(x1,5,5,stride=1,pad=0)
print(col1.shape)#(9,75) 5*5*3
x2= np.random.rand(10,3,7,7)#데이터 10개
col = im2col(x2,5,5,stride=1,pad=0)
print(col2.shape) #(90,75) 배치 크기 10배
합성곱 계층 구현
class Convolution:
def __init__(self, W, b, stride=1, pad=0):
self.W = W #가중치
self.b = b #bias
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
# reshape에서 -1 : 원소 개수에 맞춰 적절하게 묶어줌.
# transpose : 다차원 배열의 축 순서를 바꿔줌(N,H,W,C) -> (N,C,H,W)
out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)
return out
reshape에 -1을 지정하면 다차원 배열의 원소수가 변환후에도 똑같이 유지되도록 적절히 유지해줌
ex) (10,3,5,5) 형상의 W의 원소수 10*3*5*5 = 750개, 여기에 reshape(10,-1)을 호출하면 750개의 원소를 10묶음 (10,75) 배열로 만듦.
풀링 계층 구현
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)
# 최댓값 axis : 축의 방향, 0=열방향, 1=행방향, 행별 최댓값 구하기
out = np.max(col, axis=1)
# 적절한 모양으로 성형
out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)
return out
CNN 구현하기
이 네트워크는 Conv - 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): #weight_init: 초기화 가중치 표준편차
#초기화 인수로 주어진 파라미터를 딕셔너리에서 꺼내씀
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))
# 1층의 합성곱 계층과 2, 3층의 완전연결 계층의 가중치와 편향 생성
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)
# CNN을 구성하는 계층을 생성
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
#x는 입력데이터, t는 정답데이터
def loss(self, 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 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 #grads 딕셔너리 변수에 각 가중치 매개변수의 기울기 저장
CNN 시각화하기
학습을 마친 필터는 규칙성 있는 이미지가 되었음. 흰색에서 검은색, 덩어리(blob)을 가지거나 규칙을 띄는 이미지로 변환되었다.
세로줄 느낌의 필터1은 세로 엣지에 반응하고, 가로줄 느낌의 필터1는 가로 엣지에 반응하는 것을 알 수 있다.
이처럼 합성곱 계층의 필터는 엣지나 블롭 등의 원시적인 정보를 추출한다.
층 깊이가 길어 질수록 더 복잡하고 추상화된 정보가 추출된다. 뉴런이 반응하는 대상이 단순한 모양에서 사물의 의미를 이해하도록 변화한다.
대표적인 CNN
LeNet
20년전 첫 제안된 CNN으로, LeNet의 대부분의 유닛은 sigmoid 함수를 사용하고 subsampling을 통해 중간 데이터의 크기를 줄인다. F6 layer에는 tanh를 사용하고, F7에는 RBF (Euclidian Radia basis function unit)을 사용했다.
AlexNet
기본적인 구성은 LeNet과 크게 다르지 않다. conv layer와 max pool layer를 거듭하고 마지막에는 fully connected layer를 거친다.
활성화 함수로 처음 ReLU를 사용했고, 오버피팅 방지를 위해 데이터 증강과 드롭아웃,norm layer( 원시적인 형태의 batch normalization )를 사용했다.
'Deep Learning > from scratch I' 카테고리의 다른 글
[밑바닥부터 시작하는 딥러닝 - 7장 합성곱 신경망(CNN) I] (1) | 2024.02.10 |
---|---|
[밑바닥부터 시작하는 딥러닝 - 6장 학습 관련 기술들 II] (1) | 2024.02.09 |
[밑바닥부터 시작하는 딥러닝 - 6장 학습 관련 기술들 I] (0) | 2024.02.08 |
[밑바닥부터 시작하는 딥러닝 - 5장 오차역전파법 II] (1) | 2024.02.08 |
[밑바닥부터 시작하는 딥러닝 - 5장 오차역전파법 I] (0) | 2024.02.08 |