오뚝이개발자

[CH4] 신경망 학습 본문

AI/밑바닥딥러닝1

[CH4] 신경망 학습

땅어 2020. 6. 19. 18:12
728x90
300x250

손실함수란?


신경망이 최적의 매개변수를 탐색해 학습할 수 있도록 해주는 지표

 

데이터 주도 학습


기계학습은 데이터가 생명이다. 데이터에서 답을 찾고, 특징을 추출해 패턴을 발견하는 방식.

 

신경망의 이점


모든 문제를 같은 맥락에서 풀 수 있다.

EX) 손글씨로부터 숫자 5를 인식하는 문제, '개'를 인식하는 문제 등.

 

Training data, Test data


  • training data : 최적의 매개변수를 찾기 위한 '학습'에 이용

  • test data : 학습한 모델의 '평가'에 이용

두 개를 나누는 이유 : 범용능력을 제대로 평가하기 위해

범용능력이란? 아직 보지 못한 데이터로도 문제를 올바르게 풀어내는 능력으로 기계학습의 최종 목표

 

손실함수 예시


1. 평균제곱오차(Mean Square Error, MSE)

MSE의 수식

위의 수식에서 y는 신경망 출력, t는 정답레이블, k는 데이터 차원 수이다.

EX) MNIST 손글씨 숫자 인식에서

y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]

t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]

y는 softmax로 출력한 확률값이고, t는 ont-hot encoding 방식으로 저장한 정답 레이블이다.

import numpy as np

def mean_squared_error(y, t):
    return 0.5 * np.sum((y-t)**2)


t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]

# 예시 1: 3번째 클래스일 확률이 가장 높다고 추정함
y1 = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
print(mean_squared_error(np.array(y1), np.array(t)))   # 0.0975...

# 예시 2: 8번째 클래스일 확률이 가장 높다고 추정함
y2 = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
print(mean_squared_error(np.array(y2), np.array(t)))   # 0.5975....

 

2. 교차 엔트로피 오차(Cross-entropy Error)

CEE 수식

수식에서의 log는 자연로그이다.

t가 원-핫 인코딩 형태의 레이블이므로, 전체 값은 정답일 때의 출력에 의해 결정된다.

자연로그 그래프

위는 자연로그의 그래프이다. x가 1일 때 y=0, x가 0에 가까워질수록 y는 작아지고 음의 무한대로 발산한다.

위의 수식 E에서 정답에 해당하는 출력 y_k가 커질수록 log값이 0에 가까워져 오차가 작아진다. 그러다 y_k가 1이 되면 오차는 0이 된다.

import numpy as np

def cross_entropy_error(y, t):
    delta = 1e-7
    return -np.sum(t*np.log(y+delta))   # np.log()함수에 0을 입력하면 -inf되므로 아주작은 수인 delta를 더해줌

t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]

# 예시 1: 3번째 클래스일 확률이 가장 높다고 추측
y1 = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
print(cross_entropy_error(np.array(y1), np.array(t)))   # 0.5108...

# 예시 2: 8번째 클래스일 확률이 가장 높다고 추측
y2 = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
print(cross_entropy_error(np.array(y2), np.array(t)))   # 2.3025...

 

데이터가 여러 개일 경우의 손실함수


 

데이터가 여러 개일 경우 손실함수

N은 데이터 수, nk는 n번째 데이터에서 k차원 째의 값이다.

 

미니배치(mini-batch) 학습


데이터가 많은 경우 모두에 대해 손실함수의 합을 구하려면 시간이 많이 소모된다.

이 때, 훈련 데이터로부터 일부만 골라 학습을 진행하는 것이 미니배치 학습이다.

import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist

(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)

print(x_train.shape)    # (60000, 784)
print(t_train.shape)    # (60000, 10)

train_size = x_train.shape[0]
batch_size = 10
# train_size미만의 수 중 batch_size만큼을 랜던하게 선택해 ndarray로 출력
batch_mask = np.random.choice(train_size, batch_size)
print(batch_mask) 
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]

 

왜 손실함수를 쓸까?


여기서 좀 근본적인 의문이 든다. 우리의 궁극적 목표는 '정확도'를 높이는 것이다. 그럼 정확도를 지표로 쓰면 되지 않나? 답은 NO이다. 이에 대해 설명하기 전에 신경망이 어떻게 학습을 하는지 살펴보자.

신경망은 학습 시 미분을 이용해 매개변수의 조정 방향을 선택한다. 가중치 매개변수를 조금 변화시켰을 때, 손실함수가 어떻게 변하는가를 측정하는 것이다. 그리하여 미분 값이 음수면 매개변수를 양의 방향으로 변화시키고, 양수라면 음의 방향으로 변화시켜 조정한다.

이제 정확도를 살펴보자. 일례로 한 신경망이 100장의 훈련 데이터 중 32장을 올바로 인식한다면 정확도는 32%이다. 매개변수를 약간만 조정해서는 정확도가 개선되지 않고 일정하게 유지된다. 혹, 개선된다 하더라도 그 값은 32.0132%처럼 연속적인 값이 아니라 33%, 34%처럼 불연속적인 값이다. 반면, 손실함수의 값은 0.9234...같은 수치로 나타나고, 매개변수의 값이 조금 변하면 그에 반응해 연속적인 수치로 나타난다.

결론적으로 정확도를 지표로 삼으면 매개변수의 미분이 대부분의 장소에서 0이 되기 때문에 학습의 방향을 조절해 가중치를 갱신하기에는 용이하지 않다.

 

경사하강법(Gradient descent method)


경사법이란 기울기를 활용해 함수의 최솟값(혹은 최댓값)을 찾으려는 최적화 방법이다.

최솟값을 찾으려는 것이 경사하강법, 최댓값을 찾으려는 것이 경사상승법이다.

주의) 기울기가 가리키는 곳이 정말 최솟값이라는 것이 보장되지 않는다.(saddle point나 plateau에 도달하게 될 수도 있으므로) 다만, 그 방향으로 나아가면 함수값을 줄일 수는 있다.

cf) saddle point : '안장점'으로 어느 방향에서 보면 극대, 다른 방향에서 보면 극소인 지점

     plateau : '고원'으로 그래프에서 평평한 지점

 

신경망에서의 기울기


신경망 학습에서의 기울기는 다음과 같이 계산된다. 가중치가 W, 손실함수가 L인 신경망을 생각해보자.

신경망 가중치의 기울기

 

'얼마나' 학습을 시킬 것인가?


위에 대한 답이 바로 학습률(learning rate)이다. 학습률이란 매개변수를 갱신하는 정도를 말한다. 수식으로 나타내면 아래와 같다.

학습률 수식

  학습률이

  • 너무 크면 => 큰 값으로 발산
  • 너무 작으면 => 거의 갱신되지 않은 채 학습이 끝남

따라서 적절한 값을 설정해주어야 한다!

cf) 가중치와 편향처럼 자동으로 학습되는 값이 아니라, 학습률과 같이 사람이 직접 설정해줘야 하는 매개변수를 하이퍼 파라미터라고 한다.

import numpy as np

# f(x0, x1) = x0^2 + x1^2 함수의 구현
def function_2(x):
    return x[0]**2 + x[1]**2

# 편미분을 이용한 기울기 계산
def numerical_gradient(f, x):
    h = 1e-4
    grad = np.zeros_like(x) # 형상이 x와 같고 모두 0 인 배열

    for idx in range(x.size):
        tmp_val = x[idx]
        
        # f(x+h) 계산
        x[idx] = tmp_val + h
        fxh1 = f(x)

        # f(x-h) 계산
        x[idx] = tmp_val - h
        fxh2 = f(x)

        grad[idx] = (fxh1 - fxh2) / (2*h)
        x[idx] = tmp_val

    return grad
# function_2 그래프의 (x0,x1) = (3, 4)에서의 기우릭
print(numerical_gradient(function_2, np.array([3.0, 4.0]))) #[6. 8.]

# 경사하강법
# f: 최적화하려는 함수 , x: 초깃값, lr: 학습률, step_num: 경사법에 따른 반복횟수
def gradient_descent(f, init_x, lr=0.01, step_num=100):
    x = init_x

    for i in range(step_num):
        grad = numerical_gradient(f, x)
        x -= lr*grad
    return x

print(gradient_descent(function_2, np.array([-3.0, 4.0]), 0.1, 100))    
# [-6.11110793e-10  8.14814391e-10]으로 결과값이 최솟값을 갖는 지점인(0,0)에 가깝게 나온다.

# 학습률이 너무 큰 예 : lr=10.0
print(gradient_descent(function_2, np.array([-3.0, 4.0]), 10.0, 100))
# [-2.58983747e+13 -1.29524862e+12]으로 발산해버림

# 학습률이 너무 작은 예 : lr=1e-10
print(gradient_descent(function_2, np.array([-3.0, 4]), 1e-10, 100))
# [-2.99999994  3.99999992]으로 거의 갱신되지 않고 학습 끝나버림

 

2층 신경망 클래스 구현


2층 신경망을 클래스로 만들어 gradient도 구현해보자. 아래의 세 코드는 깃헙 링크를 참조하길 바란다.

# 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

 

미니배치 학습 구현


# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
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)
train_loss_list = []

# 하이퍼파라미터
iters_num = 10000   #반복횟수
train_size = x_train.shape[0]
batch_size = 100    # 미니배치 크기
learning_rate = 0.1
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

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)

    # 매개변수 갱신
    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)

# train_loss_list의 값을 그래프로 나타내면 학습이 진행될 수록 손실함수가 감소하는 것을 볼 수 있다.

 

시험 데이터로 평가하기


# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
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)

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

train_size = x_train.shape[0]
batch_size = 100    # 미니배치 크기

# 1에폭당 반복 수
iter_per_epoch = max(train_size/batch_size, 1)

# 하이퍼파라미터
iters_num = 10000   #반복횟수
learning_rate = 0.1
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

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)

    # 매개변수 갱신
    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))

 

 

 

 

 

 

728x90
300x250

'AI > 밑바닥딥러닝1' 카테고리의 다른 글

[CH6] 학습 관련 기술들  (0) 2020.06.21
[CH5] 오차역전파법  (0) 2020.06.20
[CH3] 신경망  (0) 2020.06.13
[CH2] 퍼셉트론(Perceptron)  (0) 2020.06.10
[CH1] Numpy, Matplotlib 실습  (0) 2020.06.09
Comments