티스토리 뷰
오차역전파
Affine/Softmax 계층 구현하기
Affine 계층
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | >>> import numpy as np >>> X=np.random.rand(2) #input >>> W=np.random.rand(2,3) #weight >>> B=np.random.rand(3) #bias >>> >>> print(X.shape) (2,) >>> print(W.shape) (2, 3) >>> print(B.shape) (3,) >>> >>> Y=np.dot(X,W)+B >>> print(Y) [0.4074836 0.32326323 1.24433478] | cs |
아래 코드는 데이터를 배치로 묶어서 나타낸 것입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | class Affine: def __init__(self, W, b): self.W = W self.b = b self.x = None self.original_x_shape = None # 가중치와 편향 매개변수의 미분 self.dW = None self.db = None def forward(self, x): # 텐서 대응 self.original_x_shape = x.shape x = x.reshape(x.shape[0], -1) self.x = x out = np.dot(self.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) dx = dx.reshape(*self.original_x_shape) # 입력 데이터 모양 변경(텐서 대응) return dx | cs |
Softmax-with-Loss
뉴럴네트워크 말단에 보통 Softmax-with-Loss 노드를 둡니다. Softmax-with-Loss란 소프트맥스 함수와 교차 엔트로피(Cross-Entropy) 오차를 조합한 노드를 뜻합니다. 소프트맥스 함수와 교차 엔트로피의 수식은 아래와 같습니다.
ak=노드의 입력값, L=노드의 출력값(Loss) tk=정답 레이블(0 혹은 1), n=정답 범주 개수
yk=exp(ak)∑ni=1exp(ai)
L=−∑ktklogyk
Softmax-with-Loss 노드의 계산그래프를 매우 단순하게 그리면 아래와 같습니다.
위 그림을 설명하자면 이렇습니다. Softmax-with-Loss 노드는 a를 입력으로 받아서 Loss L을 출력합니다. 역전파하는 그래디언트는 yk−tk가 됩니다. 예컨대 정답이 t3이라면 역전파되는 그래디언트는 각각 y1,y2,y3−1이 됩니다.(yk-tk 자체가 오차를 정확하게 드러내면서 그 오차가 앞 계층으로 저내짐)
사실 소프트맥스 함수의 손실 함수로 교차 엔트로피 오차를 사용하니 역전파가 yk-tk가 되는 것은 교차 엔트로피 오차라는 함수가 그렇게 설계되었기 때문입니다. 또 회귀의 출력층에서 사용하는 항등함수의 손실 함수로 MSE를 이용하는 이유도 이와 같습니다. 즉, 항등함수의 손실 함수로 평균 제곱 오차를 사용하면 역전파의 결과가 yk-tk로 깔끔하게 떨어집니다.
예를 들면 정답 레이블이 (0,1,0)일 때 만약 Softmax 계층이 (0.3, 0.2, 0.5)를 출력했다면 오차는 (0.7,-0.8,0.5)입니다. 이 오차를 앞 계층으로 보내면서 오차를 점점 줄이는 것입니다.
이제 Softmax-with-Loss 계층을 구현한 코드를 보겠습니다.
123456789101112131415161718192021 import numpy as npfrom common.functions import *from common.util import im2col,col2im class Softmax_with_Loss: def __init__(self): self.loss=None self.y=None 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] dx=(self.y-self.t)/batch_size return dx cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import numpy as np from common.functions import * from common.util import im2col,col2im class Softmax_with_Loss: def __init__(self): self.loss=None self.y=None 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] dx=(self.y-self.t)/batch_size return dx | cs |
역전파 때 전파하는 값을 배치의 수로 나눠서 데이터 1개당 오차를 앞 계층으로 전파합니다.
오차역전파 구현하기
1)전제
2)미니배치
3)기울기 산출
4)매개변수갱신
5)반복
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | # 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 | cs |
계층으로 신경망을 구축함으로써 좀 더 계층을 깊게 구현하고 싶으면 단순히 필요한 만큼 계층을 더 추가해 주면 됩니다.
오차역전파법으로 구한 기울기 검증
기울기를 구하는 방법에는 수치 미분을 이용하는 방법과 오차역전파법을 이용하여 구하는 방법 총 두가지를 말했습니다. 그런데 여기서 수치 미분은 느리기 때문에 기울기는 보통 오차역전파법으로 많이 구합니다. 그래서 수치 미분을 이용해 기울기를 구하는 방법이 쓸모없다고 생각할 수 있지만 수치 미분은 구현이 간단하기 때문에 오차역전파법이 제대로 구현이 되고 버그가 없는지 확인하는 용도로 많이 사용됩니다.
기울기를 한번 구해 보겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | # 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)) | cs |
먼저 MNIST데이터셋을 읽고, 훈련 데이터 일부를 수치 미분으로 구한 기울기와 오차역전파법으로 구한 기울기의 오차를 확인합니다. 여기에서는 각 가중치 매개변수의 차이의 절댓값을 구하고, 이를 평균한 값이 오차가 됩니다.
코드를 실행해보면 수치 미분과 오차역전파법으로 구한 기울기가 차이가 매우 적은 것을 확인할 수 있습니다.
오차역전파법을 사용한 학습 구현하기
마지막으로 오차역전파법을 사용해 신경망 학습을 구해보겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | # 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) | cs |
'AI > 밑바닥부터 시작하는 딥러닝' 카테고리의 다른 글
가중치 초기화, 배치 정규화 (0) | 2018.09.30 |
---|---|
매개변수 갱신 (0) | 2018.09.27 |
오차역전파법(1) (0) | 2018.09.17 |
신경망 학습 (0) | 2018.09.13 |
신경망(2) (0) | 2018.09.09 |