파이썬으로 딥러닝하기| CNN(Convolution Neural Network) Part2. 구현하기(FULL CODE)
안녕하세요~
2018년 올해도 벌써 약 3달 남지 않았네요~
컴퓨터가 2018이라는 필기체 숫자를 인식하려면 어떻게 해야할까요?
딥러닝을 공부한다면 꼭 해야하는 코스죠?
가장 많은 사랑받고 있는 MNIST 데이터 셋을 이용하여
CNN 신경망을 통한 이미지 인식 훈련을 해보도록 하겠습니다!
오늘은 python으로 구현한 CNN코드를 통해 알아보겠습니다.
아래의 코드는 크게 3가지 부분으로 나눌 수 있습니다.
1. 합성곱 신경망(CNN) class 구현하기
2. 훈련 & 테스트 하기
3. 그래프 그리기
1. 합성곱 신경망(CNN) class 구현하기
CNN 신경망은 단순한 합성곱 신경망으로 아래와 같이 구성하였습니다.
각각의 계층에 대해 궁금하시면 이론편을 먼저 보고오세요~
(각 계층과 활성함수는 자유롭게 구성할 수 있습니다!)
# 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 # 수치미분 함수
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from common.trainer import Trainer
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 #conv공식
pool_output_size = int(filter_num * (conv_output_size / 2) * (conv_output_size / 2)) #pooling공식
# 가중치 초기화
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. 훈련 & 테스트 하기
위에서 언급했던 것과 같이 아래의 코드는 MNIST 데이터 셋을 이용합니다!
그렇다면 머신러닝 입문자들은 왜 MNIST 데이터 셋을 이용하여 학습할까요?
우선, MNIST 데이터란?
필기(손으로 쓰여진 이미지) 숫자들의 그레이스케일 28x28 픽셀 이미지를 보고,
0부터 9까지 모든 숫자들에 대해 이미지가 어떤 숫자를 나타내는지 판별하는 것입니다.
출처: Tensorflowkorea
즉, 이미 가공된 데이터 셋으로 전처리가 크게 필요하지 않기때문에
쉽게 훈련시킬 수 있으며 정확도가 높게 나옵니다.
위에서 만든 합성곱 신경망(CNN)을 이용하여 MNIST 데이터 셋 훈련을 하겠습니다.
# 데이터 읽기
(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)
# 매개변수 보존
network.save_params("params.pkl")
print("Saved Network Parameters!")
# 하이퍼파라미터
iters_num = 10000 # 반복 횟수를 적절히 설정한다
train_size = x_train.shape[0] # 60000 개
batch_size = 100 # 미니배치 크기
learning_rate = 0.1
train_loss_list = []
train_acc_list = []
test_acc_list = []
# 1에폭당 반복 수
iter_per_epoch = max(train_size / batch_size, 1)
print(iter_per_epoch) # 600
for i in range(iters_num): # 10000
# 미니배치 획득 # 랜덤으로100개씩 뽑아서10000번을 수행하니까 백만번
batch_mask = np.random.choice(train_size, batch_size) # 100개 씩 뽑아서10000번 백만 번
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
# 기울기 계산
#grad = network.numerical_gradient(x_batch, t_batch)
grad = network.gradient(x_batch, t_batch)
# 매개변수 갱신
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learning_rate * grad[key]
# 학습 경과 기록
loss = network.loss(x_batch, t_batch)
train_loss_list.append(loss) # cost 가 점점 줄어드는것을 보려고
# 1에폭당 정확도 계산# 여기는 훈련이 아니라1에폭 되었을때 정확도만 체크
if i % iter_per_epoch == 0: # 600 번마다 정확도 쌓는다
print(x_train.shape) # 60000,784
train_acc = network.accuracy(x_train, t_train)
test_acc = network.accuracy(x_test, t_test)
train_acc_list.append(train_acc) # 10000/600 개 16개# 정확도가 점점 올라감
test_acc_list.append(test_acc) # 10000/600 개16개# 정확도가 점점 올라감
print("train acc, test acc | " + str(train_acc) + ", " + str(test_acc))
3. 그래프 그리기
위에서 만든 train과 test의 정확도를 비교하여
overfitting, underfitting이 발생하는지 확인하고
신경망의 효율과 정확도를 올리기위해 다양한 파라미터를 조정할 수 있습니다.
이 블로그의 포스팅 " 신경망의 효율과 정확도를 올리기위한 방법 "에서 확인할 수 있습니다.
# 그래프 그리기
markers = {'train': 'o', 'test': 's'}
x = np.arange(len(train_acc_list))
plt.plot(x, train_acc_list, label='train acc')
plt.plot(x, test_acc_list, label='test acc', linestyle='--')
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()
코드출처: R과 Python을 활용한 빅데이터 머신러닝 전문가 양성
오늘의 포스팅은 여기까지!!
궁금한 점은 댓글로 남겨주세요
Tensorflow2로 구현하는 방법도 포스팅했습니다!
현업에서 많이 사용하는 Python 모듈 | Tensorflow 2