본문 바로가기

AI/딥러닝

[밑바닥부터 시작하는 딥러닝] 5장 오차역전파법

728x90
반응형

5.1 계산그래프

오차역전파법을 제대로 이해하는 방법
1. 수식을 통해
2. 계산 그래프를 통해
 
계산그래프 - 계산 과정을 그래프로 나타낸 것. 복수의 노드와 에지(노드 사이의 직선)로 표현

 

5.1.1 계산그래프로 풀다

문제2) 슈퍼에서 사과를 2개, 귤을 3개 샀습니다. 사과는 1개에 100원, 귤은 1개 150원입니다. 소비세가 10%일 때 지불금액을 구하세요.

덧셈노드, 곱셈노드 사용

<계산 그래프를 이용한 문제풀이>
1. 계산그래프를 구성한다.
2. 그래프에서 계산을 왼쪽에서 오른쪽으로 진행한다.
 
순전파 - 계산을 왼쪽에서 오른쪽으로 진행하는 단계
 
5.1.2 국소적 계산
국소적 - 자신과 직접 관계된 작은 범위

각 노드에서의 계산은 국소적 계산
자신과 관련한 계산(입력된 두 숫자의 덧셈) 외에는 신경쓰지 않음
 
5.1.3 왜 계산 그래프로 푸는가?
<계산 그래프의 이점>
1. 국소적 계산 - 복잡해도 단순한 계산에 집중하여 문제를 단순화 할 수 있음
2. 중간 계산 결과를 모두 보관할 수 있음
3. 역전파를 통해 미분을 효율적으로 계산할 수 있음
 

'사과 개수에 대한 지불 금액의 미분' , '소비세에 대한 지불금액의 미분'
 
 

5.2 연쇄법칙

국소적 미분을 전달하는 원리 → 연쇄법칙에 따름
 
5.2.1 계산그래프의 역전파

신호 E에 노드의 국소적 미분을 곱한 후 다음 노드로 전달
(상류에서 전달된 값에 국소적 미분을 곱해 앞쪽 노드로 전달)
 
합성함수 - 여러함수로 구성된 함수

합성 함수의 미분은 합성 함수를 구성하는 각 함수의 미분의 곱으로 나타낼 수 있음
연쇄법칙 = 함성함수의 미분에 대한 성질
 

역전파가 하는 일 = 연쇄법칙의 원리
 

5.3 역전파

<덧셈노드>

상류에서 전해진 미분 = 1
덧셈 노드 역전파는 1을 곱하기만 하기에 상류의 값을 그대로 다음 노드로 보냄.
 
<곱셈노드>

상류에서 전해진 미분- x였다면 역전파에서는 y / y였다면 역전파에서는 x
상류의 값에 순전파 때의 입력 신호들을 '서로 바꾼 값'을 곱해서 하류로 보냄
순방향 입력 신호의 값 필요
 

사과 가격에 대한 지불 금액의 미분, 사과 개수에 대한 지불 금액의 미분, 소비세에 대한 지불금액의 미분
 

5.4 단순한 계층 구현하기

 forward() 순전파, backward() 역전파
 
<곱셈 계층 MulLayer>

class MulLayer:
  def __init__(self):
    self.x = float(None)
    self.y = float(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

순전파 때의 값을 '서로 바꿔' 곱한 후 하류로 흘림

    dx=dout*self.y
    dy=dout*self.x

backward() 가 받는 인수 - '순전파의 출력에 대한 미분'
 
 
<덧셈 계층 AddLayer>

class AddLayer:
  def __init__(self):
    None
  
  def forward(self,x,y):
    out=x+y
    return out

  def backward(self,dout):
    dx=dout*1
    dy=dout*1
    return dx, dy

 
 

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

<ReLu 계층>

 

입력이 x가 0보다 크면 역전파는 상류의 값 그대로 흘림
x가 0 이하면 역전파 때는 하류로 신호 보내지 않음

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

mask의 원소가 True인 곳(값이 0 이하)에서는 상류에서 전파된 dout을 0으로 설정
 
<Sigmoid 계층>

exp 노드, / 노드 등장
 
1단계

상류에서 흘러온 값(순전파의 출력 제곱 후 마이너스) 에 -y^2 을 곱해 하류로 전달
 
2단계

'+'노드는 상류의 값을 그대로 하류로 보냄
 
3단계

순전파 때의 출력을 곱해 하류로 전파
 
4단계
'X' 노드는 순전파 때의 값을 서로 바꿔 곱함

-1 곱해서 하류로 전파
 
중간과정을 묶어 '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 계층 구현하기

신경망의 순전파 때 수행하는 행렬의 곱 = 어파인 변환
 

Y= np.dot(X,W) + B

Y를 활성화 함수로 변환해 다음 층으로 전파함 - 신경망 순전파의 흐름
행렬의 곱 계산은 대응하는 차원의 원소 수를 일치시켜야 함
 

<Affine 계층의 역전파>

노드 사이에 행렬(다차원 배열)이 흐르고 있음.
→ 차원의 원소 수를 일치시켜야함
 
 

<배치용 Affine 계층>

데이터를 N개인 경우로 확장시킴.
묶은 데이터 = 배치
 
순전파 때는 편향이 XW에 대한 편향이 각 데이터에 더해짐
역전파 때는 각 데이터의 역전파 값이 편향의 원소에 모여야함
편향의 역전파는 데이터에 대한 미분을 데이터마다 더해서 구함
 
 

<Softmax-with-Loss 계층>

softmax 계층은 입력 값을 정규화 (출력의 합이 1이 되도록 변형) 하여 출력함
 
신경망에서 수행하는 작업- 학습, 추론
추론할 때는 Softmax 계층을 일반적으로 사용 X, Softmax 앞의 Affine 층의 출력을 점수라고 함
 
손실함수인 교체 엔트로피 오차도 포함하여 'Softmax-with-Loss 계층'을 구현

Softmax 계층의 역전파 = (y1-t1, y2-t2, y3-t3) = Softmax 계층의 출력과 정답레이블의 오차(차분)
 

class SoftmaxWithLoss:
  def __init__(self):
    self.loss=None #손실
    self.y=None #softmax의 출력
    self.x=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]
    dx=(self.y-self.t)/batch_size #batch size로 나눠서 데이터 1개당 오차를 앞 계층으로 전파

    return dx

 
 

5.7 오차역전파법 구현하기

<신경망 학습의 순서>
1단계 - 미니배치
훈련 데이터 중 일부를 무작위로 가져옴. 선별한 데이터 = 미니배치, 미니배치의 손실함수 값을 줄이는 것이 목표
2단계 - 기울기 산출
미니배치의 손실 함수 값을 줄이기 위해 각 가중치 매개변수의 기울기를 구함
3단계 - 매개변수 갱신
가중치 매개변수를 기울기 방향으로 아주 조금 갱신
4단계 - 반복
1~3단계 반복
 

import sys, os
sys.path.append
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)
 
        # 계층 생성
        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'])
 
        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

 

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

수치미분의 이점 - 구현하기 쉬움 / 느림
오차역전파법의 이점 - 매개변수 많아도 효율적으로 계산 가능 /  복잡해서 실수 있을 수 있음

import sys, os
sys.path.append
import numpy as np
from dataset.mnist import load_mnist
from ch05.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))

수치 미분으로 구한 기울기와 오차역전파법으로 구한 기울기의 오차 확인
 

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

import sys, os
sys.path.append
 
import numpy as np
from dataset.mnist import load_mnist
from ch05.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)

 

 

+) softmax, cross entropy error 함수 유도과정 따라해보기

728x90
반응형