Backpropagation and Automatic Differentiation
Here is the function the book opens §5.6 with:
and here is its derivative, written out:
The book’s comment: writing out the gradient in this explicit way is often impractical since it often results in a very lengthy expression for a derivative. In practice, it means that, if we are not careful, the implementation of the gradient could be significantly more expensive than computing the function.
And then the claim that makes the whole chapter land — the derivative costs about what the function costs, not more. The book calls that “quite counterintuitive” itself. This page counts the operations.
What you’ll learn
Section titled “What you’ll learn”- §5.6.1: how the chain rule cascades through a -layer network, Equations 5.111 to 5.118.
- §5.6.2: forward and reverse mode as two bracketings of the same product, Equations 5.119 to 5.121 — and why the shape of the Jacobian decides which is cheaper.
- Example 5.14 walked node by node, with the forward values of Equations 5.123–5.128 and the adjoints of Equations 5.135–5.142.
- What dropping the second term of Equation 5.137 costs: between and , measured.
- The general algorithm, Equations 5.143 to 5.145, and why it is just the chain rule with bookkeeping.
- Why AD beats finite differencing by five orders of magnitude on the same function, with no step size to tune.
- A measured look at vanishing gradients: falling from to as depth goes .
Intuition: multiply the same factors in the other order
Section titled “Intuition: multiply the same factors in the other order”Take the book’s Figure 5.10 — data flowing . The chain rule says
Three factors, and matrix multiplication is associative, so you may bracket them either way:
Equation 5.120 accumulates from the output backwards — that is reverse mode, and it is backpropagation. Equation 5.121 accumulates from the input forwards — forward mode, gradients flowing with the data.
Same answer, same factors, different intermediate objects. And that is the entire difference: which partial products you have to store, and how big they are.
flowchart TD CR["dy/dx = (dy/db)(db/da)(da/dx)
Eq 5.119 — three factors"] CR -->|"bracket from the LEFT"| REV["Eq 5.120: reverse mode
accumulate output → input
= backpropagation"] CR -->|"bracket from the RIGHT"| FWD["Eq 5.121: forward mode
accumulate input → output"] REV --> RC["cost: one sweep per OUTPUT
a scalar loss ⇒ ONE sweep"] FWD --> FC["cost: one sweep per INPUT
a million weights ⇒ a million sweeps"] RC --> WIN["reverse wins when n ≫ m
crossover at n = 3
a network: n in the millions, m = 1"] FC --> WIN REV --> ALG["the algorithm, Eq 5.143–5.145
forward: xᵢ = gᵢ(x_Pa(i))
seed: ∂f/∂x_D = 1
sweep: ∂f/∂xᵢ = Σ over CHILDREN"] ALG --> SUM["that Σ is the whole subtlety —
a node with two children needs
a SUM, not one product
(Eq 5.137)"] SUM --> COST["dropping one term: 9.5%–47.8% wrong"]
The math
Section titled “The math”§5.6.1 — the chain rule through a deep network
Section titled “§5.6.1 — the chain rule through a deep network”A deep network is an extreme case of composition:
with each layer for an activation — logistic sigmoid, , or a rectified linear unit. Concretely:
and we want the parameters minimising the squared loss
The chain rule then gives, layer by layer from the top:
Look at what those four lines share. Every one of them starts with the same prefix, and each is the previous one with one more factor inserted. So computing them top-down means each layer reuses the product the layer above already formed — one new factor per layer, not a fresh product per layer. That reuse is backpropagation, and Equation 5.118 is the pattern it exploits.
§5.6.2 — automatic differentiation, in general
Section titled “§5.6.2 — automatic differentiation, in general”Equation 5.143 is the forward propagation of the function; Equation 5.145 is the backpropagation of the gradient through the graph.
The one subtlety in the whole algorithm is that . Most nodes have one child and their adjoint is a single product. A node with two children needs both terms — and that is Equation 5.137 in the worked example below.
Worked example by hand
Section titled “Worked example by hand”Example 5.14, forward
Section titled “Example 5.14, forward”Rather than evaluate Equation 5.109 as written, save work with intermediate variables:
At :
| variable | value |
|---|---|
Six elementary operations, and note that is computed once and used twice — by and by . That reuse is why the graph is a graph rather than a chain, and it is where the interesting frame is.
The elementary derivatives
Section titled “The elementary derivatives”Every one is a one-line school derivative. Nothing here is hard; the algorithm is in the order.
Example 5.14, backward
Section titled “Example 5.14, backward”Working from the output:
At :
| adjoint | computation | value |
|---|---|---|
| seeded (Eq 5.144) | ||
Against Equation 5.110 evaluated directly: both ways, gap .
The cost, counted
Section titled “The cost, counted”| operations | |
|---|---|
| forward pass (Equations 5.123–5.128) | |
| reverse sweep ( graph edges, one multiply and one add each) | |
| ratio | |
| Equation 5.110 evaluated as written | , and it recomputes and |
That is the book’s counterintuitive claim, made concrete. The derivative is times the function — a constant factor, not a blow-up — and the reason is that the reverse sweep reuses every forward value instead of recomputing it. Equation 5.110 does recompute them, which is exactly the “unnecessary overhead” the book warns about.
See it move
Section titled “See it move”Six intermediates, then the sweep. Stop on the frame where a's adjoint is built: it is the only node with two children, so its adjoint is Equation 5.137's sum rather than a single product. The final frame checks the answer three independent ways.
Two tanh layers and a squared loss. The adjoints arriving at each weight are exactly what backpropagation computes, and the operation counts show the reverse sweep costing twice the forward pass.
From scratch
Section titled “From scratch”import numpy as np
def f_5_14(x):
"""Eq 5.109, via the intermediates of Eq 5.123-5.128."""
a = x ** 2
b = np.exp(a)
c = a + b
d = np.sqrt(c)
e = np.cos(c)
return d + e
def dfdx_closed(x):
"""Eq 5.110, the book's explicit gradient."""
u = x ** 2 + np.exp(x ** 2)
return 2 * x * (1 / (2 * np.sqrt(u)) - np.sin(u)) * (1 + np.exp(x ** 2))
def reverse_5_14(x):
"""Forward pass, then Eq 5.135-5.142."""
a = x ** 2 # Eq 5.123
b = np.exp(a) # Eq 5.124
c = a + b # Eq 5.125
d = np.sqrt(c) # Eq 5.126
e = np.cos(c) # Eq 5.127
f = d + e # Eq 5.128
adj_f = 1.0 # Eq 5.144
adj_d = adj_f * 1.0 # Eq 5.134
adj_e = adj_f * 1.0
adj_c = adj_d * (1 / (2 * np.sqrt(c))) + adj_e * (-np.sin(c)) # Eq 5.135
adj_b = adj_c * 1.0 # Eq 5.136
adj_a = adj_b * np.exp(a) + adj_c * 1.0 # Eq 5.137 <- a SUM
adj_x = adj_a * 2 * x # Eq 5.138
return dict(a=a, b=b, c=c, d=d, e=e, f=f,
adj_c=adj_c, adj_b=adj_b, adj_a=adj_a, adj_x=adj_x)
x0 = 0.9
st = reverse_5_14(x0)
print(f"forward pass at x = {x0} (Eq 5.123-5.128)")
for k in ("a", "b", "c", "d", "e", "f"):
print(f" {k} = {st[k]:.9f}")
print()
print("reverse sweep (Eq 5.135-5.142)")
print(f" adj[c] = 1*(1/(2*sqrt(c))) + 1*(-sin(c)) = {st['adj_c']:.9f}")
print(f" adj[b] = adj[c]*1 = {st['adj_b']:.9f}")
print(f" adj[a] = adj[b]*exp(a) + adj[c]*1 = {st['adj_a']:.9f} <- a SUM, Eq 5.137")
print(f" adj[x] = adj[a]*2x = {st['adj_x']:.9f}")
print()
print(f"Eq 5.110 (the explicit gradient) {dfdx_closed(x0):.12f}")
print(f"reverse mode {st['adj_x']:.12f}")
print(f"gap {abs(st['adj_x'] - dfdx_closed(x0)):.1e}")
print()
print("what dropping the second term of Eq 5.137 costs")
for x in (0.3, 0.6, 0.9, 1.2, 1.5):
a = x ** 2
b = np.exp(a)
c = a + b
adj_c = (1 / (2 * np.sqrt(c))) + (-np.sin(c))
full = (adj_c * np.exp(a) + adj_c * 1.0) * 2 * x
dropped = (adj_c * np.exp(a)) * 2 * x
exact = dfdx_closed(x)
print(f" x = {x:<4} full {full:>13.8f} dropping the second term {dropped:>13.8f}"
f" relative error {abs(dropped - exact) / abs(exact):>8.1%}")forward pass at x = 0.9 (Eq 5.123-5.128)
a = 0.810000000
b = 2.247907987
c = 3.057907987
d = 1.748687504
e = -0.996500481
f = 0.752187023
reverse sweep (Eq 5.135-5.142)
adj[f] = 1
adj[d] = adj[e] = 1
adj[c] = 1*(1/(2*sqrt(c))) + 1*(-sin(c)) = 0.202341706
adj[b] = adj[c]*1 = 0.202341706
adj[a] = adj[b]*exp(a) + adj[c]*1 = 0.657187244 <- a SUM, Eq 5.137
adj[x] = adj[a]*2x = 1.182937038
Eq 5.110 (the explicit gradient) 1.182937038340
reverse mode 1.182937038340
gap 2.2e-16
what dropping the second term of Eq 5.137 costs
x = 0.3 full -0.58642666 dropping the second term -0.30639903 relative error 47.8%
x = 0.6 full -1.75775264 dropping the second term -1.03538738 relative error 41.1%
x = 0.9 full 1.18293704 dropping the second term 0.81872197 relative error 30.8%
x = 1.2 full 9.93868781 dropping the second term 8.03497839 relative error 19.2%
x = 1.5 full 27.78045491 dropping the second term 25.13160340 relative error 9.5%On real data
Section titled “On real data”Reading the plot
Section titled “Reading the plot”From the graph figure. Follow the red numbers right to left. They start at — Equation 5.144’s seed — and every node multiplies by one local derivative as the sweep passes. Six of the seven nodes have a single incoming red edge, so their adjoint is one product. The seventh is , and its box is where the sum happens.
The number to check is . Both contributions come from the same : one routed through and scaled by , one routed directly through and scaled by . Add them and you get . Multiply by and you have the answer.
From the cost figure. The left panel’s two lines cross at and never come back. That is the whole argument for backpropagation, and it does not depend on anything about the function — only on the shape of its Jacobian.
The heatmap makes the rule general: forward mode costs one sweep per input, reverse mode one per output. So the question is never “which is better”, it is “which dimension is bigger”. A Jacobian-vector product wants forward mode; a scalar loss wants reverse. A network has a million-plus inputs and one output, which is why reverse mode is not a preference there but the only option.
From the accuracy figure. The flat green line is the point: AD has no , so it has no V. Its error is — one rounding — and it is the same at every scale.
The comparison worth quoting is against the best possible difference:
| method | best error | at |
|---|---|---|
| forward difference | ||
| central difference | ||
| reverse-mode AD | no |
Four orders of magnitude better than the best central difference — and you do not get to pick the best in advance. On a coarser grid of sixteen step sizes the best central difference is , which is times worse than AD. That factor is why nobody trains with finite differences and why gradient checking is a debugging tool rather than a method.
Pitfalls
Section titled “Pitfalls”Compare
Section titled “Compare”| method | exactness | cost of one gradient | needs |
|---|---|---|---|
| by hand (Eq 5.110) | exact | your time; and it recomputed and | a closed form |
| forward difference | , floor | evaluations | nothing |
| central difference | , floor | evaluations | nothing |
| forward-mode AD | machine precision | sweeps | the code |
| reverse-mode AD | machine precision | sweeps, one pass | the code, plus memory for the tape |
| symbolic | exact | expression grows with depth | a closed form |
The two AD rows are the same algorithm bracketed differently — Equations 5.120 and 5.121 — and the choice between them is arithmetic on and , not judgement.
-
Forward and reverse mode are described as differing 'in the order of multiplication'. What exactly is being reordered?
Same factors, same answer, different partial products to store. Reverse mode accumulates from the output and so pays one sweep per output; forward mode accumulates from the input and pays one per input.
pch.quizShowAnswer
B — The bracketing of one product of Jacobians. Matrix multiplication is associative, so (dy/db · db/da) · da/dx and dy/db · (db/da · da/dx) give the same answer — Equations 5.120 and 5.121 — with different intermediate objects — Same factors, same answer, different partial products to store. Reverse mode accumulates from the output and so pays one sweep per output; forward mode accumulates from the input and pays one per input.
-
Why is reverse mode the only practical choice for training a network?
Note the last option is backwards: reverse mode uses MORE memory, because every intermediate has to survive until the sweep reaches it. That is why gradient checkpointing exists.
pch.quizShowAnswer
B — Because the cost is one sweep per OUTPUT, and a loss is one scalar — while forward mode would need one sweep per parameter. The crossover measured here is n = 3, and a network has millions of parameters — Note the last option is backwards: reverse mode uses MORE memory, because every intermediate has to survive until the sweep reaches it. That is why gradient checkpointing exists.
-
Equation 5.137 is a sum of two terms. What happens if you keep only the first?
a is the only node in Example 5.14 with two children, so it is the only adjoint that is a sum rather than a single product — which is exactly why it is easy to forget. And the error being worst for SMALL x inverts the usual debugging instinct.
pch.quizShowAnswer
B — The gradient stays the right sign and order of magnitude but is wrong by between 9.5 and 47.8 percent, worst for small x — plausible enough to pass a casual check — a is the only node in Example 5.14 with two children, so it is the only adjoint that is a sum rather than a single product — which is exactly why it is easy to forget. And the error being worst for SMALL x inverts the usual debugging instinct.
-
The reverse sweep on Example 5.14 costs 16 operations against the forward pass's 6. Why is that the interesting number?
The reuse is the mechanism: the reverse sweep reads forward values instead of recomputing them. Symbolic differentiation does recompute them, which is the 'unnecessary overhead' the book warns about at Equation 5.110.
pch.quizShowAnswer
B — Because it is a constant factor — 2.67x — rather than a blow-up, which is the book's counterintuitive claim. Equation 5.110 written out is 11 operations AND recomputes x-squared and exp(x-squared) that the forward pass already had — The reuse is the mechanism: the reverse sweep reads forward values instead of recomputing them. Symbolic differentiation does recompute them, which is the 'unnecessary overhead' the book warns about at Equation 5.110.
-
The gradient norm fell from 5.3e-01 at depth 1 to 3.9e-04 at depth 16. Is backpropagation losing accuracy?
Measured against finite differences the reverse-mode gradient agrees to 6.8e-08 even at depth 16, so it is computing correctly. Vanishing gradients are a property of the product that Eq 5.118 describes, which is why ReLU (derivative exactly 1 when active) and residual connections both attack the factors rather than the algorithm.
pch.quizShowAnswer
B — No. Equation 5.118 makes the gradient at layer i a product of K minus i Jacobians, and every tanh derivative has magnitude at most 1 — so the product can only shrink. The algorithm is exact; the answer is genuinely small — Measured against finite differences the reverse-mode gradient agrees to 6.8e-08 even at depth 16, so it is computing correctly. Vanishing gradients are a property of the product that Eq 5.118 describes, which is why ReLU (derivative exactly 1 when active) and residual connections both attack the factors rather than the algorithm.
🧪 Try It Yourself
Section titled “🧪 Try It Yourself”Exercise 1 – Example 5.14, forward and backward
Section titled “Exercise 1 – Example 5.14, forward and backward”Exercise 2 – What dropping Equation 5.137’s second term costs
Section titled “Exercise 2 – What dropping Equation 5.137’s second term costs”Exercise 3 – AD against differencing, at every step size
Section titled “Exercise 3 – AD against differencing, at every step size”Exercise 4 – Reverse mode on a deep chain, and vanishing gradients
Section titled “Exercise 4 – Reverse mode on a deep chain, and vanishing gradients”Exercise 5 – Count the operations
Section titled “Exercise 5 – Count the operations”Recall card
Section titled “Recall card”- Forward and reverse mode are the same product bracketed differently — Equations 5.120 and 5.121. Same factors, same answer, different intermediates to store.
- Reverse mode costs one sweep per OUTPUT, forward mode one per INPUT. With a scalar loss the crossover is n = 3, and a network has millions of parameters — so reverse mode is not a preference there, it is the only option.
- Equation 5.145’s sum is the whole subtlety. A node with one child has a single-product adjoint; a node with two children needs both terms.
- Example 5.14’s node a is the only one with two children, so Equation 5.137 is the only sum. Dropping one term leaves the gradient the right sign and order of magnitude but 9.5 to 47.8 percent wrong — worst for SMALL x.
- The derivative costs a constant factor of the function, not a blow-up. Measured: 6 forward operations against 16 in the reverse sweep — 2.67x — because the sweep reuses forward values instead of recomputing them.
- Equation 5.110 written out is 11 operations and recomputes x-squared and exp(x-squared), which is exactly the “unnecessary overhead” the book warns about.
- AD has no step size, so no V-shaped error curve. Measured: 2.2e-16 against a best central difference of 9.8e-12 over 200 step sizes — and you must already know the best h to get that.
- Reverse mode trades memory for time. Every intermediate must survive until the sweep reaches it, which is the real constraint on training and the reason gradient checkpointing exists.
- Vanishing gradients come from Equation 5.118’s product, not from the algorithm. Measured on a tanh chain: the gradient norm falls from 5.30e-01 at depth 1 to 3.87e-04 at depth 16, while still agreeing with finite differences to 6.8e-08.
- §5.6.1’s cascade reuses its own prefix. Equations 5.115 to 5.118 each extend the previous product by one factor, and exploiting that reuse is what backpropagation is.
Next: Higher-Order Derivatives — the Hessian, and the question the gradient cannot answer.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading