밑바닥부터 시작하는 딥러닝 - 신경망(3)

신경망의 학습

손실 함수

신경망 학습에는 학습이 얼마나 잘 되고 있는지를 알기 위한 '척도'가 필요하다.

일반적으로 학습 단계의 특정 시점에서 신경망의 성능을 나타내는 척도로 '손실'을 사용한다.

손실은 학습 데이터(학습 시 주어진 정답 데이터)와 신경망이 예측한 결과를 비교하여 예측이 얼마나 나쁜가를 산출한 단일 값(스칼라)이다.

 

신경망의 손실은 손실 함수를 사용해 구한다. 다중 클래스 분류 신경망에서는 손실 함수로 흔히 교차 엔트로피 오차를 이용한다.

 

교차 엔트로피 오차는 신경망이 출력하는 각 클래스의 '확률'과 '정답 레이블'을 이용해 구할 수 있다.

앞에서 구현한 신경망에 Softmax 계층과 Cross Entropy Error 계층을 새로 추가한다.

x는 입력 데이터, t는 정답 레이블, L은 손실을 나타낸다. 이때 Softmax 계층의 출력은 확률이 되어, 다음 계층인 Cross Entropy Error 계층에는 확률과 정답 레이블이 입력된다.

소프트맥스 함수

출력이 총 n개일 때, k번째의 출력 Yk를 구하는 계산식이다.

소프트맥스 함수의 출력의 각 원소는 0 ~ 1 이하의 실수이다. 그리고 그 원소들을 모두 더하면 1이 된다.

교차 엔트로피 오차


미분과 기울기

신경망 학습의 목표는 손실을 최소화하는 매개변수를 찾는 것이다. 이때 중요한 것이 '미분'과 '기울기'이다.


연쇄 법칙

학습 시 신경망은 학습 데이터를 주면 손실을 출력한다. 여기서 얻고자 하는 것은 각 매개변수에 대한 손실의 기울기이다. 기울기를 얻을 수 있으면 매개변수를 갱신할 수 있다. 신경망의 기울기는 오차역전파로 구할 수 있다.

 

연쇄 법칙이란 합성함수에 대한 미분의 법칙이다.

 


계산 그래프

계산 그래프는 노드와 화살표로 그린다. 더하기를 + 노드로 나타내고, 변수 x와 y를 해당 화살표 위에 쓴다.

계산 그래프를 이용하면 계산을 시각적으로 파악이 가능하다. 기울기가 순전파와 반대 방향으로 전파되는데, 이 반대 방향의 전파가 '역전파'이다.

곱셈 노드

곱셈 노드

곱셈 노드란 '상류로부터 받은 기울기'에 '순전파 시의 입력을 서로 바꾼 값'을 곱한다.

 

분기 노드

분기 노드

따로 그리지 않고 단순히 선이 두 개로 나뉘도록 그림, 이때 같은 값이 값이 복제되어 분기한다.

 

Repeat 노드

Repeat 노드

2개로 분기하는 분기 노드를 일반화하면 N개로의 분기가 된다.

import numpy as np
D, N = 8, 7
x = np.random.randn(1, D) # 입력
y = np.repeat(x, N, axis = 0) # 순전파
dy = np.random.randn(N, D) # 무작위 기울기
dx = np.sum(dy, axis = 0, keepdims = True) # 역전파

코드로는 위의 처럼 구현할 수 있다.

여기서 np.repeat() 메서드가 원소 복제를 수행한다. 이 코드에서는 배열 x를 N번 복제하는데, 이때 axis를 지정하여 어느 축 방향으로 복제할지를 조정할 수 있다.

역전파에서는 총합을 구해야 하므로 np.sum() 메서드를 이용한다. 이때도 axis 인수를 설정하여 어느 축 방향으로 합을 구할지 지정한다. 또한 인수로 keepdims = True를 설정하여 2차원 배열의 차원 수를 유지한다.

위의 코드에서는 keepdims가 True면 np.sum()의 결과의 형상은 (1, D)가 되며, False면 (D)가 된다.

 

Sum 노드

Sum 노드

import numpy as np
D, N = 8, 7
x = np.random.randn(N, D) # 입력
y = np.sum(x, axis = 0, keepdims = True) # 순전파
dy = np.random.randn(1, D) # 무작위 기울기
dx = np.repeat(dy, N, axis = 0) # 역전파

위의 코드에서 Sum 노드의 순전파는 np.sum() 메서드로, 역전파는 np.repeat() 메서드로 구현할 수 있다.

여기서 Sum 노드와 Repeat 노드는 서로 '반대 관계'라는 것이다. 반대 관계란 Sum 노드의 순전파가 Repeat 노드의 역전파가 되며, Sum 노드의 역전파가 Repeat 노드의 순전파가 된다는 뜻이다.

 

MatMul 노드

MatMul 노드

class MatMul:
    def __init__(self, W):
        self.params = [W]
        self.grads = [np.zeros_like(W)]
        self.x = None
        
    def forward(self, x):
        W, = self.params
        out = np.matmul(x, W)
        self.x = x
        return out
    
    def backward(self, dout):
        W, = self.params
        dx = np.matmul(dout, W.T)
        dW = np.matmul(self.x.T, dout)
        self.grads[0][...] = dW
        return dx

MatMul 계층은 학습하는 매개변수를 params에 보관한다. 거기에 대응시키는 형태로, 기울기는 grads에 보관한다. 역전파에서는 dx와 dW를 구해 가중치의 기울기를 인스턴스 변수인 grads에 저장한다.

기울기 값을 설정하는 grads[0][...] = dW 코드에서 점 3개로 이뤄진 생략 기호를 사용했다. 이렇게 되면 넘파이 배열이 가리키는 메모리 위치를 고정시킨 다음, 그 위치에 원소들을 덮어쓴다.

 

기울기 도출과 역전파 구현

Sigmoid 계층

Sigmoid 계층

class Sigmoid:
    def __init__(self):
        self.params, self.grads = [], []
        self.out = None
        
    def forward(self, x):
        out = 1 / (1 + np.exp(-x))
        self.out = out
        return out
    
    def backward(self, dout):
        dx = dout * (1.0 - self.out) * self.out
        return dx

순전파 때는 출력을 인스턴스 변수 out에 저장하고, 역전파를 계산할 때 이 out 변수를 사용한다.

 

Affine 계층

Affine 계층

class Affine:
    def __init__(self, W, b):
        self.params = [W, b]
        self.grads = [np.zeros_like(W), np.zeros_like(b)]
        self.x = None
        
    def forward(self, x):
        W, b = self.params
        out = np.matmul(x, W) + b
        self.x = x
        return out
    
    def backward(self, dout):
        W, b = self.params
        dx = np.matmul(dout, W.T)
        dW = np.matmul(self.x.T, dout)
        db = np.sum(dout, axis = 0)
        
        self.grads[0][...] = dW
        self.grads[1][...] = db
        return dx

구현 규칙에 따라 인스턴스 변수 params에는 매개변수를, grads에는 기울기를 저장한다.

Affine 역전파는 MatMul 노드와 Repeat 노드의 역전파를 수행하면 구할 수 있다.

Repeat 노드의 역전파는 np.sum() 메서드로 계산할 수 있는데, 이때 행렬의 형상을 잘 살펴보고 어느 축(axis)으로 합을 구할지를 명시해야 한다. 마지막으로, 가중치 매개변수의 기울기를 인스턴스 변수 grads에 저장한다.

 

가중치 갱신

확률적 경사 하강법(SGD)

가중치 갱신 기법의 종류 중 하나로, 무작위로 선택된 데이터에 대한 기울기를 이용한다는 뜻이다.

SGD

가중치를 기울기 방향으로 일정한 거리만큼 갱신한다.

 

그렇다면 코드로 알아보자.

class SGD:
    def __init__(self, lr = 0.01):
        self.lr = lr
        
    def update(self, params, grads):
        for i in range(len(params)):
            params[i] -= self.lr * grads[i]

초기화 인수 lr은 학습률을 뜻하며, 그 값을 인스턴스 변수로 저장해둔다. 그리고 update 메서드로 매개변수 갱신을 처리한다.

 

TAGS.

Comments