신경망에서 값이 흐르는 길은 둘이에요. 입력을 넣어 결과를 받아오는 순전파와, 그 결과의 오차를 거꾸로 되돌리며 가중치를 고치는 역전파예요. 학습이 실제로 일어나는 쪽은 역전파인데, 이게 성립하려면 조건이 하나 붙어요. 활성화 함수를 미분할 수 있어야 해요.
가중치를 고치는 한 줄
뉴런 하나짜리 예제로 보면 역전파는 코드 한 줄로 줄어들어요.
self.w = self.w - a * (self.output - target) * self.getActGrad(self.output) * self.input학습률 a를 빼면 곱해진 항이 셋이에요.
| 항 | 무엇 | 0이 되면 |
|---|---|---|
output - target | 오차 함수를 출력으로 미분한 값 (MSE면 정확히 이 형태) | 오차가 없으니 갱신 없음 (정상) |
getActGrad | 활성화 함수의 기울기 | 오차가 아무리 커도 갱신 0 |
input | 그 가중치에 들어온 입력값 | 기여가 없었으니 갱신 없음 (정상) |
세 항의 곱이 곧 체인룰이고, 손실에서 가중치까지 가는 길에 놓인 미분값을 순서대로 곱하는 구조예요. 층이 깊어져도 원리는 같아요. 층마다 이 곱셈이 한 번씩 더 붙을 뿐이에요.
곱셈이라는 점이 중요해요. 세 항 중 하나라도 0이면 갱신량 전체가 0이 돼요.
계단 함수로는 배울 수 없어요
퍼셉트론이 원래 쓰던 계단 함수는 분류기로서는 자연스럽지만, 위의 세 항 중 가운데가 0이 되니 가중치가 한 발짝도 안 움직여요. 계단 함수를 쓰면서 다층 신경망을 학습시킬 수 없었던 이유가 여기 있어요.
시그모이드는 이 문제를 푸는 함수예요. 계단 함수와 모양은 비슷하게 0과 1 사이를 오가는데, 각진 곳 없이 S자로 부드럽게 이어져서 어디서든 미분이 돼요. 미분 가능한 형태로 계단을 흉내 냈다고 보면 정확해요.
0.25가 층마다 곱해지면
그런데 시그모이드는 다른 문제를 가져와요. 도함수가 σ(x)(1 − σ(x))인데 값이 가장 클 때가 x = 0이고 그때가 0.25예요.
출력층 근처는 그럭저럭 학습되는데 입력층 쪽 가중치는 사실상 멈춰 있게 돼요. 이게 기울기 소실이고, 층을 깊게 쌓으면 오히려 학습이 안 되던 실질적인 원인이었어요.
ReLU가 한 일
ReLU는 음수를 0으로 자르고 양수는 그대로 통과시키는 함수예요. 생김새는 시그모이드보다 훨씬 단순한데, 양수 구간의 기울기가 1로 일정하다는 점이 결정적이에요. 1은 몇 번을 곱해도 1이라 층이 깊어져도 신호가 줄지 않아요.
계산 비용도 비교 한 번이면 끝나서 지수 함수를 계산하는 시그모이드보다 훨씬 빨라요. 지금 합성곱 신경망의 중간 층이 거의 전부 ReLU를 쓰는 이유예요.
여기서 활성화 함수를 고르는 기준이 하나 정리돼요. 값을 어떤 범위로 눌러줄지가 아니라, 그 함수의 기울기가 층을 지나며 살아남는지를 봐야 해요.
얕으면 시그모이드로도 돌아가요
그렇다고 시그모이드가 못 쓰는 함수라는 뜻은 아니에요. XOR을 푸는 예제 신경망은 층 구성이 [2, 2, 1]이라 곱셈이 두어 번밖에 안 붙어요. 0.25가 두 번 곱해지는 정도면 학습이 느려질 뿐 멈추지는 않아서, 에포크를 1,000회쯤 돌리면 수렴해요.
기울기 소실은 함수 자체의 결함이라기보다 깊이와 맞물려 생기는 문제라는 얘기예요. 얕은 신경망에서 잘 돌아가던 구성을 그대로 깊게 쌓았을 때 갑자기 학습이 안 되면, 코드보다 활성화 함수를 먼저 의심하는 게 맞아요.
전처리 쪽에서 비슷하게 조용히 틀리는 경우는 MNIST 학습은 됐는데 직접 쓴 숫자만 틀리는 전처리 불일치에 따로 정리했어요.