수치 미분을 통한 기울기 산출은 단순하지만, 계산 시간이 오래 걸린다. 하지만 오차역전파법(backpropagation) 을 통해 기울기를 효율적으로 계산할 수 있다.
오차역전파법을 이해하기 위해서는 연쇄법칙을 이해해야한다. 먼저 합성함수의 미분에 대한 중요한 성질 하나를 짚고 넘어가도록 하자.
합성함수의 미분은 합성 함수를 구성하는 각 함수의 미분의 곱으로 나타낼 수 있다.
[예시1]
를 대상으로 그 역전파를 살펴보도록 하자. 해당 식의 미분은 과 과 같이 해석적으로 계산될 수 있다. 이는 상류에서 전해진 미분에 1을 곱하여 그대로 하류로 내보낸다는 뜻이다.
의 미분은 다음과 같다.
즉 곱셈 노드의 역전파는 상류의 값에 순전파 입력 신호를 서로 바꾼 값을 곱해서 하류로 보낸다.
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
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
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
sigmoid 수식은 다음과 같다.
이를 미분해보도록 하자
는 덧셈이므로 바로를 미분해보자
그리고 에 -1을 곱하므로 역전파를 전할 때 -1을 곱하면 된다.
상류에서 온 값을 라 할 때 sigmoid 계층의 역전파는 다음과 같이 정리가 가능하다.
파이썬으로는 다음과 같이 구현 가능하다.
class sigmoid:
def __init__(self):
self.out = None
def forward(self, x):
out = 1 / (1 + np.exp(-x))
self.out = out
return out
def backward(self, dout):
dx = dout * (1 - self.out) * self.out
return dx
라 할 때, 역전파 수식은 다음과 같이 나타낼 수 있다.
와 , 그리고 와 의 형상은 서로 같음에 주의해야 한다.
편향 덧셈은 에 대한 편향이 각 데이터에 더해진다. 그러므로 각 데이터의 역전파 값은 편향의 원소에 모여야한다.
파이썬 구현은 다음과 같다.
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 계층을 구현한 코드는 다음과 같다.
class SoftmaxWithLoss:
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, t)
return self.loss
def backward(self, dout = 1)
batch_size = self.t.shape[0]
dx = (self.y - self.t)/batch_size
상세한 구현 코드는 여기를 참고할 것.
핵심은 TwoLayerNet 클래스 정의시, layers 인스턴스를 OrderedDict, 순서가 있는 딕셔너리로 구현한다는 것이다. 그리고 역전파를 구할 때, 이 인스턴스를 reverse()하여 backward(dout) 메서드를 반복적으로 수행한다.
이렇게 구한 기울기는 반드시 수치 미분으로 구현한 기울기와 일치하는지 검증해야한다.