2장. 신경망과 딥러닝의 기본 원리
역전파와 자동 미분
일이 잘못되었을 때 회사에서 원인을 따지는 모습을 떠올려 보세요. 부장은 결과가 틀린 만큼 각 팀장의 의견이 얼마나 영향을 주었는지 따지고, 팀장은 다시 각 담당자에게 받은 책임을 나눕니다. 결재가 올라간 길을 거꾸로 내려오며 책임을 나누는 셈입니다. 신경망에서는 이것을 역전파라고 합니다.
순전파 그림과 방향이 반대입니다. 왼쪽 오차에서 출발해 출력층, 은닉층 2, 은닉층 1 로 거꾸로 가며, 가중치마다 "이 가중치를 조금 바꾸면 손실이 얼마나 바뀌나", 곧 기울기를 구합니다. 여기서 결과의 잘못이 손실, 사람마다 나눠 받은 책임이 기울기입니다.
이름의 유래와 뜻
역전파는 영어로 backpropagation 이고, backward propagation of errors(오차를 뒤로 퍼뜨리기)를 줄인 말입니다. 줄여서 backprop 이라고도 부릅니다. 우리말 이름도 낱말을 그대로 옮겼습니다.
| 우리말 | 영어 | 뜻 |
|---|---|---|
| 역 | back(ward) | 거꾸로, 뒤로 |
| 전파 | propagation | 퍼뜨리기, 전하기 |
| 순전파 | forward propagation | 입력에서 출력 쪽으로 앞으로 전하기 |
| 역전파 | backpropagation | 오차를 출력에서 입력 쪽으로 거꾸로 전하기 |
순전파가 "입력 → 예측"을 앞으로 계산한다면, 역전파는 "오차 → 가중치마다의 기울기"를 거꾸로 계산합니다. 두 이름이 짝을 이룹니다.
계산 방법의 뿌리는 1970년 무렵으로 거슬러 올라갑니다. 핀란드의 린나인마(Seppo Linnainmaa)가 복잡한 계산의 미분을 뒤에서부터 구하는 방법을 내놓았고, 미국의 워보스(Paul Werbos)가 1974년 박사 논문에서 이를 신경망 학습에 쓸 수 있다고 제안했습니다. 이 방법이 널리 퍼진 것은 1986년 럼멜하트(David Rumelhart), 힌턴(Geoffrey Hinton), 윌리엄스(Ronald Williams)가 은닉층이 있는 신경망을 역전파로 학습시킨 결과를 학술지 Nature 에 발표하면서입니다. 오늘날의 딥러닝과 LLM 도 모두 이 방법으로 학습합니다.
자동 미분이 해 주는 일
가중치 수백 개의 기울기를 사람이 식으로 구할 수는 없습니다. PyTorch 는 순전파를 하는 동안 어떤 계산을 했는지 기록해 두었다가, backward() 한 줄로 모든 기울기를 거꾸로 구해 줍니다. 이것이 자동 미분입니다. 영어로 automatic differentiation 이고 줄여서 autodiff 라고 부릅니다. PyTorch 에서 이 일을 맡는 부분의 이름 autograd(automatic gradient, 자동 기울기)도 여기서 왔습니다.
| 코드 | 하는 일 |
|---|---|
torch.tensor(..., requires_grad=True) | "이 값의 기울기가 필요하다"고 표시한다. 학습할 가중치에 붙인다 |
loss.backward() | 손실에서 거꾸로 따라가며 기울기를 구한다 |
w.grad | 구한 기울기가 담기는 곳 |
torch.no_grad() | 이 안의 계산은 기록하지 않는다. 가중치를 고칠 때와 평가할 때 쓴다 |
import torch
w = torch.tensor(2.0, requires_grad=True) # 기울기가 필요한 값
y = w ** 2 + 3 * w # 계산을 하면 PyTorch 가 과정을 기록한다
y.backward() # y 를 w 로 미분한 값(기울기)을 구한다: 2w + 3
print(w.grad) # w = 2 일 때 7w.grad 에 tensor(7.) 이 담깁니다. 미분 식(2w + 3)을 우리가 적지 않았는데도 PyTorch 가 계산 기록을 거꾸로 따라가 구했습니다.
이 코드가 뜻하는 것
이 코드는 "w 를 아주 조금 바꾸면 y 가 얼마나 바뀌나"를 PyTorch 에 물어본 것입니다. 답 7 은 "w 를 0.001 늘리면 y 가 약 0.007 늘어난다"는 뜻입니다. w 를 2 에서 2.001 로 바꿔 y 를 직접 계산하면 y 의 변화 ÷ 0.001 이 7.001 로, 7 과 거의 같습니다.
| 줄 | 하는 일 | 회사로 치면 |
|---|---|---|
requires_grad=True | w 를 "책임을 따질 대상"으로 정한다 | 결재 라인에 담당자 이름을 올린다 |
y = w ** 2 + 3 * w | 계산하면서 무엇을 했는지 기록한다 | 서류가 지나간 길을 결재 기록에 남긴다 |
y.backward() | 기록을 거꾸로 따라가 기울기를 구한다 | 결과를 보고 길을 거꾸로 짚으며 책임을 나눈다 |
w.grad | 구한 기울기를 읽는다 | 담당자가 받은 책임의 크기를 본다 |
계산이 기록되었다는 표시는 y 를 찍어 보면 보입니다. print(y) 는 tensor(10., grad_fn=<AddBackward0>) 를 냅니다. grad_fn 은 "이 값이 덧셈(Add)으로 만들어졌고, 거꾸로 갈 때 쓸 계산(Backward)이 붙어 있다"는 꼬리표입니다. backward() 는 이 꼬리표를 따라 거꾸로 내려갑니다.
책임이 거꾸로 전해지는 모습
가중치가 둘인 아주 작은 신경망으로 역전파가 책임을 나누는 순서를 따라가 봅니다. 입력 x 는 3, 정답은 4, 가중치는 w1 = 0.5, w2 = 2 입니다.
import torch
x = torch.tensor(3.0) # 입력(학습하지 않는 값이라 표시하지 않는다)
true = torch.tensor(4.0) # 정답
w1 = torch.tensor(0.5, requires_grad=True) # 첫 층의 가중치
w2 = torch.tensor(2.0, requires_grad=True) # 둘째 층의 가중치
h = w1 * x # 순전파 1: 0.5 × 3 = 1.5
out = w2 * h # 순전파 2: 2 × 1.5 = 3.0
loss = (out - true) ** 2 # 손실: (3 - 4)² = 1
loss.backward() # 역전파: 손실에서 거꾸로 기울기를 구한다
print(w1.grad, w2.grad) # 결과: w1 은 -12, w2 는 -3실행하면 tensor(-12.) tensor(-3.) 이 나옵니다. backward() 가 거꾸로 간 길을 손으로 따라가면 이렇습니다.
| 순서 | 따지는 것 | 계산 | 값 |
|---|---|---|---|
| 1 | 손실이 출력 out 에 얼마나 민감한가 | 2 × (out − 정답) = 2 × (3 − 4) | -2 |
| 2 | w2 의 책임: out = w2 × h 이므로 h 만큼 곱한다 | -2 × h = -2 × 1.5 | -3 |
| 3 | h 가 받은 책임: out 에서 w2 만큼 곱해 넘어온다 | -2 × w2 = -2 × 2 | -4 |
| 4 | w1 의 책임: h = w1 × x 이므로 x 만큼 곱한다 | -4 × x = -4 × 3 | -12 |
책임은 출력에서 출발해 한 층씩 거꾸로 내려가며, 지나는 곳마다 그 자리의 값이 곱해집니다. 그래서 앞에서 구한 값(1번의 -2, 3번의 -4)을 다시 쓰며 아래 층으로 내려갑니다. 가중치가 수십억 개여도 계산을 거꾸로 한 번만 훑으면 모든 기울기가 나오는 까닭이 이것입니다. 두 기울기가 모두 음수이므로, 경사하강법은 w1 과 w2 를 키웁니다. 둘을 키우면 출력 3 이 정답 4 쪽으로 커집니다.
주의할 점
w.grad는backward()를 부를 때마다 앞의 값에 더해집니다. 그래서 학습 루프에서는 매번zero_grad()로 지우고 시작합니다. 빠뜨리면 기울기가 쌓여 학습이 엉뚱하게 흘러갑니다.- 회사의 책임 따지기와 다른 점이 있습니다. 역전파는 누구를 탓하려는 것이 아니라, 고칠 방향과 크기를 정하려는 것입니다.