Neural Networks/Backpropagation

Lesson 3.42,814 words

Backpropagation

Backpropagation is the chain rule run backward over a computational graph. We formalize the graph, derive the four backprop equations for an MLP, present the forward and backward passes as algorithms, and work a tiny two-layer net by hand with explicit numbers.

╌╌╌╌

The multilayer perceptron gave us a forward pass: inputs flow up through affine maps and nonlinearities to a scalar loss. Training needs the reverse: how the loss responds to every weight, . Backpropagation computes that gradient exactly, in a single backward sweep, by applying the chain rule to the training loop's forward computation read as a graph. It is not a learning algorithm; it is the differentiation algorithm that feeds the optimizer.1

The computational graph

Every model is a composition of primitive operations, and that composition has a shape: a directed acyclic graph whose nodes compute and whose edges carry values.

Nodes are operations; edges are tensors. Take the single-example squared error of a linear unit, . Its graph threads through a multiply, two adds, and a square — and once forward values are pinned to the nodes, backprop annotates each edge with a gradient flowing the other way.

Computational graph of . Black edges carry forward values; blue edges carry the gradient written right to left.

The forward pass labels every edge with a value; the backward pass labels every edge with the loss's sensitivity to that value. The two passes share the same graph and traverse it in opposite topological directions.

The chain rule for vector functions

A node rarely carries a scalar; it carries a tensor. So the local derivative is not a number but a Jacobian, and the chain rule multiplies Jacobians.

For a deep net the output of interest is the scalar loss , so the quantity we propagate is the gradient row vector . Pushing it through a node requires not the full Jacobian but the product of the incoming gradient with — the vector-Jacobian product.2

The VJP is the core operation: each operation supplies a rule for turning its output gradient into its input gradient, without ever materializing the (possibly huge) Jacobian . For a matrix multiply , and the VJP is just , a second matrix multiply, transposed.

node Jacobian VJP
matrix multiply
add (and )
elementwise
sum
scale

For a single node the rule is: multiply the upstream gradient by the local derivative to get the downstream gradient. Backprop repeats this step at every node.

The local-gradient gate. A node multiplies the upstream gradient by its local Jacobian transpose to emit the downstream gradient.

The four backprop equations

Now specialize to an MLP. Layers compute pre-activations and activations

with a per-example loss (the output , or for a linear final layer). Define the error of layer as the gradient of the loss with respect to that layer's pre-activation,

Everything reduces to computing the . The four equations below do it recursively, then read the parameter gradients off the errors.

Two facts make these equations the entire algorithm. First, the recursion BP2 is the VJP of the affine-plus-activation block, reused at every layer — the same two matrix operations, transposed. Second, BP4 needs the cached activation from the forward pass, which is why the forward pass must store its intermediates.3

Every shape, checked

The four equations are easy to write and easy to get wrong by a transpose. Pin down every shape and the algebra becomes mechanical. Let layer have units and inputs, so and . Every activation, pre-activation, and error at layer is a vector of length ; every weight gradient matches its weight.

objectexpressionshape
activation
weight
pre-activation
activation derivative
error
transposed weight
back-propagated error
weight gradient
bias gradient

BP2 reads left to right as a shape contraction: the transpose hits the upstream error to give an vector, which the Hadamard product with leaves at — exactly the shape of . BP4 is an outer product: the column times the row yields the matrix that matches entry for entry. A single rule catches most sign-and- transpose bugs: every gradient carries the shape of the thing it differentiates, is shaped like , is shaped like .

The batched form

Training runs on a minibatch of examples at once, not one vector. Stack the activations of layer as columns of a matrix (some frameworks use rows; the transposes flip but the content is the same). The forward pass becomes a matrix product with a broadcast bias, and the backward pass sums the per-example gradients — the reason a minibatch gradient is an average over examples.

with and the all-ones vector that broadcasts the bias across columns. The error matrix holds one per column, and the batched equations are the single-example ones with the outer product replaced by a matrix product that sums over the batch axis:

The weight gradient contracts the error against the transposed activations, and the shared batch axis vanishes — the matrix product is the sum over examples. The bias gradient is that same sum over the batch axis. Dividing by (or folding into ) turns the summed gradient into the mean gradient the optimizer expects.

The dimensions flow one direction on the forward pass and the mirror direction on the backward pass, with the batch axis carried along both ways.

Delta recursion with tensor shapes. Forward (black) carries of shape ; backward (blue) carries of the same shape, gated by .

The error flows right to left across the layers, the mirror of the forward signal. Each layer turns the incoming error into the outgoing one by a transposed weight multiply and a gate by .

Backward flow through an MLP. The error propagates right to left through each weight transpose and activation gate; weight gradients fall out as outer products.

Forward and backward passes

The two passes are one algorithm in two directions. The forward pass evaluates the graph in topological order and caches every pre-activation and activation ; the backward pass walks the layers in reverse, applying BP1–BP4.

Algorithm:Forward(x,{W(l),b(l)})\textsc{Forward}(x, \{W^{(l)}, b^{(l)}\}) — evaluate and cache activations
  1. 1
    h(0)xh^{(0)} \gets x
  2. 2
    for l1l \gets 1 to LL do
  3. 3
    z(l)W(l)h(l1)+b(l)z^{(l)} \gets W^{(l)} h^{(l-1)} + b^{(l)}
    cache z(l)z^{(l)}
  4. 4
    h(l)g(z(l))h^{(l)} \gets g\parens{z^{(l)}}
    cache h(l)h^{(l)}
  5. 5
    y^h(L)\hat y \gets h^{(L)}
  6. 6
    return y^\hat y and the cache {z(l),h(l)}\{z^{(l)}, h^{(l)}\}
Algorithm:Backward(y^,y,cache)\textsc{Backward}(\hat y, y, \text{cache}) — gradients via BP1–BP4
  1. 1
    δy^(y^,y)g(z(L))\delta \gets \nabla_{\hat y}\,\ell(\hat y, y) \odot g'\parens{z^{(L)}}
    BP1
  2. 2
    for lLl \gets L down to 11 do
  3. 3
    /W(l)δh(l1)\partial \ell / \partial W^{(l)} \gets \delta \, h^{(l-1)\top}
    BP4
  4. 4
    /b(l)δ\partial \ell / \partial b^{(l)} \gets \delta
    BP3
  5. 5
    if l>1l > 1 then
  6. 6
    δ(W(l)δ)g(z(l1))\delta \gets \parens{W^{(l)\top}\delta} \odot g'\parens{z^{(l-1)}}
    BP2
  7. 7
    return {/W(l),/b(l)}\{\partial \ell / \partial W^{(l)}, \partial \ell / \partial b^{(l)}\}

In modern frameworks both passes are automatic. As Stevens describes, PyTorch's autograd records each operation into a graph as the forward pass runs; calling .backward() on the scalar loss replays that tape in reverse, evaluating one VJP per recorded op. The four equations are what autograd executes: you never hand-derive them, yet they are the rules autograd applies underneath.

A worked example

Take a concrete two-layer net: input , hidden layer of two sigmoid units, a single linear output unit, target , and squared loss . Parameters:

Forward pass. Compute , gate through , then the linear readout .

quantityformulavalue

Backward pass. The output is linear, so and BP1 gives . BP4/BP3 read off the layer-2 gradients as and . BP2 then backs the error into the hidden layer using , and BP4/BP3 again yield the layer-1 gradients.

quantityequationvalue

Every gradient is now explicit, and a single descent step would nudge all six tensors. Notice the hidden-layer gradients are an order of magnitude smaller than the output's — the factor, capped at , already shrinks the signal. Stack many sigmoid layers and those factors multiply toward zero: the vanishing gradient that motivates ReLU and careful initialization.

The backward pass is a chain of three multiplications seeded at the loss. The scalar enters the hidden layer through the transposed weight, , then the sigmoid gate scales each component, producing . The figure traces those numbers end to end.

Numeric backward pass for the worked two-layer net. The output error enters through , is gated by , and yields .

Gradient checking

A hand-derived or hand-coded backward pass is easy to get wrong by a transpose or a missing factor. Gradient checking verifies it against the definition of the derivative: perturb one parameter by a tiny , remeasure the loss, and compare the finite difference to the analytic gradient.

The centered difference is not optional. The one-sided form carries error; the centered form cancels the first-order term and drops to , gaining several digits of agreement for the same . Check on the worked net above: at , bumping to and remeasuring gives a finite difference of , matching the analytic from BP4 to four digits.

Gradient checking is a debugging tool, not a training step — one finite difference per parameter costs a full forward pass, so it runs on a small net, on a handful of parameters, once. Practical cautions carry over from Goodfellow: use double precision, avoid so small that rounding dominates ( to is the usable band), and check a few parameters rather than all of them. Turn it off before training; it is the unit test for the backward pass, not part of it.4

Reverse mode versus forward mode

Backprop is one of two ways to evaluate a chain of Jacobians. Forward mode multiplies them input-to-output, propagating a directional derivative; reverse mode multiplies output-to-input, propagating a gradient. For a composition the cost is set by which end you start from.

forward modereverse mode (backprop)
propagatesJacobian-vector product vector-Jacobian product
sweepsinput outputoutput input
one pass yieldsone column of (one input's effect)one row of (one output's gradient)
passes for full (number of inputs) (number of outputs)
cheap wheninputs few: outputs few:
memory extra (no tape) (cache activations)

A neural network has in the millions (parameters) and (the scalar loss). Reverse mode delivers the entire gradient in a single backward pass; forward mode would need one pass per parameter. That asymmetry is the whole reason backpropagation — not forward-mode differentiation — trains deep nets.5

Computational cost

Backprop is cheap in time and expensive in memory, a trade every deep-learning system must manage.

passtimememoryneeds
forwardactivations cachedinputs, parameters
backwardgradients (same shape as params)cached activations
forward + backward forwardactivations + gradientsfull cache

The forward pass writes the cache; the backward pass consumes it. The timeline below shows the schedule, with the cache as the shared store.

The two-pass schedule. The forward pass caches activations left-to-right; the backward pass reads them right-to-left, so peak memory holds the whole cache.

When memory is the binding constraint, gradient checkpointing trades it back for time: store only a sparse subset of activations on the forward pass and recompute the rest during the backward pass. Caching every -th layer cuts activation memory from to at the cost of one extra forward segment — the standard lever for training networks too deep to cache whole.6

strategyactivation memoryextra compute
cache all (default)none
-checkpointing extra forward
recompute everything extra forwards

Chain rule as a product of Jacobians

Zoom out and the whole network is one long composition, and its gradient is the product of per-layer Jacobians, read right to left, which is reverse mode.

Associativity lets us choose the multiplication order, and that choice is the difference between forward and reverse mode. Multiplying left-to-right keeps a running row vector (a gradient) — cheap, because every partial product stays a vector. Multiplying right-to-left would build full matrices. Reverse mode is simply the left-to-right grouping of this product.

The gradient is a product of per-layer Jacobians. Reverse mode carries a row vector through each as a vector-Jacobian product, never forming a full matrix.

Autodiff as a compiler

Backprop predates deep learning, and modern implementations treat it as a compiler problem rather than a hand derivation. Three developments extend Goodfellow's chapter.

The algorithm predates the 1986 paper. Reverse-mode automatic differentiation was described by Linnainmaa in 1970 as the reverse accumulation of rounding errors, and Werbos applied it to networks in his 1974 thesis. What made it famous was Rumelhart, Hinton & Williams's 1986 Nature paper, which showed backprop learning useful internal representations in a working network — the demonstration, not the derivative, was the contribution. The four equations here are the same reverse sweep those authors ran by hand.

Autodiff is now define-by-run. Modern frameworks split into two styles. The tape-based / define-by-run style (PyTorch autograd) records each operation as the forward pass executes and replays the tape backward, which the worked example above mirrors. The traced / transform style (JAX) treats differentiation as a program transformation: grad rewrites a function into the function that computes its gradient, and composable transforms like vmap (auto-batching) and jit (fusion) stack on top. Both compute exactly the VJPs of this lesson; they differ only in when the graph is built. In practice no one writes BP1–BP4 by hand anymore, but the framework runs precisely these equations, which is what makes gradient bugs readable.

Memory, not arithmetic, is the modern bottleneck. The activation cache is the binding constraint at scale, and the checkpointing lever of this lesson (Chen et al., 2016, Training Deep Nets with Sublinear Memory Cost) generalized into a small industry: reversible layers (Gomez et al., 2017) reconstruct each layer's input from its output so no activations need storing at all, and fused kernels like FlashAttention (Dao et al., 2022) recompute the attention matrix in the backward pass rather than caching it. Each is the same trade this lesson names — spend a little recompute to save a lot of memory — pushed to where a network otherwise would not fit on the hardware.7

Takeaways

  • A computational graph has operations for nodes and tensors for edges; the forward pass evaluates it in topological order, the backward pass differentiates it in reverse.
  • The chain rule for vector maps multiplies Jacobians; backprop never forms them, propagating vector-Jacobian products instead — for a matmul the VJP is just .
  • The four equations are the entire MLP algorithm: (BP1), the recursion (BP2), and the parameter gradients (BP3), (BP4).
  • Track shapes: every gradient carries the shape of what it differentiates, so is and is . Batched over examples, the outer product becomes a matrix product that sums per-example gradients over the batch axis.
  • Reverse mode wins for deep nets because the loss is scalar (): one backward pass yields the full gradient, versus one forward pass per parameter.
  • Gradient checking validates a backward pass against centered finite differences ( error, relative agreement below ); it is the unit test for the derivatives, run once on a small net, never during training.
  • Backprop costs the forward pass in time and in memory for the activation cache; checkpointing buys memory back by recomputing activations, the lever that fits very deep nets on a fixed budget.
  • The next step is to use this gradient well: the optimizer decides how far and in what direction to step, given the backprop hands it.

Footnotes

  1. Goodfellow, Deep Learning, §6.5 — Back-Propagation: reverse-mode differentiation over the computational graph, distinguished from the learning rule (gradient descent) it feeds.
  2. Goodfellow, Deep Learning, §6.5.1–6.5.2 — the computational graph and the chain rule of calculus: each node propagates a vector-Jacobian product, never materializing the full Jacobian.
  3. Goodfellow, Deep Learning, §6.5.4 — Back-Propagation in fully connected MLPs: the recurrence for the layer error and the outer-product weight gradient .
  4. Goodfellow, Deep Learning, §6.5.10 — verifying an implementation with centered finite differences; the centered estimate and the double-precision relative-error threshold for a correct backward pass.
  5. Goodfellow, Deep Learning, §6.5.6–6.5.9 — general reverse-mode autodiff: cheap when outputs are few ( scalar loss, parameters in the millions), the exact regime of neural-network training.
  6. Stevens, Deep Learning with PyTorch, Ch. 5 — autograd records the forward graph and replays it backward via .backward(); the cached-activation cost is what gradient checkpointing trades against recompute.
  7. Primary sources: Linnainmaa (1970) for reverse-mode accumulation and Rumelhart, Hinton & Williams, Learning Representations by Back-Propagating Errors (Nature, 1986) for the network demonstration; Bradbury et al., JAX: composable transformations of Python+NumPy programs (2018) for the transform-style autodiff; Chen et al., Training Deep Nets with Sublinear Memory Cost (2016) for checkpointing, Gomez et al., The Reversible Residual Network (2017), and Dao et al., FlashAttention (2022) for recompute-over-cache memory strategies.

╌╌ END ╌╌