연쇄 법칙과 역전파: 딥러닝 학습의 핵심 수학
합성 함수를 미분하는 연쇄 법칙을 톱니바퀴와 계산 그래프로 이해하고, 뉴런 하나의 역전파를 실제 숫자로 손으로 계산해요. 이어서 2층 신경망의 역전파를 넘파이로 직접 짜고 기울기 검사로 확인하며, 자동 미분과 기울기 소실까지 연결해요.
신경망은 함수 수백 개를 겹겹이 합성한 거대한 함수예요. 학습하려면 수백만 개의 파라미터 각각에 대한 손실의 기울기가 필요한데, 하나씩 수치 미분으로 구하면 파라미터 수만큼 신경망을 돌려야 해서 영원히 걸려요. 역전파는 연쇄 법칙을 영리하게 써서, 신경망을 앞으로 한 번, 뒤로 한 번 돌리는 것만으로 모든 기울기를 구하는 방법이에요. 딥러닝을 가능하게 만든 핵심 기술이고, 이번 강의를 마치면 그 원리를 숫자로 직접 확인하게 돼요.
연쇄 법칙 다시 보기
이고 이면
변화율은 곱해서 전달돼요. 톱니바퀴 세 개를 생각해 보세요. 가 1만큼 돌 때 가 3만큼 돌고, 가 1만큼 돌 때 가 2만큼 돈다면, 가 1만큼 돌 때 는 만큼 돌아요.
여러 단계로 이어져도 마찬가지예요.
여러 갈래로 갈라질 때
가 두 경로로 에 영향을 준다면, 각 경로의 변화율을 더해요.
"경로마다 곱하고, 경로끼리는 더한다." 이 한 문장이 역전파의 전부예요.
계산 그래프
계산을 작은 단계로 쪼개 화살표로 이은 그림을 계산 그래프라고 해요. 뉴런 하나로 예를 들어 볼게요.
w ─┐
├─[ × ]─┐
x ─┘ ├─[ + ]── z ──[ σ ]── a ──[ (a-y)² ]── L
b ─────────┘
- 순전파: 왼쪽에서 오른쪽으로 값을 계산해요. 입력에서 손실까지.
- 역전파: 오른쪽에서 왼쪽으로 기울기를 전달해요. 손실에서 파라미터까지.
각 상자는 자기 국소 기울기(자기 출력이 자기 입력에 대해 얼마나 변하는지)만 알면 돼요. 뒤에서 넘어온 기울기에 자기 국소 기울기를 곱해서 앞으로 넘기면 끝이에요. 상자가 몇 개든, 전체 구조가 어떻든 상관없어요. 이 지역성 덕분에 아무리 복잡한 신경망도 같은 방법으로 미분할 수 있어요.
손으로 해 보는 역전파
숫자를 넣어 볼게요. 입력 , 정답 , 현재 파라미터 , 이에요.
순전파
모델은 0.75를 예측했는데 정답은 1이에요. 손실을 줄이려면 와 를 어느 쪽으로 움직여야 할까요?
역전파
뒤에서부터 국소 기울기를 하나씩 구해요.
| 단계 | 국소 기울기 | 값 |
|---|---|---|
| , | , |
연쇄 법칙으로 곱하면
둘 다 음수예요. 와 를 늘리면 손실이 줄어든다는 뜻이에요. 예측(0.75)이 정답(1)보다 작으니 출력을 키워야 하는 게 맞죠.
한 걸음 내딛기
학습률 0.5로 경사 하강법을 한 번 해요.
다시 순전파하면 손실이 에서 로 줄어요. 이 과정을 수천 번 반복하는 것이 학습이에요.
기울기 공유: 계산을 재사용하는 비밀
와 계산에서 앞의 두 곱 를 같이 썼다는 점을 보세요. 이 값()을 한 번만 계산해 두고 두 파라미터가 나눠 써요.
신경망 전체에서도 같아요. 뒤쪽 층에서 계산한 기울기를 앞쪽 층이 그대로 받아 써서, 중복 계산이 하나도 없어요. 그래서 역전파의 비용은 순전파 한 번과 비슷한 수준이에요. 파라미터가 100만 개여도 기울기 100만 개를 순전파 몇 번의 비용으로 구해요. 수치 미분이었다면 순전파를 100만 번 해야 했을 거예요.
행렬로 쓰는 역전파
신경망 한 층은 였어요. 손실의 에 대한 기울기 가 뒤에서 넘어왔다면
세 번째 식이 앞 층으로 넘겨줄 기울기예요. 순전파에서는 를 곱했고, 역전파에서는 를 곱해서 거꾸로 보내요. 선형대수의 전치가 여기서 쓰여요. 활성화 함수를 지날 때는 그 미분값을 자리끼리 곱해요(ReLU라면 양수였던 자리는 1, 아니면 0).
직접 짜 보는 2층 신경망
넘파이만으로 2층 신경망을 만들고, 역전파로 학습시켜 볼게요.
import numpy as np
rng = np.random.default_rng(0)
X = rng.normal(size=(64, 3)) # 데이터 64개, 특징 3개
y = X[:, 0] - 2 * X[:, 1] + 0.5 * X[:, 2] ** 2 # 맞히고 싶은 값
W1 = rng.normal(scale=0.5, size=(3, 8)); b1 = np.zeros(8)
W2 = rng.normal(scale=0.5, size=(8, 1)); b2 = np.zeros(1)
def forward(X):
z1 = X @ W1 + b1
h = np.maximum(0, z1) # ReLU
out = (h @ W2 + b2).ravel()
return z1, h, out
def loss_fn():
return np.mean((forward(X)[2] - y) ** 2)
def backward():
z1, h, out = forward(X)
n = len(y)
d_out = (2 / n) * (out - y) # dL/d(out)
dW2 = h.T @ d_out[:, None]
db2 = d_out.sum(keepdims=True)
d_h = d_out[:, None] @ W2.T # 앞 층으로: W2의 전치를 곱해요
d_z1 = d_h * (z1 > 0) # ReLU의 미분
dW1 = X.T @ d_z1
db1 = d_z1.sum(axis=0)
return dW1, db1, dW2, db2
데이터를 행으로 쌓았기 때문에 위의 행렬 식과 곱하는 순서가 전치된 모양이에요(X @ W1, X.T @ d_z1). 모양을 맞추는 게 헷갈리면 각 배열의 shape를 출력해 보세요. 기울기의 모양은 항상 그 파라미터의 모양과 같아야 해요.
기울기 검사
직접 짠 역전파가 맞는지는 수치 미분과 비교해서 확인해요.
dW1 = backward()[0]
eps = 1e-6
W1[0, 0] += eps; up = loss_fn()
W1[0, 0] -= 2 * eps; down = loss_fn()
W1[0, 0] += eps
print(round(dW1[0, 0], 6), round((up - down) / (2 * eps), 6)) # -0.002042 -0.002042
두 값이 같으면 역전파 코드가 맞아요. 역전파를 직접 구현할 때 반드시 하는 검사예요.
학습
print(round(loss_fn(), 4)) # 학습 전: 6.1107
for step in range(2000):
dW1, db1, dW2, db2 = backward()
W1 -= 0.05 * dW1; b1 -= 0.05 * db1
W2 -= 0.05 * dW2; b2 -= 0.05 * db2
print(round(loss_fn(), 4)) # 학습 후: 0.0114
손실이 6.11에서 0.011로 줄었어요. 제곱 항()이 섞인 비선형 관계인데도, ReLU로 꺾인 2층 신경망이 잘 맞혔어요. 지금 여러분은 딥러닝 프레임워크 없이 신경망을 처음부터 학습시킨 거예요.
자동 미분: 프레임워크가 해 주는 일
실제로는 역전파를 손으로 짜지 않아요. PyTorch 같은 프레임워크는 순전파를 실행하는 동안 계산 그래프를 기록해 두었다가, backward() 한 번에 연쇄 법칙을 자동으로 적용해 줘요. 이걸 자동 미분이라고 해요.
import torch # pip install torch
w = torch.tensor(0.5, requires_grad=True)
b = torch.tensor(0.1, requires_grad=True)
x, y = torch.tensor(2.0), torch.tensor(1.0)
a = torch.sigmoid(w * x + b)
L = (a - y) ** 2
L.backward()
print(w.grad, b.grad) # tensor(-0.1872) tensor(-0.0936)
손으로 계산한 값과 정확히 같아요. 세 가지 미분 방법을 비교하면 이래요.
| 방법 | 원리 | 쓰임 |
|---|---|---|
| 수치 미분 | 조금 움직여 보고 차이를 잼 | 느림. 검사용 |
| 기호 미분 | 수식을 규칙대로 변형(SymPy) | 수식이 커지면 폭발. 수학용 |
| 자동 미분 | 계산 단계마다 연쇄 법칙 적용 | 빠르고 정확. 딥러닝의 표준 |
기울기 소실
연쇄 법칙은 기울기를 곱해서 전달해요. 그런데 곱하는 값이 계속 1보다 작으면 어떻게 될까요?
시그모이드의 미분은 최대 0.25였어요. 시그모이드 층 10개를 지나면 기울기에 0.25를 최소 10번 곱하게 되니
앞쪽 층에는 기울기가 거의 전달되지 않아요. 앞쪽 층이 학습되지 않으니 깊은 신경망이 제대로 배우지 못했어요. 이 기울기 소실 문제가 2000년대까지 깊은 신경망의 발목을 잡았어요.
해결책들이 오늘날 딥러닝의 표준이 됐어요.
- ReLU: 양수 구간의 미분이 정확히 1이라 곱해도 줄어들지 않아요.
- 좋은 초기화: 층을 지나도 값과 기울기의 크기가 유지되게 가중치를 시작해요.
- 잔차 연결: 층을 건너뛰는 지름길(덧셈)을 만들어요. 덧셈의 미분은 1이라 기울기가 지름길로 그대로 흘러요. 트랜스포머를 포함한 거의 모든 현대 모델이 써요.
- 정규화 층: 각 층의 값 분포를 일정하게 맞춰요.
딥러닝 트랙의 학습 기법 강의에서 하나씩 다뤄요.
연습 문제
- 일 때 를 연쇄 법칙으로 구하세요.
- 뉴런 예제에서 정답이 이라면 의 부호는? 그 의미는?
- 순전파에서 를 곱했다면, 역전파에서 앞 층으로 기울기를 보낼 때는 무엇을 곱하나요?
- ReLU를 지나는 역전파에서, 순전파 때 입력이 음수였던 자리의 기울기는?
- 역전파가 수치 미분보다 훨씬 빠른 이유를 한 문장으로 설명해 보세요.
정답
- 이라 양수예요. 를 줄여야 출력이 0에 가까워진다는 뜻이에요.
- (전치 행렬)예요.
- 0이에요. ReLU의 그 구간 미분이 0이라 기울기가 끊겨요.
- 뒤에서 계산한 기울기를 앞 층들이 재사용해서, 순전파 한 번 정도의 비용으로 모든 파라미터의 기울기를 한꺼번에 구하기 때문이에요.
다음 강의
미분은 "순간의 변화"였어요. 그 반대인 "변화를 쌓아 올린 전체"를 다루는 것이 적분이에요. 다음 강의에서는 넓이와 누적, 그리고 확률 분포 아래의 넓이가 곧 확률이라는 사실을 배워요. 확률과 통계 트랙으로 넘어가는 다리예요.