5장. 오차역전파법 backpropagation - 2

괴도소녀·2021년 7월 27일
0

TFMaster

목록 보기
6/9

오차역전파법 구현하기

신경망 학습의 전체 그림

구체적인 구현에 들어가기 전에 신경망 학습의 전체 그림을 복습해 보자. 다음은 신경망 학습의 순서이다.

전제

신경망에는 적응 가능한 가중치와 편향이 있고, 이 가중치와 편향을 훈련 데이터에 적응하도록 조정하는 과정을 '학습'이라 한다. 신경망 학습은 다음과 같이 4단계로 수행한다.

  • 1단계 - 미니배치
    훈련 데이터 중 일부를 무작위로 가져온다. 이렇게 선별한 데이터를 미니배치라 하며, 그 미니배치의 손실함수 값을 줄이는 것이 목표이다.

  • 2단계 - 기울기 산출
    미니배치의 손실 함수 값을 줄이기 위해 각 가중치 매개변수의 기울기를 구한다. 기울기는 손실 함수의 값을 가장 작게 하는 방향을 제시한다.

  • 3단계 - 매개변수 갱신
    가중치 매개변수를 기울기 방향으로 아주 조금 갱신한다.

  • 4단계 - 반복
    1~3단계를 반복한다.

지금까지 설명한 오차역전파법이 등장하는 단계는 두 번째인 '기울기 산출'이다. 오차역전파법을 이용하면 느린 수치 미분과 달리 기울기를 효율적이고 빠르게 구할 수 있다.

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

[TwoLayerNet] 클래스의 인스턴스 변수

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

[TwoLayerNet] 클래스의 메서드

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

앞과 다른 부분은 계층을 사용한다는 것이다. 계층을 사용함으로써 인식 결과를 얻는 처리(predict())와 기울기를 구하는 처리(gradient()) 계층의 전파만으로 동작이 이루어지는 것이다.

# coding: utf-8 
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, 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

계층 생성 부분을 잘 보자. 신경망의 계층을 OrderedDict()에 보관하는 점이 중요하다. OrderedDict는 순서가 있는 딕셔너리다. 그래서 순전파 때는 추가한 순서대로 각 계층의 forward() 메서드를 호출하기만 하면 처리가 완료된다. 마찬가지로 역전파 때는 계층을 반대 순서로 호출하기만 하면 된다.

이처럼 신경망을 '계층'으로 모듈화해서 구현한 효과는 아주 크다.
1층, 2층, 5층, 여러층과 같이 깊은 신경망을 만들고 싶다면, 단순히 필요한 만큼 계층을 더 추가하면 된다.

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

수치 미분은 느리지만, 구현이 간단하다. 그래서 오차역전파법으로 구한 기울기가 일치함을 체크함으로써 계산의 정확성을 확인할 수 있다. 이와 같이 수치미분의 결과와 오차역전파법의 결과를 비교하여 오차역전파법을 제대로 구현했는지 검증하는 작업을 기울기 확인(gradient check)이라고 한다.

# coding: utf-8 
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))
----------------------------------------------------------------
W1:4.992444822296182e-10 b1:2.7775308568029376e-09 
W2:6.1427267257827926e-09 b2:1.4103044333468872e-07

각 가중치 매개변수의 차이의 절댓값을 구하고, 이를 평균한 값을 구한 것이다.

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

마지막으로 오차역전파법을 사용한 신경망 학습을 구현해 보자. 지금까지와 다른 부분은 기울기를 오차역전파법으로 구현한다는 것 뿐이다.

# coding: utf-8 
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) 

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)

정리

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

0개의 댓글