AI

[Deep Learning from Scratch] CHAPTER 5 오차역전파법

위시리 2023. 2. 21. 22:50

이전 까지는 신경망의 가중치 매개변수에 대한 손실 함수의 기울기는 수치 미분을 사용하여 구했습니다.

그러나 수치 미분은 단순하고 구현하기도 쉽지만 계산이 오래 걸린다는 단점이 있습니다.

 

따라서 5장에서는 가중치 매개변수의 기울기를 효율적으로 계산하는 '오차역전파법(backpropagation)'을 소개합니다.

 

1) 계산 그래프를 사용하여 시각적인 이해를 한 후

2) 수식을 사용한 구현

 

5.1 계산 그래프

계산 그래프(computational graph) : 계산 과정을 그래프로 나타낸 것

→ 노드(node)와 에지(edge)로 표현

     (노드 : 원, 에지 : 노드 사이의 직선)

 

5.1.1 계산 그래프로 풀다

  • 순전파(forward propagation) 계산 방향 : 왼쪽에서 오른쪽으로 진행 (출발점 → 종착점)
  • ↔ 역전파(backward propagation) : 미분할 때 중요한 역할

5.1.2 국소적 계산

(계산 그래프의 특징) 국소적 계산 : 자신과 직접 관계된 작은 범위

→ 전체에서 어떤 일이 벌어지든 상관 없이 자신과 관계된 정보만으로 결과를 출력

즉, 계산 시 각 노드는 자신과 관련된 계산 외에는 신경 쓸 것이 없음

 

5.1.3 왜 계산 그래프로 푸는가?

  1. 국소적 계산 : 전체가 아무리 복잡해도 각 노드에서는 단순한 계산에 집중하여 문제를 단순화 할 수 있음
  2. 중간 계산 결과 보관 가능
  3. 역전파를 통해 각 변수의 미분을 효율적으로 구할 수 있음

 


5.2 연쇄법칙

역전파는 국소적인 미분을 오른쪽에서 왼쪽으로 전달(⇐)

국소적 미분을 전달하는 원리 : 연쇄법칙(chain rule)

 

5.2.1 계산 그래프의 역전파

ex. y=f(x) 계산의 역전파

 

역전파 계산 순서 : 신호 E에 노드의 국소적 미분(∂y/∂x)을 곱한 뒤 → 다음 노드로 전달

이때 국소적 미분(∂y/∂x) = 순전파 때의 y=f(x) 계산의 미분 = x에 대한 y의 미분

 

이 국소적 미분을 상류에서 전달된 값(ex. E)에 곱해 앞쪽 노드로 전달

 

목표로 하는 미분 값을 효율적으로 구할 수 있다는 것이 역전파의 핵심

 

5.2.2 연쇄법칙이란?

합성 함수의 미분의 성질에서 찾아 볼 수 있음

합성 함수의 미분 : 합성 함수를 구성하는 각 함수의 미분의 곱으로 나타낼 수 있다

 

5.2.3 연쇄법칙과 계산 그래프

(노드로 들어온 입력 신호) X (그 노드의 국소적 미분(편미분)) 다음 노드로 전달

 


5.3 역전파

5.3.1 덧셈 노드의 역전파

ex. z = x+y

왼쪽이 순전파, 오른쪽이 역전파

 

역전파 : 상류에서 전해진 미분에 1을 곱하여 하류로 전달

 

즉, 덧셈 노드의 역전파는 입력 신호를 다음 노드로 그대로 전달

 

5.3.2 곱셈 노드의 역전파

ex. z=xy

왼쪽이 순전파, 오른쪽이 역전파

곱셈 노드의 역전파 : 순전파 때의 입력 신호를 서로 바꾼뒤 곱하여 하류로 보냄 (x ↔ y)

 

 곱셈의 역전파는 순방향 입력 신호의 값 필요

      곱셈 노드를 구현할 때는 순전파의 입력 신호를 변수에 저장해 둠

 

5.4 단순한 계층 구현하기

5.4.1 곱셈 계층

forward( ) : 순전파

backward( ) : 역전파

 

곱셈계층 MulLayer 구현

class MulLayer:
    def __init__(self):
        self.x = None
        self.y = None

    def forward(self, x, y):
        self.x = x
        self.y = y
        out = x*y
   
        return out
   
    def backward(self, dout):
        dx = dout * self.y
        dy = dout * self.x
       
        return dx, dy

 

__init__( ) : 인스턴스 변수인 x와 y 초기화

x, y 변수는 순전파 시의 입력값을 유지하기 위함

forward( ) : x와 y를 인수로 받고 두 값을 곱해서 반환

backward( ) : 상류에서 넘어온 미분 dout에 순전파 때의 값을 서로 바꿔 곱한 후 하류로 보냄 

 

ex. 사과 2개 구입, 사과는 1개당 100원, 소비세 1.1%

apple = 100
apple_num = 2
tax = 1.1

mul_apple_layer = MulLayer()
mul_tax_layer = MulLayer()

# forward
apple_price = mul_apple_layer.forward(apple, apple_num)
price = mul_tax_layer.forward(apple_price, tax)

print(price)

# backward
dprice = 1
dapple_price, dtax = mul_tax_layer.backward(dprice)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)

print(dapple, dapple_num, dtax)

* backward( )의 호출 순서는 forward( ) 때와 반대

* backward( )가 받은 인수는 순전파의 출력에 대한 미분

 

5.4.2 덧셈 계층

덧셈 계층 구현

class AddLayer:
    def __init__(self):
        pass
   
    def forward(self, x, y):
        out = x+y
        return out
   
    def backward(self, dout):
        dx = dout*1
        dy = dout*1
        return dx, dy

__init__( ) : 덧셈 계층에서는 초기화가 필요 없으니 아무 일도 하지 않음 (pass)

forward( ) : 입력 받은 두 인수 x,y를 더하여 반환

backward( ) : 상류에서 내려온 미분(dout)을 그대로 하류로 보냄

 

ex. 사과 개당 100원 2개 구매, 귤 개당 150원 3개 구매, tax 1.1

class MulLayer:
    def __init__(self):
        self.x = None
        self.y = None

    def forward(self, x, y):
        self.x = x
        self.y = y
        out = x*y
   
        return out
   
    def backward(self, dout):
        dx = dout * self.y
        dy = dout * self.x
       
        return dx, dy

class AddLayer:
    def __init__(self):
        pass
   
    def forward(self, x, y):
        out = x+y
        return out
   
    def backward(self, dout):
        dx = dout*1
        dy = dout*1
        return dx, dy


apple = 100
apple_num = 2
orange = 150
orange_num = 3
tax = 1.1

mul_apple_layer = MulLayer()
mul_orange_layer = MulLayer()
add_apple_orange_layer = AddLayer()
mul_tax_layer = MulLayer()


# forward
apple_price = mul_apple_layer.forward(apple, apple_num)
orange_price = mul_orange_layer.forward(orange, orange_num)
all_price = add_apple_orange_layer.forward(apple_price, orange_price)
price = mul_tax_layer.forward(all_price, tax)

print(price)

# backward
dprice = 1

dall_price, dtax = mul_tax_layer.backward(dprice)
dapple_price, dorange_price = add_apple_orange_layer.backward(dall_price)
dorange, dorange_num = mul_orange_layer.backward(dorange_price)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)

print(dapple, dapple_num, dorange, dorange_num, dtax)

[결과]

price : 715.0000000000001
dapple : 2.2

dapple_num : 110.00000000000001

dorange : 3.3000000000000003

dorange_num : 165.0 650

 


5.5 활성화 함수 계층 구현하기

계산 그래프를 신경망에 적용

신경망 구현 계층 각각을 하나로 구현

 

5.5.1 ReLU 계층

활성화 함수로 사용되는 ReLU 함수 수식

위 식의 x에 대한 y의 미분

순전파일 때의 입력인 x가 0보다 크면 역전파는 상류의 값을 그대로 하류로 보냄

반면, 순전파 때 x가 0 이하이면 역전파 때는 하류로 신호를 보내지 않음

 

 

ReLU 계층 구현

신경망 계층의 forward( )와 backward( ) 함수는 넘파이 배열을 인수로 받는다고 가정

class ReLU:
    def __init__(self):
        self.mask = None

    def forward(self, x):
        self.mask = (x <= 0)
        out = x.copy()
        out[self.mask] = 0

        return out

    def backward(self, dout):
        dout[self.mask] = 0
        dx = dout

        return dx

ReLU 클래스는 mask라는 인스턴스 변수를 가짐

mask : True / False로 구성된 넘파이 배열

           순전파 입력인 x의 원소 값이 0 이하인 인덱스는 True, 그 외(0보다 큰 원소)는 False로 유지

 

순전파 때의 입력 값이 0 이하면 역전파 때의 값은 0이 되어야 함

따라서 역전파 때는 순전파 때 만들어둔 mask를 써서 mask의 원소가 True인 곳에는 상류에서 전파된 dout을 0으로 설정

 

5.5.2 Sigmoid 계층

Sigmoid 함수 식

 

Sigmoid 함수 계층 계산 그래프

 

Sigmoid 식은 국소적 계산의 전파로 이루어짐

 

 # 1단계

'/' 노드 : y = 1/x 미분

→ 역전파 때 상류에 흘러들어온 값에 -y^2을 곱하여 하류로 전달

 

 # 2단계

'+' 노드는 상류의 값을 여과 없이 하류로 보냄

 

 # 3단계

'exp' 노드 : y=exp(x) 미분

 

 # 4단계

'x' 노드 : 순전파 때의 값을 서로 바꿔 곱

 

역전파의 최종 출력인

 

값이 하류 노드로 전달

 

최종 출력은 순전파의 입력 x와 출력 y만으로 계산 가능

 

Sigmoid 계층의 계산 그래프(간소화)

 

 * 간소화 계산 그래프의 장점

 : 역전파 과정의 중간 계산 생략 더 효율적인 계산

   노드를 그룹화하여 세세한 내용을 노출하지 않고 입력과 출력에만 집중 가능

 

수식을 다음과 같이 정리 할 수 있고 따라서 Sigmoid 계층의 역전파는 순전파의 출력(y)만으로 계산 가능

 

Sigmoid 계층 구현

class Sigmoid:
    def __init__(self):
        self.out = None

    def forward(self, x):
        out = sigmoid(x)
        self.out = out
        return out

    def backward(self, dout):
        dx = dout * (1.0 - self.out) * self.out

        return dx

 

 

5.6 Affine / Softmax 계층 구현

5.6.1 Affine 계층

어파인 변환(Affine transformation) : 신경망의 순전파 때 수행하는 행렬의 곱

어파인 변환을 수행하는 처리를 'Affine 계층'이라는 이름으로 구현

 

신경망의 순전파에서 가중치 신호의 총합을 계산하기 위해 np.dot( ) 사용

행렬의 곱 → 대응하는 차원의 원소 수가 일치해야 함

 

np.dot(X * W) + B 의 Affine 계층의 계산 그래프 (변수 : 행렬)

 

이때 X와 ∂L/∂X, W와  ∂L/∂W는 같은 형상

 

 

행렬 곱('dot' 노드)의 역전파는 행렬의 대응하는 차원의 원소 수가 일치하도록 곱을 조립하여 구할 수 있음

 

5.6.2 배치용 Affine 계층

위의 Affine 계층은 입력 데이터 X 하나만 고려한 것

→ 데이터 N개를 묶어 순전파 구현 (배치용 Affine 계층)

 

데이터가 N개인 배치용 Affine 계층의 계산 그래프

입력을 나타내는 X의 형상이 (N,2)로 바뀜

 

역전파 : 행렬의 형상에 주의하여 ∂L/∂X와  ∂L/∂W 도출 가능

 

편향(B)를 더할 때의 주의사항

 - 순전파 때의 편향 덧셈은 X * W에 대한 편향이 각 데이터에 더해짐

    N=2 (데이터가 2개)인 경우, 편향은 2개의 데이터에 각각 더해짐

 

순전파에서의 편향의 합 예

import numpy as np

X_dot_W = np.array([[0,0,0],[10,10,10]])
B = np.array([1,2,3])

print(X_dot_W)

print(X_dot_W + B)

[결과]

X_dot_W : [[ 0  0  0], [10 10 10]]
X_dot_W + B : [[ 1  2  3],  [11 12 13]]

 

역전파 일 때는 각 데이터의 역전파 값이 편향의 원소에 모여야 함

역전파에서의 편향의 합 예

dY = np.array([[1,2,3], [4,5,6]])
print(dY)

dB = np.sum(dY, axis=0)
print(dB)

[결과]

dY : [[1 2 3],  [4 5 6]]
dB : [5 7 9]

 

편향의 역전파 : 두 데이터(N=2)에 대한 미분을 데이터마다 더해서 구함

np.sum( )에서 0번째 축(데이터를 단위로 한 축)에 대해 (axis=0)의 총합을 구함

 

Affine 구현

import numpy as np

class Affine:
    def __init__(self, W, b):
        self.W = W
        self.b = b
        self.x = None
        # 가중치와 편향 매개변수의 미분
        self.dW = None
        self.db = None

    def forward(self, x):
        self.x = x
        out = np.dot(x, self.W) + self.b

        return out

    def backward(self, dout):
        dx = np.dot(dout, self.W.T)
        self.dW = np.dot(self.x.T, dout)
        self.db = np.sum(dout, axis=0)
       
        return dx

 

5.6.3 Softmax - with - Loss 계층

출력층에서 사용하는 소프트맥스 함수는 입력 값을 정규화(출력 값의 합이 1이 되도록)하여 출력 확률로써 사용 가능

 

ex. 손글씨 숫자 인식에서의 소프트맥스 계층 출력

입력 이미지가 Affine 계층과 ReLU 계층을 통과하며 변환

마지막 소프트맥스 계층에 의해 10개의 입력이 정규화

 

숫자 '0'에 대한 점수는 5.3이고 이 값이 소프트맥스 계층에 의해 0.008(0.8%)로 변환

손글씨 숫자의 가지수 : 10개 → 소프트맥스 계층의 입력 : 10개

 

신경망에서 수행하는 작업 : 1. 학습, 2. 추론

신경망은 추론할 때 마지막 Affine 계층의 출력을 인식 결과로 이용

즉, 신경망 추론에서 답을 하나만 내는 경우에는 가장 높은 점수만 알면 됨으로

추론할 때는 일반적으로 소프트맥스 계층 필요x

BUT 신경망을 학습할 때는 소프트맥스 계층 필요

 

소프트맥스 계층의 계산 그래프 ( + 손실함수인 교차 엔트로피 오차도 포함)

소프트맥스 계층 - 손실함수(교차 엔트로피 오차) 계산 그래프
'간소화한' 소프트맥스 계층 - 손실 함수 교차 엔트로피 오차 계산 그래프

 

3 클래스 분류 가정 : 이전 계층에서 3개의 입력(점수)를 받음

 

softmax 계층 입력 (a1, a2, a3)를 정규화 하여 (y1, y2, y3)를 출력

cross entropy error 계층은 softmax의 출력 (y1, y2, y3)와 정답 레이블(t1, t2, t3)를 받고 이 데이터로 부터 손실 L 출력

 

* 역전파의 결과

softmax  계층의 역전파 : (y1 - t1, y2 - t2, y3 - t3)

= softmax 계층의 출력과 정답 레이블의 차분

→ 신경망의 역전파에서는 이 차이인 오차가 앞 계층에 전해짐

 

신경망 학습의 목적

: 신경망의 출력(소프트맥스의 출력)이 정답 레이블과 가까워지도록 가중치 매개변수의 값을 조정하는 것

따라서 신경망의 출력과 정답 레이블의 오차를 효율적으로 앞 계층에 전달해야 함

 

ex. 정답 레이블 : (0,1,0), softmax 계층 출력 결과 : (0.3, 0.2, 0.5) 일 때

정답의 인덱스 1

출력에서 이때의 확률은 0.2(20%) -- 신경망이 제대로 인식하지 못하고 있음

역전파 : (0.3, -0.8, 0.5)라는 오차 전파

즉, softmax 계층의 앞 계층들은 큰 오차로부터 큰 깨달음을 얻음

 

ex. 정답 레이블 : (0,1,0), softmax 계층 출력 결과 : (0.01, 0.99 0) 일 때

정답의 인덱스 1

출력에서 이때의 확률은 0.99(99%) -- 신경망이 꽤 정확히 인식 중

역전파 : (0.01, -0.01, 0)라는 오차 전파

→ 앞 계층으로 전달된 오차가 작으므로 학습하는 정도도 작아짐

 

Softmax-with-Loss 계층 구현

class SoftmaxWithLoss:
    def __init__(self):
        self.loss = None # 손실함수
        self.y = None    # softmax의 출력
        self.t = None    # 정답 레이블(원-핫 인코딩 형태)
       
    def forward(self, x, t):
        self.t = t
        self.y = softmax(x)
        self.loss = cross_entropy_error(self.y, self.t)
       
        return self.loss

    def backward(self, dout=1):
        batch_size = self.t.shape[0]
        if self.t.size == self.y.size: # 정답 레이블이 원-핫 인코딩 형태일 때
            dx = (self.y - self.t) / batch_size
        else:
            dx = self.y.copy()
            dx[np.arange(batch_size), self.t] -= 1
            dx = dx / batch_size
       
        return dx

주의 : 역전파 때 전파하는 값을 배치 수(batch_size)로 나눠서 데이터 1개당 오차를 앞 계층으로 전파

 


5.7 오차역전파법 구현하기

앞서 구현한 계층들을 조합하여 신경망 구축하기

 

5.7.1 신경망 학습의 전체 그림

신경망 학습 순서

0. 전제 : 신경망에는 적응 가능한 가중치와 편향이 있고, 이 가중치와 편향을 훈련 데이터에 적응하도록 조정하는 과정을 '학습'이라 함

신경망 학습은 아래와 같이 4단계로 수행

  1. 단계 - 미니배치 : 훈련 데이터 중 일부를 무작위로 가져오는데 이렇게 선별한 데이터를 미니배치라고 함. 이 미니배치의 손실 함수 값을 줄이는 것이 목표
  2. 단계 - 기울기 산출 : 미니배치의 손실 함수 값을 줄이기 위해 각 가중치 매개변수의 기울기를 구함. 기울기는 손실 함수의 값을 가장 작게 하는 방향 제시
  3. 단계 - 매개변수 갱신 : 가중치 매개변수를 기울기 방향으로 아주 조금 갱신
  4. 단계 - 반복 : 1~3단계 반복

오차역전파법은 2단계의 '기울기 산출' 단계

전 장에서는 수치 미분을 이용하여 기울기를 구함

수치 미분은 구현에는 쉽지만 계산이 오래걸린다는 단점이 있음

따라서 오차역전파법을 이용하면 기울기를 효율적이고 빠르게 구할 수 있음

 

5.7.2 오차역전파법을 적용한 신경망 구현하기

수치 미분 구현과의 차이점은 계층(layer)을 사용한다는 점

계층을 사용함으로써 인식 결과를 얻는 처리(predict( ))와 기울기를 구하는 처리(gradient( )) 계층 전파만으로 동작이 이루어짐

 

import sys, os
sys.path.append(os.pardir)
import numpy as np
from common.layers import *
from common.gradient import numerical_gradient
from collections import OrderedDict

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)

        # 계층 생성
        # layers : 순서가 있는 딕셔너리 변수, 신경망의 계층 보관
        self.layers = OrderedDict()
        self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
        self.layers['Relu1'] = Relu()
        self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])

        # 신경망의 마지막 계층 : softmax-with-loss 계층
        self.lastLayer = SoftmaxWithLoss()
   
    def predict(self, x):
        for layer in self.layers.values():
            x = layer.forward(x)
       
        return x
       
    # x : 입력 데이터, t : 정답 레이블
    def loss(self, x, t):
        y = self.predict(x)
        return self.lastLayer.forward(y, t)
   
    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        if t.ndim != 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):
        # forward
        self.loss(x, t)

        # backward
        dout = 1
        dout = self.lastLayer.backward(dout)
       
        layers = list(self.layers.values())
        layers.reverse()
        for layer in layers:
            dout = layer.backward(dout)

        # 결과 저장
        grads = {}
        grads['W1'], grads['b1'] = self.layers['Affine1'].dW, self.layers['Affine1'].db
        grads['W2'], grads['b2'] = self.layers['Affine2'].dW, self.layers['Affine2'].db

        return grads

 * 중요

: 신경망의 계층을 OrderDict에 보관

OrderDict : 순서가 있는 딕셔너리 ('순서가 있는' : 딕셔너리에 추가한 순서를 기억)

따라서 순전파 때는 추가한 순서대로 각 계층의 forward( ) 메서드를 호출하기만 하면 처리 완료

마찬가지로 역전파 때는 계층을 반대 순서로 호출하면 됨

 

Affine 계층과 ReLU 계층이 각자의 내부에서 순전파와 역전파를 제대로 처리하고 있으니

그냥 계층을 올바른 순서로 연결한 다음 순서대로(혹은 역순으로) 호출

 

이처럼 신경망의 구성 요소를 '계층'으로 구현한 덕분에 신경망을 쉽게 구축 가능

 

* '계층'으로 모듈화해서 한 구현 효과

5, 10, 15층 ... 과 같이 깊은 신경망을 만들고 싶다면, 단순히 필요한 만큼 계층을 더 추가하면 됨

 

5.7.3 오차역전파법으로 구한 기울기 검증하기

여태까지 공부한 기울기 구하는 방법 두 가지

1. 수치 미분

 - 느림

 - 구현이 쉬움

2. 해석적으로 수식을 풀어 구함

 - 오차역전파법

 - 매개변수가 많아도 효율적으로 계산 가능

 

오차역전파법을 제대로 구현하면 수치 미분은 더 이상 필요 x

그렇다면 수치 미분은 아무런 쓸모가 없는 것인가?

>> 수치 미분은 오차역전파법을 정확히 구현했는지 확인하기 위함

 

수치 미분은 구현이 쉽기 때문에 버그가 숨어있기 어려움

반면, 오차역전파법은 구현하기 복잡하여 실수 발생 가능

 

이처럼 두 방식으로 구한 기울기가 일치함을 확인하는 작업 : 기울기 확인(gradient check)

 

기울기 확인 구현 코드

import sys, os
sys.path.append(os.pardir
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)

x_batch = x_train[:3]
t_batch = t_train[:3]

grad_numerical = network.numerical_gradient(x_batch, t_batch)
grad_backprop = network.gradient(x_batch, t_batch)

# 각 가중치의 절대 오차의 평균을 구한다.
for key in grad_numerical.keys():
    diff = np.average( np.abs(grad_backprop[key] - grad_numerical[key]) )
    print(key + ":" + str(diff))

1. MNIST 데이터셋을 읽음

2. 훈련 데이터 일부를 수치 미분으로 구한 기울기와 오차역전파법으로 구한 기울기의 오차 확인

3. 각 가중치 매개변수의 차이의 절댓값을 구함

4. 이를 평균한 값이 오차

 

[실행 결과]

W1:3.929648552069195e-10
b1:2.0355001660891587e-09
W2:5.117373812507328e-09
b2:1.3924826404165812e-07

 

>> 수치 미분과 오차역전파법으로 구한 기울기의 차이가 작음을 보여줌

따라서 오차역전파법으로 구한 기울기도 올바름을 확인

 

5.7.4 오차역전파법을 사용한 학습 구현하기

오차역전파법을 사용한 신경망 학습 구현

import sys, os
sys.path.append(os.pardir)

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

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