본문 바로가기
ML & DL/책 & 강의

[밑시딥 1] CHAPTER 4 신경망 학습

by 공부하는 무니 2023. 6. 15.
반응형

학습이란 훈련 데이터로부터 가중치 매개변수의 최적값을 자동으로 획득하는 것

신경망이 학습할 수 있도록 해주는 지표인 손실 함수에 대해 배운다.

4.1 데이터에서 학습한다!

데이터에서 학습한다는 것은 가중치 매개변수의 값을 데이터를 보고 자동으로 결정한다는 뜻이다.

4.1.1 데이터 주도 학습

손글씨 숫자 '5'의 예: 사람마다 자신만의 필체가 있다

'5'를 제대로 분류하는 프로그램을 직접 고안해 설계하기란 어렵다. 이미지에서 특징을 추출하고 그 특징의 패턴을 기계학습 기술로 학습시키는 방법을 사용할 수 있다. 다만 이때 그 특징은 여전히 사람이 설계해야 한다. 반면 신경망(딥러닝)을 활용하면 사람의 개입없이도 학습이 가능하다.

규칙을 '사람'이 만드는 방식에서 '기계'가 데이터로부터 배우는 방식으로의 패러다임 전환: 회색 블록은 사람이 개입하지 않음을 뜻한다.

4.1.2 훈련 데이터와 시험 데이터

훈련 데이터(training data): 훈련데이터를 사용하여 학습하면서 최적의 매개변수(파라미터)를 찾는다.

시험 데이터(test data): 시험데이터를 사용하여 앞서 훈련한 모델의 실력을 평가한다. 범용 능력을 제대로 평가하기 위해서 훈련 데이터와 시험 데이터를 분리한다.

범용능력은 아직 보지 못한 데이터에서도 올바르게 문제를 풀어내는 능력을 말한다. 훈련 데이터 셋에 지나치게 최적화된 상태를 오버피팅이라고 한다. 오버피팅을 피하는 과제는 기계학습의 중요한 과제이기도 하다.

4.2 손실 함수

신경망은 '하나의 지표'를 기준으로 최적의 매개변수 값을 탐색한다. 신경망 학습에서 사용하는 지표는 손실 함수(loss function)이다. 이 손실 함수는 임의의 함수를 사용할 수도 있지만, 일반적으로는 오차제곱합과 교차 엔트로피 오차를 사용한다. 

4.2.1 오차제곱합

가장 많이 쓰이는 손실 함수는 오차제곱합이다.

여기서 y들은 신경망의 출력(신경망이 추정한 값), t들은 정답레이블, k는 데이터의 차원 수를 나타낸다.

예를 들어 '3.6 손글씨 숫자 인식'에서는 아래와 같이 나타낼 수 있다.

위의 t처럼 한 원소만 1로 하고 그 외는 0으로 나타내는 표기법을 원-핫 인코딩이라고 한다.

파이썬으로 구현해보자.

t 가 정답, y가 예측한 값이었다.

첫 번째 추정 결과가 오차제곱합이 더 작으니 정답에 더 가까울 것으로 판단할 수 있다.

4.2.2 교차 엔트로피 오차

y는 신경망의 출력, t는 정답 레이블. 이때 t는 정답에 해당하는 인덱스 원소만 1이고 나머지는 0이다.(원-핫 인코딩) 따라서 실질적으로 정답일 때의 추정(t가 1일때의 y)의 자연로그를 계산하는 식이 된다.

자연로그 y = logx의 그래프

x가 1일 때 y는 0이 되고, x가 0에 가까워질 수록 y의 값은 점점 작아진다. 마찬가지로 교차 엔트로피 오차 식도 정답에 해당하는 출력이 커질수록 0에 다가가고, 그 출력이 1일 때 0이 된다. 반대로 정답일 때의 출력이 작아질수록 오차는 커진다. 

교차 엔트로피 오차를 구현해보자.

여기서 np.log를 계산할 때 아주 작은 값인 delta를 더했다. 이 이유는 np.log() 함수에 0을 입력하면 마이너스 무한대가 되어서 더 이상 계산을 할 수 없기 때문이다. 

마찬가지로 첫번째 추정에서 오차값이 작으므로 더 정답일 가능성이 높다고 판단할 수 있다. 앞서 오차제곱합의 판단과 일치한다.

4.2.3 미니배치 학습

지금까지 데이터 하나에 대한 손실 함수만 생각해왔는데, 이제 훈련 데이터 모두에 대한 손실 함수의 합을 구하는 방법을 생각해보자.

예를 들어 교차 엔트로피 오차는 아래 식과 같다.

이때 데이터가 N개라면 tnk는 n번째 데이터의 k번째 값을 의미한다. N으로 나눔으로써 '평균 손실 함수'를 구하고 있다.

그런데 MNIST 데이터셋은 훈련 데이터가 6만개였다! 그래서 모-든 데이터를 대상으로 손실 함수의 합을 구하려면 시간이 많이 걸린다. 더 나아가서 빅데이터 수준이 되면, 데이터 수는 수백만에서 수천만도 넘어가게 되므로 이 많은 데이터를 대상으로 일일이 손실 함수를 계산하는 것은 현실적이지 않다. 따라서 신경망에서는 훈련 데이터로부터 일부만 골라 학습을 수행한다. 이때 고른 일부를 미니배치(mini-batch)f라고 한다. 그리고 이러한 학습 방법을 미니배치 학습이라고 한다.

4.2.4 (배치용) 교차 엔트로피 오차 구현하기

미니배치 같은 배치 데이터를 지원하는 교차 엔트로피 오차 구현

4.2.5 왜 손실 함수를 설정하는가?

왜 정확도라는 지표를 안쓰고 손실함수를 지표로 쓸까? 신경망 학습에서의 '미분'의 역할에 주목하면 된다. 정확도를 지표로 삼아서는 안되는 이유는 미분 값이 대부분의 장소에서 0이 되어 매개변수를 갱신할 수 없기 때문이다. 정확도는 매개변수의 작은 변화에는 반응을 보이지 않고, 반응이 있더라도 불연속적으로 갑자기 변하기 때문이다. 이는 '계단 함수'를 활성화 함수로 사용하지 않는 이유와도 들어맞는다. 

계단 함수와 시그모이드 함수: 계단 함수는 대부분의 장소에서 기울기가 0이지만, 시그모이드 함수의 기울기(접선)은 0이 아니다.

4.3 수치 미분

4.3.1 미분

  • 위 방식은 h가 너무 작아 제대로 계산이 안되는 문제가 있음
  • 해결책: 중심 차분/중앙 차분 이용

경사법에 의한 f(x0, x1) = x0**2 + x1**2의 갱신 과정: 점섬은 함수의 등고선을 나타낸다.

4.4.2 신경망에서의 기울기

손실 함수의 기울기

import sys, os
import numpy as np
from common.functions import softmax, cross_entropy_error
from common.gradient import numerical_gradient

class simpleNet:
  def __init__(self):
    self.W = np.random.randn(2, 3)
    
  def predict(self, x):
    return np.dot(x, self.W)
    
  def loss(self, x, t):
    z = self.predict(x)
    y = softmax(z)
    loss = cross_entropy_error(y, t)
    
    return loss

net = simpleNet()
print(net.W) # dummy W 확인

x = np.array([0.6, 0.9])
p = net.predict(x)
print(p) 

np.argmax(p)

t = np.array([0, 0, 1]) 
net.loss(x,t) # 현재 세팅된 값 기준으로 loss 함수 실행해 보기

 

4.4 기울기

4.4.1 경사법(경사 하강법)

경사법을 수식으로 나타내보자

4.5 학습 알고리즘 구현하기

  • 신경망 학습 절차
    • 1단계 - 미니배치
    • 2단계 - 기울기 산출
    • 3단계 - 매개변수 갱신
    • 4단계 - 반복

4.5.1 2층 신경망 클래스 구현하기

import sys, os
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

4.5.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 = 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()

손실 함수 값의 추이: 왼쪽은 10,000회 반복까지의 추이, 오른쪽은 1,000회 반복까지의 추이

학습 횟수가 늘어가면서 손실 함수의 값이 줄어들고 있다. 이는 학습이 잘 되고 있다는 것이고, 신경망의 가중치 매개변수가 데이터에 적응하고 있다는 것을 의미한다.

4.5.3 시험 데이터로 평가하기

위에서 손실 함수가 서서히 내려가는 것을 확인했는데 여기서 손실함수의 값은 정확히 말하면 '훈련 데이터의 미니배치에 대한 손실 함수'의 값이다. 이 로스값이 서서히 내려가고 있다는 것은 신경망이 잘 학습하고 있다는 것의 방증이지만 오버피팅이 된 건 아닌지 확인해보아야 한다. 오버피팅을 확인하려면 학습 도중 정기적으로 훈련 데이터와 시험 데이터를 대상으로 정확도를 기록해서 모니터링 해야 한다.

훈련 데이터와 시험 데이터에 대한 정확도 추이

여기서는 오버피팅이 안일어났다. 만약 오버피팅이 일어난다면 어느 순간부터 시험 데이터에 대한 정확도가 점차 떨어지기 시작한다. 

4.6 정리

  • 기계학습에서 사용하는 데이터셋은 훈련 데이터와 시험 데이터로 나눠 사용한다.
  • 훈련 데이터로 학습한 모델의 범용 능력을 시험 데이터로 평가한다.
  • 신경망 학습은 손실 함수를 지표로, 손실 함수의 값이 작아지는 방향으로 가중치 매개변수를 갱신한다.
  • 가중치 매개변수를 갱신할 때는 가중치 매개변수의 기울기를 이용하고, 기울어진 방향으로 가중치의 값을 갱신하는 작업을 반복한다.
  • 아주 작은 값을 주었을 때의 차분으로 미분하는 것을 수치 미분이라고 한다.
  • 수치 미분을 이용해 가중치 매개변수의 기울기를 구할 수 있다.
  • 수치 미분을 이용한 계산에는 시간이 걸리지만, 그 구현은 간단하다. 한편, 다음 장에서 구현하는 (다소 복잡한) 오차역전파법은 기울기를 고속으로 구할 수 있다.
반응형

댓글