ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 4. 오차역전파법(4)
    AI 모델(딥러닝 기초)/4. 오차역전파법 2023. 1. 26. 10:29
    728x90

    ※ 오차역전파법을 활용한 신경망 개선

     

    이전 3장 마지막에서 기울기 개선에 대해서 살짝 언급하고 지나간 적이 있는 것을 기억할지 모르겠다. 신경망의 학습 순서 중 기울기 산출 단계에서 이전에는 수치미분을 사용하여 기울기를 산출했다. 하지만, 수치 미분법은 시간이 오래 걸려 현업에서는 잘 사용하지 않는다. 그렇다면 지금까지 배운 오차역전파법을 사용하여 기울기 산출 단계를 개선해 보자. 

     

    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)   # 1층 가중치
            self.params['b1'] = np.zeros(hidden_size)                                        # 1층 편향
            self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)  # 2층 가중치
            self.params['b2'] = np.zeros(output_size)                                        # 2층 편향
            
            # Layer 계층 생성
            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():      # OrderedDict 딕셔너리의 value 안에서 순전파로 예측
                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):    # numerical_gradient 개선법 - 기울기 고속으로 구하기
            
            # 순전파
            self.loss(x, t)
            
            # 역전파
            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

     

    개선 1. 계층 초기화 단계에서 OrderedDict라는 collections 모듈의 딕셔너리를 사용했다. OrderedDict는 순서가 있는 딕셔너리 이기 때문에 저장 시 자동으로 순서를 기억해 준다. 이 것을 통해 순전파 시에 추가한 순서대로 forward() 메서드만 호출하면 되도록 구현했고, 역전파 시에는 그 반대 순서로 호출해 주기만 하면 된다. 또한, 마지막 Layer의 경우 SoftmaxwithLoss() 메서드를 사용하여 처리해 주었다.

     

    개선2. predict() 함수를 OrderedDict 딕셔너리의 value들을 forward() 메서드만 호출하는 식으로 처리해주었다.

     

    개선3. loss() 함수의 return 값을 softmaxwithLoss 처리가 된 lastLayer 들의 순전파 값으로 처리했다.

     

    개선4. 새로운 gradient() 함수를 만들어 순전파와 역전파 방식으로 처리했다. Affine과 Relu 계층으로 layer를 구성했고 잘 처리하고 있으므로 OrderedDict 딕셔너리를 사용한 방식대로 순서대로 호출, 반대 순서대로 호출하는 식으로 구현했다.

     

    계층으로 구현하여 신경망을 손쉽게 구축하는 모습이다. 층을 늘리고 싶다면 단순히 계층만 계속 추가해주면 된다. 국소적으로 구현한 방식이기 때문에 layer를 늘리거나 줄일 때 효율적이다.

     

     

    ※ 오차역전파법의 기울기 검증

    - 오차역전파법 : 빠르지만 구현이 어려워 실수가 발생할 수 있다.

    - 수치미분 : 느리지만 구현이 쉬워 실수가 거의 발생하지 않는다.

     

    오차역전파법을 현업에서 주로 사용하지만, 실수가 발생할 수 있으므로 수치미분과의 비교를 통해 기울기가 잘 구해졌는지 검증하는 과정이 필요하다. 이를 기울기 확인이라고 한다.

     

    import sys, os
    sys.path.append(os.pardir)
    import numpy as np
    from dataset.mnist import load_mnist
    from two_layer_network 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(f"{key} : {str(diff)}")

     

    신경망의 데이터를 읽어와서 grad_numercial(수치미분), grad_backprop(오차역전파법)의 차분을 구한다. 절댓값 형식으로 처리하고 평균을 구하여 값을 나타낸다.

    W1 : 1.3277321934853825e-07
    b1 : 1.4872281499453704e-06
    W2 : 5.933398213160213e-09
    b2 : 1.4056022481639953e-07

    값이 이처럼 굉장히 작은 수로 0에 가깝게 나오게 되면 올바르게 오차역전파법으로 기울기를 구했다고 생각할 수 있다. 컴퓨터의 계산 정밀도가 유한하여 오차가 0이 되는 일은 거의 드물기 때문에 0에 가까운 수로 값이 나오게 되면 올바른 기울기를 구했다고 판단하면 된다.

     

     

    ※ 오차역전파법을 이용한 신경망 학습 개선

    이전 3장에서 신경망을 학습 시켜 train과 test 데이터셋의 accuracy를 뽑아냈던 것을 기억할 것이다. 이제 오차역전파법을 이용하여 신경망 학습에서 개선된 기울기 산출법까지 사용하여 학습을 시켜보자.

     

    from dataset.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)
    
    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.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 : {train_acc}, test : {test_acc}")

     

    위의 TwoLayerNet class를 그대로 사용하여 mnist 데이터셋을 학습시킨 코드이다. epoch과 train, test 데이터셋, 학습률, 배치방식, 2층 신경망에서 학습시킨 것은 동일하다.

     

    개선1. 기울기 산출방식에서 grad값을 개선된 오차역전파법을 이용한 gradient() 함수로 사용했다. 

     

    개선2. loss 값을 개선된 코드의 loss() 함수로 사용했다.

     

    개선3. accuracy 값을 개선된 코드의 accuracy() 함수로 사용했다.

     

    train : 0.15176666666666666, test : 0.1484
    train : 0.9036666666666666, test : 0.9064
    train : 0.9215833333333333, test : 0.9226
    train : 0.93565, test : 0.9355
    train : 0.9424666666666667, test : 0.9419
    train : 0.9490333333333333, test : 0.9464
    train : 0.9540833333333333, test : 0.9508
    train : 0.959, test : 0.9568
    train : 0.9620333333333333, test : 0.9586
    train : 0.96545, test : 0.9609
    train : 0.9698, test : 0.9634
    train : 0.9709666666666666, test : 0.9654
    train : 0.9720666666666666, test : 0.9662
    train : 0.9718333333333333, test : 0.965
    train : 0.9757166666666667, test : 0.9692
    train : 0.9770166666666666, test : 0.9684
    train : 0.9769, test : 0.9683

    이렇게 값을 산출했고, 직접 코드를 돌려보면 이전보다 확실히 빠르게 값이 산출되어 나오는 것을 확인할 수 있을 것이다.

     

     

    728x90

    'AI 모델(딥러닝 기초) > 4. 오차역전파법' 카테고리의 다른 글

    4. 오차역전파법(3)  (0) 2023.01.25
    4. 오차역전파법(2)  (0) 2023.01.24
    4. 오차역전파법(1)  (0) 2023.01.20
Designed by Tistory.