해당 포스팅은 '밑바닥부터 시작하는 딥러닝1'을 공부하고 정리, 요약한 글입니다. 모든 내용은 해당 도서 기준입니다.
◾ 4.1 데이터에서 학습
이번 장의 주제는 신경망의 학습이다. 여기서 학습이란 훈련 데이터로부터 가중치 매개변수의 최적값을 자동으로 획득하는 것을 의미한다.
또, 신경망의 특징은 데이터를 보고 학습할 수 있다는 점이다.
▪️ 4.1.1 데이터 주도 학습
기계학습은 데이터가 생명이다. 데이터에서 답을 찾고 데이터에서 패턴을 발견하고 데이터로 이가리르 만드는 것이 기계학습이다. 그래서 기계학습의 중심에는 데이터가 존재한다.
기계학습에서는 사람의 개입을 최소화하고 수집한 데이터로부터 패턴을 찾으려 시도한다. 게다가 신경망과 딥러닝은 기존 기계학습에서 사용하던 방법보다 사람의 개입을 더욱 배제할 수 있게 해주는 중요한 특성을 지녔다.
이미지에서 특징(feature)을 추출하고 그 특징의 패턴을 기계학습 기술로 학습하는 방법이 있다. 특징은 입력 데이터에서 본질적인 데이터를 정확하게 추출할 수 있도록 설계뙨 변환기를 가리킨다. 이미지의 특징은 보통 벡터로 기술하고, 컴퓨터 비전 분야에서는 SIFT, SURF, HOG 등의 특징을 많이 사용한다.
이런 특징을 사용하여 이미지 데이터를 벡터로 변환하고 변환된 벡터를 가지고 지도 학습 방식의 대표 분류 기법인 SVM, KNN 등으로 학습할 수 있다.
이미지를 벡터로 변환할 때 사용하는 특징은 여전히 '사람'이 설계해야 한다. 이는 문제에 적합한 특징을 쓰지 않으면 좀처럼 좋은 결과를 얻을 수 없다는 뜻이다.
그림 4-2와 같이 신경망은 이미지를 '있는 그대로' 학습한다. 신경망은 이미지에 포함된 중요한 특징까지도 기계가 스스로 학습한다.
▪️ 4.1.2 훈련 데이터와 시험 데이터
기계학습 문제는 데이터를 훈련 데이터와 시험 데이터로 나눠 학습과 실험을 수행한다.
우선 훈련 데이터만 사용해 학습하면서 최적의 매개변수를 찾고 시험 데이터를 사용해 앞서 훈련한 모델의 실력을 평가한다.
우리가 원하는 것은 범용적으로 사용할 수 있는 모델이기 때문에 훈련 데이터와 시험 데이터를 나눠야 한다. 이 범용 능력을 제대로 평가하기 위해 훈련 데이터와 시험 데이터를 분리하는 것이다.
범용 능력은 아직 보지 못한 데이터로도 문제를 올바르게 풀어내는 능력으로, 이 범용 능력을 획득하는 것이 기계학습이 최종 목표이다.
그렇기에 데이터셋 하나로만 매개변수의 학습과 평가를 수행하면 올바른 평가가 될 수 없다. 한 데이터셋에만 지나치게 최적화 된 상태를 오버피팅(overfitting)이라고 하며 오버피팅 피하기는 기계학습의 중요한 과제이다.
◾ 4.2. 손실 함수
신경망 학습에서는 현재의 상태를 '하나의 지표'로 표현하며 그 지표를 가장 좋게 만들어주는 가중치 매개변수의 값을 탐색한다.
신경망 학습에서 사용하는 지표는 손실 함수(loss function)이라고 하며 손실 함수로는 일반적으로 평균 제곱 오차와 교차 엔트로피 오차를 사용한다.
▪️ 4.2.1 평균 제곱 오차
가장 많이 쓰이는 손실 함수는 평균 제곱 오차(MSE)이다.
여기서 yk는 신경망의 출력(신경망이 추정한 값), tk는 정답 레이블, k는 데이터의 차원 수를 나타낸다.
신경망 출력 y는 소프트맥스 함수의 출력이 된다.
▪️ 4.2.2 교차 엔트로피 오차
또 다른 손실 함수로서 교차 엔트로피 오차(CEE)도 자주 이용한다.
yk는 신경망 출력, tk는 정답 레이블이다. tk는 정답에 해당하는 인덱스의 원소만 1이고 나머지는 0이다(원-핫 인코딩). 실질적으로 정답일 때의 추정(tk가 1일 때의 yk)의 자연로그를 계산하는 식이 된다.
교차 엔트로피 초파는정답일 때의 출력이 전체 값을 정하게 된다.
def cross_entropy_error(y, t):
delta = 1e-7
return -np.sum(t*np.log(y+delta))
교차 엔트로피 오차를 파이썬으로 구현하면 위와 같다. 여기서 마지막을 보면 np.log를 계산할 때 아주 작은 값인 delta를 더했다. np.log() 함수에 0을 입력하면 마이너스 무한대를 의마하는 -inf가 되어 계산을 더이상 진행할 수 없기 때문에, 마이너스 무한대가 발생하지 않도록 한 것이다.
▪️ 4.2.3 미니 배치 학습
기계학습 문제는 훈련 데이터에 대한 손실 함수의 값을 구하고 그 값을 최대한 줄여주는 매개변수를 찾아 낸다. 모든 훈련 데이터를 대상으로 손실 함수 값을 구해야 한다.
훈련 데이터 모두에 대한 손실 함수의 합을 구하는 방법은 식 4.3과 같다.
데이터가 N개라면 tnk는 n번째 데이터의 k번재 값을 의미한다. 마지막에 N으로 나누어 정규화하고 있으며 N으로 나눔으로써 평균 손실 함수를 구하는 것이다.
신경망 학습에서도 훈련 데이터의 일부를 추려 전체의 근사치로 이용한다. 이 일부를 미니매치라고 하며 이런 학습 방법을 미니배치 학습이라고 한다.
▪️ 4.2.4 (배치용) 교차 엔트로피 오차 구현하기
미니배치 같은 배치 데이터를 지원하는 교차 엔트로피 오차는 다음과 같이 구현할 수 있다.
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
y는 신경망의 출력, t는 정답 레이블이다. y가 1차원이라면(데이터 하나 당 교차 엔트로피 오차를 구하는 경우) reshape 함수로 데이터의 형상을 바꿔준다. 배치의 크기로 나눠 정규화하고 이미지 1장 당 평균의 교차 엔트로피 오차를 계산한다.
원-핫 인코딩일 때 t가 0인 원소는 교차 엔트로피 오차도 0이므로 그 계산을 무시하고 정답에 해당하는 신경망의 출력만으로 교차 엔트로피 오차를 계산할 수 있다.
▪️ 4.2.5 왜 손실 함수를 설정하는지
궁극적인 목적은 높은 정확도를 끌어내는 매개변수 값을 찾는 것이다. 손실 함수의 값이라는 우회적인 방법을 택하는 이유는 신경망 학습에서 미분의 역할에 주목하면 알 수 있다.
신경망 학습에서는 최적의 매개변수(가중치와 편향)를 탐색할 때 손실 함수의 값을 가능한 한 작게 하는 매개변수 값을 찾는다. 이때 매개변수 의 미분을 계산하고 그 미분 값을 단서로 매개변수의 값을 서서히 갱신하는 과정을 반복한다.
그 가중치 매개변수의 손실 함수의 미분이란 가중치 매개변수의 값을 아주 조금 변화 시켰을 때 손실 함수가 어떻게 변하냐의 의미이다. 이 미분 값이 음수면 그 가중치 매개변수를 양의 방향으로 변화시켜 손실 함수의 값을 줄일 수 있다. 반대로 이 미분 값이 양수면 그 가중치 매개변수를 음의 방향으로 변화시켜 손실 함수의 값을 줄일 수 있다.
하지만 미분 값이 0이라면 가중치 매개변수를 어느 쪽으로 움직여도 손실 함수의 값은 달리지지 않는다. 그래서 가중치 매개변수의 갱신은 거기서 멈춘다.
정확도를 지표로 삼아서는 안되는 이유는 미분 값이 대부분의 장소에서 0이 되어 매개변수를 갱신할 수 없기 때문이다.
정확도는 매개변수의 미소한 변화에는 거의 반응을 보이지 않고 반응이 있더라도 그 값이 불연속적으로 갑자기 변화한다.
만약 활성화 함수로 계단 함수를 사용하면 신경망의 학습이 잘 이루어지지 않는다. 계단 함수의 미분은 그림 4-4와 같이 대부분의 장소에서 0이다.
따라서 계단 함수를 이용하면 손실 함수를 지표로 삼는 게 아무 의미가 없게 된다.
시그모이드 함수의 미분은 그림 4-4와 같이 출력(세로 축의 값)이 연속적으로 변하고 곡선의 기울기도 연속적으로 변한다. 즉, 시그모이드 함수의 미분은 어느 장소라도 0이 되지 않는다.
이는 신경망 학습에서 중요한 성질로, 기울기가 0이 되지 않는 덕분에 신경망이 올바르게 학습할 수 있는 것이다.
◾ 4.3 수치 미분
▪️ 4.3.1 미분
미분은 한순간의 변화량을 표시한 것이다.
x의 작은 변화가 함수 f(x)를 얼마나 변화 시키느냐를 의미한다. 이때 시간의 작은 변화, 즉 시간을 뜻하는 h를 한없이 0에 가깝게 한다는 의미를 lim으로 나타낸다.
식 4.4를 참고해 함수를 미분하는 계산을 파이썬으로 구현하면 아래와 같다.
def numerical_diff(f,x):
h=10e-50
return (f(x+h) - f(x)) / h
h에 가급적 작은 값을 대입하기 위해 10e-50이라는 작은 값을 이용했다.
이 방식은 반올림 오차문제를 일으킨다. 반올림 오차는 작은 값이 생략되어 최종 계산 결과에 오차가 생기게 한다. 파이썬에서는 1e-50을 float32형으로 나타내면 0.0이 되어 올바르게 표현할 수 없다.
위의 구현에서는 x+h와 x 사이의 함수 f의 차분을 계산하고 있지만 이 계산에는 오차가 있다는 사실에 주의해야 한다.
그림 4-5와 같이 진정한 미분은 x 위치의 함수의 기울기에 해당하지만 이번 구현에서는 미분은 (x+h)와 x사이의 기울기에 해당한다.
그림 4-5와 같이 수치 미분에는 오차가 포함된다. 이 오차를 줄이기 위해 (x+h)와 (x-h)일 때의 함수 f의 차분을 계산하는 방법을 쓰기도 한다. 이 차분은 x를 중심으로 그 전후의 차분을 계산한다는 의미에서 중심 차분 혹은 중앙 차분이라고 한다. (x+h)와 x의 차분은 전방 차분이라고 한다.
이를 적용해 수치 미분을 다시 구현하면 아래와 같다.
def numerical_diff(f,x):
h = 1e-4
return (f(x+h) - f(x-h)) / (2*h)
▪️ 4.3.3 편미분
위의 식은 인수들의 제곱 합을 계산하는 단순한 식이며 변수가 2개라는 점에 주의해야 한다.
식 4.6은 변수가 2개이기 때문에 어느 변수에 대한 미분인지, x0과 x1 중 어느 변수에 대한 미분이냐를 구별해야 한다. 이와 같이 변수가 여럿인 함수에 대한 미분을 편미분이라고 한다.
편미분은 변수가 하나인 미분과 마찬가지로 특정 장소의 기울기를 구한다. 여러 변수 중 목표 변수 하나에 초점을 맞추고 다른 변수는 값을 고정한다.
앞 장에서는 목표 변수를 제외한 나머지를 특정 값에 고정하기 위해 새로운 함수를 정의했으며, 이 장에선 그 새로 정의한 함수에 대해 그동안 사용한 수치 미분 함수를 적용하여 편미분을 구한 것이다.
◾ 4.4 기울기
모든 변수의 편미분을 벡터로 정리한 것을 기울기라고 한다.
그림 4-9를 보면 여기서 기울기는 함수의 가장 낮은 장소(최솟값)를 가리키는 것으로 이해할 수 있다.
가장 낮은 곳에서 멀어질수록 화살표의 크기가 커짐을 알 수 있다.
사실 기울기는 각 지점에서 낮아지는 방향을 가리킨다. 기울기가 가리키는 쪽은 각 장소에서 함수의 출력 값을 가장 크게 줄이는 방향이다.
▪️ 4.4.1 경사법(경사 하강법)
기계학습 문제 대부분은 학습 단계에서 최적의 매개변수를 찾아낸다.
매개변수 공간이 광대하여 어디가 최솟값이 되는 곳인지를 짐작할 수 없다. 이런 상황에서 기울기를 잘 이용해 함수의 최솟값을 찾으려는 것이 경사법이다.
여기서 주의할 점은 각 지점에서 함수의 값을 낮추는 방안을 제시하는 지표가 기울기라는 것이다. 실제로 복잡한 함수에서는 기울기가 가리키는 방향에 최솟값이 없는 경우가 대부분이다.
경사법은 현 위치에서 기울어진 방향으로 일정 거리만큼 이동한다. 이후 다음 이동한 곳에서도 마찬가지로 기울기를 구하고 또 그 기울어진 방향으로 나아가기를 반복한다. 이렇게 함수의 값을 점차 줄이는 것이 경사법이다.
경사법은 기계학습을 최적화하는 데 흔히 쓰는 방법이다.
경사법은 최솟값을 찾는 것을 경사 하강법이라고 하며 최댓값을 찾는 것을 경사 상승법이라고 한다.
식 4.7에서 η(에타)는 갱신하는 양을 의미한다. 이를 신경망 학습에서는 학습률이라고 한다.
한 번의 학습으로 얼마만큼 학습해야 할지 매개변수 값을 얼마나 갱신하느냐를 정하는 것이 학습률이다.
또, 학습률 값은 0.01이나 0.001 등 미리 특정 값으로 정해두어야 하는데 이 값이 너무 크거가 작으면 좋은 장소를 찾을 수 없다. 이 학습률 값을 변경하면서 올바르게 학습하고 있는지를 확인하면서 진행한다.
학습률이 너무 크면 큰 값으로 발산하고 반대로 너무 작으면 거의 갱신되지 않은 채로 학습이 끝나버리기 때문이다.
경사 하강법은 다음과 같이 간단하게 구현할 수 있다.
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
함수의 기울기는 numerical_gradient(f,x)로 구하고 그 기울기에 학습률을 곱한 값으로 갱신하는 처리를 step_num번 반복한다.
▪️ 4.4.2 신경망에서 기울기
신경망 학습에서도 기울기를 구해야 한다. 기울기는 가중치 매개변수에 대한 손실 함수의 기울기이다.
shape이 2x3, 가중치가 W, 손실 함수가 L인 신경망에서, 경사는 식 4.8로 나타낼 수 있다.
실제로 기울기를 구하는 코드(simpleNet)는 아래와 같다.
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
x = np.array([0.6, 0.9])
t = np.array([0, 0, 1])
net = simpleNet()
f = lambda w: net.loss(x, t)
dW = numerical_gradient(f, net.W)
여기서 기울기를 구하기 위해선 앞서 구현한 numerical_gradient(f,x)를 사용하면 된다.
신경망의 기울기를 구한 다음에는 경사법에 따라 가중치 매개변수를 갱신하면 된다.
◾ 4.5 학습 알고리즘 구현
신경망 학습의 절차는 다음과 같다.
- 전체 : 신경망에는 적응 가능한 가중치와 편향이 있고 이 가중치와 편향을 훈련 데이터에 적응하도록 조정하는 과정을 학습이라고 한다.
- 1단계 : 미니 배치
- 훈련 데이터 중 일부를 무작위로 가져온다. 미니배치의 손실 함수 값을 줄이는 것이 목표이다.
- 2단계 : 기울기 산출
- 각 가중치 매개변수의 기울기를 구한다. 기울기는 손실 함수의 값을 가장 작게 하는 방향을 제시한다.
- 3단계 : 매개변수 갱신
- 가중치 매개 변수를 기울기 방향으로 아주 조금 갱신한다.
- 4단계 : 1~3단계를 반복한다.
경사 하강법으로 매개변수를 갱신하는 방법이며 이때 데이터를 미니배칠오 무작위로 선정하기 때문에 확률적 경사 하강법(SGD)이라고 부른다.
▪️ 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
def gradient(self, x, t):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
grads = {}
batch_num = x.shape[0]
# forward
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
# backward
dy = (y - t) / batch_num
grads['W2'] = np.dot(z1.T, dy)
grads['b2'] = np.sum(dy, axis=0)
da1 = np.dot(dy, W2.T)
dz1 = sigmoid_grad(a1) * da1
grads['W1'] = np.dot(x.T, dz1)
grads['b1'] = np.sum(dz1, axis=0)
return grads
가중치 매개변수의 초깃값을 무엇으로 설정하냐가 신경망 학습의 성공을 좌우하기도 한다.
loss(self, x, t)는 손실 함수의 값을 계산하는 메서드이다. 이 메서드는 predict()의 결과와 정답 레이블을 바탕으로 교차 엔트로피 오차를 구하도록 구현했다.
▪️ 4.5.2 미니배치 학습 구현
import sys, os
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet
# data load
(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)
# 매개변수 갱신
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))
손실 함수의 값이 변화하는 추이를 그래프로 나타내면 그림 4-11처럼 된다.
학습 횟수가 늘어가면서 손실 함수의 값이 줄어든다. 학습이 잘 되고 있다는 뜻으로 신경망의 가중치 매개변수가 서서히 데이터에 적응하고 있음을 의미한다.
▪️ 4.5.3 시험 데이터로 평가
신경망 학습에서는 훈련 데이터 외의 데이터 외의 데이터를 올바르게 인식하는지를 확인 해야 한다.
오버피팅을 일으키지 않는지 확인해야 한다. 오버피팅되었다는 것은 훈련 데이터에 포함된 이미지만 제대로 구분하고 그렇지 않은 이미지는 식별할 수 없다는 뜻이다.
학습 결과를 그래프로 그려보면 그림 4-12와 같다.
에폭이 진행될수록 훈련 데이터와 시험 데이터를 사용하고 평가한 정확도가 모두 좋아지고 있다.
이 말은 이번 학습에서는 오버피팅이 일어나지 않았다는 것이다.
◾ 정리
기계 학습에서 사용하는 데이터셋은 훈련 데이터와 시험 데이터로 나눠 사용한다.
훈련 데이터로 학습한 모델의 범용 능력을 시험 데이터로 평가한다.
신경망 학습은 손실 함수를 지표로, 손실 함수의 값이 작아지는 방향으로 가중치 매개변수를 갱신한다.
가중치 매개변수의 기울기를 이용하고 기울어진 방향으로 가중치의 값을 갱신하는 작업을 반복한다.