Deep Learning

[Deep Learning] 오차역전파법 구현(Back Propagation)

heedy 2022. 11. 7. 13:56
728x90

신경망 학습의 전체 그림

신경망 학습의 순서

  • 전제
    • 신경망에는 적응 가능한 가중치와 편향이 있고, 이 가중치와 편향을 훈련 데이터에 적응하도록 조정하는 과정을 '학습'이라 합니다. 신경망 학습은 다음과 같이 4단계로 수행합니다.
  • 1단계: 미니배치
    • 훈련 데이터 중 일부를 무작위로 가져옵니다. 이렇게 선별한 데이터를 미니배치라 하며, 그 미니배치의 손실 함수 값을 줄이는 것이 목표입니다.
  • 2단계: 기울기 산출
    • 미니배치의 손실 함수 값을 줄이기 위해 각 가중치 매개변수의 기울기를 구합니다. 기울기는 소실 함수의 값을 가장 작게 하는 방향을 제시합니다.
  • 3단계: 매개변수 갱신
    • 가중치 매개변수를 기울기 방향으로 아주 조금 갱신합니다.
  • 4단계: 반복
    • 1~3단계를 반복합니다.
  • 오차 역전파법이 등장하는 단계는 두번째인 '기울기 산출'입니다.

오차역전파법을 적용한 신경망 구현하기

2층 신경망을 TwoLayerNet 클래스로 구현합니다.

TwoLayerNet 클래스의 인스턴스 변수

  • params
    1. 딕셔너리 변수로, 신경망의 매개변수를 보관
    2. params['W1']은 1번째 층의 가중치, params['b1']은 1번째 층의 편향
    3. params['W2']은 2번째 층의 가중치, params['b2']은 2번째 층의 편향
  • layers
    1. 순서가 있는 딕셔너리 변수로, 신경망의 계층을 보관 2, layers['Affine1'],ayers['Relu1'],layer['Affine2']와 같이 각 계층을 순서대로 유지
  • lastLayer
    1. 신경망의 마지막 계층
    2. 이 예에서는 SoftmaxWithLoss 계층

TwoLayerNet 클래스의 메서드

  • __init__(self. input_size, hidden_size, output_size, weight_init_std)
    1. 초기화를 수행한다.
    2. 인수는 앞에서부터 입력층 뉴런 수, 은릭층 뉴런 수, 출력층 뉴런 수, 가중치 초기화 시 정규분포의 스케일
  • predict(self, x)
    1. 예측(추론)을 수행한다.
    2. 인수 x는 이미지 데이터
  • loss(self, x, t)
    1. 손실 함수의 값을 구한다.
    2. 인수 x는 이미지 데이터, t는 정답 레이블
  • accuracy(self, x, t) : 정확도를 구한다.
  • numerical_gradient(self, x, t) : 가중치 매개변수의 기울기를 수치 미분 방식으로 구한다.
  • gradient(self, x, t): 가중치 매개변수의 기울기를 오차역전파법으로 구한다.

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)
    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, aixs = 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):
    # 순전파
    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

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

# 기울기 확인
import sys, os
sys.path.append(os.pardir)
import numpy as np
from mnist import load_mnist
from two_layer_net_upgrade 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}: {diff}')

# 오차 수치가 매우 작으므로 오차역전파법으로 구현한 기울기를 실수없이 구현해냄.

>>>  W1: 3.877678837664857e-10
        b1: 2.404053409935016e-09
        W2: 4.137311604506492e-09
        b2: 1.3975200895965934e-07

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

import sys, os
sys.path.append(os.pardir)
import numpy as np
from mnist import load_mnist
from two_layer_net_upgrade 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.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.09775 0.0942
0.9047333333333333 0.9091
0.9239833333333334 0.928
0.9384333333333333 0.9378
0.9451 0.9436
0.94965 0.9487
0.9542666666666667 0.9522
0.9590666666666666 0.9561
0.9631666666666666 0.9568
0.9658333333333333 0.9606
0.9675166666666667 0.9617
0.9706166666666667 0.963
0.9712833333333334 0.9639
0.9733833333333334 0.9651
0.9745166666666667 0.9659
0.9743333333333334 0.9667
0.9770833333333333 0.9679


정리

  • 계산 그래프를 이용하면 계산 과정을 시각적으로 파악할 수 있다.
  • 계산 그래프의 노드는 국소적 계산으로 구성된다. 국소적 계산을 조합해 전체 계산을 구성한다.
  • 계산 그래프의 순전파는 통상의 계산을 수행한다. 한편, 계산 그래프의 역전파로는 각 노드의 미분을 구할 수 있다.
  • 신경망의 구성 요소를 계층으로 구현하여 기울기를 효율적으로 계산할 수 있다.(오차역전파법)
  • 수치 미분과 오차역전파법의 결과를 비교하면 오차역전파법의 구현에 잘못이 없는지 확인할 수 있다.(기울기 확인)

 

참고: 밑바닥부터 시작하는 딥러닝 1
notebook ipynb file: https://github.com/heejvely/Deep_learning/blob/main/%EB%B0%91%EB%B0%94%EB%8B%A5%EB%B6%80%ED%84%B0_%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94_%EB%94%A5%EB%9F%AC%EB%8B%9D_1/%EC%98%A4%EC%B0%A8%EC%97%AD%EC%A0%84%ED%8C%8C%EB%B2%95.ipynb

 

GitHub - heejvely/Deep_learning: deep learning 기초 공부

deep learning 기초 공부. Contribute to heejvely/Deep_learning development by creating an account on GitHub.

github.com

 

728x90