티스토리 뷰

신경망의 학습

Data(데이터)

데이터 주도 학습

머신러닝은 데이터가 생명입니다. 왜냐하면 알고리즘을 명시적으로 설계하는 것보다 주어진 데이터를 잘 활용해 문제를 해결하는 것이 더 쉽고 간단하기 때문입니다. 그런 방법 중 하나로, 이미지에서 특징(feature)을 추출하고 그 특징의 패턴을 기계학습 기술로 학습하는 방법이 있습니다.

기계는 모아진 데이터로부터 규칙을 찾아내는 역할을 담당하고, 사람은 이미지를 벡터로 변환할 때 사용하는 특징을 설계하는 역할을 합니다. 여기서 사람이 특징을 설계하기 때문에 문제에 적합한 특징을 쓰지 않으면(혹은 특징을 설계하지 않으면)좀처럼 좋은 결과를 얻을 수 없다는 말이 됩니다.

여기서 머신러닝과는 다르게 신경망(딥러닝)의 이점이 나타납니다. 딥러닝은 사람의 역할조차 기계가 하고, 개를 인식하든 사람의 얼굴을 인식하든 모든 문제를 같은 맥락에서 풀 수 있다는 점이 가장 큰 이점입니다.

위의 설명을 그럼으로 표혀해 보겠습니다.



훈련 데이터와 시험 데이터

머신러닝 문제는 데이터를 훈련 데이터시험 데이터로 나눠 학습과 실험을 수행하는 것이 일반적입니다. 우선 훈련 데이터만 사용하여 학습하면서 최적의 매개변수를 찾습니다. 그런 다음 시험 데이터를 사용하여 앞서 훈련한 모델의 실력을 평가하는 것입니다. 이렇게 하는 이유는 overfiting(과적합)을 막기 위해서 입니다. 과적합이란 기계가 너무 훈련 데이터에만 모델이 학습된 것을 말합니다. (자세한 내용은 여기서 확인)

손실 함수

신경망이 학습을 할 때는 현재의 상태를 하나의 지표로 표현합니다. 신경망은 지표를 기준으로 최적의 매개변수 값을 탐색합니다. 여기서 말하는 지표를 손실 함수(cost function)이라고 합니다.

평균 제곱 오차(MSE)

평균 제곱 오차의 수식은 다음과 같습니다.

여기서 y는 신경망의 출력(예측한 값), t는 정답 레이블, k는 데이터의 차원 수를 나타냅니다.


위 수식을 코드로 구현해 보겠습니다.

1
2
def MSE(y, t):
    return 0.5*np.sum((y-t)**2)
cs
여기서 인수 y와 t는 넘파이 배열을 받습니다.

교차 엔트로피 오차

평균 제곱 오차와 같이 많이 쓴는 손실함수 입니다. 수식은 다음과 같습니다.

log는 밑이 e인 자연로그입니다. y는 신경망 출력, t는 정답 레이블입니다. 여기서 t는 정답에 해당하(확률이 제일 높은 원소)는 원소만 1이고 나머지는 0입니다.(one-hot encoding) 따라서 교차 엔트로피 오차는 정답이 1일 때 0에(오차가 0일 때) 가까워집니다.


이제 구현을 해 보겠습니다.

1
2
3
def cee(y,t):
    delta=1e-7
    return -np.sum(t*np.log(y+delta))
cs

여기서 delta를 더한 이유는 log에 0이 들어가면 무한대가 되어 버리기 때문에 더이상 계산이 안 됩니다. 그래서 0이 되지 않도록 더해 주는 겁니다.


미니배치 학습

미니배치를 설명하기 전에 먼저 교차 엔트로피 오차 함수를 정규화 해 보겠습니다. 정규화 하는 것은 데이터가 100개가 있으면 100으로 교차 엔트로피 오차 함수를 나눠주면 됩니다. 이렇게 데이터의 갯수만큼 나눠줌으로서 평균 손실 함수를 구현하고, 평균 손실 함수를 구현함으로써 훈련 데이터가 100개든 1000개든 통일된 지표를 얻을 수 있게 됩니다.


그런데 평균 손실 함수를 구해 통일된 지표를 얻어도 빅데이터와 같이 엄청나게 많은 데이터를 다 학습하는 것은 현실적이지 않습니다. 이런 경우 데이터 일부를 추려 전체의 근사치로 이용할 수 있습니다. 따라서 신경망 학습에서도 훈련 데이터로부터 일부만 골라 학습을 수행합니다. 그리고 이 일부를 고루는 것을 미니배치라고 합니다. 예를 들어 60000만개의 데이터 중에서 100개를 무작위로 골라 그 100장만 이용하여 학습하는 것입니다. 이런 학습 방법을 미니배치 학습이라고 합니다.


미니배치 학습을 하는 코드를 구현해 보겠습니다.(데이터는 MNIST 데이터셋을 사용)

1
2
3
4
5
6
7
8
9
10
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)
cs

위 코드는 미니배치를 하기 전에 MNIST데이터를 얻어오기 위한 함수입니다.


load_mnist()는 다른 파일에 직접 정의한 함수입니다. 이 함수는 훈련 뎅터와 시험 데이터를 읽습니다. 호출할 때  one_hot_label=True로 지정하여 원-핫 인코딩으로 값을 얻습니다.


앞의 코드에서 MNIST데이터를 읽은 결과, 휸련 데이터는 60,000개고, 입력 데이터는 784열(28*28)인 이미지 데이터임을 알 수 있습니다. 또, 정답 레이블은 10줄짜리 데이터입니다. 그래서 앞의 x_train,t_train의 모습은 각각 (60000,784)와 (60000,10)이 됩니다.

1
2
3
4
5
train_size=x_train.shape[0]
batch_size=10
batch_mask=np.random.choice(train_size,batch_size)
x_batch=x_train[batch_mask]
t_batch=t_batch[batch_mask]
cs

위 코드는 60,000개의 데이터 중에서 10장만 빼내는 코드입니다.


이제 무작위로 선택한 이 인덱스를 사용해 미니배치를 뽑아내기만 하면 됩니다. 손실 함수도 이 미니배치로 계산합니다.


(배치용)교차 엔트로피 오차 구현

아래 코드는 데이터가 하나인 경우와 데이터가 배치로 묶여 입력되 경우 모두를 처리할 수 있도록 구현하겠습니다.
1)정답 레이블이 원-핫 인코딩
1
2
3
4
5
6
7
def cee1(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))/batch_size
cs

이 코드에서는 y가 1차원이라면, 즉 데이터 하나당 교차 엔트로피 오차를 구하는 경우는 reshape 함수로 데이터의 형상을 바꿔줍니다. 그리고 배치의 크기로 나눠 정규화하고 이미지 1장당 평균의 교차 엔트로피 오차를 계산합니다.


2)정답 레이블이 0~9인 숫자

1
2
3
4
5
6
7
def cee2(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]))/batch_size
cs

이 코드에서는 원-핫 인코딩일 때 t가 0인 원소는 교차 엔트로피 오차도 0이므로, 그 계산은 무시해도 좋다는 것이 핵심입니다. 다시 말하면 정답에 해당하는 신경망의 출력만으로 교차 엔트로피 오차를 계산할 수 있습니다. 그래서 원-핫 인코딩 시 t*np.log(y)였던 부분을 레이블로 표현할 때는 np.log(y[np.arange(batch_size),t]로 구현합니다.


np.log(y[np.arange(batch_size),t]에 대해 설명해보면 np.arange(batch_size)는 0부터 batch_size-1까지 배열을 생성합니다. 즉, batch_size가 5이면 np.arange(batch_size)는 [0,1,2,3,4]라는 넘파이 배열을 생성합니다. [2,7,0,9,4]와 같이 저장되어 있으므로 y[np.arange(batch_size),t]는 각 데이터의 정답 레이블에 해당하는 신경망의 출력을 추출합니다.(여기서는 [y[0,2],y[1,7],y[2,0],y[3,9],y[4,4]]인 배열을 생성합니다.)


기울기

기울기는 모든 변수의 편미분을 벡터로 정리한 것을 말합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import numpy as np
import matplotlib.pylab as plt
from mpl_toolkits.mplot3d import Axes3D
 
 
def _numerical_gradient_no_batch(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] = float(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
 
 
def numerical_gradient(f, X):
    if X.ndim == 1:
        return _numerical_gradient_no_batch(f, X)
    else:
        grad = np.zeros_like(X)
        
        for idx, x in enumerate(X):
            grad[idx] = _numerical_gradient_no_batch(f, x)
        
        return grad
 
 
def function_2(x):
    if x.ndim == 1:
        return np.sum(x**2)
    else:
        return np.sum(x**2, axis=1)
 
 
def tangent_line(f, x):
    d = numerical_gradient(f, x)
    print(d)
    y = f(x) - d*x
    return lambda t: d*+ y
     
if __name__ == '__main__':
    x0 = np.arange(-22.50.25)
    x1 = np.arange(-22.50.25)
    X, Y = np.meshgrid(x0, x1)
    
    X = X.flatten()
    Y = Y.flatten()
    
    grad = numerical_gradient(function_2, np.array([X, Y]) )
    
    plt.figure()
    plt.quiver(X, Y, -grad[0], -grad[1],  angles="xy",color="#666666")#,headwidth=10,scale=40,color="#444444")
    plt.xlim([-22])
    plt.ylim([-22])
    plt.xlabel('x0')
    plt.ylabel('x1')
    plt.grid()
    plt.legend()
    plt.draw()
    plt.show()
 
cs
numericl_gradient(f,x) 함수에서 인수인 f는 함수이고, x는 넘파이 배열이므로 넘파이 배열 x의 각 원소에 대해서 수치 미분을 구합니다. np.zeros_like(x)는 x와 형상이 같고 그 원소가 모두 0인 배열을 만듭니다.

이 코드를 실행해 보면 아래 그림과 같이 기울기가 방향을 가진 벡터(화살표)로 그려집니다. 

이 그림을 보면 기울기는 함수의 가장 낮은 장소(최적값)을 가리킵니다. 하지만 실제로 반드시 그렇다고 볼 수는 없습니다. 사실 기울기는 각 지점에서 낮아지는 방향을 가리킵니다. 더 정확히 말하면 기울기가 가리키는 쪽은 각 장소에서 함수의 출력 값을 가장 크게 줄이는 방향입니다.

경사 하강법

경사 하강법은 학습 할 때최적의 매개변수(가충치와 편향)을 기울기를 이용해 함수의 최솟값(또는 가능한 한 작은 값)을 찾으려 할때 사용되는 것입니다.
여기에 중요한 점은 각 지점에서 함수의 값을 낮추는 방안을 제시하는 지표가 기울기라는 것입니다. 하지만 복잡한 하수에서는 기울기가 가리키는 방향에 최솟값이 없는 경우가 많습니다. 그래도 그 방향으로 나아가야 함수의 값을 줄일 수 있는 것은 사실이기에 최솟값이 되는 곳을 찾는 문제나 가능한 한 작은 값이 되는 장소를 찾는 문제에서는 기울기 정보를 단서로 나아갈 방향을 정해야 합니다.

이제 경사 하강법에 대해 설명해 보겠습니다. 경사 하강법은 현 위치에서 기울어진 방향으로 일정 거리만큼 이동합니다. 그런 다음 이동한 곳에서도 기울기를 구하고, 또 그 기울어진 방향으로 나아가기를 반복합니다. 이렇게 해서 함수의 값을 점차 줄이는 것을 경사 하강법(gradient descent)라고 합니다.

경사 하강법을 수식으로 나타내면 다음과 같습니다.

cost(W)는 손실함수인 MSE를 정의한 것입니다. 알파는 학습률(learning rate)를 나타내는 것으로 한번 갱신 할 때 매개변수를 얼마나 갱신시켜줘야 하는지를 정하는 것입니다. 학습률을 잘 정하는 것은 매우 중요한 일입니다. 너무 크면 overshooting이 되어 inf가 될 수 있고, 너무 작으면 10일 걸릴 학습을 100일을 할 수도 있기 때문에 적당한 값을 잘 정해줘야 합니다.


학습은 두 번째 식을 적절한 매개변수를 찾을 때까지 계속 반복하면 됩니다. 아래는 경사 하강법을 구현한 코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# coding: utf-8
import numpy as np
import matplotlib.pylab as plt
from gradient_2d import numerical_gradient
 
 
def gradient_descent(f, init_x, lr=0.01, step_num=100):
    x = init_x
    x_history = []
 
    for i in range(step_num):
        x_history.append( x.copy() )
 
        grad = numerical_gradient(f, x)
        x -= lr * grad
 
    return x, np.array(x_history)
 
 
def function_2(x):
    return x[0]**2 + x[1]**2
 
init_x = np.array([-3.04.0])    
 
lr = 0.1
step_num = 20
x, x_history = gradient_descent(function_2, init_x, lr=lr, step_num=step_num)
 
plt.plot( [-55], [0,0], '--b')
plt.plot( [0,0], [-55], '--b')
plt.plot(x_history[:,0], x_history[:,1], 'o')
 
plt.xlim(-3.53.5)
plt.ylim(-4.54.5)
plt.xlabel("X0")
plt.ylabel("X1")
plt.show()
cs

gradient-descent(f,init_x,lr=0.01,step_num=100) 함수에서 인수 f는 최적화하려는 함수, init_x는 초깃값, lr은 learning_rate를 의미하는 학습률, step_num은 경사법에 따른 반복 횟수를 뜻합니다. 이 함수를 사용하면 극솟값도 구할 수 있고 최솟값도 구할 수 있습니다.


이 코드를 실행해 보면 아래 그림이 나오는데, 값이 가장 낮은 장소인 원점에 점차 가까워지고 있는 것을 볼 수 있습니다.


신경망에서의 기울기

신경망 학습에서도 기울기를 구해야 합니다. 여기서 말하는 기울기는 가중치 매개변수에 대한 손실 함수의 기울기입니다. 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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):
        z = self.predict(x)
        y = softmax(z)
        loss = cross_entropy_error(y, t)
 
        return loss
 
= np.array([0.60.9])
= np.array([001])
 
net = simpleNet()
 
= lambda w: net.loss(x, t)
dW = numerical_gradient(f, net.W)
 
print(dW)
cs
simpleNet 클래스는 형상이 2x3인 가중치 매개변수 하나를 인스턴스 변수로 갖습니다. predict(x)는 예측을 수행하고, loss(x,t)는 손실 함수의 값을 구합니다.

학습 알고리즘 구현하기

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

1) 전제

신경망에는 적응 가능한 가중치와 편향... 가중치와 편향을 훈련 데이터에 적응하도록 조정하는 것이 '학습'.


2) 1단계: 미니배치

훈련 데이터 중 일부 무작위 추출. 미니배치의 손실 함수를 줄이는 것을 목표


3) 2단계: 기울기 산출

미니배치 손실 함수 값을 줄이기 위해 가중치 매개변수의 기울기 구함. 기울기는 손실 함수의 값을 최소화하는 방향을 가리킴


4) 3단계: 매개변수 갱신

가중치 매개변수 기울기 방향으로 조금 갱신


5) 4단계 : 반복

1~3 단계 반복


위 방법들이 신경망 학습이 이뤄지는 순서입니다. 이는 경사 하강법으로 매개변수를 갱신하는 방법이며, 이때 데이터를 미니배치로 무작위로 선정하기 때문에 stochastic gradient descent(확률적 경사 하강법)이라고 하며 많이들 들어봤을 것입니다.


학습 횟수가 늘어날수록 손실 함수의 값이 떨어진다면 이는 신경망의 가중치들이 학습 데이터에 잘 맞아들고 있음을 의미하며 이를 학습이 잘되고 있다고 볼 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# 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
        
    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) #2번째 층의 가중치의 기울기
        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
 
cs
TwoLayerNet 클래스는 딕셔너리인 params와 grads를 인스턴스 변수로 갖습니다. params 변수에는 가중치 매개변수가 저장되는데, 예를 들어 1번째 층의 가중치 매개변수는 params['W1'] 키에 넘파이 배열로 저장됩니다. 마찬가지로 1번째 층의 편향은 params['b1']키로 접근합니다. 아래는 그 예시입니다.
1
2
3
4
5
6
7
8
9
10
net=TwoLayerNet(input_size=784,hidden_size=100,output_size=10)
a=net.params['W1'].shape
b=net.params['b1'].shape
c=net.params['W2'].shape
d=net.params['b2'].shape
 
print(a) #(784, 100)
print(b) #(100,)
print(c) #(100, 10)
print(d) #(10,)
cs


grads변수에는 params 변수에 대응하는 각 매개변수의 기울기가 저장됩니다. 

1
2
3
4
5
6
7
8
9
x=np.random.rand(100,784#더미 입력 데이터(100장 분량)
t=np.random.rand(100,10#더미 정답 레이블(100장 분량)
 
grads=net.numerical_gradient(x,t) #기울기 계산
 
print(grads['W1'].shape) #(784, 100)
print(grads['b1'].shape) #(100,)
print(grads['W2'].shape) #(100, 10)
print(grads['b2'].shape) #(10,)
cs


미니배치 학습 구현&시험 데이터로 평가하기

미니배치 학습이란 훈련 데이터 중 일부를 무작위로 꺼내고(미니배치), 그 미니배치에 대해서 경사법으로 배개변수를 갱신합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
import matplotlib.pyplot as plt
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 = [] #시험 데이터에 대한 정확도를 저장할 곳
 
# 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)
    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)
    
    # 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))
 
# 그래프 그리기
markers = {'train''o''test''s'}
= np.arange(len(train_acc_list))
plt.plot(x, train_acc_list, label='train acc')
plt.plot(x, test_acc_list, label='test acc', linestyle='--')
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(01.0)
plt.legend(loc='lower right')
plt.show()
 
cs

100개의 미니배치를 대상으로 SDG를 수행해 매개변수를 갱신합니다. 그리고 갱신할 때마다 훈련 데이터에 대한 손실 함수를 계산하고, 그 값을 배열에 추가합니다.


위 코드에서는 학습을 반복할수록 손실함수의 값이 서서히 내려갑니다. 학습을 통해 올바른 매개변수를 찾아가고 있기 때문이죠. 여기서 손실함수의 값이란, 정확히는 훈련 데이터의 미니배치에 대한 손실 함수의 값입니다. 훈련데이터에서는 잘 학습이 되었으니 이 훈련데이터 말고도 다른 데이터에서도 올바른 결과값을 보여주는지 확인해 봐야 합니다. 그렇지 않으면 overfiting이 일어나 훈련데이터에서만 올바른 값이 나올 수 있기 때문입니다. 


신경망 학습의 원래 목표는 범용저인 능력을 익히는 것입니다. 범용 능력을 평가하기 위해 학습 도중 정기적으로 훈련 데이터와 시험 데이터를 대상으로 정확도를 기록했습니다.


위 코드에서는 1에폭마다 모든 훈련 데이터와 시험 데이터에 대한 정확도를 계산하고, 그 결과를 기록합니다.


아래 그래프는 위 코드로 얻은 결과입니다.

실선은 훈련데이터에 대한 정확도를. 점선은 시험 데이터에 대한 정확도를 그렸습니다. 에폭이 진행될수록(학습이 진행될수록) 훈련데이터와 시험 데이터를 사용하고 평가한 정확도가 좋아지고 있는 것을 볼 수 있습니다. Overfiting이 일어나지 않고 있는 거죠.


만약 Overfiting이 일어났다면 어느 순간부터 시험 데이터에 대한 정확도가 점차 떨어졌을 것입니다. 이 순간을 포착해 학습을 중단하는 기법을 조기종료라 하며 '가중치 감소','드롭아웃'과 함꼐 대표적인 오버피팅 예방법입니다.




'AI > 밑바닥부터 시작하는 딥러닝' 카테고리의 다른 글

오차역전파(2)  (0) 2018.09.18
오차역전파법(1)  (0) 2018.09.17
신경망(2)  (0) 2018.09.09
신경망(1)  (0) 2018.09.03
퍼셉트론  (0) 2018.08.27
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함