-이미지를 잘 인식한다. 그러기 위해 일반적인 신경망과 조금 다르다.
-출력값이 입력값의 일부만 영향받는 국소성이 강한 처리가 수행된다.
-위의 층들은 모두 오차의 전파가 있다.
-컨볼루션은 학습되는 파라미터가 필터,편향이며 풀링층은 없고 전결합층은 가중치와 편향이 있다.
-위에서 컨볼루션층에서 몇 번 반복된후에 (컨볼루션층->풀링층)과정이 또 몇 번 반복된다. 이후 전결합층에서 몇 번 반복된후에야 출력이 나온다.
-컨볼루션층을 입력되는 이미지에 여러개의 필터처리가 된 후에 이미지의 특징을 나타내는 여러개의 이미지로 변환된다.
-풀링층에서는 이미지의 크기가 축소된다. (이미지의 특징을 잃어버리지 않기 위해)
-이미지에서 각 픽셀이 가까이 있는 픽셀과 가지고 있는 관련성이 얼마나 강한지 보여주는 성질이다.
-이미지의 특징을 강화or약화시킬 수 있다.
-이미지의 국소성을 이용하여 이미지의 특징을 뽑아낸다. 필터마다 뽑아내는 특징을 다르다.
-컨볼루션을 적용하면 이미지의 크기가 줄어든다.
-일반적인 이미제 데이터는 각 픽셀이 RGB의 3개을 가지며 이 RCB의 장수를 채널수라고 부르며 만약 흑백이라면 채널수는 1개이다.
-각 필터들은 입력 이미지와 같은 수의 채널 수를 가진다. 만약 input img가 채널수가 3이면 필터도 3개다.
-위에서 입력이미지의 필터에서 채널을 각각 컨볼루션을 적용시키면 각 필터마다 처리 수행된 이미지가 나온다. 이것의 갯수도 채널 수와 같다. 이렇게 생성된 이미지들은 편향을 더해서 활성화 함수에서 처리하며 나머지는 순전파의 과정과 같다.
-이미지를 여러 영역으로 구획하고 각 영역을 대표하는 값을 추출하여 새로운 이미지를 만든다. 이 과정을 풀링이라 한다.
-풀링 처리시 이미지가 축소된다.
-컨볼루션층와 풀링층의 몇 번 반복후에 이용되는 층이며 여기서 나온 값을 가지고 계산이 수행된다.
-입력받은 이미지를 벡터로 변환해준다.
-입력이미지를 둘러싸는 것을 패딩이라한다.
-제로 패딩: 이미지의 주위에 0픽셀로 둘러 싼다.
-패딩은 입력이미지를 둘러싸아주니 이미지의 크기가 늘어난다. 이것으로 컨볼루션 처리를 하면 이미지가 계속 작아지는 것을 막아준다.
-가장자리 데이터의 컨볼루션 적용횟수도 증가시켜주는 장점도 있다.
-컨볼루션에서 필터가 이동하는 간격이다.
-스트라이드가 증가하며 이동 간격도 증가하여 생성 이미지 크기가 작아진다.
-컨볼루션층에서 (입력이미지의 높이x입력이미지의 너비)의 입력이미지가 (배치사이즈x입력이미지 채널수)의 수만큼 존재한다. 즉 4차원 배열, 4차원 텐서이다.
-기본적으로 이미지상의 직사각형 영역을 행렬의 열로 변환한다.
-배치와 채널의 수를 고려하지 않은 경우 생성되는 행렬은 행은 (출력이미지의 높이x출력이미지의 너비)이며 열은 (필터의 높이x필터의 너비)이다.
-배치와 채널수를 고려하며 위에서 행에 배치사이즈가 곱해지며 열에는 채널 수가 곱해진다.
-필터도 (채널수x필터의 높이x필터의 너비)x(필터의 수)의 행렬로 나타낼수 있다.
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
# -- 손글씨 숫자 데이터 불러오기 --
digits_data = datasets.load_digits()
input_data = digits_data.data
correct = digits_data.target
n_data = len(correct)
# -- 입력 데이터 표준화 --
ave_input = np.average(input_data)
std_input = np.std(input_data)
input_data = (input_data - ave_input) / std_input
# -- 정답을 원-핫 인코딩으로 표현 --
correct_data = np.zeros((n_data, 10))
for i in range(n_data):
correct_data[i, correct[i]] = 1.0
# -- 훈련데이터와 테스트데이터 --
index = np.arange(n_data)
index_train = index[index%3 != 0]
index_test = index[index%3 == 0]
input_train = input_data[index_train, :] # 훈련 데이터 입력
correct_train = correct_data[index_train, :] # 훈련 데이터 정답
input_test = input_data[index_test, :] # 테스트 데이터 입력
correct_test = correct_data[index_test, :] # 테스트 데이터 정답
n_train = input_train.shape[0] # 훈련데이터 샘플 수
n_test = input_test.shape[0] # 테스트 데이터 샘플 수
# -- 각 설정값 --
img_h = 8 # 입력 이미지 높이
img_w = 8 # 입력 이미지 너비
img_ch = 1 # 입력 이미지 채널 수
wb_width = 0.1 # 가중치와 편향 설정을 위한 정규분포 표준편차
eta = 0.01 # 학습률
epoch = 50
batch_size = 8
interval = 10 # 경과표시 간격
n_sample = 200 # 오차 계산 샘플 수
def im2col(images, flt_h, flt_w, out_h, out_w, stride, pad):
n_bt, n_ch, img_h, img_w = images.shape
img_pad = np.pad(images, [(0,0), (0,0), (pad, pad), (pad, pad)], "constant")
cols = np.zeros((n_bt, n_ch, flt_h, flt_w, out_h, out_w))
for h in range(flt_h):
h_lim = h + stride*out_h
for w in range(flt_w):
w_lim = w + stride*out_w
cols[:, :, h, w, :, :] = img_pad[:, :, h:h_lim:stride, w:w_lim:stride]
cols = cols.transpose(1, 2, 3, 0, 4, 5).reshape(n_ch*flt_h*flt_w, n_bt*out_h*out_w)
return cols
def col2im(cols, img_shape, flt_h, flt_w, out_h, out_w, stride, pad):
n_bt, n_ch, img_h, img_w = img_shape
cols = cols.reshape(n_ch, flt_h, flt_w, n_bt, out_h, out_w).transpose(3, 0, 1, 2, 4, 5)
images = np.zeros((n_bt, n_ch, img_h+2*pad+stride-1, img_w+2*pad+stride-1))
for h in range(flt_h):
h_lim = h + stride*out_h
for w in range(flt_w):
w_lim = w + stride*out_w
images[:, :, h:h_lim:stride, w:w_lim:stride] += cols[:, :, h, w, :, :]
return images[:, :, pad:img_h+pad, pad:img_w+pad]
class ConvLayer:
#(self , 입력된 채널수 , 입력이미지 높이 , 입력이미지 폭 , 필터수 , 필터 높이 , 필터 너비 , 스트라이드 너비 , 패딩 너비)
def __init__(self , x_ch , x_h , x_w , n_flt , flt_h , flt_w , stride , pad):
self.params=(x_ch , x_h , x_w , n_flt , flt_h , flt_w , stride , pad)#파라미터들을 정리한다.
self.w= wb_width * np.random.randn(n_flt , x_ch , flt_h , flt_w )#필터의 초기값
self.b=wb_width * np.random.randn(1, n_flt)#편향의 초기값
self.y_ch = n_flt #출력되는 채널 수
self.y_h = (x_h - flt_h + 2*pad) // stride + 1 #출력 높이
self.y_w = (x_w - flt_w + 2*pad) // stride + 1 #출력 너비
#_____컨볼루션의 순전파 과정______#
#1.입력이미지를 행렬로 바꾼다 im2col
#2.필터와 im2col로 나온 행렬을 곱한다.
#3.편향을 더한다.
#4.활성화 함수로 ㄱㄱ
def forward(self, x):
n_bt = x.shape[0]
x_ch, x_h, x_w, n_flt, flt_h, flt_w, stride, pad = self.params#파라미터들을 받는다
y_ch, y_h, y_w = self.y_ch, self.y_h, self.y_w#파라미터들을 받는다.
#입렫된 이미지를 행렬로 바꾼다 im2col
self.cols = im2col(x, flt_h, flt_w, y_h, y_w, stride, pad)
self.w_col = self.w.reshape(n_flt, x_ch*flt_h*flt_w)
#필터와 im2col행렬 곱하고 전치한다음에 편향을 더해준다.
u = np.dot(self.w_col, self.cols).T + self.b
#출력의 형태로 만들어주고
self.u = u.reshape(n_bt, y_h, y_w, y_ch).transpose(0, 3, 1, 2)
#활성화 함수로 ReLu를 써준다.
self.y = np.where(self.u <= 0, 0, self.u)
#_____컨볼루션의 역전파 과정______#
def backward(self, grad_y):
n_bt = grad_y.shape[0]
x_ch, x_h, x_w, n_flt, flt_h, flt_w, stride, pad = self.params
y_ch, y_h, y_w = self.y_ch, self.y_h, self.y_w
#활성화 함수의 미분
delta = grad_y * np.where(self.u <= 0, 0, 1)#ReLu함수의 미분형태
delta = delta.transpose(0,2,3,1).reshape(n_bt*y_h*y_w, y_ch)#행렬로 변환
grad_w = np.dot(self.cols, delta)#필터의 기울기를 구하기 위해 먼저 행렬곱을 구한다.
self.grad_w = grad_w.T.reshape(n_flt, x_ch, flt_h, flt_w)
self.grad_b = np.sum(delta, axis=0)#기울기 구한다.
#입력의 기울기를 구한다.
grad_cols = np.dot(delta, self.w_col)
x_shape = (n_bt, x_ch, x_h, x_w)
self.grad_x = col2im(grad_cols.T, x_shape, flt_h, flt_w, y_h, y_w, stride, pad)
def update(self, eta):
self.h_w += self.grad_w * self.grad_w
self.w -= eta / np.sqrt(self.h_w) * self.grad_w
self.h_b += self.grad_b * self.grad_b
self.b -= eta / np.sqrt(self.h_b) * self.grad_b
class PoolingLayer:
#(self , 입력 채널수 , 이미지 높이 , 이미지 폭 , 풀링영역 크기 , 패딩 너비)
def __init__(self, x_ch, x_h, x_w, pool, pad):
#파라미터 정리
self.params = (x_ch, x_h, x_w, pool, pad)
self.y_ch = x_ch #출력 채널 수
self.y_h = x_h//pool if x_h%pool==0 else x_h//pool+1 #출력 높이
self.y_w = x_w//pool if x_w%pool==0 else x_w//pool+1 #출력 너비
def forward(self, x):
n_bt = x.shape[0]
x_ch, x_h, x_w, pool, pad = self.params
y_ch, y_h, y_w = self.y_ch, self.y_h, self.y_w
#im2col을 이용하여 입력 이미지를 행렬로 변환
cols = im2col(x, pool, pool, y_h, y_w, pool, pad)
cols = cols.T.reshape(n_bt*y_h*y_w*x_ch, pool*pool)#정치
#맥스풀링을 이용하여 각열의 최댓값을 구한다
y = np.max(cols, axis=1)
self.y = y.reshape(n_bt, y_h, y_w, x_ch).transpose(0, 3, 1, 2)
#나중에 역전파에서 사용하기 위해서 최대값 인덱스를 저장해둔다.
self.max_index = np.argmax(cols, axis=1)
def backward(self, grad_y):
n_bt = grad_y.shape[0]
x_ch, x_h, x_w, pool, pad = self.params
y_ch, y_h, y_w = self.y_ch, self.y_h, self.y_w
grad_y = grad_y.transpose(0, 2, 3, 1)
grad_cols = np.zeros((pool*pool, grad_y.size))
grad_cols[self.max_index.reshape(-1), np.arange(grad_y.size)] = grad_y.reshape(-1)
grad_cols = grad_cols.reshape(pool, pool, n_bt, y_h, y_w, y_ch)
grad_cols = grad_cols.transpose(5,0,1,2,3,4)
grad_cols = grad_cols.reshape( y_ch*pool*pool, n_bt*y_h*y_w)
x_shape = (n_bt, x_ch, x_h, x_w)
self.grad_x = col2im(grad_cols, x_shape, pool, pool, y_h, y_w, pool, pad)
#전결합층의 부모 클래스
class BaseLayer:
def __init__(self, n_upper, n):
self.w = wb_width * np.random.randn(n_upper, n)
self.b = wb_width * np.random.randn(n)
self.h_w = np.zeros(( n_upper, n)) + 1e-8
self.h_b = np.zeros(n) + 1e-8
def update(self, eta):
self.h_w += self.grad_w * self.grad_w
self.w -= eta / np.sqrt(self.h_w) * self.grad_w
self.h_b += self.grad_b * self.grad_b
self.b -= eta / np.sqrt(self.h_b) * self.grad_b
#은닉층
class MiddleLayer(BaseLayer):
def forward(self, x):
self.x = x
self.u = np.dot(x, self.w) + self.b
self.y = np.where(self.u <= 0, 0, self.u)
def backward(self, grad_y):
delta = grad_y * np.where(self.u <= 0, 0, 1)
self.grad_w = np.dot(self.x.T, delta)
self.grad_b = np.sum(delta, axis=0)
self.grad_x = np.dot(delta, self.w.T)
#전결합 출력층
class OutputLayer(BaseLayer):
def forward(self, x):
self.x = x
u = np.dot(x, self.w) + self.b
self.y = np.exp(u)/np.sum(np.exp(u), axis=1).reshape(-1, 1)
def backward(self, t):
delta = self.y - t
self.grad_w = np.dot(self.x.T, delta)
self.grad_b = np.sum(delta, axis=0)
self.grad_x = np.dot(delta, self.w.T)
Epoch:0/50 Error_train:2.313444925243301 Error_test:2.2732680735004114
Epoch:10/50 Error_train:0.06301925868300119 Error_test:0.11549727354094838
Epoch:20/50 Error_train:0.03182391916705235 Error_test:0.06491675693204245
Epoch:30/50 Error_train:0.02024532185366062 Error_test:0.07937429718617148
Epoch:40/50 Error_train:0.014171703376369495 Error_test:0.06639814055505816