Deep Learning/from scratch I

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

해파리냉채무침 2024. 2. 8. 22:53

Affine/Softmax 계층 구현

Affine 계층

어파인계층은 행렬의 곱을 사용하여 계산한다. 

https://velog.io/@rjtp5670/%EB%94%A5%EB%9F%AC%EB%8B%9D%EA%B3%BC-%EC%9D%B8%EA%B3%B5%EC%8B%A0%EA%B2%BD%EB%A7%9D-Affine-%EA%B3%84%EC%B8%B5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0

X는 ∂L/ ∂X과 같은 형상이고, W는  ∂L/ ∂W와 같은 형상이다. 

행렬의 곱의 역전파는 대응하는 차원의 원소수가 일치하도록 곱을 조립한다. 

배치용 Affine 계층

데이터N개를 묶어 순전파 하는 경우에는, 배치용 affine 계층을 생각해볼 수 있다. 

https://velog.io/@kylebae1017/Backpropagation-in-Affine-Layer

 순전파 때의 편향 덧셈은 X*W 에 대한 편향이 각 데이터에 전해진다. 예를들어 N=2 일 경우, 편향이 두개의 데이터 각각에 더해진다.

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

순전파 일 때는, 편향이 각각에 데이터에 더해진다.  역전파 일때는 다음과 같이 역전파값이 편향의 원소에 모여야 한다.

dY = np.array([[1,2,3],[4,5,6]])
dB = np.sum(dY, axis = 0)
dB
array([5, 7, 9])

편향의 역전파는 두 데이터의 미분을 데이터 마다 더한다. 그러므로 axis=0 세로축으로 하여 총합을 구한다.

affine 구현은 다음과 같이 한다.

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-with-Loss 계층

softmax 계층은 입력값을 정규화(출력의합이 1이 되도록) 하여 출력

https://velog.io/@rjtp5670/%EB%94%A5%EB%9F%AC%EB%8B%9D%EA%B3%BC-%EC%9D%B8%EA%B3%B5%EC%8B%A0%EA%B2%BD%EB%A7%9D-Affine-%EA%B3%84%EC%B8%B5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0

class(숫자 0 ~9)가 10개가 softmax 계층에 입력,출력되고, 숫자 2가 99.1% 확률로 출력이 되는 것을 알 수 있음. 그리고 이것의 affine 계층의 점수는 10.1이다. 

softmax-with-loss 계층은 softmax계층+ cross entropy error 계층 같이 합하여 구현된다. 

https://kiyosucyberclub.web.fc2.com/PytnPage06-06.html

순전파: (a1,a2,a3)를 정규화하여  (y1,y2,y3)을 출력한다. cross entropy error 계층에서 정답레이블 (t1,t2,t3)을 받고 손실 L을 출력함.

역전파: softmax 계층의 출력 (y1,y2,y3)에서 정답레이블 (t1,t2,t3)을 빼줌. (y1-t1, y2-t2,y3-t3) 값이 앞 노드에 전해준다.

코드를 구현하면 다음과 같다

class SoftmaxWithLoss:
    def __init__(self):
        self.loss = None #손실
        self.y = None    #softmax의 출력
        self.t = None    #정답 레이블(one hot incoding)
        
    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

역전파 시 전파값을 batch size로 나눠 각 데이터의 오차를 앞 계층에 전파한다. 

오차역전파 구현

1단계 미니배치-> 훈련데이터 일부를 무작위로 가져와서 loss function 최소화 

2단계 기울기 산출 -> loss function을 최소화 하기 위해 가중치 매개변수의 기울기를 구함.

3단계 매개변수 갱신 -> 가중치 매개변수를 기울기 방향으로 갱신

1~3단계 반복

import sys,os
sys.path.append(os.pardir)
import numpy as np
from common.functions 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
        
     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
    
    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):
        #순전파
        self.loss(x, t)

        #역전파
        dout = 1 #맨 마지막 층이므로 다음 층에서 흘러들어오는 값이 없으므로 1(downstream층에서 곱하기 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['Affine'].dW
        grads['b1'] = self.layers['Affine'].db
        grads['W2'] = self.layers['Affine'].dW
        grads['b2'] = self.layers['Affine'].db
        return grads

 

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

수치 미분과 오차역전파 결과를 비교하여 두 방식의 기울기가 거의 같은지 확인한다.

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

W2:9.71260696544e-13

b2:1.20570232964e-10

W1:2.86152966578e-13
b1:1.19419626098e-12

수치 미분과 오차역전파법으로 구한 기울기 차이가 매우 작다. -> 이는 잘 구현됐음을 알 수 있다

만약 값이 크면 오차역전파법을 잘못 구했다고 의심할 수 있음