여기서 학습이란 훈련 데이터로부터 가중치 매개변수의 최적값을 자동으로 획득하는 것을 뜻한다. 이번 포스팅에선 신경망이 학습할 수 있도록 해주는 지표인 손실함수(Loss Function)을 소개한다. 이 Loss function의 결과값을 가장 작게 만드는 가중치 매개변수(W)를 찾는 것이 학습의 목표이다. Loss의 값을 가급적 작게 만드는 기법으로, 함수의 기울기(미분)를 활용하는 경사법을 소개한다.
신경망의 특징은 데이터를 보고 학습할 수 있다는 점이다. 데이터에서 학습한다는 것은 가중치 매개변수의 값을 데이터를 보고 자동으로 결정한다는 뜻이다.
기계학습의 중심에는 데이터가 존재한다. 기계학습에서는 모아진 데이터로부터 규칙을 찾아내는 역할을 '기계'가 담당하며, 무로부터 알고리즘을 설계하는 것보다 효율이 높아 문제를 해결해야 하는 사람의 부담도 덜어준다.
이미지 분야에서의 데이터 학습에 대해 간략하게 훑어보자.
MNIST데이터 셋을 보면 사람의 필기체를 밑바닥부터 우리가 설계하는 대신, 주어진 데이터를 활용해서 해결하고 싶을 것이다. 해결방법 중 하나로, 이미지에서 특징(feature)을 추출하고 그 특징의 패턴을 기계학습 기술로 학습한다. 여기서 말하는 특징은 입력데이터(입력 이미지)에서 본질적인 데이터(중요한 데이터)를 정확하게 추출할 수 있또록 설계된 변환기를 가르킨다. 이미지의 특징은 보통 벡터로 기술하고, 컴퓨터 비전 분야에서는 SIFT, SURF, HOG 등의 특징을 많이 사용한다. 이런 특징을 사용하여 이미지 데이터를 벡터로 변환하고, 변환된 벡터를 가지고 지도 학습(Supervised Learning) 방식의 대표 분류 기법인 SVM(Support Vector Machine), KNN(k Nearest Neighbors)등으로 학습할 수 있다.
다만, 이미지를 벡터로 변환할 때 사용하는 특징은 여전히 '사람'의 개입이 들어가 설계된다. 이 말은 문제에 적합한 특징을 쓰지 않으면(or 특징을 설계하지 않으면)좀처럼 좋은 결과를 얻을 수 없다는 뜻이다.
위 이미지와 같이 신경망은 이미지를 "있는 그대로" 학습한다. 중간의 접근 방식(특징과 기계학습 방식)에서는 특징을 사람이 설계했지만, 신경망은 이미지에 포함된 중요한 특징까지도 '기계'가 스스로 학습한다.
신경망은 모든 문제를 주어진 데이터 그대로를 입력 데이터로 활용해 'end-to-end'로 학습할 수 있다.
기계학습에서 데이터 취급 시 주의할 점을 이야기해보자.
기계학습 문제는 데이터를 훈련 데이터(training data)와 시험 데이터(test data)로 나눠 학습과 실험을 수행하는 것이 일반적이다. 우선적으로 training data를 사용해 학습하면서 최적의 매개변수를 찾은 후, test data를 사용하여 앞서 훈련한 모델의 실력을 평가하는 것이다.
데이터를 나누는 이유는 범용적으로 사용할 수 있는 모델을 우리가 원하기 때문이다. 이 범용능력을 제대로 평가하기 위해서 데이터를 분리하는 것이다.
*범용능력이란 아직 보지 못한 데이터로도 문제를 올바르게 풀어내는 능력이다. 이 범용능력을 획득하는 것이 기계학습의 최종 목표이다.*
데이터셋 하나로만 매개변수의 학습과 평가를 수행한다면 올바른 평가가 될 수 없으며, 한 데이터셋에만 지나치게 최적화된 상태를 오버피팅(overfitting)이라고 한다.
신경망 성능의 "얼마나 구린지"를 나타내는 지표가 "손실함수(Loss function)"이다.
이 Loss값을 최소로 만드는 것이 신경망의 목표 중 하나이다. Loss function은 임의의 함수를 사용할 수도 있지만 일반적으로 오차제곱합과 교차 엔트로피 오차를 사용한다.
가장 많이 쓰이는 손실 함수는 오차제곱합 ( sum of squares for error, SSE)이며, 수식은 아래와 같다.
는 신경망의 출력(신경망이 추정한 값), 는 정답 레이블, 는 데이터의 차원 수를 나타낸다.
MNIST의 예에서 와 는 다음과 같은 원소 10개짜리 데이터이다.
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]
이 배열들의 원소는 첫번째 인덱스부터 순서대로 숫자 '0', '1', '2', ... 일때 값이며, 여기에서 신경망의 출력 y는 소프트맥스(softmax) 함수의 출력이다. 소프트맥스 함수의 출력은 확률로 해석할 수 있으므로, 이미지가 '0'일 확률은 0.1, '1'일 확률은 0.05, .... 이런식으로 해석이 된다. 이처럼 한 원소만 1로하고 그 외는 0으로 나타내는 표기법을 원-핫 인코딩(one-hot encoding)이라 한다.
오차제곱합을 파이썬 코드로 구현해보자.
def sum_squares_error(y, t):
return 0.5 * np.sum((y-t)**2)
와 는 넘파이 배열이다.
또 다른 Loss Function으로 교차 엔트로피 오차(cross entropy error, CEE)도 자주 이용한다. 수식은 아래와 같다.
여기서 는 밑이 인 자연로그()이다. 는 신경망의 출력, 는 정답 레이블이다.
교차 엔트로피를 구현해보자.
def cross_entropy_error(y, t):
delta = 1e-7
return -np.sum(t * np.log(y + delta))
, 는 넘파일 배열이다. 코드 마지막을 보면 delta를 더했다. 이는 np.log()함수에 0을 입력하면 마이너스 무한대(-infinite)가 되어 더 이상 계싼을 진행할 수 없는 상태가 된다. 아주 작은 값을 더해서 마이너스 무한대가 발생하지 않도록 사전에 방지한 것이다. 위에서 구현한 함수를 이용하여 간단한 계산을 해보자.
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]
cross_entropy_error(np.array(y), np.array(t))
y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
cross_entropy_error(np.array(y), np.array(t))
------------------------------------------------------------
0.510825457099338
2.302584092994546
지금까지 데이터 하나에 Loss Function만 생각해왔으니, 이제 training data에 대한 Loss Function의 값을 구하고, 그 값을 최대한 줄여주는 매개변수를 찾아야한다. 이렇게 하려면 모든 training data를 대상으로 Loss Function 값을 구해야 한다. 이제 training data 모두에 대한 손실 함수의 합을 구하는 방법을 생각해보자.
데이터가 개라면 는 번째 데이터의 번째 값을 의미한다. 수식이 복잡해 보이지만 데이터 하나에 대한 Loss Function인 위 식을 단순히 개의 데이터로 확장했을 뿐이다. 다만, 마지막에 으로 나누어 정규화함으로써 'average Loss Function(평균 손실 함수)'를 구하는 것이다. 이렇게 평균을 구해 사용하면 training data 개수와 관계없이 언제든 통일된 지표를 얻을 수 있다.
MNIST같은 데이터셋은 training data가 60,000개가 넘으며, 모든 데이터를 대상으로 Loss의 합을 구하려면 시간이 걸리며, 더 나아가 빅데이터 수준이 되면 그 수는 수백만에서 수천만도 넘는 거대한 값이 되기 마련이다. 이 많은 데이터를 대상으로 일일이 Loss값을 구하는 것을 현실적이지 않으니, 데이터 일부를 추려서 전체의 '근사치'로 이용하는 방법이 있다. 이 일부를 미니배치(mini-batch)라고 한다.
예를 들어, 60,000장의 training data 중 100장을 무작위로 뽑아 그 100장만 사용하여 학습하는 등의 학습 방법을 미니배치 학습이라고 한다.
미니배치 학습을 실습하기 위해 MNIST예제를 읽어오자.
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)
읽어온 데이터 중에서 무작위로 10장만 빼내려면 밑에 코드와 같이 batch_size
로 정해준후 np.random.choice
함수를 사용한다.
train_size = x_train.shape[0] #60000
batch_size = 10
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
이제 무작위로 선택한 이 인덱스를 사용하여 미니배치를 뽑아내기만 하면된다. Loss Function도 이 미니배치로 계산한다.
def cross_entropy_error(y, t):
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
batch_size = y.shape[0]
return -np.sum(t * np.log(y + 1e-7)) / batch_size
는 신경망의 출력, 는 정답 레이블이다. 가 1차원이라면, 데이터 하나당 교차 엔트로피 오차를 구하는 경우 reshape
함수로 데이터의 형상을 바꿔준다. 그리고 배치의 크기로 나눠 정규화하고 이미지 1장당 평균의 교차 엔트로피 오차를 계산한다.
정답레이블이 원-핫 인코딩이 아니라 '2'나 '7'등과 같이 숫자 레이블로 주어졌을 때 교차 엔트로피 오차는 밑에와 같이 구현할 수 있다.
def cross_entropy_error(y, t):
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
batch_size = y.shape[0]
return -np.sum(np.log( y[np.arange(batch_size), t] + 1e-7 )) / batch_size
원-핫 인코딩일 때 가 0인 원소는 교차 엔트로피 오차도 0이므로, 그 계산은 무시해도 좋다는 것이다. 다시 말해 정답에 해당하는 신경망의 출력만으로 교차 엔트로피 오차를 계산할 수 있다는 뜻이다.
왜 Loss Function을 사용해야 하는 걸까? 이 의문은 신경망 학습에서 '미분'의 역할에 주목하면 해결된다. 신경망 학습에서는 최적의 매개변수(가중치, 편향)를 탐색할 때 손실 함수의 값을 가능한 작게 하는 매개변수 값을 찾는다. 이때 매개변수의 미분(기울기)를 계산하고, 그 미분 값을 단서로 매개변수의 값을 서서히 갱신하는 과정을 반복한다.
정확도를 지표로 삼지 않는 이유는미분 값이 대부분의 장소에서 0이 되어 매개변수를 갱신할 수 없기 때문이다. 그럼 정확도를 지표로 삼으면 매개변수의 미분이 대부분의 장소에서 0이 되는 이유가 무엇일까?
한 신경망이 100장의 training data 중 32장을 올바르게 인식한다고 치자. 그렇다면 정확도는 32%이다. 만약 정확도가 지표였다면 가중치의 매개변수의 값을 조금 바꾼하 해도 정확도는 그대로 32%일 것이다.
반면 Loss Function을 지표로 삼았다면 매개변수의 값이 조금 변하면 그에 반응하여 Loss Function의 값도 연속적으로 변화한다.
이는 활성화 함수로 "계단 함수"를 사용하지 않는 이유와도 들어맞는다. 계단 함수에서의 미분은 대부분의 장소에서 0이다. 그 결과, 계단 함수를 이용하면 Loss Function을 지표로 삼는게 아무런 의미가 없어진다.
반면 시그모이드 함수의 미분(접선)은 출력이 연속적으로 변하고 곡선의 기울기도 연속적으로 변한다. 즉, 시그모이드 함수의 미분은 어느 장소라도 0이 되지는 않으며, 이는 신경망 학습에서 중요한 성질로, 기울기가 0이 되지 않는 덕분에 신경망이 올바르게 학습할 수 있다.