밑바닥부터 시작하는 딥러닝 #3-5 학습 알고리즘 구현

jakeseo_me·2020년 3월 25일
0

밑바닥부터 시작하는 딥러닝 #3-5 학습 알고리즘 구현

Prologue

나는 여전히 웹개발자이지만, 회사에 부쩍 딥러닝 과제가 많아져서 딥러닝 공부가 많이 필요하게 됐다. 밑바닥부터 시작하는 딥러닝 책이 좋다고 해서 천천히 하나하나 공부해보려 한다.

학습 알고리즘 구현 개요

  • 전제
    • 신경망에는 적응 가능한 가중치와 편향이 있고, 이 가중치와 편향을 훈련 데이터에 적응하도록 조정하는 과정을 '학습'이라 합니다.
  1. 미니배치
    • 훈련 데이터중 일부를 무작위로 가져와서 미니배치를 만듭니다.
  2. 기울기 산출
    • 미니배치의 가중치 매개변수의 기울기를 구합니다. 기울기는 손실함수의 값을 가장 작게 하는 방향을 가리킵니다.
  3. 매개변수 갱신
    • 가중치 매개변수를 기울기 방향으로 아주 조금(LR에 비례해) 갱신합니다.
  4. 반복
    • 1~3단계를 반복합니다.

위 단계들은 경사 하강법으로 매개변수를 갱신하는 방법입니다. 미니배치를 랜덤으로 선정하기에 확률적 경사 하강법(stochastic gradient descent, SGD) 이라 불립니다.

2층 신경망 클래스 구현해보기

import sys, os
import numpy as np
sys.path.append(os.pardir)
from common.functions import *
from common.gradient import numerical_gradient

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def softmax(x):
    c = np.max(x) # 오버플로 방지 대책
    exp_x = np.exp(x - c) # single source of truth
    return exp_x / np.sum(exp_x)

# 1개의 값을 위한 CEE
def cross_entropy_error(x, t):
    delta = 1e-7
    return -np.sum(t * np.log(x + delta))

# 배치를 위한 CEE
def cross_entropy_error_batch(x, t):
    delta = 1e-7
    
    if x.dim == 1:
        x = x.reshape(1, x.size)
        t = t.reshape(1, t.size)
        
    batch_size = x.shape[0]
    return -np.sum(t * np.log(x + delta)) / batch_size

class TwoLayerNet:
    
    # 신경망의 초기에서 입력 크기, 은닉 크기, 출력 크기, std의 가중치 초기를 구함
    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['W2'] = weight_init_std * \
                            np.random.randn(hidden_size, output_size)
        # 편향은 일단 0으로 초기화
        self.params['b1'] = np.zeros(hidden_size)
        self.params['b2'] = np.zeros(output_size)
        
    # 신경망을 거쳐 예측하는 함수, 분류 문제의 결과로 softmax된 값을 반환함
    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
    
    # 손실함수 값 구하는 함수
    def loss(self, x, t):
        y = self.predict(x) # 신경망 거쳐온 결과 값
        return cross_entropy_error_batch(y, t) # 손실함수의 값을 구함 -> 미분 필요
    
    # 정확도 구하는 함수
    def accuracy(self, x, t):
        y = self.predict(x) # 신경망 다 거쳐서 softmax화 된 값
        
        y = np.argmax(y, axis=1) # 예측 정답 인덱스
        t = np.argmax(t, axis=1) # 실제 정답 인덱스
        
        accuracy = np.sum(y == t) / float(x.shape[0]) # 맞는 정답의 개수 / 전체 문제의 개수
        return accuracy
    
    # 기울기 구하는 함수
    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t) # 가중치를 받아서 loss를 구하는 함수의 결과
        
        # 손실함수에 대해 기울기(각 가중치의 편미분 벡터)를 구함
        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

나중에는 수치미분을 직접하지 않고, 오차역전파법이라는 방법을 통해 수치미분과 거의 같은 결과를 훨씬 빠르게 얻는 방법을 알게 될 것입니다.

미니배치 학습 구현

미니배치 학습은 데이터 일부(미니)를 무작위로 꺼내고, 그 미니배치에 대해 경사법으로 매개변수를 갱신하여 학습하는 것을 말합니다.

# 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 = 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()
profile
대전에 있는 (주) 아이와즈에서 풀스택 웹개발자로 일하고 있는 서진규입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. Javascript를 좋아합니다.

0개의 댓글