The credit assignment problem
A network with a million weights makes a bad prediction. Which weights are to blame, and how much should each one change? Gradient descent needs for every weight, at every step.
The naive approach is to nudge one weight, rerun the network, see how the loss changes, and repeat for the next weight. That costs one forward pass per weight: a million passes for a single update. Hopeless.
Backpropagation computes every one of those derivatives in a single backward sweep, at a cost comparable to one extra forward pass. It is the reason deep learning is possible at all, and it rests entirely on one idea from calculus.
The chain rule
If depends on , and depends on , then
Rates of change multiply along a chain. The mathematician George Simmons put it memorably: if a car travels twice as fast as a bicycle, and the bicycle four times as fast as a walking man, then the car travels times as fast as the man.
A quick example: at . Let . Then and , so .
Computational graphs
To apply the chain rule to a whole network, break the computation into a graph of primitive operations. Then run it twice:
- 1
Forward pass
Compute every node's value from left to right, and remember them: the backward pass needs them.
- 2
Backward pass
Start at the loss with and walk right to left. At every node, apply the chain rule locally:
Each node only ever needs its own local derivative. A handful of patterns covers most of what you'll meet:
| Gate | Forward | Backward rule |
|---|---|---|
| add | copies the incoming gradient to both inputs | |
| multiply | each input gets the incoming gradient times the other input | |
| ReLU | passes the gradient if , blocks it otherwise | |
| square | multiplies the incoming gradient by |
For , the gradient arriving at is . If and , what is ?
Backprop by hand
Enough reading: it's your turn to be the backward pass. The lab gives you one neuron, , with real numbers. Compute every gradient yourself. Get them all right on the first try and something shiny happens.
Interactive lab
Backprop by hand
Forward pass · step 1 of 4
- Next: compute m
Backprop for a whole network
The same bookkeeping scales to entire networks, where it's written with vectors and matrices. Take a two-layer network on one example:
Derive the gradients (try on paper first!)
Walk backwards, one line per node:
The pattern repeats in every layer. An error signal arrives, gets multiplied elementwise () by the activation's slope, and is passed back through the transpose of the layer's weights. (The in the loss is a convenience: it cancels the 2 from differentiating the square.)
That elementwise multiply by explains the vanishing gradient problem you were warned about. Each sigmoid layer multiplies the signal by at most , so after ten layers it can shrink by a factor of . That's one reason ReLU (slope 1), careful initialisation, normalisation layers and residual connections became standard.
Trust, but verify: gradient checking
Hand-derived gradients are easy to get slightly wrong. Worse, a buggy gradient often still reduces the loss, just badly. Careful researchers check their gradients numerically with the centred difference:
import numpy as np
def grad_check(f, grad_f, theta, eps=1e-5):
"""Relative error between an analytic gradient and centred finite differences."""
analytic = grad_f(theta)
numeric = np.zeros_like(theta)
for i in range(theta.size):
step = np.zeros_like(theta)
step.flat[i] = eps
numeric.flat[i] = (f(theta + step) - f(theta - step)) / (2 * eps)
diff = np.linalg.norm(analytic - numeric)
return diff / (np.linalg.norm(analytic) + np.linalg.norm(numeric))
# Around 1e-7 or smaller: great. Around 1e-2 or larger: almost certainly a bug.
Autograd: let the machine do it
In practice nobody writes backward passes by hand any more. Frameworks such as PyTorch and JAX record the computational graph as your code runs and apply the chain rule for you. Here is the lab's neuron in PyTorch. It prints exactly the gradients you just computed:
import torch
x, y = torch.tensor(3.0), torch.tensor(4.0)
w = torch.tensor(2.0, requires_grad=True)
b = torch.tensor(-1.0, requires_grad=True)
a = torch.relu(w * x + b) # forward pass: a = 5
loss = (a - y) ** 2 # loss = 1
loss.backward() # backward pass
print(w.grad, b.grad) # tensor(6.) tensor(2.)
Understanding what loss.backward() does, and when it can silently go wrong, is what separates someone who uses deep learning from someone who can research it.
Four pages that changed the field. A perfect candidate for your first three-pass read in Chamber 6.
Watch: backpropagation, intuitively
Want to go deeper? Build a tiny autograd engine yourself, line by line, with Andrej Karpathy:
Key takeaways
- Backpropagation is the chain rule applied systematically on a computational graph: forward to compute values, backward to compute gradients.
- Each node multiplies the upstream gradient by its local derivative. Add gates copy, multiply gates swap, ReLU gates route.
- One backward sweep yields every gradient, which makes training networks with millions of weights feasible.
- Check hand-written gradients with centred finite differences. Beware vanishing gradients in deep stacks of saturating activations.
Checkpoint
Prove it to the labyrinth
Answer every question to clear this chamber. First-try answers earn the most XP.
Let and . What is at ?
During the backward pass, what does each node in a computational graph compute?
Why is backpropagation so much faster than estimating each weight's gradient separately (e.g. by nudging weights one at a time)?
You implemented backprop by hand. What is the standard way to check it's correct?
End of the chamber
Clear this chamber
- Questions in this chamber (0/5 solved)Next unsolved
- Bonus: Backprop by hand (+50 XP)