3단계 첫 번째 논문. 이 글은 David E. Rumelhart, Geoffrey E. Hinton, Ronald J. Williams의 Learning representations by back-propagating errors, Nature 323, 533–536 (1986-10-09)를 직접 읽고 썼다. 저자 공개본인 원문 PDF는 4쪽 스캔이므로 식·그림을 페이지 이미지로 직접 확인했다. **backpropagation(역전파)**은 신경망이 한 번 예측하고 loss(손실)를 계산한 뒤, chain rule(연쇄 법칙)로 “각 weight(가중치)를 아주 조금 바꾸면 loss가 얼마나 바뀌는가”를 output에서 input 쪽으로 효율적으로 계산하는 알고리즘이다. 그 gradient(기울기)를 썼을 때 weight를 실제로 바꾸는 gradient descent(경사 하강법)·momentum(관성)·AdamW는 별개의 단계다.
이 글에서 얻을 답과 범위
LLM을 공부하면 Transformer, attention, RLHF, RAG를 먼저 만난다. 그런데 pretraining과 fine-tuning에서 “loss를 backward한다”는 한 줄이 실제로 무엇인지 모르면, learning rate(학습률), activation(활성화 함수), vanishing gradient(기울기 소실), GPU memory, optimizer가 모두 외워야 하는 주문이 된다.
정답 0.8을 원했는데 model 출력이 0.48358이라면
어느 layer의 어느 weight가 얼마나 책임이 있는가?
출력에 닿지 않는 hidden unit은 target이 없는데 무엇을 배워야 하는가?
weight가 수백억 개여도 parameter마다 output을 다시 계산하지 않고
어떻게 모든 ∂L/∂w를 구하는가?
이 글을 다 읽으면 다음을 답할 수 있어야 한다.
- perceptron(퍼셉트론)처럼 input과 output을 바로 잇는 model과 hidden layer(은닉층)가 있는 model의 학습 난점은 무엇인가?
- input x, pre-activation z, activation a, weight W, bias b, target t, loss L, gradient ∂L/∂W는 각각 무엇인가?
- 원 논문 식 (1)~(9)의 sigmoid network에서 output error와 hidden error가 chain rule로 어떻게 계산되는가?
- delta가 단순한 “오답 크기”가 아니라 왜 ∂L/∂z인지, 그리고 ∂L/∂W=delta aᵀ가 왜 나오는가?
- forward pass(순전파), loss, backward pass(역전파), optimizer update(최적화 갱신)를 왜 분리해야 하는가?
- finite difference(유한 차분)로 analytic gradient를 어떻게 검증하고, 한 update 뒤 loss가 실제로 감소하는지 어떻게 확인하는가?
- 1986년 대칭·가계도 실험은 hidden representation(은닉 표현)에 대해 무엇을 보여 주었고, 무엇을 보여 주지 않았는가?
- LLM은 원 논문의 sigmoid·MSE·full batch와 다르면서도 왜 backpropagation을 여전히 쓰는가?
범위: 원 논문 Nature 323, 533–536의 feed-forward network(순방향 연결망), sigmoid(시그모이드), squared error(제곱 오차), momentum update, symmetry·family-tree 예제, iterative/recurrent net으로의 확장, local minimum·biological plausibility 한계를 다룬다. 저자들이 함께 쓴 더 긴 1986년 chapter Learning internal representations by error propagation도 원문 스캔으로 확인했다. Transformer의 모든 미분, cross-entropy(교차 엔트로피), AdamW, distributed training은 이 글의 주제가 아니므로, 이 논문을 이해하는 데 필요한 연결과 경계까지만 설명한다.
먼저 외울 한 문장
역전파는 “오차를 뒤로 던지는 마법”이 아니다. output의 loss가 어떤 계산 경로를 거쳐 각 weight에 도달했는지 chain rule로 거꾸로 따라가, 각 weight가 loss를 키우는 방향과 크기인 gradient를 한 backward pass에 모으는 계산법이다.
왜 필요한가: hidden unit에게 “네가 얼마나 잘못했는지” 누가 알려 줄까
12살 비유: 단체 요리에서 누구의 양념을 고칠지 찾는 문제
여러 명이 릴레이로 수프를 만든다.
재료 담당 → 1차 조리 담당 → 2차 조리 담당 → 최종 맛
손님이 “너무 짜다”고 말하면, 마지막 담당자만 혼내서는 해결되지 않는다. 앞사람의 소금, 중간 사람의 농축, 마지막 사람의 간이 함께 결과를 만들었다. 모든 사람의 가능한 행동을 하나씩 바꿔 수프를 다시 끓이면 너무 오래 걸린다.
backpropagation은 “최종 맛이 조금 덜 짜려면 마지막 간은 어느 방향으로, 그 전 농축은 어느 방향으로, 그 전 소금은 어느 방향으로 바뀌어야 하는가?”를 계산 경로의 곱으로 나눠 주는 방법이다. 이때 받은 값은 벌점이 아니라 미세한 변화가 final loss에 미치는 민감도다.
직접 연결만으로는 input feature를 조합해야 하는 문제를 풀기 어렵다
원 논문은 input unit과 output unit이 직접 연결되면 desired output을 보고 connection weight를 조절하는 규칙을 찾기 비교적 쉽다고 시작한다. 더 흥미로운 경우는 hidden unit의 desired state가 task에 주어지지 않는 경우다. hidden unit은 “cat detector”, “대칭성”, “친족의 세대” 같은 useful feature(유용한 특징)를 스스로 만들어야 한다.
XOR(배타적 논리합)는 이 차이를 보는 최소 예다.
| 입력 x₁ | 입력 x₂ | XOR target |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
직선 하나는 (0,1), (1,0)을 1로, (0,0), (1,1)을 0으로 동시에 가를 수 없다. hidden layer는 input을 새 coordinate(좌표)로 바꾸어 output layer가 쉽게 분리할 수 있게 한다. 하지만 hidden unit의 target은 데이터에 없다. 이를 credit assignment problem(공로·책임 배분 문제)이라고 부른다.
target은 final output에만 있다.
hidden unit에는 “너는 0.742가 되어라”가 주어지지 않는다.
그런데도 hidden weight를 바꿔야 한다.
→ final loss가 hidden output에 얼마나 민감한지 계산해야 한다.
원 논문의 핵심은 바로 이 난점을 해결해 **internal representation(내부 표현)**을 학습할 수 있게 한 것이다. Nature abstract는 이전 perceptron-convergence procedure와 달리 useful new feature를 만들 수 있는 점을 강조한다. 원 논문 Nature page의 abstract와 p.533을 직접 확인했다.
backpropagation은 이 논문이 처음 발명한 calculus가 아니다
역전파의 핵심인 chain rule은 미적분의 오래된 법칙이고, 유사한 reverse derivative 계산은 이 논문 이전에도 있었다. 이 글은 “누가 backpropagation을 최초 발명했는가”를 판정하지 않는다. 1986년 Rumelhart·Hinton·Williams 논문이 직접 보인 것은 다층 neurone-like unit network에서 hidden representation을 학습하는 일반적이고 비교적 단순한 procedure와 사례다. 저자 Hinton의 Backpropagation Learning bibliography도 Nature 논문과 더 긴 1986 chapter를 별도로 적고 있다.
네 단계를 섞으면 거의 모든 입문 설명이 틀어진다
1. forward pass : x와 현재 parameter로 prediction을 계산한다.
2. loss : prediction과 target의 차이를 숫자 하나로 만든다.
3. backward pass : 각 parameter의 gradient ∂L/∂θ를 계산한다.
4. optimizer : gradient를 보고 θ를 실제로 업데이트한다.
| 이름 | 하는 일 | 하지 않는 일 |
|---|---|---|
| neural network | 입력을 여러 affine(선형 변환 + bias)·비선형 변환으로 output으로 만든다 | 저절로 학습하지 않음 |
| loss function | 예측이 target과 얼마나 다른지 scalar(숫자 하나)로 정한다 | 어느 weight를 고칠지 직접 알려 주지 않음 |
| backpropagation | ∂L/∂θ를 계산한다 | parameter를 스스로 갱신하지 않음 |
| gradient descent | θ ← θ - η∂L/∂θ로 이동한다 | gradient 계산법 자체가 아님 |
| momentum / AdamW | gradient history·scale·regularization으로 update를 조절한다 | chain rule을 대체하지 않음 |
LLM pretraining에서 “backward”라고 log에 찍힐 때, 보통 3번을 뜻한다. optimizer step은 그 뒤 별도 stage다.
무엇인가: 계산 graph를 거꾸로 미분해 모든 weight의 책임도를 구하는 방법
neural network를 가장 작은 부품으로 쪼갠다
한 unit(뉴런 같은 계산 단위)은 아래 두 일을 한다.
[ z_j=\sum_i a_iw_{ij}+b_j, \qquad a_j=\phi(z_j) ]
| 기호 | 이름 | 12살 설명 |
|---|---|---|
| aᵢ | activation(활성값) | 앞 unit이 보낸 숫자 |
| wᵢⱼ | weight(가중치) | i번 신호를 j번 unit이 얼마나 중요하게 들을지 정하는 다이얼 |
| bⱼ | bias(편향) | 모든 입력이 0이어도 j번 unit이 가진 기본 출발점 |
| zⱼ | pre-activation / total input | 가중합을 마친, 비선형 함수 직전 숫자 |
| φ | activation function | 직선 계산을 구부려 복잡한 패턴을 만들게 하는 함수 |
| aⱼ | output activation | j번 unit이 다음 layer로 전달하는 숫자 |
원 논문 식 (1)은 xⱼ=Σᵢ yᵢwᵢⱼ로 total input을, 식 (2)는 sigmoid로 output을 쓴다. 여기서는 modern notation과 연결하기 위해 논문의 xⱼ를 zⱼ, 논문의 yⱼ를 aⱼ로도 병기한다. bias는 항상 1인 extra input unit과 그 weight로 넣을 수 있다고 원 논문 p.533이 설명한다. 오늘날 코드의 +bⱼ와 같은 뜻이다.
sigmoid는 원 논문의 activation function이다
[ \sigma(z)=\frac{1}{1+e^{-z}} ]
sigmoid는 어떤 실수도 0과 1 사이로 눌러 준다. 역전파에서 편한 이유는 미분이 자기 output만으로 표현되기 때문이다.
[ \sigma'(z)=\sigma(z)(1-\sigma(z))=a(1-a) ]
하지만 이것은 1986 논문의 선택이다. 현재 LLM hidden layer는 보통 sigmoid가 아니라 GELU·SwiGLU 같은 activation, output은 softmax, loss는 cross-entropy를 쓴다. backpropagation은 sigmoid 전용 알고리즘이 아니다. 각 local operation의 derivative를 chain rule로 연결할 수 있으면 된다.
loss는 전체 network의 목표를 한 숫자로 만든다
원 논문 식 (3)은 training case c, output unit j에 대해 total squared error를 다음처럼 둔다.
[ E=\frac{1}{2}\sum_c\sum_j(a_{j,c}-d_{j,c})^2 ]
d는 desired output(target), a는 actual output이다. 1/2는 미분할 때 제곱의 2를 지워 식을 단순하게 한다. 한 output·한 case만 보면 L=1/2(a-t)²다. 예측 a=0.48358, target t=0.8이면 L≈0.05006이다.
loss가 낮다고 모든 output이 맞다는 뜻은 아니고, training loss가 낮다고 unseen data에도 맞는다는 뜻도 아니다. loss는 optimizer가 따라갈 objective(목표 함수)일 뿐, evaluation metric(평가 지표)과 generalization(일반화)은 별도 질문이다.
gradient는 “현재 위치에서 아주 조금 움직였을 때 loss의 변화율”이다
weight w를 아주 작은 ε만큼 바꿨다고 하자.
[ L(w+\epsilon)\approx L(w)+\frac{\partial L}{\partial w}\epsilon ]
| ∂L/∂w 부호 | w를 조금 키우면 | loss를 낮추려면 |
|---|---|---|
| 양수 | loss가 커진다 | w를 줄인다 |
| 음수 | loss가 작아진다 | w를 키운다 |
| 0 근처 | 이 지점에서 1차 변화가 작다 | 다른 direction·data·scale도 점검 |
gradient descent의 가장 단순한 update는 w ← w - η∂L/∂w다. η는 learning rate다. 너무 크면 diverge(발산)하거나 NaN이 날 수 있고, 너무 작으면 거의 안 움직인다. backpropagation은 ∂L/∂w까지, update는 그 다음 optimization step이다.
computation graph와 chain rule: 한 edge의 책임은 경로의 미분 곱이다
아주 짧은 graph를 보자.
w → z = wa + b → h = sigmoid(z) → L = 1/2(h - t)²
w가 L에 영향을 주는 길은 w→z→h→L 하나다. chain rule은 그 길의 local derivative를 곱한다.
[ \frac{\partial L}{\partial w} =\frac{\partial L}{\partial h} \frac{\partial h}{\partial z} \frac{\partial z}{\partial w} ]
각 항은 ∂L/∂h=h-t, ∂h/∂z=h(1-h), ∂z/∂w=a다. 따라서 ∂L/∂w=(h-t)h(1-h)a다.
hidden unit은 output 여러 개로 갈 수 있다. 그러면 hidden activation이 loss에 영향을 주는 모든 다음 edge의 기여를 합친 뒤 자기 activation derivative를 곱한다. 이것이 역전파의 “뒤로”다. error라는 물질이 역류하는 것이 아니다. L의 derivative를 다음 local calculation에 필요한 형태로 바꾸어 전달한다.
선행 개념: partial derivative, matrix shape, batch, representation을 재귀적으로 닫는다
partial derivative는 “다른 다이얼은 고정하고 이 다이얼만 돌렸을 때”의 변화율이다
network에는 w₁, w₂, ..., wₚ가 있다. ∂L/∂w₇은 다른 parameter를 잠깐 고정한 채 7번 weight만 바꾸었을 때 loss가 얼마나 변하는지다. 이 값 전체를 모은 vector를 gradient라고 쓴다.
gradient = [∂L/∂w₁, ∂L/∂w₂, ..., ∂L/∂wₚ]
P가 1억이면 output을 1억 번 다시 계산해 각 derivative를 finite difference로 구하는 것은 비현실적이다. 역전파는 forward 때 만든 intermediate를 저장해 두고 각 edge를 일정 횟수만 훑어 모든 derivative를 얻는다.
representation은 label이 없는 중간 coordinate다
input x는 raw feature다. hidden activation a¹은 model이 만든 representation이다.
raw input: [키, 몸무게, 색, 관계, token embedding, ...]
hidden representation: [0.03, 0.91, 0.44, ...]
output: [강아지일 확률, 다음 token logit, ...]
hidden unit 하나가 사람이 읽을 수 있는 이름 하나와 정확히 대응할 필요는 없다. 여러 unit이 같이 “대칭”, “세대”, “문법”, “의미”를 distributed representation(분산 표현)으로 나타낼 수 있다. 원 논문 family-tree experiment의 중요한 관찰도 input/output에 명시적으로 들어 있지 않은 family structure가 hidden weights에 나타났다는 것이다. 그 표현이 인간의 개념과 1:1로 같다고 증명한 것은 아니다.
scalar 식을 layer matrix 식으로 바꾼다
layer ℓ의 unit 수를 n_ℓ라고 하자. column vector convention을 쓴다.
[ z^{(\ell)}=W^{(\ell)}a^{(\ell-1)}+b^{(\ell)} ] [ a^{(\ell)}=\phi(z^{(\ell)}) ]
| tensor | shape | 뜻 |
|---|---|---|
| a^(ℓ-1) | n_(ℓ-1) × 1 | 이전 layer activation |
| W^(ℓ) | n_ℓ × n_(ℓ-1) | 현재 layer의 모든 weight |
| b^(ℓ) | n_ℓ × 1 | 현재 layer bias |
| z^(ℓ), a^(ℓ) | n_ℓ × 1 | 현재 layer의 pre-activation / activation |
이 글은 W[row_of_destination][column_of_source] convention을 쓴다. 원 논문은 edge를 source i에서 destination j로 두고 wᵢⱼ로 표기한다. 표기 방향이 바뀌면 transpose 위치가 달라진다. code와 수식을 볼 때 제일 먼저 shape를 적는 이유다.
batch는 sample 하나가 아니라 sample 여러 개의 gradient를 합치거나 평균 낸다
원 논문 p.534는 각 input-output case 뒤 바로 weight를 바꾸는 방법과, 모든 case를 지난 뒤 ∂E/∂w를 accumulate(누적)해서 바꾸는 방법을 모두 언급한다. 논문이 보고한 연구에서는 후자를 썼다. 당시의 sweep은 전체 training set을 한 번 돈다는 뜻이다.
현대 training은 보통 mini-batch(미니배치) B개를 같이 계산한다.
[ L_{\text{batch}}=\frac1B\sum_{r=1}^{B}L_r ] [ \nabla_\theta L_{\text{batch}}=\frac1B\sum_{r=1}^{B}\nabla_\theta L_r ]
gradient를 평균 내면 batch size가 달라도 learning-rate scale의 의미를 비교하기 쉬워진다. GPU는 matrix multiply로 B개 forward/backward를 병렬화한다. 하지만 activation memory도 B에 비례해 늘어난다.
밑바닥 원리와 알고리즘: 원 논문 식 (1)~(9)을 delta까지 유도한다
1단계 — forward pass: 아래 layer에서 위 layer로 값을 계산한다
원 논문은 input layer가 bottom, output layer가 top이고 layer 안 연결과 higher-to-lower 연결은 금지된 layered network를 설명한다. intermediate layer를 건너뛰는 connection은 허용했다. 각 layer의 unit은 병렬로 activation을 정하지만, layer들은 input에서 output으로 순서대로 정해진다.
input activation a⁰
→ z¹ = W¹a⁰ + b¹ → a¹ = sigmoid(z¹)
→ z² = W²a¹ + b² → a² = sigmoid(z²)
→ loss L(a², target)
forward pass가 끝나면 loss를 계산할 수 있지만 hidden unit이 무엇을 고쳐야 할지는 아직 모른다. 그러므로 a와 z를 저장한다. backward에서 sigmoid derivative a(1-a)를 계산하려면 activation이 필요하기 때문이다.
2단계 — output layer의 delta는 loss derivative와 activation derivative의 곱이다
원 논문의 한 output unit에 대해 다음을 둔다.
[ L=\frac12(a-t)^2, \qquad a=\sigma(z) ]
먼저 output activation에 대한 derivative를 계산한다.
[ \frac{\partial L}{\partial a}=a-t \tag{원 논문 식 (4)} ]
여기서 a-t는 prediction error이지만, 아직 z나 weight의 gradient는 아니다. a가 z를 거쳐 나오므로 chain rule을 한 번 더 적용한다.
[ \underbrace{\frac{\partial L}{\partial z}}_{\delta} =\frac{\partial L}{\partial a}\frac{\partial a}{\partial z} =(a-t)a(1-a) \tag{원 논문 식 (5)의 뜻} ]
이 글에서
[ \boxed{\delta_j\equiv\frac{\partial L}{\partial z_j}} ]
로 둔다. delta는 “unit j가 틀린 정도”가 아니다. unit j의 total input zⱼ를 아주 조금 바꿨을 때 loss가 얼마나 변하는가다. 그래서 activation function derivative가 포함된다.
3단계 — weight gradient는 destination delta × source activation이다
원 논문 edge i→j에 대해 zⱼ=Σᵢaᵢwᵢⱼ+bⱼ이므로, ∂zⱼ/∂wᵢⱼ=aᵢ다. 따라서
[ \boxed{ \frac{\partial L}{\partial w_{ij}} =\frac{\partial L}{\partial z_j} \frac{\partial z_j}{\partial w_{ij}} =\delta_j a_i } \tag{원 논문 식 (6)} ]
이다. bias는 zⱼ에 더해지는 값이고 ∂zⱼ/∂bⱼ=1이므로 ∂L/∂bⱼ=deltaⱼ다.
말로 풀면, j번 destination이 loss에 민감할수록(deltaⱼ가 큼), 그리고 i번 source가 많이 켜져 있을수록(aᵢ가 큼), 그 edge weight의 책임이 커진다.
4단계 — hidden unit은 모든 다음 unit에 미친 영향을 합산한다
hidden unit i의 output aᵢ는 여러 다음 unit j의 zⱼ에 들어간다. 따라서 loss로 가는 길이 여러 개다.
[ \frac{\partial L}{\partial a_i} =\sum_j \frac{\partial L}{\partial z_j} \frac{\partial z_j}{\partial a_i} =\sum_j\delta_jw_{ij} \tag{원 논문 식 (7)의 같은 내용} ]
그 다음 aᵢ=σ(zᵢ)를 통과한다.
[ \boxed{ \delta_i= \frac{\partial L}{\partial z_i} =\left(\sum_jw_{ij}\delta_j\right)a_i(1-a_i) } ]
이 식이 hidden unit의 credit assignment다. target이 hidden unit에 직접 없어도, 다음 layer가 받은 delta와 연결 weight를 합쳐 hidden unit의 zᵢ가 loss를 키운 방향을 계산한다.
matrix notation에서는 다음이 된다.
[ \delta^{(L)}= \nabla_{a^{(L)}}L\odot\phi'(z^{(L)}) ]
[ \delta^{(\ell)}= \left((W^{(\ell+1)})^\top\delta^{(\ell+1)}\right) \odot\phi'(z^{(\ell)}) ]
[ \boxed{ \frac{\partial L}{\partial W^{(\ell)}} =\delta^{(\ell)}(a^{(\ell-1)})^\top, \qquad \frac{\partial L}{\partial b^{(\ell)}}=\delta^{(\ell)} } ]
⊙는 element-wise multiplication(원소별 곱)이다. transpose는 “다음 layer의 error responsibility를 현재 layer unit별로 합산”하는 matrix 형태다.
5단계 — update: 원 논문의 gradient descent와 momentum
원 논문 식 (8)은 accumulated gradient를 쓴 기본 update다.
[ \Delta w=-\epsilon\frac{\partial E}{\partial w} ]
원 논문 식 (9)은 현재 gradient와 앞 update를 섞는 momentum이다.
[ \Delta w(t)=-\epsilon\frac{\partial E}{\partial w}(t)+\alpha\Delta w(t-1) ]
[ w(t+1)=w(t)+\Delta w(t) ]
epsilon은 learning rate, alpha는 이전 update의 비중이다. 이 식은 modern framework의 optimizer와 표기·세부 구현이 완전히 같다고 가정하면 안 된다. 예를 들어 AdamW는 gradient의 1차·2차 moment와 decoupled weight decay(분리된 가중치 감쇠)를 추가로 관리한다. 공통점은 역전파가 만든 gradient가 optimizer의 입력이라는 점이다.
6단계 — symmetry breaking: 모두 같은 weight로 시작하면 모두 같은 feature만 배운다
동일 layer의 hidden unit 두 개가 같은 incoming weight와 bias로 시작하면, 같은 input에서 같은 z, 같은 a, 같은 delta, 같은 gradient를 받는다. update도 같으므로 영원히 복제본이다. hidden unit 두 개를 둔 이유가 사라진다.
원 논문은 symmetry를 깨기 위해 small random weight로 시작한다고 p.535에 적는다. 현대 initialization은 fan-in/fan-out과 activation에 맞춘 Xavier/He initialization처럼 더 정교하지만, 핵심은 같다.
같은 초기값 + 같은 계산 + 같은 gradient
→ 같은 update
→ 같은 unit이 두 개
작은 서로 다른 초기값
→ 서로 다른 activation / gradient
→ 서로 다른 feature를 맡을 기회
내부 구조와 실제 실행 흐름: 1986 원 논문 실험과 오늘의 training loop를 분리한다
원 논문이 보인 대칭 탐지 network
Figure 1은 8 input unit, 2 hidden unit, 1 output unit network가 one-dimensional binary input vector의 mirror symmetry를 탐지하는 예다. 논문 caption과 p.534의 사실은 다음이다.
| 항목 | 원 논문 사실 |
|---|---|
| task | input vector가 midpoint를 기준으로 대칭인지 탐지 |
| architecture | input 8, hidden 2, output 1 |
| train | 64개 가능한 input vector를 1,425 sweep |
| initial weight | -0.3~0.3 uniform random |
| update parameter | epsilon=0.1, alpha=0.9 |
| learned pattern | 각 hidden unit의 midpoint 대칭 weight가 크기는 같고 부호는 반대가 되는 구조 |
해석은 이렇다. 대칭 pattern이면 두 hidden unit의 input이 정확히 balance돼 negative bias 때문에 꺼지고, output의 positive bias로 output이 켜질 수 있다. 비대칭이면 둘 중 하나가 켜져 output을 억제한다. 이 network는 “대칭”이라는 label을 hidden unit에 준 적 없이, output loss를 낮추다가 input 조합의 구조를 잡았다.
이 결과는 hidden representation이 생길 수 있음을 보인다. 8-bit toy task가 현대 vision·language representation 전체를 설명하거나, hidden neuron 하나에 인간 해석 가능한 개념 하나가 항상 생긴다는 증거는 아니다.
family-tree experiment는 단순 암기가 아닌 일부 generalization을 보였다
Figure 2~4는 English·Italian 두 isomorphic family tree(동형 가계도)를 (person1, relationship, person2) triple로 표현했다. 첫 두 항을 input으로 encoding하고, 세 번째 person을 output unit으로 활성화하는 task다. Figure 3 caption 기준 architecture와 관찰은 다음과 같다.
input layer:
사람(person 1) 24 unit + 관계(relationship) 12 unit
hidden / central:
6-unit group 두 개 → 12-unit central layer → 6-unit penultimate layer
output:
person 2를 나타내는 output unit
training:
104 possible triples 중 100개
1,500 sweep
처음 20 sweep: epsilon=0.005, alpha=0.5
이후: epsilon=0.01, alpha=0.9
매 20 sweep: 모든 weight를 0.2% 줄이는 weight decay
result:
학습하지 않은 4 triple에도 올바르게 generalize
Figure 4의 weight 그림은 일부 hidden unit이 English/Italian 구분, 세대, family branch 같은 구조를 distributed manner로 잡은 것으로 해석된다. 원 논문 p.535은 이 feature들이 input/output coding에 명시적으로 들어 있지 않다고 설명한다.
주의: “4개 unseen triple을 맞췄다”는 작은 controlled experiment의 결과다. modern train/test split, held-out data 규모, seed 분산, model selection 방식이 갖춰진 대규모 generalization claim과 동일시하면 안 된다.
original recurrent/iterative extension은 unrolling의 핵심을 이미 짚었다
Figure 5는 three iterations를 실행하는 synchronous iterative network와 그것에 해당하는 layered network를 보인다. time step 하나를 layer 하나로 펼치는 것을 unrolling(시간축 펼치기)이라고 한다.
recurrent state at t=0 → state at t=1 → state at t=2 → state at t=3
같은 weight W를 매 time step에서 공유
원 논문은 두 complication을 적는다.
- backward를 하려면 intermediate unit output history를 저장해야 한다.
- shared weight가 여러 time step에 나타나므로, 각 copy의 ∂E/∂w를 average/accumulate해 같은 original weight에 적용해야 한다.
이것은 오늘날 BPTT(Backpropagation Through Time, 시간을 통한 역전파)의 핵심 직관이다. 이 글은 RNN·LSTM·Transformer sequence training의 상세를 다루지 않지만, “한 parameter가 graph에 여러 번 쓰이면 gradient contribution을 모두 더한다”는 규칙은 여기서 이미 보인다.
modern framework의 실제 training loop는 이렇게 보인다
다음은 PyTorch 같은 framework를 특정 version API로 인용한 것이 아니라, backpropagation이 실제 system에서 실행되는 일반 흐름이다.
batch tokens / features + labels
→ forward: model parameter와 activation으로 logits/prediction 계산
→ loss: scalar L 계산
→ backward: computational graph의 각 operation VJP를 역순으로 실행,
parameter.grad에 ∂L/∂θ 누적
→ optimizer: grad, optimizer state로 θ update
→ grad reset: 다음 batch와 섞이지 않도록 gradient buffer 초기화
VJP(Vector-Jacobian Product, 벡터-야코비안 곱)는 모든 Jacobian 행렬을 실제로 만들지 않고 upstream gradient와 local derivative를 곱해 이전 input의 gradient를 내보내는 local 계산이다. matmul, add, sigmoid, GELU, softmax, cross entropy 각각은 backward rule을 가진다. autodiff(자동 미분)는 이 rule들을 graph 연결 순서대로 적용한다. 역전파는 reverse-mode autodiff(역방향 모드 자동 미분)의 neural-network training 적용으로 볼 수 있다.
LLM에서는 무엇이 같고 무엇이 다른가
| 1986 paper | modern LLM training | 같은 핵심 |
|---|---|---|
| sigmoid unit | attention, RMSNorm, GELU/SwiGLU, softmax 등 | differentiable operation마다 local derivative를 역순 연결 |
| squared error | token cross-entropy | scalar loss에서 parameter gradient 계산 |
| sweep 전체 gradient | mini-batch / data-parallel all-reduce | sample gradient를 합/평균 |
| simple momentum | AdamW, schedule, clipping, mixed precision | gradient는 optimizer 입력 |
| 작은 layered net | billions of parameters, GPU cluster | edge/operation별 forward state와 backward rule |
LLM의 training 한 batch는 대략 다음이다.
정답 token sequence
→ embedding + Transformer blocks
→ 각 position의 vocabulary logits
→ 정답 다음 token과 cross-entropy loss
→ backward로 embedding·attention·MLP·output head 모든 parameter gradient
→ AdamW update
inference(추론)에서 사용자가 prompt를 넣고 답을 생성할 때는 보통 target label이 없으므로 training backward를 하지 않는다. “model이 답을 길게 생각한다”는 test-time compute와 “loss를 backward해 weight를 바꾼다”는 training-time backpropagation은 다른 경로다.
직접 검증과 재현: 2-2-1 sigmoid network의 gradient를 두 방법으로 대조한다
실행 환경과 검증 범위
직접 실행 환경: macOS local shell, Python 3.9.6, 표준 라이브러리 math.exp만 사용했다. NumPy, PyTorch, GPU, 원 논문 당시 소프트웨어는 사용하지 않았다.
검증 대상: 원 논문의 sigmoid·squared error·chain rule을 modern row-major matrix notation의 2-input → 2-hidden → 1-output network에 적용한다.
x = [0.40, -0.70]
target = 0.80
W1 = [[ 0.10, -0.20],
[ 0.30, 0.25]]
b1 = [0.00, 0.10]
W2 = [[-0.40, 0.20]]
b2 = [0.05]
다음 두 값이 같아야 한다.
- analytic gradient: chain rule로 구한 ∂L/∂W1[0][1].
- numeric gradient: weight를 +h, -h로 아주 작게 바꿔 loss 차이를 나눈 central finite difference.
[ \frac{\partial L}{\partial w} \approx\frac{L(w+h)-L(w-h)}{2h} ]
finite difference는 training에 쓰기에는 parameter 수만큼 forward가 필요해 비싸다. 대신 backprop implementation을 debug할 때 작은 network에서 analytic gradient가 맞는지 확인하는 gradient check(기울기 검사)다.
실제 실행한 전체 코드
from math import exp
def sigmoid(z):
return 1.0 / (1.0 + exp(-z))
def forward(params, x):
w1, b1, w2, b2 = params["w1"], params["b1"], params["w2"], params["b2"]
z1 = [sum(w1[row][col] * x[col] for col in range(2)) + b1[row] for row in range(2)]
a1 = [sigmoid(value) for value in z1]
z2 = sum(w2[0][col] * a1[col] for col in range(2)) + b2[0]
return z1, a1, z2, sigmoid(z2)
def loss(params, x, target):
return 0.5 * (forward(params, x)[3] - target) ** 2
def backward(params, x, target):
z1, a1, z2, a2 = forward(params, x)
delta2 = (a2 - target) * a2 * (1.0 - a2)
grad_w2 = [[delta2 * a1[0], delta2 * a1[1]]]
delta1 = [
params["w2"][0][index] * delta2 * a1[index] * (1.0 - a1[index])
for index in range(2)
]
grad_w1 = [[delta1[row] * x[col] for col in range(2)] for row in range(2)]
return {"w1": grad_w1, "b1": delta1, "w2": grad_w2, "b2": [delta2]}
def copy_params(params):
return {
"w1": [row[:] for row in params["w1"]],
"b1": params["b1"][:],
"w2": [row[:] for row in params["w2"]],
"b2": params["b2"][:],
}
def finite_difference(params, x, target, epsilon=1e-5):
plus, minus = copy_params(params), copy_params(params)
plus["w1"][0][1] += epsilon
minus["w1"][0][1] -= epsilon
return (loss(plus, x, target) - loss(minus, x, target)) / (2.0 * epsilon)
def step(params, gradients, learning_rate):
updated = copy_params(params)
for row in range(2):
for col in range(2):
updated["w1"][row][col] -= learning_rate * gradients["w1"][row][col]
for index in range(2):
updated["b1"][index] -= learning_rate * gradients["b1"][index]
updated["w2"][0][index] -= learning_rate * gradients["w2"][0][index]
updated["b2"][0] -= learning_rate * gradients["b2"][0]
return updated
params = {
"w1": [[0.10, -0.20], [0.30, 0.25]],
"b1": [0.00, 0.10],
"w2": [[-0.40, 0.20]],
"b2": [0.05],
}
x, target = [0.40, -0.70], 0.80
z1, a1, z2, a2 = forward(params, x)
gradients = backward(params, x, target)
analytic = gradients["w1"][0][1]
numeric = finite_difference(params, x, target)
before = loss(params, x, target)
after = loss(step(params, gradients, 0.5), x, target)
assert abs(analytic - numeric) < 1e-8
assert after < before
print("python_scalar_backprop_verification")
print(f"z1={[round(v, 9) for v in z1]}; a1={[round(v, 9) for v in a1]}")
print(f"z2={z2:.9f}; prediction={a2:.9f}; loss_before={before:.12f}")
print(f"delta2={gradients['b2'][0]:.12f}; delta1={[round(v, 12) for v in gradients['b1']]}")
print(f"analytic_dL_dw1_0_1={analytic:.12f}")
print(f"numeric_dL_dw1_0_1={numeric:.12f}")
print(f"absolute_difference={abs(analytic - numeric):.12e}")
print(f"loss_after_one_gradient_step={after:.12f}")
실행 명령은 다음이다.
python3 -m py_compile verify_backpropagation.py
python3 verify_backpropagation.py
내가 실제 실행한 출력이다.
Python 3.9.6
python_scalar_backprop_verification
z1=[0.18, 0.045]; a1=[0.544878892, 0.511248102]
z2=-0.065701937; prediction=0.483580422; loss_before=0.050060674662
delta2=-0.079019586971; delta1=[0.007838296884, -0.00394897984]
analytic_dL_dw1_0_1=-0.005486807818
numeric_dL_dw1_0_1=-0.005486807819
absolute_difference=7.394111364856e-13
loss_after_one_gradient_step=0.045251506732
출력 한 줄씩 읽기
prediction 0.48358 < target 0.80
→ a2 - t 는 음수
→ sigmoid derivative는 양수
→ delta2는 음수
첫 hidden unit에서 두 번째 input으로 가는 W1[0][1]의 analytic gradient는 -0.005486807818이다. central finite difference는 -0.005486807819다. 차이는 약 7.39e-13으로 floating-point round-off 범위다. 부호가 음수이므로 gradient descent update w ← w - eta g는 이 weight를 증가시킨다. learning rate 0.5로 모든 parameter를 한 번 update한 뒤 loss가 0.050060674662 → 0.045251506732로 감소했다.
이것이 global training 성공의 증명은 아니다. 한 sample·한 update·선택한 learning rate에서 local first-order direction이 loss를 줄였음을 보인 검산이다. random initialization, multiple batch, non-convex loss landscape에서는 여러 step의 결과가 다를 수 있다.
실행한 script SHA-256은 90b44f0c90325fb16974920a4c6351057317aca7346fdf62e5b778ead5d513d8다. 직접 읽은 저자 공개 Nature PDF SHA-256은 d26997baf588222109d32545604a2a2ed400dc769a21fd49a5acdc4a955396ae, 1986 PDP chapter PDF SHA-256은 e07ac110beb11000d9201b37d0203cd7a45c53f2a10519600b42772907e27d62다.
재현에서 일부러 값을 바꿔 봐야 하는 두 실패
1. finite-difference epsilon을 너무 작게 한다.
- L(w+h)와 L(w-h)가 floating-point에서 거의 같아져 cancellation이 커진다.
2. learning_rate를 과하게 키운다.
- 한 step에서 loss가 감소하던 방향도 너무 멀리 가면 증가할 수 있다.
gradient check에서 analytic/numeric 차이가 크면 먼저 shape transpose, bias gradient, activation derivative, batch mean/sum, parameter를 in-place로 너무 일찍 update했는지 확인한다. backward 모든 gradient를 계산하기 전에 weight를 하나씩 바꾸면, 뒤 gradient가 서로 다른 parameter snapshot을 보고 계산되는 심각한 bug가 된다.
성능, 복잡도와 트레이드오프: “한 번의 backward”가 공짜는 아니다
reverse-mode가 빠른 이유와 정확한 한계
loss L은 scalar 하나이고 parameter P는 많다. 우리가 원하는 값은 P개인 ∂L/∂theta_p다.
naive finite difference:
parameter 하나마다 +h/-h forward 두 번
→ 대략 2P forward pass
backpropagation / reverse-mode:
forward 한 번 + graph edge를 역순으로 한 번 처리
→ 모든 parameter gradient를 한 backward traversal에 누적
fully connected layer n_in → n_out의 forward matrix multiply는 대략 O(n_in n_out) 연산이다. backward도 input gradient와 weight gradient를 구하기 위해 같은 차수의 matrix multiply를 더 한다. 전체 network에서는 edge 수를 E라고 하면 forward와 backward가 모두 보통 O(E) 규모다. “backward 한 번”은 parameter 수와 무관한 상수 시간이 아니라, 각 connection/operation을 일정 횟수만 훑어도 되는 효율이다.
memory: backward는 forward activation을 기억해야 한다
sigmoid derivative에 a(1-a)가 필요했고, weight gradient에 source activation aᵢ가 필요했다. 따라서 backward를 하려면 forward intermediate를 저장해야 한다.
| 자원 | 왜 필요한가 | LLM에서의 의미 |
|---|---|---|
| parameters | update할 weight | model size |
| activations | local derivative·weight gradient | sequence length·batch·layer에 비례, training memory의 큰 부분 |
| gradients | parameter별 ∂L/∂theta | parameter 규모 |
| optimizer state | momentum/Adam 계열 history | AdamW는 추가 state 필요 |
| temporary buffers | matmul·communication | hardware/kernel에 따라 변함 |
activation checkpointing(활성값 체크포인팅)은 일부 activation을 저장하지 않고 backward 때 forward 일부를 다시 계산해 memory와 compute를 교환한다. FlashAttention이 attention intermediate를 저장하지 않고 재계산하는 이유도 “backward에 무엇을 기억할까”라는 같은 큰 문제의 한 특수 사례다.
gradient는 곱의 연쇄라서 사라지거나 폭발할 수 있다
[ \delta^{(\ell)}= \left((W^{(\ell+1)})^\top\delta^{(\ell+1)}\right) \odot\phi'(z^{(\ell)}) ]
sigmoid의 최대 derivative는 0.25다. saturation(포화) 영역에서 a≈0 또는 a≈1이면 a(1-a)≈0이다. 여러 layer를 거치며 작은 수를 곱하면 early layer gradient가 매우 작아지는 vanishing gradient가 생긴다. 큰 weight/derivative가 이어지면 exploding gradient(기울기 폭발)도 가능하다.
원 논문은 deep Transformer를 다루지 않았지만 p.535에서 local minima와 weight-space dimension을 논의했고, p.536에서 현 형태의 procedure가 뇌의 plausible model이 아니라고 명시했다. 현대 deep learning은 ReLU 계열 activation, initialization, normalization, residual connection, optimizer, gradient clipping, data/architecture scale 등으로 이러한 난점을 다룬다. 어느 하나만으로 “역전파 문제가 완전히 해결됐다”고 말하면 틀리다.
forward 정확도와 gradient 정확도는 다른 test target이다
forward unit test:
입력 → prediction이 기대값인가?
backward gradient check:
analytic ∂L/∂w가 finite difference와 맞는가?
optimization test:
적당한 learning rate의 여러 step에서 loss가 감소하는가?
generalization evaluation:
training에서 못 본 data에서도 목적 metric이 좋은가?
loss가 한 step 낮아졌다는 것으로 gradient 전체가 맞거나, validation quality가 좋거나, deployment 안전성이 증명되지는 않는다. 네 test를 분리해야 한다.
실패, 한계, 장애와 운영: loss가 내려가도 model이 제대로 배운 것은 아니다
자주 만나는 실패와 관찰 순서
| 증상 | 흔한 원인 | 먼저 확인할 것 | 대응 |
|---|---|---|---|
| loss가 NaN/Inf | learning rate 과대, overflow, invalid data, zero division | input range, logit/loss, gradient norm, mixed-precision scaler | batch 중단, lr 낮춤, numerical guard |
| loss가 처음부터 안 내려감 | target/shape mismatch, gradient detach, wrong derivative, frozen parameter | tiny overfit test, gradient check | 1 batch에 과적합 가능한지 확인 |
| gradient가 거의 0 | sigmoid saturation, bad initialization, mask bug, loss reduction 오류 | layer별 activation mean/std, grad norm, zero fraction | activation/init/norm/lr 검토 |
| gradient가 폭발 | scale 문제, 긴 unroll, lr 과대 | global/layer grad norm, update norm | clipping, lr·init·architecture 검토 |
| train loss만 낮고 validation 악화 | overfitting, train/validation leakage, distribution shift | split, per-slice metric, train-val gap | data·regularization·early stopping 재검토 |
| 결과가 run마다 다름 | random init, data order, non-deterministic kernel | seed, code/data/model hash, environment | 실험 metadata와 checkpoint 보존 |
| 한 layer만 안 배움 | dead activation, optimizer param group 누락 | parameter별 grad/update norm | parameter group·activation 확인 |
DBA·backend 관점의 training observability
LLM training pipeline도 data pipeline이다. loss 하나만 database에 남기면 incident를 재현할 수 없다.
run_id
model architecture/config hash
code commit / container image / dependency lock hash
tokenizer + dataset snapshot + split definition
random seed / data-shuffle seed
batch size / sequence length / accumulation steps
optimizer / learning-rate schedule / precision mode
per-step loss, gradient norm, parameter/update norm
validation metrics, checkpoint URI + checksum
parameter update는 DB transaction처럼 atomic하지 않은 대규모 distributed operation일 수 있다. worker 하나가 NaN을 all-reduce하면 전체 gradient가 망가질 수 있다. checkpoint에는 model weight만 아니라 optimizer state, scheduler state, tokenizer/config, global step을 함께 저장해야 동일 지점에서 재개할 수 있다.
security와 data boundary
backpropagation 자체는 access control mechanism이 아니다. private training data는 gradient와 model weight에 statistical influence를 남길 수 있고, training log의 prompt/target 자체가 민감 정보일 수 있다. 최소한 다음은 분리한다.
raw training data access ≠ training job submit 권한
training metrics read ≠ raw example trace read
checkpoint download ≠ optimizer-state download
experiment metadata retention ≠ prompt/label 원문 retention
이 글은 membership inference나 privacy-preserving training을 다루지 않는다. 다만 “raw input을 model에 넣고 loss를 backward한다”는 pipeline은 data governance 밖에 있지 않다는 점은 기억해야 한다.
biological plausibility와 engineering usefulness를 혼동하지 않는다
원 논문 p.536은 당시 형태의 learning procedure가 brain learning의 plausible model이 아니라고 명시한다. neural network라는 이름이 붙었다고 해서, GPU training의 backward signal이 실제 biological neuron의 synapse에서 똑같이 일어난다는 뜻은 아니다.
반대로 biological plausibility가 불완전하다고 engineering algorithm으로 무가치한 것도 아니다. LLM training system에서 핵심 질문은 “뇌와 같은가?”보다 “정의한 loss의 gradient를 정확하고 안정적으로 계산하며, data·compute·memory·evaluation 비용 안에서 원하는 task quality를 내는가?”다.
대안, 비교와 선택 기준: 역전파와 경쟁하는 것은 무엇인가
같은 층위의 개념을 비교한다
| 방법/개념 | 하는 일 | backprop와 관계 | 언제 쓰나 |
|---|---|---|---|
| finite difference | parameter를 직접 흔들어 numeric gradient 근사 | 작은 test용 oracle, training에는 너무 비쌈 | custom backward 검증 |
| forward-mode AD | input tangent를 forward로 전파 | output이 많고 input parameter가 적을 때 유리할 수 있음 | sensitivity/JVP 중심 문제 |
| reverse-mode AD | scalar loss의 gradient를 graph 역순으로 전파 | neural-network backprop의 일반화 | parameter 많고 loss scalar인 training |
| manual backprop | 각 operation derivative를 사람이 구현 | framework autograd의 내부와 같은 수학 | kernel·custom op·학습용 이해 |
| SGD | gradient 반대 방향으로 parameter update | backprop output을 소비 | 기본 optimizer |
| momentum/AdamW | gradient history/scale/decay로 update 조절 | backprop를 대체하지 않음 | 현대 large-scale training |
| evolutionary / gradient-free search | loss gradient 없이 candidate parameter를 탐색 | 다른 optimization family | non-differentiable objective 등 특수 경우 |
| local learning rule | layer-local signal만 사용하려는 방법 | global chain-rule backprop와 다름 | biological plausibility/연구 목적 |
선택 기준: “미분 가능성”과 “정답 신호”를 먼저 묻는다
model calculation과 loss가 미분 가능한가?
예 → backprop/autodiff가 parameter gradient의 기본 선택이다.
아니오 → surrogate loss, score-function estimator, gradient-free search,
differentiable relaxation 등을 별도 검토한다.
loss가 실제 제품 품질을 잘 대표하는가?
아니오 → backprop을 아무리 정확히 해도 잘못된 objective를 빠르게 최적화한다.
예 → data, optimizer, memory, evaluation을 포함해 training을 설계한다.
RAG retrieval의 top-k document 선택, tool의 외부 API 결과, discrete database transaction처럼 일부 단계는 그 자체로 미분 가능하지 않을 수 있다. 그때 “backprop이 안 된다”와 “AI가 학습할 수 없다”는 동의어가 아니다. differentiable surrogate를 만들지, offline label로 별도 model을 학습할지, reinforcement learning/selection을 쓸지 task에 맞게 결정해야 한다.
처음 ML을 구현하는 내가 택할 실습 순서
1. scalar 2-2-1 network를 이 글 코드처럼 직접 만든다.
2. forward 값과 analytic gradient를 종이에 적는다.
3. finite difference로 한 weight를 확인한다.
4. XOR 4개를 tiny network로 overfit한다.
5. batch mean/sum·bias·shape를 바꿔 에러를 일부러 낸다.
6. framework autograd 결과와 내 gradient를 비교한다.
7. 그 뒤에야 MLP, Transformer, LoRA training log를 읽는다.
이 순서가 bottom-up의 목적이다. production LLM을 scratch로 학습하기 전에 loss.backward가 무엇을 약속하고 무엇을 보장하지 않는지 손으로 검증한다.
흔한 오해와 최초 질문의 답
오해 1: “역전파는 error 값을 그대로 hidden layer에 복사한다”
아니다. output error a-t에 activation derivative를 곱해 delta=∂L/∂z를 만들고, hidden layer에는 다음 layer들의 Wᵀdelta를 합친 뒤 다시 자기 activation derivative를 곱한다. 값은 layer마다 다르고 connection weight에 의존한다.
오해 2: “backpropagation이 weight를 업데이트한다”
아니다. backpropagation은 gradient를 계산한다. SGD, momentum, AdamW가 update rule을 정한다. 같은 backprop gradient도 optimizer·learning rate·weight decay가 다르면 다른 model path를 만든다.
오해 3: “gradient가 음수면 model이 틀린 것이다”
아니다. gradient의 부호는 그 parameter를 증가시킬 때 loss가 증가/감소하는 local direction이다. 위 직접 검산에서 ∂L/∂W1[0][1]은 음수였고, gradient descent는 그 weight를 키워 loss를 낮췄다.
오해 4: “sigmoid와 MSE가 있어야 역전파를 쓴다”
아니다. 원 논문은 sigmoid·squared error 예를 사용했다. 현대 training에서는 layer마다 다른 differentiable operation과 loss를 쓴다. local derivative와 chain rule이 있으면 backprop의 구조는 같다.
오해 5: “한 backward pass면 global minimum을 찾는다”
아니다. backward pass는 현재 point의 derivative를 정확히 계산할 수 있다. 그 gradient를 따라가는 optimization이 global minimum에 도달한다는 보증은 별개다. 원 논문 p.535도 local minima 때문에 gradient descent가 global minimum을 보장하지 않는다고 쓴다.
오해 6: “loss가 내려가면 hidden representation을 이해했고 model도 믿을 수 있다”
아니다. training loss는 정의한 train objective가 내려갔다는 신호다. generalization, robustness, fairness, security, task correctness는 별도 evaluation이 필요하다. representation은 distributed할 수 있어 unit 이름을 붙이는 것도 조심해야 한다.
오해 7: “LLM이 답을 생성할 때도 매 token마다 backprop한다”
보통 inference에서는 하지 않는다. 정답 label을 가진 training batch에서 loss를 계산할 때 backward를 한다. 추론은 forward decoding과 KV cache 중심이다. test-time compute는 후보 생성·검증·tool 사용을 늘리는 것이지, 일반적으로 weight gradient를 업데이트하는 과정이 아니다.
최초 질문에 대한 답: hidden unit은 어떻게 배우는가
hidden unit에게 target을 직접 주지 않는다. output loss가 hidden activation에 얼마나 민감한지 ∂L/∂a_hidden을 다음 layer의 weight와 delta로 합산하고, hidden activation function의 derivative를 곱해 delta_hidden=∂L/∂z_hidden을 얻는다. 그 delta_hidden과 이전 activation의 곱이 hidden incoming weight의 gradient다.
output이 틀렸다
→ 마지막 layer가 loss를 얼마나 키웠는지 계산
→ 그 layer가 어떤 hidden output에 의존했는지 weight로 나눔
→ hidden unit의 nonlinearity를 통과
→ hidden incoming edge마다 “얼마나 바꿀지” gradient 생성
이 계산 덕분에 hidden layer는 target 없이도 final task에 유용한 representation을 만들 수 있다. 다만 “유용한 representation이 반드시 생긴다”, “어떤 data에도 잘 일반화한다”, “최적점에 도달한다”는 보장은 아니다. data, architecture, loss, initialization, optimizer, compute, evaluation 모두가 추가 조건이다.
출처
1차 자료
- David E. Rumelhart, Geoffrey E. Hinton, Ronald J. Williams. Learning representations by back-propagating errors, Nature 323, 533–536, 1986. DOI: 10.1038/323533a0. 원 논문의 bibliographic record·abstract·published date를 확인했다.
- 같은 논문의 저자 공개 원문 PDF. p.533 식 (1)
(2), p.534 식 (3)(6)·Figure 13, p.535 식 (7)(9)·Figure 4~5·local minimum 설명, p.536 biological plausibility 한계를 페이지 이미지로 직접 확인했다. - David E. Rumelhart, Geoffrey E. Hinton, Ronald J. Williams. Learning internal representations by error propagation, Parallel Distributed Processing, Vol. 1, 1986, 저자 공개 PDF. Nature letter의 더 긴 동시대 원전이다. 이 글은 Nature paper를 중심으로 쓰고, 반복 network·shared weight의 설명을 교차 확인했다.
- Geoffrey Hinton. Backpropagation Learning bibliography. 1986 Nature paper와 chapter의 저자 페이지 연결을 확인했다.
직접 검증 자료
- /tmp/backpropagation-paper-research/verify_backpropagation.py: Python 3.9.6 표준 라이브러리로 만든 2-2-1 sigmoid network. chain-rule analytic gradient와 central finite difference, 한 gradient step 뒤 loss 감소를 직접 실행했다. 코드·출력·SHA-256은 본문에 기록했다.
- /tmp/backpropagation-paper-research/naturebp.pdf: 저자 공개 4-page Nature 스캔. pdftotext로 text layer가 나오지 않아 300 DPI page image를 직접 읽었다. PDF SHA-256은 본문에 기록했다.
이 글의 해석 경계
LLM training loop, activation checkpointing, observability schema, security/data boundary, production runbook은 1986 논문의 실험 결과를 그대로 인용한 것이 아니다. 원 논문의 chain-rule·activation 저장·shared-weight gradient 누적 원리를 현대 backend·DBA·LLM training 운영으로 연결한 해석과 설계 제안이다. 원 논문의 작은 sigmoid experiment가 현대 Transformer의 안정성·성능·안전성을 보장하지 않는다.
댓글