본문 바로가기
데이터 청년 캠퍼스/연관분석, 인공신경망

[인공신경망] 오류역전파

by 뚱뚜루뚱 2022. 7. 19.
계산 그래프

Q1. 100원인 사과 2개의 구매금액은? 단 소비세 10%을 부과한다

문제풀이 흐름: 계산 그래프를 구성한다 → 그래프에서 계산을 왼쪽에서 오른쪽으로 진행한다(순전파 Forward Propabation)

 

국소적 계산

계산 그래프는 국소적 계산을 전파해 최종 결과를 얻는다. 국소적은 자시과 직접 관계된 작은 범위를 의미한다. 각 노드는 자신과 관련된 계산만을 담당하며, 노드까지의 중간 결과는 저장한다. 이러한 방식은 복잡한 문제를 단수화하는데 도움이 된다.

 

역전파 Back Propagation

오른쪽에서 왼쪽으로 전달되는 흐름을 의미한다. 계산 그래프에서 오른쪽으로 왼쪽으로 진행하며 미분값을 구한다

 

 

 

연쇄법칙

합성함수의 미분

z = t**2

t = x+ + y

연쇄법칙과 계산 그래프

각 노드는 이전 노드에서 전달 받은 미분에 해당 노드의 미분을 곱해 전달하는 과정을 통하여 전체 그래프의 미분을 구할 수 있다.

 

덧셈 노드의 역전파

z = x + y : 덧셈의 미분은 1이므로 역전파에서는 입력값을 그대로 전달한다

 

곱셈 노드의 역전파

z = xy : 곱셈의 역전파에서는 서로 바꾼 값을 전달한다

class MulLayer :
    def __init__(self):
        self.x = None
        self.y = None
        
    def forward (self, x, y) :
        self.x = x
        self.y = y
        out = x*t
        return out
    
    def backward (self, dout) :
        dx = dout * self.x   
        dy = dout * self.y
        return dx, dy

 

 

클래스 Class

파이썬은 모든 것을 클래스로 정의하는 언어이다. 클래스는 변수, 메소드를 정의하며 프로그램 구조화 모델링을 위한 일종의 틀이다. 데이터 분류에서 사용하는 분류대상을 가리키는 클래스와는 다른 의미이다. 생성된 인스턴스(객체)는 상태를 보관하고 메소드를 실행할 수 있다.

  • 과자틀 =  클래스 Class = 유형 / 타입 Type
  • 과자 = 객체 Object = 실체 Instnace = 값 Value

 

 

단순한 계층 구현하기

덧셈 계층 구현

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

 

 

활성화 함수 계층 구현하기

ReLU 계층

역전파에서 양수값은 그대로, 음수값은 0으로 전달한다.  mask 인스턴스 변수는 True/False로 구성된 넘파이 배열로, 순전파의 입력인 x의 원소값이 0 이하인 인덱스는 True, 그 외의 0보다 큰 원소는 False로 유지한다.

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

 

Sigmoid 계층

class Sigmoid :
    def __init__(self) :
        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-self.out) * self.out
        return dx

 

 

 

Affine 계층

 신경망에서 신호의 전달은 가중치와 신호를 곱하고 편향을 더하는 행렬 연산으로 표현한다. 이때 행렬의 곱을 수행하는 층을 Affine 계층이라 부르고, 미분도 행렬의 미분으로 처리한다.

행렬의 곱 미분

행렬에서 변수 X에 의한 미분은 다음과 같이 표현되며 두 식은 동일한 형상을 갖는다. 동일한 형상으로 복원하기 위해 행렬의 곱을 전치 Transpose하고 이웃하여 수행한다

X = np.array ([10,20])
W = np.array ([[0.1,0.2,0.3],[0.4,0.5,0.6]])
B = np.array ([100,200,300])

print ("X,W,B's shape ", X.shape, W.shape, B.shape)
Y = np.dot (X, W) + B
print ("Y's shape ",Y.shape)

 

행렬곱의 역전파(미분)

배치용 Affine 계층

N개의 데이터를 묶어서 처리할 수 있는 계층이다. 편향의 역전파 처리시, 형상을 편항의 모양에 맞추기 위해서는 N개 데이터의 미분을 데이터마다 더해서 구한다

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

 

 

 

Softmax 계층 구현하기

Softmax - with - Loss 계층

 출력층에서 함수의 입력값을 정규화하여 출력한다. 학습 단계 출력층인 소프트맥스 계층을 지나면 손실함수를 계산하는 교차 엔트로피 오차 계층이 배치된다. 이를 하나로 묶어 Softmax-with-loss 계층이라는 이름으로 구현하며 네트워크 그래프로 표현할 수 있다. 역전파 도는 값은 소프트맥스 계층의 출력값과 정답레이블의 차이다.

class SoftmaxWithLoss :
    def __init__(self):
        self.loss = None
        self.y = None
        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]
        dx = (self.y - self.t) / batch_size
        return dx

 

 

 

오차 역전파법 구현하기
  • 수치미분으로 구현한 신경망: 느리지만 구현원리가 단순해 검증에 이용한다
  • 오차 역전파법(해석적 방법)으로 구현한 신경망: 수행속도가 빠르고 복잡한 망도 구현할 수 있다

두 방식으로 구한 기울기가 근사함을 확인하는 작업을 Gradient Check라고 한다

 

1. TwoLayerNet 신경망 정의

from common.layers import *
from common.gradient import numerical_gradient
import numpy as np
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
    
    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
    
    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'] = self.layers['Affine1'].dW
        grads['b1'] = self.layers['Affine1'].db
        grads['W2'] = self.layers['Affine2'].dW
        grads['b2'] = self.layers['Affine2'].db
        
        return grads

 

2. 기울기 검증

from mnist import load_mnist

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

print ('수치미분으로 구한 기울기와 오차 역전파로 구한 기울기 차이 비교')
for key in grad_numerical.keys() :
    diff = np.average (np.abs (grad_backprop[key]-grad_numerical[key]))
    print (f'{key} : {diff}')

 

 

3. 학습 구현

(x_train, t_train), (x_test, t_test ) = load_mnist (normalize = True, one_hot_label = True)

train_loss_list = []
train_acc_list = []
test_acc_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)
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.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(f'train_acc={train_acc:.4f},', f' test_acc={test_acc:.4f}')