본 포스팅은 '밑바닥 부터 시작하는 딥러닝' 교재로 공부한 것을 정리했습니다.
가중치 매개변수의 기울기를 효율적으로 계산하는 방법으로 수식과 계산 그래프를 통해 이해할 수 있다.
계산 과정을 그래프 자료구조로 나타낸 것으로, 복수의 노드(node)와 에지(edge)로 표현된다.




역전파 의 계산 절차







곱셈 계층 구현
class MulLayer:
def __init__(self):
self.x = None
self.y = None
def forward(self, x, y): #순전파
self.x = x
self.y = y
out = x * y
return out
def backward(self, dout): #역전파
dx = dout * self.y
dy = dout * self.x
return dx, dy
사과 쇼핑 구현

apple = 100
apple_num = 2
tax = 1.1
# 계층들
mul_apple_layer = MulLayer()
mul_tax_layer = MulLayer()
# 순전파
apple_price = mul_apple_layer.forward(apple, apple_num)
price = mul_tax_layer.forward(apple_price, tax)
print(price)
>>> 220.00000000000003
# 각 변수에 대한 미분
dprice = 1
dapple_price, dtax = mul_tax_layer.backward(dprice)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)
print(dapple, dapple_num, dtax)
>>> 2.2 110.00000000000001 200
덧셈 계층 구현
class AddLayer:
def __init__(self):
pass #초기화 필요 없음
def forward(self, x, y):
out = x + y
return out
def backward(self, dout):
dx = dout * 1
dy = dout * 1
return dx, dy
사과 쇼핑 구현

apple = 100
apple_num = 2
orange = 150
orange_num = 3
tax = 1.1
# 계층들
mul_apple_layer = MulLayer()
mul_orange_layer = MulLayer()
add_apple_orange_layer = AddLayer()
mul_tax_layer = MulLayer()
# 순전파
apple_price = mul_apple_layer.forward(apple, apple_num) # (1)
orange_price = mul_orange_layer.forward(orange, orange_num) # (2)
all_price = add_apple_orange_layer.forward(apple_price, orange_price) # (3)
price = mul_tax_layer.forward(all_price, tax) # (4)
# 역전파
dprice = 1
dall_price, dtax = mul_tax_layer.backward(dprice) # (4)
dapple_price, dorange_price = add_apple_orange_layer.backward(dall_price) # (3)
dorange, dorange_num = mul_orange_layer.backward(dorange_price) # (2)
dapple, dapple_num = mul_apple_layer.backward(dapple_price) # (1)
print(price)
>>> 715.0000000000001
print(dapple_num, dapple, dorange, dorange_num, dtax)
>>> 110.00000000000001 2.2 3.3000000000000003 165.0 650
순서
활성화 함수 ReLU와 Sigmoid 계층 구현



ReLU 계층 구현
class Relu:
def __init__(self):
self.mask = None
def forward(self, x):
self.mask = (x <= 0)
out = x.copy()
out[self.mask] = 0
return out
def backward(self, dout):
dout[self.mask] = 0
dx = dout
return dx
import numpy as np
x = np.array( [[1.0, -0.5], [-2.0, 3.0]])
print(x)
>>> [[ 1. -0.5]
[-2. 3. ]]
mask = (x <= 0)
print(mask)
>>> [[False True]
[ True False]]


Affine 계층의 역전파를 계산그래프로 나타내면 다음과 같다.

와 , 와 의 형상이 같다는 것에 주목한다.

앞선 계산 그래프는 하나의 입력 데이터를 고려하였다. 데이터 N개를 묶어 순전파를 전하는 경우, 즉 배치용 Affine 계층을 생각해보자.

기존과 다른 부분은 입력 의 형상이 가 된 것이다.
이후로는 지금과 같이 계산 그래프의 순서를 따라 순순히 행렬 연산을 하게 된다.
역전파 때는 행렬의 형상에 주의하면 , 은 이전과 같이 도출할 수 있다.
편향 덧셈 주의 : 순전파 때의 편향 덧셈은 에 대한 편향이 각 데이터에 더해진다.
ex) N = 2일때, 편향은 그 두 데이터 각각의 계산결과에 더해짐
# 순전파 편향 덧셈
X_dot_W = np.array([[0,0,0], [10,10,10]])
B = np.array([1,2,3])
print(X_dot_W)
>>> [[ 0 0 0]
[10 10 10]]
print(X_dot_W + B)
>>> [[ 1 2 3]
[11 12 13]]
dY = np.array([[1,2,3],[4,5,6]])
print(dY)
>>> [[1 2 3]
[4 5 6]]
dB = np.sum(dY, axis=0)
print(dB)
>>> [5 7 9]
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(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)
return dx
신경망에서 수행하는 작업은 학습과 추론 두 가지이다. 일반적으로 추론일 때는 Softmax 계층(layer)을 사용하지 않는다. Softmax 계층 앞의 Affine 계층의 출력을 점수(score)라고 하는데, 딥러닝의 추론에서는 답을 하나만 예측하는 경우에는 가장 높은 점수만 알면 되므로 Softmax 계층이 필요없다. 반면, 딥러닝을 학습할 때는 Softmax 계층이 필요하다.
손글씨 숫자 인식에서의 Softmax 계층 출력은 다음과 같다.

이와 같이 Softmax 계층은 입력값을 정규화(출력의 합이 1이 되도록 변형)하여 출력한다.
또한 손글씨 숫자는 가짓수가 10개이므로 Softmax 계층의 입력은 10개가 된다.
소프트맥스 계층 구현 시, 손실 함수인 교차 엔트로피 오차도 포함하여 'Softmax-with-Loss 계층'이라는 이름으로 구현한다. Softmax-with-Loss 계층의 계산 그래프는 다음과 같다.


Softmax : 소프트맥스 함수Cross Entropy Error : 교차 엔트로피 오차[순전파]
[역전파]
Softmax 계층의 역전파는 라는 결과를 내놓는다.
는 Softmax 계층의 출력이고 는 정답 레이블이므로, 는 Softmax 계층의 출력과 정답 레이블의 차분인 셈.
신경망의 역전파에서는 이 차이인 오차가 앞 계층에 전해지는 것이며, 이는 신경망 학습의 중요한 성질이다.
Softmax-with-Loss 계층 구현
class SoftmaxWithLoss:
def __init__(self):
self.loss = None # 손실
self.y = None # softmax의 출력
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]
if self.t.size == self.y.size:
return dx