Affine/Softmax 계층 구현
Affine 계층
어파인계층은 행렬의 곱을 사용하여 계산한다.
X는 ∂L/ ∂X과 같은 형상이고, W는 ∂L/ ∂W와 같은 형상이다.
행렬의 곱의 역전파는 대응하는 차원의 원소수가 일치하도록 곱을 조립한다.
배치용 Affine 계층
데이터N개를 묶어 순전파 하는 경우에는, 배치용 affine 계층을 생각해볼 수 있다.
순전파 때의 편향 덧셈은 X*W 에 대한 편향이 각 데이터에 전해진다. 예를들어 N=2 일 경우, 편향이 두개의 데이터 각각에 더해진다.
X_dot_W = np.array([[0,0,0], [10,10,10]])
B = np.array([1,2,3])
X_dot_W
array([[ 0, 0, 0],
[10, 10, 10]])
X_dot_W + B
array([[ 1, 2, 3],
[11, 12, 13]])
순전파 일 때는, 편향이 각각에 데이터에 더해진다. 역전파 일때는 다음과 같이 역전파값이 편향의 원소에 모여야 한다.
dY = np.array([[1,2,3],[4,5,6]])
dB = np.sum(dY, axis = 0)
dB
array([5, 7, 9])
편향의 역전파는 두 데이터의 미분을 데이터 마다 더한다. 그러므로 axis=0 세로축으로 하여 총합을 구한다.
affine 구현은 다음과 같이 한다.
class Affine:
def __init__(self, W, b):
self.W = W
self.b = b
self.x = None
self.dW = None
self.db = None
def forward(self, x):
self.x = x
out = np.dot(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)
return dx
Softmax-with-Loss 계층
softmax 계층은 입력값을 정규화(출력의합이 1이 되도록) 하여 출력
class(숫자 0 ~9)가 10개가 softmax 계층에 입력,출력되고, 숫자 2가 99.1% 확률로 출력이 되는 것을 알 수 있음. 그리고 이것의 affine 계층의 점수는 10.1이다.
softmax-with-loss 계층은 softmax계층+ cross entropy error 계층 같이 합하여 구현된다.
순전파: (a1,a2,a3)를 정규화하여 (y1,y2,y3)을 출력한다. cross entropy error 계층에서 정답레이블 (t1,t2,t3)을 받고 손실 L을 출력함.
역전파: softmax 계층의 출력 (y1,y2,y3)에서 정답레이블 (t1,t2,t3)을 빼줌. (y1-t1, y2-t2,y3-t3) 값이 앞 노드에 전해준다.
코드를 구현하면 다음과 같다
class SoftmaxWithLoss:
def __init__(self):
self.loss = None #손실
self.y = None #softmax의 출력
self.t = None #정답 레이블(one hot incoding)
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
역전파 시 전파값을 batch size로 나눠 각 데이터의 오차를 앞 계층에 전파한다.
오차역전파 구현
1단계 미니배치-> 훈련데이터 일부를 무작위로 가져와서 loss function 최소화
2단계 기울기 산출 -> loss function을 최소화 하기 위해 가중치 매개변수의 기울기를 구함.
3단계 매개변수 갱신 -> 가중치 매개변수를 기울기 방향으로 갱신
1~3단계 반복
import sys,os
sys.path.append(os.pardir)
import numpy as np
from common.functions 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
def loss(self, x, t):
y = self.predict(x)
return cross_entropy_error(y, t)
def accuracy(self, x, t):
y = self.predict(x)
y = np.argmax(y, axis = 1)
t = np.argmax(t, axis = 1)
accuracy = np.sum(y == t)/float(x.shape[0])
return accuracy
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 #맨 마지막 층이므로 다음 층에서 흘러들어오는 값이 없으므로 1(downstream층에서 곱하기 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['Affine'].dW
grads['b1'] = self.layers['Affine'].db
grads['W2'] = self.layers['Affine'].dW
grads['b2'] = self.layers['Affine'].db
return grads
오차역전파법으로 구한 기울기 검증
수치 미분과 오차역전파 결과를 비교하여 두 방식의 기울기가 거의 같은지 확인한다.
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))
W2:9.71260696544e-13
b2:1.20570232964e-10
W1:2.86152966578e-13
b1:1.19419626098e-12
수치 미분과 오차역전파법으로 구한 기울기 차이가 매우 작다. -> 이는 잘 구현됐음을 알 수 있다
만약 값이 크면 오차역전파법을 잘못 구했다고 의심할 수 있음
'Deep Learning > from scratch I' 카테고리의 다른 글
[밑바닥부터 시작하는 딥러닝 - 6장 학습 관련 기술들 II] (1) | 2024.02.09 |
---|---|
[밑바닥부터 시작하는 딥러닝 - 6장 학습 관련 기술들 I] (0) | 2024.02.08 |
[밑바닥부터 시작하는 딥러닝 - 5장 오차역전파법 I] (0) | 2024.02.08 |
밑바닥부터 시작하는 딥러닝 - 4장 신경망 학습 II] (0) | 2024.02.07 |
밑바닥부터 시작하는 딥러닝 - 4장 신경망 학습 I] (1) | 2024.02.07 |