4장-2. 신경망 학습

괴도소녀·2021년 7월 21일
0

TFMaster

목록 보기
4/9

수치 미분 (numerical differentiation)

경사법에서는 기울기(경사) 값을 기준으로 나아갈 방향을 정한다.

미분

우리가 마라톤 선수이고 처음부터 10분에 2km씩 달렸다 치자. 이때의 속도는 간단히
2 / 10 = 0.2 km/m이라 계산할 수 있따. 즉, 1분에 0.2km만큼의 속도(변화)로 뛰었다고 해석할 수 있다. 이 예시에서는 '달린 거리'가 '시간'에 대해 얼마나 변화했는가를 계산하였다. 정확하게는 10분 동안의 '평균 속도'를 구한 것이다. 미분은 '특정 순간'의 변화량을 뜻한다. 그래서 10분이라는 시간을 가능한 줄여서 한 순간의 변화량(어느 순간의 속도)를 얻는 것이다(직전 1초에 달린거리, 직전 0.1초에 달린 거리, ...).

미분은 한 순간의 변화량을 표시한 것이다. 수식을 다음과 같다.

df(x)dx=limh0f(x+h)f(x)h[식44]\begin{matrix} df(x) \over dx \end{matrix} = \lim_{h \rightarrow 0} \begin{matrix} f(x + h) - f(x) \over h \end{matrix} \qquad\text[식 4-4]

좌변은 f(x)f(x)xx에 대한 미분(xx에 대한 f(x)f(x)의 변화량)을 나타내는 기호이다.
xx의 '작은 변화'가 함수f(x)f(x)를 얼마나 변화시키느냐를 의미한다. 이때의 시간의 작은 변화, 즉 시간을 뜻하는 hh를 한없이 0에 가깝게 한다는 의미를 limh0\lim_{h \rightarrow 0}로 나타낸다.
[44][식4-4]를 참고하여 함수로 구현해보면 아래와 같다. 하지만 나쁜(?) 예제이다.

def numerical_diff(f, x):
	h = 10e-50
    return (f(x + h) - f(x)) / h

함수의 이름은 수치 미분에서 따온 numerical_diff(f, x)로 했다.이 함수는 '함수 f'와 '함수 f에 넘길 인수 x'라는 두 인수를 받는다.

위 함수는 개선해야 될 점이 2가지 있다.
첫번째, 10e-50이라는 값은 0.00....1 (....안에 49개의 0이 있다)라는 매우 작은 값이다. 이 방식을 반올림 오차(rounding error)문제를 일으킨다.

np.float32(1e-50)

------------------------

0.0

위와 같이 부동소수점 32bit로 나타내면 0.0이 되어 올바로 표현할 수 없으며, 너무 작은 값을 이용하면 컴퓨터로 계산 시 문제가 발생한다.

두번째, 함수 f의 차분(임의 두 점에서의 함수 값들의 차이)과 관련한 것이다.
x+h와 x사이의 함수 f의 차분을 계산하고 있찌만, 애당초 이 계산에는 오차가 있다.
아래 그림과 같이 '진정한 미분'은 xx위치의 함수의 기울기(이를 접서니라 함)에 해당하지만, 이번 구현에서의 미분은 (x+h)(x+h)xx사이의 기울기에 해당한다. 그래서 진정한 미분(진정한 접선)과 이번 구현의 값은 엄밀히 말하자면 일치하지 않는다. 이 차이는 hh를 무한히 0으로 좁히는 것이 불가능해 생기는 한계이다.

수치 미분에는 오차가 포함된다. 오차를 줄이기 위해 (x+h)와 (x-h)일 때의 함수 f의 차분을 계산하는 방법을 쓰기도 한다.

def numerical_diff(f, x):
	h = 1e-4 	# 0.0001
    	return (f(x + h) - f(x - h)) / (2*h)

NOTE! 아주 작은 차분으로 미분하는 것을 수치 미분이라 한다.

수치 미분의 예

다음과 같은 2차 함수가 있다고 하자.

y=0.01x2+0.1xy = 0.01x^2 + 0.1x

코드로 구현하면 다음과 같다.

def function_1(x):
	return 0.01*x**2 + 0.1*x

함수를 이용하여 그래프를 시각화해보자.

import numpy as np
import matplotlib.pylab as plt

x = np.arange(0.0,20.0,0.1) #0에서 20까지 0.1 간격의 배열 x를 만든다(20은 미포함)
y = function_1(x)

plt.xlabel("x")
plt.ylabel("f(x)")
plt.plot(x,y)
plt.show()

f(x)=0.01x2+0.1xf(x) = 0.01x^2 + 0.1x의 그래프

그럼 x=5일 때와 10일 때의 이 함수의 미분을 계산해보자.

numerical_diff(function_1,5)
>> 1.9999999999908982e-09

numerical_diff(function_1,10)
>> 2.999999999986347e-09

이렇게 계산한 미분 값이 xx에 대한 f(x)f(x)의 변화량이다. 즉, 함수의 기울기에 해당한다.

편미분

위의 수치 미분과는 달리 변수가 2개 이상이라는 점에 주의하자

f(x0,x1)=x02+x12f(x_0, x_1) = x_0^2 + x_1^2

위 식을 코드로 구현해보면 아래와 같다.

def function_2(x):
   return x[0]**2 + x[1]**2
   # 또는 return np.sum(x**2)

x는 넘파이 배열이라 가정한다. 이 함수의 그래프를 그려보면 3차원으로 그려진다.

식을 미분해보자. 여기서 주의할점은 수치미분과는 달리 변수가 2개이다. 그래서 '어느 변수에 대한 미분이냐', 즉 x0x_0x1x_1중 어느 변수에 대한 미분이냐를 구별해야 한다. 덧붙여서 이와 같이 변수가 여럿인 함수에 대한 미분을 편미분이라 한다. 수식으로는 fx0\partial f \over \partial x_0fx1\partial f \over \partial x_1처럼 쓴다.

연습삼아 문제를 풀어보자
문제1 : x0x_0 = 3, x1x_1 = 4일 때, x0x_0에 대한 편미분 fx0\partial f \over \partial x_0를 구하라.

def function_tmp1(x0):
	return x0*x0 + 4.0**2.0
    
numerical_diff(function_tmp1, 3.0)
>> 6.00000000000378

문제2 : x0x_0 = 3, x1x_1 = 4일 때, x1x_1에 대한 편미분 fx1\partial f \over \partial x_1를 구하라.

def function_tmp2(x1):
	return 3.0**2.0 + x1*x1
    
numerical_diff(function_tmp2, 4.0)
>> 7.9999999999999919

이 문제들은 변수가 하나인 함수를 정의하고, 그 함수를 미분하는 형태로 구현하여 풀었다.

이처럼 편미분은 변수가 하나인 미분과 마찬가지로 특정 장소의 기울기를 구한다. 단, 여러 변수 중 목표 변수 하나에 초점을 맞추고 다른 변수는 값을 고정한다.

기울기

바로 앞에서는 x0x_0x1x_1의 편미분을 변수별로 따로 계산했지만 동시에 계산하고 싶으면 (x0x_0, x1x_1)와 같이 양쪽의 편미분을 묶어서 (fx0\partial f \over \partial x_0, fx1\partial f \over \partial x_1)처럼 계산할 수 있다. (fx0\partial f \over \partial x_0, fx1\partial f \over \partial x_1)처럼 모든 변수의 편미분을 벡터로 정리한 것을 기울기(gradient)라고 한다. 다음과 같이 기울기를 구현할 수 있다.

def numerical_gradient(f,x):
    h = 1e-4 				#0.0001
    grad = np.zeros_like(x) 		#x와 형상이 같은 배열을 생성
    
    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

numerical_gradient의 동작 방식이 얼핏보면 복잡해 보이지만, 동작 방식은 변수가 하나일 때의 수치미분과 거의 동일하다. np.zeros_like(x)는 x와 형상이 같고 그 원소가 모두 0인 배열을 만든다.

함수를 사용해서 실제로 기울기를 계산해보자.
세 점 (3,4), (0,2), (3,0)에서의 기울기를 구해보겠다.

numerical_gradient(function_2, np.array([3.0,4.0]))
>> array([6., 8.])

numerical_gradient(function_2, np.array([0.0,2.0]))
>> array([0., 4.])

numerical_gradient(function_2, np.array([3.0,0.0]))
>> array([6., 0.])

위와 같이 각 점에서의 기울기를 계산할 수 있다. 이 기울기가 의미하는 것은 밑에의 그림을 보면 이해될 것이다.

방향을 가진 벡터(화살표)로 이루어져 있으며. 이 그림을 보면 기울기는 함수의 '가장 낮은 장소(최솟값)'을 가리키는 것 같다. 나침반처럼 화살표들이 한 점을 향하고 있는 것을 볼 수 있다. 또한 '가장 낮은 곳'에서 멀어질수록 화살표의 크기가 커지는 것을 볼 수 있다.

기울기는 각 지점에서 낮아지는 방향을 가리키며, 정확히 말하자면 기울기가 가리키는 쪽은 각 장소에서 함수의 출력 값을 가장 크게 줄이는 방향이다.

경사법(경사 하강법)

기계학습 문제의 대부분은 학습 단계에서 최적의 매개변수를 찾아낸다. 신경망 역시 최적의 매개변수(가중치와 편향)를 학습 시에 찾아야 하며, 여기에서 최적이란 손실 함수가 최솟값이 될 때의 매개변수 값이다.

각 지점에서 함수의 값을 낮추는 방안을 제시하는 지표가 기울기이다. 최솟값이 되는 장소를 찾는 문제에서는 기울기 정보를 단서로 나아갈 방향을 정해야 한다.

이때 경사법(gradient method)을 활용하며, 경사법은 현 위치에서 기울어진 방향으로 일정 거리만큼 이동한다. 그런 다음 이동한 곳에서도 마찬가지로 기울기를 구하고, 또 그 기울어진 방향으로 나아가는 과정을 반복한 후, 함수의 값을 점차 줄여나간다.
경사법은 기계학습을 최적화하는 데 흔히 쓰는 방법이다.

x0=x0ηfx0x_0 = \begin{matrix} x_0 - \eta \begin{matrix} \partial f \over \partial x_0 \end{matrix} \end{matrix}
x1=x1ηfx1x_1 = \begin{matrix} x_1 - \eta \begin{matrix} \partial f \over \partial x_1 \end{matrix} \end{matrix}

η\eta기호(에타)는 갱신하는 양을 나타낸다. 이를 신경망 학습에서는 학습률(learning rate)라고 한다. 한 번의 학습으로 얼마만큼 학습해야 할지, 즉 매개변수 값을 얼마나 갱신하느냐를 정하는 것이 학습률이다.
일반적으로 이 값이 너무 크거나 작으면 '좋은 장소'를 찾아갈 수 없다. 코드를 구현해보면 아래와 같다.

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
  • f는 최적화하려는 함수,
  • init_x는 초깃값,
  • lr은 learning rate를 의미하는 학습률,
  • step_num은 경사법에 따른 반복 횟수를 뜻한다.

함수의 기울기는 numerical_gradient(f, x)로 구하고, 그 기울기에 학습률을 곱한 값으로 갱신하는 처리를 step_num번 반복한다.

문제 : 경사법으로 f(x0,x1)=x02+x12f(x_0, x_1) = x_0^2 + x_1^2의 최솟값을 구해보자.

def function_2(x):
    return x[0]**2 + x[1]**2
    
init_x = np.array([-3.0, 4.0])
gradient_descent(function_2, init_x=init_x, lr=0.1, step_num=100)

>> [-6.11110793e-10  8.14814391e-10]

초깃값을 (-3.0, 4.0)을 설정한 후 경사법을 사용해 최솟값을 탐색한다. 최종 결과는 거의 (0, 0)에 가까운 결과이다. 경사법을 이용한 이 갱신 과정을 그림을 나타내면 아래와 같다.

학습률이 너무 크거나 작으면 좋은 결과를 얻을 수 없다고 앞서 이야기했다. 그럼 두 경우를 실험해보자.

# 학습률이 너무 큰 예 : lr =10.0
init_x = np.array([-3.0, 4.0])
gradient_descent(function_2, init_x=init_x, lr=10.0, step_num=100)

>> [-2.58983747e+13 -1.29524862e+12]

# 학습률이 너무 작은 예 : lr = 1e-10
init_x = np.array([-3.0, 4.0])
gradient_descent(function_2, init_x=init_x, lr=1e-10, step_num=100)

>> [-2.99999994  3.99999992]

위 실험과 같이 학습률이 너무 크면 큰 값으로 발산해버리고, 반대로 너무 작으면 거의 갱신되지 않은 채 끝나버린다.

신경망에서의 기울기

신경망 학습에서도 기울기를 구해야 한다. 여기서 말하는 기울기를 가중치 매개변수(W, b)에 대한 손실함수의 기울기이다. 예를 들어 2 x 3 가중치가 WW, 손실함수가 LL인 신경망의 경사는 LW\begin{matrix} \partial L \over \partial W \end{matrix}로 나타낼 수 있다. 수식으로는 다음과 같다.

W=(w11w12w13w21w22w23)W = \begin{pmatrix} {w_1}_1 & {w_1}_2 & {w_1}_3 \\ {w_2}_1 & {w_2}_2 & {w_2}_3 \end{pmatrix}
LW=(LW11LW12LW13LW21LW22LW23)\begin{matrix} \partial L \over \partial W \end{matrix} = \begin{pmatrix} \partial L \over \partial {W_1}_1 & \partial L \over \partial {W_1}_2 & \partial L \over \partial {W_1}_3 \\ \partial L \over \partial {W_2}_1 & \partial L \over \partial {W_2}_2 & \partial L \over \partial {W_2}_3 \end{pmatrix}

LW\partial L \over \partial W의 각 원소는 각각의 원소에 관한 편미분이다. 예를 들어 LW11\partial L \over \partial {W_1}_1W11{W_1}_1을 조금 변경했을 때 손실 함수 LL이 얼마나 변화하느냐를 나타낸다. 여기서 중요한 점은 LW\partial L \over \partial W의 형상이 WW와 같다는 것이다.

간단한 신경망을 예로 들어 실제로 기울기를 구하는 코드를 구현해보자.

import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
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): # Loss Function
        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) # 인수로 w를 받아 손실 함수를 계산
dW = numerical_gradient(f, net.W) # 미분을 사용하여 기울기를 구한다.

print(dW)

>> [[ 0.25448428  0.03691667 -0.29140095]
 [ 0.38172642  0.05537501 -0.43710143]]

결과값을 보면 w23{w_2}_3을 보면 양의 방향으로 갱신하고 w11{w_1}_1은 음의 방향으로 갱신해야 한다는 것을 알 수 있다. 신경망의 기울기를 구한 다음에는 경사법에 따라 가중치 매개변수를 갱신하기만 하면 된다.

학습 알고리즘 구현하기

신경망 학습의 절차는 다음과 같다.

[전체]
신경망에는 적응 가능한 가중치와 편향이 있고, 이 가중치와 편향을 훈련 데이터에 적응하도록 조정하는 과정을 학습이라 한다.

[1단계 - 미치배치]
훈련 데이터 중 일부를 무작위로 가져온다. 이렇게 선별한 데이터를 미니배치라 하며, 그 미니배치의 손실함수 값을 줄이는 것이 목표.

[2단계 - 기울기 산출]
미니배치의 손실 함수 값을 줄이기 위해 각 가중치 매개변수의 기울기를 구한다. 기울기는 손실 함수의 값을 가장 작게 하는 방향을 제시한다.

[3단계 - 매개변수 갱신]
가중치 매개변수를 기울기 방향으로 아주 조금 갱신한다.

[4단계 - 반복]
1~3 단계를 반복한다.

위와 같은 절차는 경사 하강버븡로 매개변수를 갱신하는 방법이며, 이때 데이터를 미니배치로 무작위로 선정하기 때문에 확률적 경사 하강법(stochastic gradient descent, SGD)라고 부른다.

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

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 = sigmod(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])
        
    # 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['W1'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
        
        return grads

[표. TwoLayerNet 클래스가 사용하는 변수]

변수설명
params신경망의 매개변수를 보관하는 딕셔너리 변수(인스턴스 변수)
params['W1']은 1번째 층의 가중치, params['b1']은 1번째 층의 편향
params['W2']는 2번째 층의 가중치, params['b2']는 2번째 층의 편향
grads기울기를 보관하는 딕셔너리 변수(numerical_gradient() 메서드의 반환 값)
grads['W1']은 1번째 층이 가중치의 기울기, grads['b1']은 1번째 층의 편향의 기울기
grads['W2']는 2번째 층의 가중치의 기울기, grads['b2]는 2번째 층의 편향의 기울기

[표. TwoLayerNet 클래스의 메서드]

메소드설명
__init__(self, input_size, hidden_size, output_size)초기화 수행, 인수는 입력층의 뉴런 수, 은닉층의 뉴런 수, 출력층의 뉴런 수
predict(self, x)예측(추론)을 수행한다. 인수 x는 이미지 데이터
loss(self, x, t)손실 함수의 값을 구한다. 인수 x는 이미지 데이터, t는 정답 레이블
accuracy(self, x, t)정확도를 구한다.
numerical_gradient(self, x, t)가중치 매개변수의 기울기를 구한다.
gradient(self, x, t)가중치 매개변수의 기울기를 구한다. numerical_gradient()의 개선판. 구현은 다음에.

TwoLayerNet클래스는 딕셔너리일 params와 grads를 인스턴스 변수로 갖는다.
params변수에는 가중치 매개변수가 저장된다. 예를 들어 1번째 층의 가중치 매개변수는 params['W1']키에 넘파이 배열로 저장된다.

net = TwoLayerNet(input_size= 784, hidden_size=100, output_size=10)

net.params['W1'].shape # (784, 100)
net.params['b1'].shape # (100,)
net.params['W2'].shape # (100, 10)
net.params['b2'].shape # (10,)

이 초기화 메서드에서는 가중치 매개변수도 초기화한다. 가중치 매개변수의 초깃값을 무엇으로 설정하냐가 신경망 학습의 성공을 좌우하기도 한다.

미니배치 학습 구현하기

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)
    # 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)

미니 배치 크기를 100으로 했다. 매번 60,000개의 훈련 데이터에서 임의로 100개의 데이터를 추려낸다. 그 100개의 미니배치를 대상으로 확률적 경사 하강법을 수행해 매개변수를 갱신한다.
경사법에 의한 갱신 횟수를 10,000번으로 설정하고, 갱신할 때마다 훈련 데이터에 대한 손실 함수를 계산하고, 그 값을 배열에 추가한다. 이 손실 함수의 값이 변화하는 추이를 그래프로 나타내면 아래와 같다.

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

시험 데이터로 평가하기

신경망 학습에서는 훈련 데이터 외의 데이터를 올바르게 인식하는지를 확인해야 하며, '오버피팅(overfitting)'을 일으키지 않는지 확인해야 한다. 오버피팅되었다는 것은, 예를 들어 훈련 데이터에 포함된 이미지만 제대로 구분하고, 그렇지 않은 이미지는 식별할 수 없다는 뜻이된다.

신경망의 원래 학습 목표는 범용적인 능력을 익히는 것이다. 범용 능력을 평가하려면 훈련 데이터에 포함되지 않은 데이터를 사용해서 평가해봐야 한다.

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)

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 = []

# 1epoch당 반복 수
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)
    
    # 1epoch당 정확도 계산
    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))

1epoch마다 모든 훈련데이터와 시험 데이터에 대한 정확도를 계산하고, 그 결과를 기록한다. 정확도를 1epoch마다 계산하는 이유는 for 문 안에서 매번 계산하기에는 시간이 오래 걸리고, 또 그렇게까지 자주 기록할 필요도 없기 때문이다. 더 큰 관점에서 그 추이를 알 수 있다면 충분하다.

훈련 데이터에 대한 정확도를 실선으로, 시험 데이터에 대한 정확도를 점선으로 그렸다.
epoch이 진행될수록(학습이 진행될수록) 훈련데이터와 시험데이터를 사용하고 평가한 정확도가 모두 좋아지는 것을 볼수 있으며, 두 정확도에는 차이가 없음을 알 수 있다. 이번 학습에서 오버피팅이 일어나지 않았다는 뜻이다.


정리

신경망 학습에 대해 정리해보자. 가장 먼저 신경망이 학습을 수행할 수 있도록 손실 함수라는 '지표'를 도입했다. 이 손실 함수를 기준으로 그 값이 가장 작아지는 가중치 매개변수 값을 찾아내는 것이 신경망 학습의 목표이다.

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

0개의 댓글