본문 바로가기
  • 비둘기다
  • 비둘기다
  • 비둘기다
AI/Deep learning from Scratch

[머신러닝, 딥러닝] 신경망 학습 (4) - 학습 알고리즘 구현

by parzival56 2023. 2. 13.

이전까지 다뤘던 개념들을 하나로 정리한 2층 신경망의 학습 코드입니다.

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

이전 페이지에서 언급한 함수들이 많아서 특별한 건 없습니다. 

순서대로 보면 생성자에서 매개변수는 순서대로 입력층의 크기, 은닉층의 크기, 출력층의 크기입니다.

그 밑의 과정은 가중치를 정해주는 과정인데 이를 딕셔너리와 랜덤 메서드를 사용해서 무작위로 설정해 주는 것을 확인할 수 있습니다. 데이터를 무작위로 설정하기 때문에 이를 확률적 경사 하강법이라고 부를 수 있습니다.

 

W와 b는 각각 가중치와 편향에 해당하는 변수이고 숫자는 층을 의미합니다.

위 학습은 MNIST 데이터를 기반으로 할 것이기 때문에 변수 x는 이미지 데이터입니다.

 

예를 들자면 MNIST는 28x28이기 때문에 입력층의 크기는 784, 출력층은 10개만을 받는다고 하면 10, 은닉층은 적당한 숫자를 넣어주면 됩니다. 

 


미니 배치 학습 구현하기

 

이전에 다룬 확률적 경사 하강법에 미니 배치를 추가하여 알아보겠습니다.

미니 배치는 이전에도 나오듯 원하는 크기의 배치만큼의 데이터만을 다룬다는 개념입니다. 

 

코드부터 살펴보겠습니다.

import numpy as np
import matplotlib.pyplot as plt
from mnist import load_mnist

# 데이터 읽기
(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 = max(train_size / batch_size, 1)

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

위 코드에는 이 페이지에 가장 윗쪽에 있는 TwoLayerNet에 있는 함수들을 사용합니다.

 

훈련 데이터, 레이블, 정답 데이터, 레이블을 입맛에 맞게 설정한 후, 각 층의 크기를 설정해 줍니다. 

여기서는 미니 배치의 크기를 100으로 하였습니다. 60000개의 데이터 중 100개 만을 추려서 이 미니 배치를 대상으로 확률적 경사 하강법을 수행하여 매개변수를 갱신합니다. 경사법에 의한 갱신 횟수를 10000번으로 설정하고, 갱신할 때마다 훈련 데이터에 대한 손실함수를 계산하고, 그 값을 배열에 추가합니다. 

 

위에서는 출력에 변화를 조금 주었습니다. 원래는 오차가 줄어드는 그래프를 보여주는 것이 일반적이나 위에서는 정확도가 점점 올라가는 그래프를 출력하게 설절하였습니다.

 

이런 대량의 데이터를 다룰 때 발생할 수 있는 것이 오버피팅인데 문제의 발생 유무는 출력을 통해 확인할 수 있습니다.

실제 위 코드의 출력인데 글자로 출력되는 정확도가 급증하고 그래프에서도 이를 확인할 수 있습니다.

오버피팅 문제는 그래프의 실선과 점선으로 확인할 수 있는데 만약 훈련 데이터에만 학습이 집중되어 있으면 위와 같이 그래프가 겹쳐서 나타나지 않을 것입니다. 왜냐하면 실제 데이터와 훈련 데이터가 다를 시 급격히 정확도가 떨어질 것이기 때문입니다. 그러나 위 출력은 그렇지 않기 때문에 오버피팅 문제는 피한 것을 알 수 있습니다. 

 


이상으로 손실함수, 미니 배치, 기울기, 경사 하강법을 이용하여 신경망의 학습에 대해 알아봤습니다.

댓글