Skip to content

Backpropagation and Automatic Differentiation

Here is the function the book opens §5.6 with:

f(x)=x2+exp(x2)+cos(x2+exp(x2))(5.109)f(x) = \sqrt{x^2 + \exp(x^2)} + \cos\bigl(x^2 + \exp(x^2)\bigr) \tag{5.109}

and here is its derivative, written out:

dfdx=2x(12x2+exp(x2)sin(x2+exp(x2)))(1+exp(x2))(5.110)\frac{\mathrm{d}f}{\mathrm{d}x} = 2x\left(\frac{1}{2\sqrt{x^2+\exp(x^2)}} - \sin\bigl(x^2+\exp(x^2)\bigr)\right)\bigl(1+\exp(x^2)\bigr) \tag{5.110}

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.

  • §5.6.1: how the chain rule cascades through a KK-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 9.5%9.5\% and 47.8%47.8\%, 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: \lVert\nabla\rVert falling from 5.3×1015.3\times10^{-1} to 3.9×1043.9\times10^{-4} as depth goes 1161 \to 16.

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 xabyx \to a \to b \to y. The chain rule says

dydx=dydbdbdadadx(5.119)\frac{\mathrm{d}y}{\mathrm{d}x} = \frac{\mathrm{d}y}{\mathrm{d}b}\frac{\mathrm{d}b}{\mathrm{d}a}\frac{\mathrm{d}a}{\mathrm{d}x} \tag{5.119}

Three factors, and matrix multiplication is associative, so you may bracket them either way:

dydx=(dydbdbda)dadx(5.120)\frac{\mathrm{d}y}{\mathrm{d}x} = \left(\frac{\mathrm{d}y}{\mathrm{d}b}\frac{\mathrm{d}b}{\mathrm{d}a}\right)\frac{\mathrm{d}a}{\mathrm{d}x} \tag{5.120} dydx=dydb(dbdadadx)(5.121)\frac{\mathrm{d}y}{\mathrm{d}x} = \frac{\mathrm{d}y}{\mathrm{d}b}\left(\frac{\mathrm{d}b}{\mathrm{d}a}\frac{\mathrm{d}a}{\mathrm{d}x}\right) \tag{5.121}

Equation 5.120 accumulates from the output backwards — that is reverse mode, and it is backpropagation. Equation 5.121 accumulates from the input forwardsforward 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.

diagram Diagram mermaid

§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:

y=(fKfK1f1)(x)=fK(fK1((f1(x))))(5.111)\mathbf{y} = (f_K \circ f_{K-1} \circ \cdots \circ f_1)(\mathbf{x}) = f_K\bigl(f_{K-1}(\cdots(f_1(\mathbf{x}))\cdots)\bigr) \tag{5.111}

with each layer fi(xi1)=σ(Ai1xi1+bi1)f_i(\mathbf{x}_{i-1}) = \sigma(\mathbf{A}_{i-1}\mathbf{x}_{i-1} + \mathbf{b}_{i-1}) for an activation σ\sigma — logistic sigmoid, tanh\tanh, or a rectified linear unit. Concretely:

f0:=x,fi:=σi(Ai1fi1+bi1),i=1,,K(5.112-5.113)\mathbf{f}_0 := \mathbf{x}, \qquad \mathbf{f}_i := \sigma_i(\mathbf{A}_{i-1}\mathbf{f}_{i-1} + \mathbf{b}_{i-1}),\quad i = 1,\dots,K \tag{5.112-5.113}

and we want the parameters θ={A0,b0,,AK1,bK1}\boldsymbol{\theta} = \{\mathbf{A}_0,\mathbf{b}_0,\dots,\mathbf{A}_{K-1},\mathbf{b}_{K-1}\} minimising the squared loss

L(θ)=yfK(θ,x)2(5.114)L(\boldsymbol{\theta}) = \lVert\mathbf{y} - \mathbf{f}_K(\boldsymbol{\theta},\mathbf{x})\rVert^2 \tag{5.114}

The chain rule then gives, layer by layer from the top:

LθK1=LfKfKθK1(5.115)\frac{\partial L}{\partial\boldsymbol{\theta}_{K-1}} = \frac{\partial L}{\partial\mathbf{f}_K}\frac{\partial\mathbf{f}_K}{\partial\boldsymbol{\theta}_{K-1}} \tag{5.115} LθK2=LfKfKfK1fK1θK2(5.116)\frac{\partial L}{\partial\boldsymbol{\theta}_{K-2}} = \frac{\partial L}{\partial\mathbf{f}_K}\frac{\partial\mathbf{f}_K}{\partial\mathbf{f}_{K-1}}\frac{\partial\mathbf{f}_{K-1}}{\partial\boldsymbol{\theta}_{K-2}} \tag{5.116} LθK3=LfKfKfK1fK1fK2fK2θK3(5.117)\frac{\partial L}{\partial\boldsymbol{\theta}_{K-3}} = \frac{\partial L}{\partial\mathbf{f}_K}\frac{\partial\mathbf{f}_K}{\partial\mathbf{f}_{K-1}}\frac{\partial\mathbf{f}_{K-1}}{\partial\mathbf{f}_{K-2}}\frac{\partial\mathbf{f}_{K-2}}{\partial\boldsymbol{\theta}_{K-3}} \tag{5.117} Lθi=LfKfKfK1fi+2fi+1fi+1θi(5.118)\frac{\partial L}{\partial\boldsymbol{\theta}_i} = \frac{\partial L}{\partial\mathbf{f}_K}\frac{\partial\mathbf{f}_K}{\partial\mathbf{f}_{K-1}}\cdots\frac{\partial\mathbf{f}_{i+2}}{\partial\mathbf{f}_{i+1}}\frac{\partial\mathbf{f}_{i+1}}{\partial\boldsymbol{\theta}_i} \tag{5.118}

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 \sum. 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.

Rather than evaluate Equation 5.109 as written, save work with intermediate variables:

a=x2,b=exp(a),c=a+b,d=c,e=cos(c),f=d+e(5.123-5.128)a = x^2,\quad b = \exp(a),\quad c = a + b,\quad d = \sqrt{c},\quad e = \cos(c),\quad f = d + e \tag{5.123-5.128}

At x=0.9x = 0.9:

variablevalue
a=x2a = x^20.8100000000.810000000
b=exp(a)b = \exp(a)2.2479079872.247907987
c=a+bc = a+b3.0579079873.057907987
d=cd = \sqrt{c}1.7486875041.748687504
e=cos(c)e = \cos(c)0.996500481-0.996500481
f=d+ef = d+e0.7521870230.752187023

Six elementary operations, and note that cc is computed once and used twice — by dd and by ee. That reuse is why the graph is a graph rather than a chain, and it is where the interesting frame is.

ax=2x,ba=exp(a),ca=1=cb(5.129-5.131)\frac{\partial a}{\partial x} = 2x,\qquad \frac{\partial b}{\partial a} = \exp(a),\qquad \frac{\partial c}{\partial a} = 1 = \frac{\partial c}{\partial b} \tag{5.129-5.131} dc=12c,ec=sin(c),fd=1=fe(5.132-5.134)\frac{\partial d}{\partial c} = \frac{1}{2\sqrt{c}},\qquad \frac{\partial e}{\partial c} = -\sin(c),\qquad \frac{\partial f}{\partial d} = 1 = \frac{\partial f}{\partial e} \tag{5.132-5.134}

Every one is a one-line school derivative. Nothing here is hard; the algorithm is in the order.

Working from the output:

fc=fddc+feec=112c+1(sinc)(5.135, 5.139)\frac{\partial f}{\partial c} = \frac{\partial f}{\partial d}\frac{\partial d}{\partial c} + \frac{\partial f}{\partial e}\frac{\partial e}{\partial c} = 1\cdot\frac{1}{2\sqrt{c}} + 1\cdot(-\sin c) \tag{5.135, 5.139} fb=fccb=fc1(5.136, 5.140)\frac{\partial f}{\partial b} = \frac{\partial f}{\partial c}\frac{\partial c}{\partial b} = \frac{\partial f}{\partial c}\cdot1 \tag{5.136, 5.140}   fa=fbba+fcca=fbexp(a)+fc1  (5.137, 5.141)\boxed{\;\frac{\partial f}{\partial a} = \frac{\partial f}{\partial b}\frac{\partial b}{\partial a} + \frac{\partial f}{\partial c}\frac{\partial c}{\partial a} = \frac{\partial f}{\partial b}\exp(a) + \frac{\partial f}{\partial c}\cdot1\;} \tag{5.137, 5.141} fx=faax=fa2x(5.138, 5.142)\frac{\partial f}{\partial x} = \frac{\partial f}{\partial a}\frac{\partial a}{\partial x} = \frac{\partial f}{\partial a}\cdot2x \tag{5.138, 5.142}

At x=0.9x = 0.9:

adjointcomputationvalue
f/f\partial f/\partial fseeded (Eq 5.144)11
f/d=f/e\partial f/\partial d = \partial f/\partial e111\cdot111
f/c\partial f/\partial c12csinc\tfrac{1}{2\sqrt{c}} - \sin c0.2023417060.202341706
f/b\partial f/\partial b0.20234170610.202341706 \cdot 10.2023417060.202341706
f/a\partial f/\partial a0.2023417062.247908+0.2023417060.202341706\cdot2.247908 + 0.2023417060.657187244\mathbf{0.657187244}
f/x\partial f/\partial x0.6571872441.80.657187244 \cdot 1.81.182937038\mathbf{1.182937038}

Against Equation 5.110 evaluated directly: 1.1829370383401.182937038340 both ways, gap 2.2×10162.2\times10^{-16}.

operations
forward pass (Equations 5.123–5.128)66
reverse sweep (88 graph edges, one multiply and one add each)1616
ratio2.67×\mathbf{2.67\times}
Equation 5.110 evaluated as written1111, and it recomputes x2x^2 and exp(x2)\exp(x^2)

That is the book’s counterintuitive claim, made concrete. The derivative is 2.672.67 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.

fieldExample 5.14, forward then backwardreverse-mode autodiff, §5.6
x0.9abcdef
phase forwardx 0.9forward ops 0reverse ops 0
x0.90000000
Forward pass. The input is x = 0.9.
1/14

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.

fieldA scalar version of §5.6.1's deep networkreverse-mode autodiff, §5.6
x0.7z1h1z2h2rL
phase forwardx 0.7forward ops 0reverse ops 0
x0.70000000
Forward pass. The input is x = 0.7.
1/14

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.

sketch Forward mode or reverse mode? p5.js
Drag the input and output widths. Forward mode needs one sweep per input, reverse mode one per output, so the cheaper choice is decided by the shape of the Jacobian and nothing else. The star marks where a neural network sits: millions of inputs, one scalar loss.
sketch Vanishing gradients, measured p5.js
A K-layer tanh network with a squared loss. Drag the depth and watch the gradient norm collapse: each layer multiplies by a tanh derivative no bigger than 1, so K of them compound. This is the reason deep networks were hard before ReLU and residual connections, and it falls straight out of Equation 5.118's product.
autodiff_from_scratch.py
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%}")
output
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%
figure The book's Figure 5.11, with numbers on it matplotlib
A computation graph of seven labelled nodes from x through a, b, c, d, e to f, each showing its forward value in white and its adjoint in red, with the edges out of a highlighted. A computation graph of seven labelled nodes from x through a, b, c, d, e to f, each showing its forward value in white and its adjoint in red, with the edges out of a highlighted.
Example 5.14 at x = 0.9. The forward values reproduce Equations 5.123 to 5.128 and the adjoints reproduce 5.135 to 5.142. The highlighted node is a, the only one with two children — its adjoint is 0.202342 times 2.247908 plus 0.202342, which is 0.657187, and then df/dx is that times 1.8.
figure Which mode, decided by the Jacobian's shape matplotlib
Left, a log-log plot of cost against number of inputs with forward mode rising linearly and reverse mode flat. Right, a heatmap over input and output widths labelling which mode is cheaper in each cell. Left, a log-log plot of cost against number of inputs with forward mode rising linearly and reverse mode flat. Right, a heatmap over input and output widths labelling which mode is cheaper in each cell.
With a scalar loss, forward mode needs n sweeps and reverse mode one, so reverse wins from n = 3. The heatmap generalises it: the choice depends only on whether inputs or outputs are more numerous. The star marks a network — many in, one out — which is the corner reverse mode was built for.
figure Automatic differentiation has no step size matplotlib
A log-log plot of derivative error against step size, with forward and central difference curves forming V shapes and a flat horizontal line for automatic differentiation far below both. A log-log plot of derivative error against step size, with forward and central difference curves forming V shapes and a flat horizontal line for automatic differentiation far below both.
The derivative of Example 5.14 three ways. AD lands at 2.2e-16 and does not depend on h at all; over 200 values of h the best central difference reaches only 9.8e-12, at h = 5.8e-08 — a factor of forty thousand worse, and that is the BEST h, which you do not know in advance.

From the graph figure. Follow the red numbers right to left. They start at 11 — 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 aa, and its box is where the sum happens.

The number to check is 0.6571870.657187. Both contributions come from the same f/c=0.202342\partial f/\partial c = 0.202342: one routed through bb and scaled by exp(a)=2.247908\exp(a) = 2.247908, one routed directly through cc and scaled by 11. Add them and you get 0.454846+0.202342=0.6571870.454846 + 0.202342 = 0.657187. Multiply by 2x=1.82x = 1.8 and you have the answer.

From the cost figure. The left panel’s two lines cross at n=3n = 3 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 hh, so it has no V. Its error is 2.2×10162.2\times10^{-16} — one rounding — and it is the same at every scale.

The comparison worth quoting is against the best possible difference:

methodbest errorat
forward difference1.7×1081.7\times10^{-8}h=2.1×109h = 2.1\times10^{-9}
central difference9.8×10129.8\times10^{-12}h=5.8×108h = 5.8\times10^{-8}
reverse-mode AD2.2×1016\mathbf{2.2\times10^{-16}}no hh

Four orders of magnitude better than the best central difference — and you do not get to pick the best hh in advance. On a coarser grid of sixteen step sizes the best central difference is 9.6×10119.6\times10^{-11}, which is 4.3×1054.3\times10^{5} 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.

methodexactnesscost of one gradientneeds
by hand (Eq 5.110)exactyour time; and it recomputed x2x^2 and exp(x2)\exp(x^2)a closed form
forward differenceO(h)O(h), floor 108\approx10^{-8}n+1n + 1 evaluationsnothing
central differenceO(h2)O(h^2), floor 1011\approx10^{-11}2n2n evaluationsnothing
forward-mode ADmachine precisionnn sweepsthe code
reverse-mode ADmachine precisionmm sweeps, 2.67×\approx2.67\times one passthe code, plus memory for the tape
symbolicexactexpression grows with deptha 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 mm and nn, not judgement.

pch.quizTag Check your understanding
  1. Forward and reverse mode are described as differing 'in the order of multiplication'. What exactly is being reordered?

    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.

  2. Why is reverse mode the only practical choice for training a network?

    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.

  3. Equation 5.137 is a sum of two terms. What happens if you keep only the first?

    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.

  4. The reverse sweep on Example 5.14 costs 16 operations against the forward pass's 6. Why is that the interesting number?

    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.

  5. The gradient norm fell from 5.3e-01 at depth 1 to 3.9e-04 at depth 16. Is backpropagation losing accuracy?

    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.

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”
  • 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.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading