5.6 Affine / Softmax 계층 구현하기

5.6.1 Affine 계층 

Affine 변환 = 순전파에서 행렬,   

Affine 계층 = Affine 변환을 수행하는 처리

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

 

그리고 행렬 곱의 핵심은 원소 개수 (차원 개수)를 일치시키는 것이다.

 

예시를 통해 그림으로 이해해보자. (계산 그래프)

X,W,B가 있다고 하자. 각각 입력, 가중치, 편향이고  shape은 각각(2,) , (2,3), (3,)이다.

X와 W가 들어와 np.dot을 통해 행렬 곱 수행 -> x * W   -> ( x * W ) +B  = Y

 

이제 역전파를 알아보자.

아래 식이 도출되는것을 확인하자.

  WT는 T의 전치행렬을 말함. 

(전치행렬 = Wij 를 Wji로 바꾼것. 아래 그림 참조)

위 식을 통해서 계산 그래프의 역전파를 구해보자.

위 그림의 네모 박스 1,2를 유심히 살펴보자. (덧셈노드는 그대로 흐르고, 곱셈 노드는 교차하므로 !!)

X,W,B의 shape은 각각(2,) , (2,3), (3,)이다.

∂L/∂Y는 (3,) 인데 W인 (2,3)과 차원이 달라 곱셈이 되지 않는다. 

따라서 WT를 통해 (3,2)로 바꿔 곱셈을 수행할 수 있도록 변환 (+X도 마찬가지)

 

5.6.2 배치용 Affine 계층

지금까지 예시로 든것은 X (입력 데이터)가 하나일 때만 고려한 상황이였다.

하지만 현실에서는 하나일 확률이 매우 적으므로 실제 Affine 계층을 생각해보자.

 

입력 데이터가 N개일 경우이다.

5.6.3 Softmax-with-Loss 계층

마지막으로 배울 것은 출력층에서 사용하는 소프트맥스 함수이다.

  • 딥러닝에서는 학습과 추론 두 가지가 있다.
  • 일반적으로 추론일 때는 Softmax 계층(layer)을 사용하지 않는다. Softmax 계층 앞의 Affine 계층의 출력을 점수(score)라고 하는데, 딥러닝의 추론에서는 답을 하나만 예측하는 경우에는 가장 높은 점수만 알면 되므로 Softmax 계층이 필요없다. 반면, 딥러닝을 학습할 때는 Softmax 계층이 필요하다.

이제 소프트맥스 계층을 구현할텐데, 손실 함수인 엔트로피 오차도 포함하여 계산 그래프를 살펴보자.

너무 복잡하다. 간소화 해보자.

여기서는 3클래스 분류를 가정하고 이전 계층에서 3개의 입력(점수)를 받는다.

그림과 같이 Softmax 계층은 입력 a1,a2,a3를 정규화하여 y1,y2,y3를 출력함

Cross-entropy계층은 y1,y2,y3와 t1,t2,t3(정답)을 넘겨받고 손실 L를 출력함.

 

위 그림에서 역전파의 결과인 (y1-t1),(y2-t2),(y3-t3)를 보면 softmax계층의 출력 정답레이블의 차분임.

따라서 신경망에서의 역전파는 '출력 - 정답', 즉 오차를 출력한다고 확인할 수 있다.

파이썬으로Softmax-with-Loss를 구현해보자.

# Softmax-with-Loss 계층 구현
# common / layer.py
 
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

5.7 오차역전파법 구현하기

5.7.1 신경망 학습의 전체 그림

 

이제 구현해보자!!

5.7.2 오차역전파를 사용한 신경망 구현

import sys, os
sys.path.append('/deep-learning-from-scratch')
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)
 
        # 계층 생성
        # OrderedDict = 순서가 있는 딕셔너리, 순서 기억
        # 순전파 때는 계층을 추가한 순서대로 / 역전파 때는 계층 반대 순서로 호출
        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

아래 표를 참고해서 위 코드를 이해합시다.

또한 위 코드에서 OrderDict주의 ( 순서가 있는 딕셔너리 ) 이를 통해 순전파, 역전파를 쉽게 사

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

  • 느린 수치 미분보다 오차역전파법을 사용한 해석적 방법이 효율적 계산 가능
  • 수치 미분은 오차역전파법을 정확히 구현했는지 확인위해 필요
  • 수치 미분은 구현하기 쉬우나, 오차역전파법은 구현 복잡해 실수가 있을 수 있음
  • 기울기 확인(gradient check) : 두 방식으로 구한 기울기가 일치(거의 같음)함을 확인 하는 작업
# 기울기 확인 (gradient check)
import sys, os
sys.path.append('/deep-learning-from-scratch')
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))
    
>>>
W1:4.1821647831167817e-10
b1:2.534937764494963e-09
W2:5.183343681548899e-09

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

import sys, os
sys.path.append('/content/drive/MyDrive/deep-learning-from-scratch')
 
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)
        
>>>
0.16111666666666666 0.1669
0.9040833333333333 0.9045
0.9236666666666666 0.9269
0.93625 0.9373
0.94525 0.944
0.9503833333333334 0.948
0.9547166666666667 0.951
0.9602166666666667 0.9569
0.9626333333333333 0.9588
0.9652166666666666 0.9598
0.9688 0.9619
0.9709833333333333 0.9641
0.9729 0.9653
0.9746166666666667 0.9667
0.97505 0.9663
0.97645 0.967
0.9784833333333334 0.9692

'밑바닥 딥러닝' 카테고리의 다른 글

Ch 6-2 학습 관련 기술들  (0) 2023.05.31
Ch 6-1 학습 관련 기술들  (0) 2023.05.29
Ch 5-1 오차 역전파 ( Error Backpropagation )  (0) 2023.05.27
Ch4-2 신경망 학습  (0) 2023.05.26
Ch4-1 신경망 학습  (0) 2023.05.17

+ Recent posts