본문 바로가기

Deep Learning

신경망 학습 - 1

이번에는 신경망의 학습에 대해 알아보자.

 

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

손실함수의 결과 값을 가장 작게 만드는 가중치 매개변수를 찾는 것이 이번장의 목표인데, 이 기법으로 함수의 기울기를 활용하는 경사법을 소개한다.

1. 데이터에서 학습한다.

1-1. 데이터 주도 학습

머신러닝과 딥러닝의 학습 매커니즘의 차이는 feature 를 인간이 설계 하느냐 feature 까지도 기계가 설계 하느냐에 달려있다.

예를 들어, 이미지 인식이라고 하면 머신러닝은 이미지에서 feature 를 추출하고 그 특징의 패턴을 기계학습 기술로 학습하게 될 것이다. 여기서 말하는 feature 는 입력데이터에서 본질적인 데이터를 정확하게 추출할 수 있도록 설계된 변환기를 말한다.

 

이미지의 특징은 보통 벡터로 기술하고, 컴퓨터 비전 분야에서는 SIFT, SURF, HOG 등의 특징을 많이 사용한다.

이런 특징을 사용하여 이미지 데이터를 벡터로 변환하고, 변환된 벡터를 가지고 SVN, KNN 등의 기법으로 학습할 수 있다.

1-2. 훈련 데이터와 시험 데이터

기계학습 문제는 데이터를 훈련 데이터시험 데이터로 나눠 학습과 실험을 수행하는 것이 일반적이다.

 

우선 훈련 데이터만 사용하여 학습하면서 최적의 매개변수를 찾은 후, 시험 데이터를 사용하여 앞서 훈련한 모델의 실력을 평가하게 된다.

이때 주의할 점은, 데이터셋 하나로만 매개변수의 학습과 평가를 수행하면 올바른 평가가 될 수 없다.

 

수중에 데이터셋은 제대로 맞히더라도 다른 데이터셋에는 엉망인 일도 벌어지게 된다. 이렇게 한 데이터에만 지나치게 최적화된 상태를 오버피팅(overfitting) 이라고 한다.

2. 손실 함수

신경망에서는 현재의 상태를 하나의 지표 로 표현한다.

그리고 그 지표를 가장 좋게 만들어주는 가중치 매개변수의 값을 탐색하게 된다.

 

이때 사용하는 지표를 손실함수(loss function) 라고 한다. 이 손실 함수는 임의의 함수를 사용할 수도 있지만, 일반적으로는 오차제곱합 과 교차 엔트로피 오차 를 사용한다.

2-1. 오차제곱합

가장 많이 쓰이는 손실함수는 오차제곱합 sum of squares for error, SEE) 이며, 수식으로는 다음과 같다.

$$E=\cfrac{1}{2}\sum_{k}(y_k-t_k)^2$$

  • $y_k$ : 신경망의 출력 (신경망이 추정한 값)
  • $t_k$ : 정답 레이블 (정답을 가리키는
  • $k$ : 데이터의 차원 수
import numpy as np


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


"""
예제
정답이 2 이기 때문에 index 2 에 가장 확률이 높다고 추정한 결과의 손실 함수쪽 출력이 적다 (즉 오차가 적다)

반대로 index 7 의 확률이 가장 높다고 추정한 
"""

# 정답은 '2'
t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]

# 예1 : '2' 일 확률이 가장 높다고 추정함 (0.6)
y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
print(sum_squares_error(np.array(y), np.array(t)))  # 0.09750000000000003

# 예2 : '7'일 확률이 가장 높다고 추정함 (0.6)
y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
print(sum_squares_error(np.array(y), np.array(t)))  # 0.5975

2-2. 교차 엔트로피 오차

또 다른 손실함수로써 교차 엔트로피 오차(cross entropy error, CEE)도 자주 이용한다. 교차 엔트로피 오차의 수식은 다음과 같다.

$$E = -\sum_{k}t_klogy_k$$

  • $log$ : 밑이 $e$ 인 자연로그 ($log_e)$ 이다.
  • $y_k$ : 신경망의 출력
  • $t_k$ : 정답 레이블 (정답에 해당하는 인덱스의 원소만 1이고 나머지는 0 이다. 원-핫 인코딩)

위의 식은 정답일 때의 추정값 ($t_k$ 가 1 일때의 $y_k$) 의 자연로그를 계산하는 식이 된다.

 

또한, 교차 엔트로피 오차는 정답일 때의 출력이 전체 값을 결정하게 된다. (원-핫 인코딩에서 정답에 해당하는 인덱스가 아닌경우 $t_k$ 값이 0이 되기 때문에 그렇다)

 

 

위 자연 로그 그래프를 살펴보자. 위의 자연로그 그래프에서 보듯이 $x$ 가 1 일때 $y$ 는 0이 되고, $x$ 가 0에 가까워질수록 $y$ 의 값은 점점 작아진다.

 

이 그래프와 마찬가지로 위의 식도 정답에 해당하는 출력이 커질수록 0에 다가가고, 반대로 정답일 때의 출력이 작아질수록 오차는 커진다.

 

import numpy as np
import matplotlib.pylab as plt

# 자연로그 그래프 그리기
x = np.arange(0.0, 1.0, 0.01)
y = np.log(x + 1e-7)
plt.ylim(-5, 0)
plt.plot(x, y)
plt.show()


# np.log() 함수에 0을 입력하면 -INF가 되어 더 이상 계산을 진행할 수 없기 때문에,
# 아주 작은 값을 더해서 절대 0이 되지 않도록 한다.
def cross_entropy_error(y, t):
    delta = 1e-7
    return -np.sum(t * np.log(y + delta))


# index 2 가 정답
t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
y = [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(y), np.array(t)))  # 0.510825457099338


y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
print(cross_entropy_error(np.array(y), np.array(t)))  # 2.302584092994546

 

2-3. 미니배치 학습

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

예를 들어, 교차 엔트로피 오차는 다음식으로 표현된다.

$$E=\cfrac{1}{N}\sum_n\sum_kt_{nk}logy_{nk}$$

  • 데이터가 $N$ 개 일 경우
  • $t_{nk}$ : $n$ 번째 데이터의 $k$ 번째 값 (정답 레이블)
  • $y_{nk}$ : 신경망의 출력

그리고 마지막에 N 으로 나눔으로써 정규화 하고 있다. 이렇게 평균을 구해 사용하면 훈련 데이터 개수와 상관없이 언제든 통일된 지표를 얻을 수 있다.

 

또한 만약에 훈련 데이터셋이 거대할 경우, 데이터 일부를 추려 전체의 근사치 로 이용할 수도 있다. 이러한 학습 방법을 미니배치 학습 이라고 한다.

 

미니배치 구현을 위해서는 넘파이의 np.random.choice() 를 이용할 수 있다. 가령 np.random.choice(60000, 10) 은 0 이상 60000 미만의 수 중에서 무작위로 10개를 골라낸다.

 

그 후, 배치 데이터를 지원하는 교차 엔트로피 오차를 구현하면 된다.

 

import sys, os

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

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

print(x_train.shape)  # (60000, 784) -> 훈련 데이터 60000 개, 입력 데이터 784 열
print(t_train.shape)  # (60000, 10) -> 정답 레이블 60000 개, 정답 데이터 10 열

train_size = x_train.shape[0]
batch_size = 10
# 0~60000 사이의 수 중 무작위로 10개를 골라낸다. (훈련 데이터 중 10개를 무작위로 골라내는 의미이다.)
batch_mask = np.random.choice(train_size, batch_size)

print(batch_mask)  # [19983  5895 16784 16337 26519  1102 43928 44010 44903 57550] -> random array

x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]

# get y_batch
network = init_network()
y_batch = predict(network, x_batch)


def cross_entropy_error(y, t):
    #  1 차원 배열인 경우, 배열의 형상을 바꿔준다.
    # 예를 들어, t = [0,0,1,0,0] 이라면 t.reshape(1,1) 을 실행하면 t=[[0,0,1,0,0]] 과 같은 2차원 배열이 된다.
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)

    delta = 1e-7
    batch_size = y.shape[0]
    return -np.sum(t * np.log(y + delta)) / batch_size


print(cross_entropy_error(y_batch, t_batch))  # 3.1737068235874175

 

2-5. 왜 손실 함수를 설정하는가?

그렇다면 우리는 왜 굳이 손실함수를 이용해야 할까? 정확도 라는 지표를 놔두고 손실 함수의 값 이라는 우회적인 방법을 택하는 이유가 있는 것일까?

 

이 의문은 신경망 학습에서의 미분 의 역할에 주목해야 한다. 신경망 학습에서는 최적의 매개변수 (가중치와 편향) 을 탐색할 때 손실 함수의 값을 가능한 한 작게 하는 매개변수 값을 찾는다.

 

이때, 매개변수의 미분을 계산하고, 그 미분값을 단서로 매개변수의 값을 서서히 갱신하는 과정을 반복하게 된다.

그런데 정확도를 지표로 하면, 미분이 대부분의 장소에서 0이 되어 버리기 때문에 매개변수를 갱신할 수 없게 된다.

 

 

참조

밑바닥부터 시작하는 딥러닝

'Deep Learning' 카테고리의 다른 글

신경망-2 (Neural Network)  (0) 2021.01.31
신경망-1 (Neural Network)  (0) 2021.01.28
Perceptron (퍼셉트론)  (0) 2021.01.17