4장에서는 신경망의 가중치 매개변수에 대한 손실 함수의 기울기는 수치 미분을 사용해 구했다.
수치 미분은 단순하고 구현하기 쉽지만 계산시간이 오래 걸린다는 단점이 있는데, 이를 효율적으로 계산하는 오차역전파법에 대해 배워보도록 하자.
5.1. 계산 그래프
문제 1. 슈퍼에서 1개에 100원인 사과를 2개 샀습니다. 이 때 지불 금액을 구하세요. 단, 소비세가 10% 부과됩니다.
위의 문제 1을 계산 그래프로 나타내면 그림 5-2와 같다.
문제2. 슈퍼에서 사과를 2개, 귤을 3개 샀습니다. 사과는 1개에 100원, 귤은 1개 150원입니다. 소비세가 10% 일 때 지불 금액을 구하세요.
위의 문제 2 또한 계산 그래프로 나타내면 그림 5-3과 같다.
이처럼 계산을 왼쪽에서 오른쪽으로 진행하는 단계를 순전파(forward propagation) 라고 한다.
또한, 순전파의 반대방향인 오른쪽에서 왼쪽으로 진행하는 역전파(backward propagation)도 있다.
✔️ 국소적 계산
계산 그래프는 국소적 계산에 집중한다. 여기서 국소적 계산이란 전체에서 어떤 일이 일어나든 상관없이 자신과 직접 관계된 정보만으로 결과를 출력한다는 것이다.
그림 5-4처럼 여러 식품을 구입하여 총 금액이 4000원이 되었을 때, 4000이라는 숫자가 어떻게 계산되었는지는 상관없이 사과 200원과 총합 4000원만 더하면 된다는 뜻이다.
이처럼 계산 그래프는 각 단계에서 단순한 국소적 계산을 함으로써 그 결과를 전달하여 전체를 구성하는 복잡한 계산을 해낼 수 있다.
✔️ 계산 그래프로 푸는 이유
계산 그래프는 역전파를 통해 '미분'을 효율적으로 계산할 수 있다.
그림 5-5의 굵은 선은 역전파를 나타내는데 이는 '국소적 미분'을 전달한다는 뜻이다.
미분값 2.2는 사과 가격에 대한 지불 금액의 미분값으로 사과가 1원 오르면 최종 금액은 2.2원이 오른다는 뜻이다.
또한, '소비세에 대한 지불금액의 미분'이나 '사과 개수에 대한 지불금액의 미분'도 같은 순서로 구할 수 있다.
이처럼 중간까지 구한 미분 결과를 공유할 수 있어 다수의 미분을 효율적으로 계산할 수 있다.
5.2. 연쇄법칙
'국소적 미분'을 전달하는 원리는 연쇄법칙(chain rule)에 따른다.
합성함수 : 여러 함수로 구성된 함수
연쇄법칙은 합성함수의 미분에 대한 성질로, 합성 함수의 미분은 합성함수를 구성하는 각 함수의 미분의 곱으로 나타낼 수 있다.
식 5-1을 예로 들면 x에 대한 z의 미분은 t에 대한 z의 미분과 x에 대한 t의 미분의 곱으로 나타낼 수 있다.
이처럼 연쇄법칙을 활용해 역전파를 구할 수 있다.
5.3. 역전파
✔️ 덧셈 노드의 역전파
상류에서 전해진 미분에 1을 곱하여 하류로 흘린다.
즉 덧셈 노드의 역전파는 1을 곱하기만 할 뿐이므로 입력된 값을 그대로 다음 노드로 보내게 된다.
✔️ 곱셈 노드의 역전파
곱셈 노드 역전파는 상류의 값에 순전파 때의 입력 신호들을 '서로 바꾼 값'을 곱해서 하류로 보낸다.
덧셈 역전파에서는 상류의 값을 그대로 흘려보내서 순방향 입력신호의 값이 필요없지만 곱셈 역전파는 순방향 입력 신호의 값이 필요하다. 그래서 곱셈 노드를 구현할 때는 순전파의 입력신호를 변수에 저장해둔다.
그림 5-3을 역전파로 풀면 그림 5-15와 같이 된다.
5.4. 단순한 계층 구현하기
✔️ 곱셈 계층
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 # x와 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
5.5. 활성화 함수 계층 구현하기
✔️ ReLU 계층
순전파의 입력 x가 0보다 크면 역전파는 상류의 값을 그대로 하류로 흘린다.
순전파의 입력 x가 0 이하면 역전파는 하류로 신호를 보내지 않는다.
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
mask라는 인스턴스 변수는 True/False로 구성된 넘파이 배열로, 순전파 입력 x가 0 이하는 True, 0보다 크면 False로 유지된다.
✔️ Sigmoid 계층
그림 5-19의 Sigmoid 계층의 순전파 계산그래프를 활용해 역전파 계산그래프를 그려보자.
1단계 : '/'노드는 식 5-10과 같이 미분된다.
2단계 : '+'노드는 상류의 값을 그대로 하류로 내보낸다.
3단계 : 'exp'노드는 식 5-11과 같이 미분된다.
4단계 : 'x'노드는 순전파 때의 값을 서로 바꿔 곱한다.
최종적으로 중간과정을 모두 묶으면 그림 5-21과 같이 나타낼 수 있다.
또한 이를 정리하면 식 5-12처럼 순전파의 출력(y)만으로 계산할 수 있다.
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.0 - self.out) * self.out
return dx
5.6. Affine/Softmax 계층 구현하기
✔️ Affine 계층
역전파를 구하면 그림 5-25와 같이 되며, 여기서 T는 전치행렬을 뜻한다.
배치용 Affine 계층은 입력 X의 형상이 (N, 2)가 된 것뿐이다.
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 계층
소프트맥스 함수는 입력값을 정규화(출력의 합이 1)하여 출력한다.
그림 5-28과 같이 숫자 '0'의 점수는 5.3에서 softmax 계층을 통과해 0.008(0.8%)가 되며, '2'의 점수는 10.1에서 0.991(99.1%)가 된다.
Softmax-with-Loss 계층은 복잡하여 간소화한 계산 그래프로 정리해보겠다.
Softmax계층은 입력 a1, a2, a3을 정규화하여 y1, y2, y3로 출력한다.
Cross Entropy Error계층은 y1, y2, y3와 정답 레이블 t1, t2, t3를 받아 이로부터 손실 L을 출력한다.
이 때 Softmax 계층의 역전파는 y-t로 Softmax 계층의 출력과 정답레이블의 차분이다.
신경망의 역전파에서는 이 차이인 오차가 앞 계층에 전해진다.
신경망 학습의 목적은 신경망의 출력(Softmax의 출력)이 정답 레이블과 가까워지도록 가중치 매개변수의 값을 조정하는 것이다.
그래서 신경망의 출력과 정답 레이블의 오차를 효율적으로 앞 계층에 전달해야 한다. 이것이 신경망의 역전파에서 그대로 드러내는 것이다.
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) # 3.5. 코드 참고
self.loss = cross_entropy_error(self.y, self.t) # 4.2. 코드 참고
return self.loss
def backward(self, dout=1):
batch_size = self.t.shape[0]
dx = (self.y - self.t) / batch_size
return dx
5.7. 오차역전파법 구현하기
https://github.com/geonsangyoo/DeepLearning/blob/master/ch05/two_layer_net.py
https://github.com/geonsangyoo/DeepLearning/blob/master/ch05/train_neuralnet.py
오차역전파법을 적용한 신경망 구현은 git 참고
5.8. 정리
- 계산 그래프를 이용하면 계산 과정을 시각적으로 파악할 수 있다.
- 계산 그래프의 노드는 국소적 계산으로 구성된다. 국소적 계산을 조합해 전체 계산을 구성한다.
- 계산 그래프의 순전파는 통상의 계산을 수행한다. 한편, 계산 그래프의 역전파로는 각 노드의 미분을 구할 수 있다.
- 신경망의 구성요소를 계층으로 구현하여 기울기를 효율적으로 계산할 수 있다.(오차역전파법)
- 수치 미분과 오차역전파법의 결과를 비교하면 오차역전파법의 구현에 잘못이 없는지 확인할 수 있다.(기울기 확인)
참고 git : https://github.com/geonsangyoo/DeepLearning/tree/master
'Deep Learning' 카테고리의 다른 글
[밑바닥부터시작하는딥러닝1] Chapter 7. 합성곱 신경망(CNN) (0) | 2024.04.13 |
---|---|
[밑바닥부터시작하는딥러닝1] Chapter 6. 학습관련기술들 (0) | 2024.04.12 |
[밑바닥부터시작하는딥러닝1] Chapter 4. 신경망 학습 (0) | 2024.04.04 |
[밑바닥부터시작하는딥러닝1] Chapter 3. 신경망 (0) | 2024.04.03 |
[밑바닥부터시작하는딥러닝1] Chapter 2. 퍼셉트론 (0) | 2024.04.02 |