본문 바로가기
Computer Science/Deep Learning

[비전공자용] [Python] 확률적 경사 하강법을 이용한 2층 신경망 미니배치 학습 구현

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

간단한 신경망 학습 구현을 위해

확률적 경사 하강법을 이용한

미니배치 학습 방법을

활용하는 예제입니다. 

 

 

이번 글에서는 MNIST 데이터셋을 사용하여 학습을 수행할 거고 신경망은 은닉층이 1개인 2층 신경망 Two Layer Net 클래스를 정의하여 이용할 것입니다. (아래 코드는 Deep Learning from Scratch의 코드를 참고하였습니다.)

시작하기에 앞서 본 글에서 이용할 방법인 신경망 학습의 4단계 절차를 정리해봅시다.
이 포스트에서는 Stochastic gradient descent(SGD, 확률적 경사 하강법)을 이용할 예정입니다.

(1) 미니배치
    훈련 데이터 중 일부를 무작위로 가져오고 이렇게 선별된 데이터를 비니배치라 하며, 미니매치의 손실 함수 loss function값을 줄이는 것이 신경망 학습의 목표다.

(2) 기울기 산출
    미니배치의 손실 함수 값을 줄이기 위해 각 가중치 매개변수의 기울기를 구한다. 기울기는 손실 함수의 값을 가장 작게 하는 방향으로 제시한다.

(3) 매개변수 갱신
    가중치 매개변수를 기울기 방향으로 조금씩 갱신한다.

(4) (1)에서 (3) 단계를 반복한다.

 

먼저 2층 신경망 클래스인 TwoLayerNet 클래스를 구현합니다.

# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
from common.functions import *
from common.gradient import numerical_gradient


class TwoLayerNet:

    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
        # 가중치 초기화
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
        self.params['b2'] = np.zeros(output_size)

    def predict(self, x):
        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']
    
        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        a2 = np.dot(z1, W2) + b2
        y = softmax(a2)
        
        return y
        
    # x : 입력 데이터, t : 정답 레이블
    def loss(self, x, t):
        y = self.predict(x)
        
        return cross_entropy_error(y, t)
    
    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        t = np.argmax(t, axis=1)
        
        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy
        
    # x : 입력 데이터, t : 정답 레이블
    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)
        
        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
        
        return grads
        
    def gradient(self, x, t):
        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']
        grads = {}
        
        batch_num = x.shape[0]
        
        # forward
        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        a2 = np.dot(z1, W2) + b2
        y = softmax(a2)
        
        # backward
        dy = (y - t) / batch_num
        grads['W2'] = np.dot(z1.T, dy)
        grads['b2'] = np.sum(dy, axis=0)
        
        da1 = np.dot(dy, W2.T)
        dz1 = sigmoid_grad(a1) * da1
        grads['W1'] = np.dot(x.T, dz1)
        grads['b1'] = np.sum(dz1, axis=0)

        return grads

 다음으로 주어진 MNIST 데이터셋을 2층 신경망으로 학습시키기 위한 코드를 구성합니다.

# 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 two_layer_net import TwoLayerNet

# 데이터 읽기
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)

network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

# 하이퍼파라미터
iters_num = 10000  # 반복 횟수를 적절히 설정한다.
train_size = x_train.shape[0]
batch_size = 100   # 미니배치 크기
learning_rate = 0.1

train_loss_list = []
train_acc_list = []
test_acc_list = []

# 1에폭당 반복 수
iter_per_epoch = train_size / batch_size

for i in range(iters_num):
    # 미니배치 획득
    batch_mask = np.random.choice(train_size, batch_size)
    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)
    
    # 1에폭당 정확도 계산
    if i % iter_per_epoch == 0:
        train_acc = network.accuracy(x_train, t_train)
        test_acc = network.accuracy(x_test, t_test)
        train_acc_list.append(train_acc)
        test_acc_list.append(test_acc)
        print("train acc, test acc | " + str(train_acc) + ", " + str(test_acc))

# 그래프 그리기
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()

 여기서 에폭 epoch은 하나의 단위를 말합니다. 1에폭은 학습에서 훈련 데이터를 모두 소진했을 때의 횟수에 해당합니다. 위의 코드에서 훈련 데이터 10,000개를 100개의 미니배치로 학습했으므로, 확률적 경사 하강법을 100회 반복하면 모든 훈련 데이터를 소진하게 되어 이 경우에는 100회가 1에폭이 된다고 할 수 있습니다.

1에폭마다 모든 훈련 데이터와 시험 데이터에 대한 정확도accuracy를 기록하여 그래프를 그려보면 다음과 같은 결과를 볼 수 있습니다. 

에폭 당 훈련 데이터와 테스트 데이터의 정확도 변화
에폭에 따른 훈련, 테스트 데이터의 정확도 변화

그래프에서 볼 수 있듯이 train accuracy와 test accuracy가 거의 동일하게 변화하는 양상을 보입니다. 이는 학습이 진행될수록(에폭이 증가할수록) 훈련 데이터와 테스트 데이터의 정확도가 증가하고 오버피팅은 일어나지 않는다는 것을 보여줍니다. 만약 오버피팅이 일어나면 훈련 데이터의 정확도는 계속 증가하는 양상을 보이는 반면, 테스트 데이터의 정확도는 증가하다가 점차 떨어지는 양상을 보였을 겁니다.

오버피팅이 발생하는 경우에는 학습을 조기 종료하거나 가중치 감소, 드롭아웃 등의 해결법으로 문제를 해결할 수 있습니다. 다음에 이 내용에 대해서도 다루려고 합니다.

 

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

728x90
반응형