10장. 오차의 강을 거슬러 — 역전파
이 장의 질문: 파라미터 수백만 개의 기울기를 어떻게 순식간에 전부 계산하는가?
loss.backward()한 줄의 실체 — 역전파를 숫자 예제로 완전히 해부한다.전제: 3장(연쇄법칙), 9장(신경망 구조).
문제의 크기를 실감하기
기울기란 "이 파라미터를 조금 건드리면 손실이 얼마나 변하나"였다(3장). 가장 우직한 계산법은 정의 그대로다 — 파라미터 하나를 0.0001만큼 키워 보고, 순전파를 다시 돌려 손실 변화를 잰다. 정확하다. 그런데 파라미터가 20만 개면 순전파 20만 번이 필요하다. GPT급의 수천억 개면… 한 걸음 내딛는 데 우주적 시간이 걸린다. 이 길은 막혔다.
역전파(backpropagation)의 약속은 이렇다. 순전파 한 번 + 역방향 계산 한 번 — 도합 순전파 두어 번의 비용으로, 모든 파라미터의 기울기를 정확하게 얻는다. 파라미터가 2개든 2천억 개든 똑같이. 이 알고리즘 없이 딥러닝은 없다.
비결은 새로운 수학이 아니다. 3장의 연쇄법칙("겹친 함수의 민감도는 단계별 민감도의 곱") 하나를, 중복 계산이 없도록 영리한 순서로 적용하는 것뿐이다.
계산 그래프: 수식을 조립 라인으로
먼저 수식을 보는 눈을 바꾼다. 아무리 복잡한 모델도 결국 단순 연산(덧셈, 곱셈, ReLU…)들의 조립 라인이다. 이 라인을 그림으로 그린 것이 계산 그래프다. 아주 작은 예로 전 과정을 굴려 보자. 뉴런 하나짜리 "신경망"이다:
입력
1단계 — 순전파: 왼쪽에서 오른쪽으로, 값을 계산하며 기록한다.
포인트: 중간값
2단계 — 역전파: 오른쪽에서 왼쪽으로, 민감도를 계산하며 거슬러 온다.
맨 끝에서 시작한다.
에 대한 민감도: 이므로 . — " 를 1 키우면 손실이 약 6 줄어든다." 에 대한 민감도: 이고 이라 ReLU는 그대로 통과시키는 중이므로 국소 민감도는 1. 연쇄법칙으로 . (만약 가 음수였다면 ReLU가 0으로 눌러 버리므로 국소 민감도 0 — 뒤에서 온 민감도가 여기서 차단된다. 기억해 두라.) 에 대한 민감도: 에서 의 국소 민감도는 . 따라서 . 에 대한 민감도: 국소 민감도 1. .
끝났다.
이 작은 예에 역전파의 전부가 들어 있다. 일반화하면:
각 연산 노드는 두 가지만 할 줄 알면 된다. ① 순전파 때 자기 계산을 하고 입력값을 기억한다. ② 역전파 때, 뒤에서 흘러온 민감도에 자기 연산의 국소 민감도를 곱해 앞으로 넘긴다.
곱셈 노드의 국소 민감도는 "반대편 입력값"(
왜 빠른가? 핵심은 재사용이다.
자동 미분: 프레임워크가 해 주는 일
PyTorch 같은 프레임워크는 위 과정을 자동화한다. 당신이 순전파 코드를 쓰면, 프레임워크는 실행하면서 몰래 계산 그래프를 기록하고(각 연산과 입력값 저장 — 위의 "기억한다"), loss.backward()가 호출되면 그 그래프를 거꾸로 걸으며 모든 파라미터의 .grad를 채운다.
import torch
w = torch.tensor(3.0, requires_grad=True) # "이 값의 기울기를 추적하라"
b = torch.tensor(1.0, requires_grad=True)
x, y = torch.tensor(2.0), torch.tensor(10.0)
z = w * x + b # 순전파 — 그래프가 몰래 기록된다
a = torch.relu(z)
loss = (a - y) ** 2
loss.backward() # 역전파 — 그래프를 거꾸로 걷는다
print(w.grad, b.grad) # tensor(-12.), tensor(-6.) ← 손 계산과 일치!손으로 구한
- 왜 학습은 추론보다 메모리를 훨씬 먹는가 — 역전파가 쓸 중간값들(위의
)을 순전파 내내 전부 들고 있어야 하기 때문이다. 추론만 할 때 torch.no_grad()로 감싸면 이 기록을 생략해 메모리가 뚝 떨어진다. - 왜
optimizer.zero_grad()를 매번 불러야 하는가 — PyTorch는.grad를 덮어쓰지 않고 누적한다(갈림길에서 더하는 규칙의 구현 방식 때문). 안 지우면 이전 배치의 기울기가 계속 합산되어 학습이 미쳐 간다. 초심자 버그 1순위다. - 왜 모든 연산이 미분 가능해야 하는가 — 그래프의 한 노드라도 국소 민감도를 정의할 수 없으면(예: "반올림") 민감도의 강이 거기서 끊긴다. 신경망 설계란 미분 가능한 부품의 조립이다.
민감도의 곱이 예고하는 재앙
역전파를 이해했으니, 다음 장의 문제를 정확히 예고할 수 있다. 층이 100개면 맨 앞 파라미터의 기울기는 국소 민감도 100개의 곱이다. 곱해지는 수들이 평균적으로 1보다 조금만 작아도 — 예컨대 0.9씩 —
이 장을 읽었다면 지금이 코드랩 01 — NumPy로 신경망 밑바닥 구현의 최적기다. 오늘 손으로 굴린 계산을 784차원 실제 데이터에서 직접 코딩하고, 수치 미분으로 검산까지 해 본다. 한 번 해 본 사람과 안 해 본 사람의 이해는 영영 다르다.
핵심 요약
- 수치 미분은 파라미터 수만큼 순전파가 필요해 불가능. 역전파는 순전파+역방향 한 번으로 전체 기울기를 정확히 얻는다.
- 원리: 모델은 단순 연산의 계산 그래프다. 순전파 때 입력값을 기억하고, 역전파 때 "뒤에서 온 민감도 × 국소 민감도"를 앞으로 넘긴다. 갈림길에서는 더한다. 중간 민감도가 재사용되므로 빠르다.
- 프레임워크의 자동 미분이 이를 대행한다. 학습이 메모리를 먹는 이유(중간값 저장),
zero_grad가 필요한 이유(기울기 누적), 미분 가능성이 설계 제약인 이유가 모두 여기서 나온다. - 깊은 망의 기울기는 국소 민감도 수십 개의 곱 — 소실과 폭발의 씨앗이다. ReLU의 승리(통과 시 민감도 1)도 이 구조에서 설명된다.
스스로 점검
- 본문 예제에서
대신 이면( ) 의 기울기는 얼마인가? ReLU의 차단이 어떤 결과를 낳는지 말로 설명해 보라. , 일 때 의 기울기를 역전파 순서로 구해 보라. 이 값의 크기에서 "곱의 재앙"의 냄새를 맡아 보라. zero_grad()를 빠뜨리면 두 번째 스텝의 실효 기울기는 무엇의 합이 되는가?- "학습 서버는 메모리 부족인데 추론 서버는 여유롭다" — 이 흔한 현상을 역전파의 관점에서 설명해 보라.
다음 장에서
엔진은 완성됐다. 그러나 이 엔진으로 진짜 깊은 망을 돌리면 — 소실, 폭발, 발산, 정체 — 온갖 방식으로 학습이 무너진다. 지난 십여 년간 인류가 쌓아 온 처방전들 — 똑똑한 초기화, 정규화 층, 잔차 연결, Adam — 을 배우고 나면, 비로소 "100층짜리 망이 학습되는 시대"가 어떻게 열렸는지 알게 된다.