본문 바로가기
Computer Science/Deep Learning

[비전공자용] [Python] CNN 합성곱 신경망 구현하기

by 롱일스 2020. 7. 28.
반응형

자자 이제 지금까지 공부했던 합성곱 계층과 풀링 계층을 이용해서 손글씨 숫자를 인식하는 CNN 합성곱 신경망을 본격적으로 구현해보도록 하겠습니다!!!!!!

합성곱 계층과 풀링 계층에 대해 개념적으로 공부하고 싶으신 분들은 아래 포스트를 참고하세요.

2020/07/10 - [Computer Science/Deep Learning] - [비전공자용]합성곱 신경망 (CNN) - 합성곱 계층 & 풀링 계층

아래 구조의 계층으로 구성되어 있는 CNN을 SimpleConvNet이라는 클래스로 구성해봅시다. 

입력데이터 ----->

Convolution -> ReLU -> Pooling  ----->

Affine -> ReLU ----->

Affine -> Softmax ------> 결과

SimpleConvNet 신경망을 Python으로 구성한 코드를 먼저 보고 세세히 살펴봅시다. 

# 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)]

predict 메소드는 초기화 때 layers에 추가한 계층을 맨 앞에서부터 차례로 forward 메소드를 호출하며 결과를 다음 계층에 전달합니다.

loss 메소드는 predict 메소드의 결과를 인수로, 마지막 층의 forward 메소드를 호출합니다. 첫 계층부터 마지막 계층까지 forward를 처리합니다.

나머지는 코드의 주석을 자세히 보면 이해가 가리라 생각합니다.

이렇게 합성곱 신경망을 구현했으니 MNIST 데이터셋을 학습해보려고 합니다. 

MNIST 데이터셋 학습을 위한 Python 코드입니다. 아래 링크를 통해 이전 포스트 설명을 참고하세요~

2020/07/08 - [Computer Science/Deep Learning] - 오차역전파법 Backpropagation 신경망 구현

# 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()

 위 코드를 실행하면 아래의 결과를 얻을 수 있습니다.

훈련 데이터에 대한 정확도는 99%, 시험 데이터에 대한 정확도는 95.83%가 되는 걸 확인할 수 있습니다. 이렇게 간단한 합성곱 신경망으로 학습시킨건데 이 정도 결과면 시험 데이터에 대한 정확도가 굉장히 높은 편이라고 볼 수 있습니다. 

소스 코드는 Deep learning from scratch의 github 에서 자세히 확인하실 수 있습니다. 
https://github.com/oreilly-japan/deep-learning-from-scratch/

 

합성곱 신경망 구현을 위해 합성곱 계층과 풀링 계층에 대해 공부하실 분들은 아래 포스트를 참고해주세요~

2020/07/28 - [Computer Science/Deep Learning] - [비전공자용] [Python] CNN(합성곱 신경망) - 풀링 계층 구현

 

[비전공자용] [Python] CNN(합성곱 신경망) - 풀링 계층 구현

이번 포스트에서는 지난 포스트(CNN-합성곱 계층 구현)에 이어서 CNN에서의 풀링 계층을 Python으로 구현해보려 합니다. 2. 풀링 계층 구현 합성곱 계층과 마찬가지로 풀링 계층에서도 im2col 함수를 �

huangdi.tistory.com

2020/07/28 - [Computer Science/Deep Learning] - [비전공자용] [Python] CNN(합성곱 신경망) - 합성곱 계층 구현

 

[비전공자용] [Python] CNN(합성곱 신경망) - 합성곱 계층 구현

이전 포스트에서 합성곱 계층과 풀링 계층에 대해 이론적으로 살펴보았습니다. 지금부터 두 계층을 직접 Python 코드로 구현하고자 합니다. 1. 합성곱 계층 구현 합성곱 계층에서는 image to column (��

huangdi.tistory.com

[출처] Deep Learning from Scratch, ゼロ から作る

728x90
반응형