Forward Propagation, Backward Propagation, and Computational Graphs
Dive into Deep Learning · §4.3
What backward() actually does the chain rule on a graph · every gradient by hand · autograd confirms each one.
Under the hood of a gradient
Motivation
Training so far: a forward pass computes the loss, then one call to backward() hands us every gradient. Today we open that black box.
Forward: push the input through the net, storing each intermediate value.
Backward: walk the same graph in reverse, accumulating gradients by the chain rule.
One rule does it all, and it explains why training costs the memory it does.
By the end we will have computed all four gradients of a real network by hand, and a six-line autograd script will print the same numbers, digit for digit.
A one-hidden-layer MLP: input, an affine map, a nonlinearity, a second affine map.
01
Forward Propagation
computing and storing each intermediate value
The forward pass, one layer at a time
Forward Propagation
For input \mathbf{x}\in\mathbb{R}^d, a one-hidden-layer net (no bias, for clarity) computes a chain of intermediate variables:
\textrm{prod} multiplies after any needed transpose or reshape: matrix multiplication for vectors, its tensor counterpart otherwise. It hides the bookkeeping so we can think one node at a time.
Backward through our network
Backpropagation
Seed \partial J/\partial L = \partial J/\partial s = 1 at the top, then walk back. Each step multiplies the incoming gradient by one local derivative:
The “+” in \partial J/\partial \mathbf{W}^{(2)} encodes a general rule: \mathbf{W}^{(2)} reaches the objective along two paths, and the gradients arriving along each path sum:
J \leftarrow L \leftarrow \mathbf{o} \leftarrow \mathbf{W}^{(2)}
\;\;(\text{the prediction path}), \qquad
J \leftarrow s \leftarrow \mathbf{W}^{(2)}
\;\;(\text{the regularizer path}).
Forgetting to accumulate at such forks (writing = where += belongs) is among the most common bugs in hand-written backward passes. Every autograd engine accumulates for exactly this reason.
One move, everywhere
Backpropagation · the trick
Strip away the symbols and every backward step is the same gesture: at each node, multiply the gradient arriving from downstream by the node’s local derivative.
Add node d = a + b \partial d/\partial a = \partial d/\partial b = 1 → passes the gradient through unchanged.
Multiply node e = d\,c \partial e/\partial d = c,\ \partial e/\partial c = d → scales the gradient by the other input.
Every backward equation in this section is an instance of this one rule.
03
A Worked Example
real numbers through the graph
A tiny graph, by hand
Worked Example
Take e = (a+b)\,c at a=2,\ b=1,\ c=-3. Forward:d = a+b = 3, then e = d\,c = -9.
Backward: seed \partial e/\partial e = 1. The multiply node gives \partial e/\partial d = c = -3; the add node passes that through to both inputs:
Pushing forward: \mathbf{z}=[-1,\,2]^\top, so \mathbf{h}=\phi(\mathbf{z})=[0,\,2]^\top (the first unit is dead), then o=-2 and L=2.
The backward sweep
Worked Example
Seed 1 at L, walk left. Add nodes pass the gradient, multiply nodes scale it; the dead ReLU unit (z_1=-1) zeros everything flowing into the top row of \mathbf{W}^{(1)}, so no signal means no learning.
Forward (black) carries values from \mathbf{x} to the loss L; backpropagation (blue) walks the same graph in reverse, multiplying each node’s local derivative to accumulate \partial L/\partial\mathbf{W}^{(2)} and \partial L/\partial\mathbf{W}^{(1)}. The dead first unit zeros the top row of \partial L/\partial\mathbf{W}^{(1)}.
The dying ReLU, made concrete
Worked Example
At the hidden layer, \phi'(z) = \mathbf{1}[z>0] acts as a gate:
Every gradient matches, down to the zeroed row for the dead unit. (The -0 is floating point’s signed zero, h_1 = 0 times a negative upstream gradient; it compares equal to 0.)
04
Why It Matters
autograd, and the cost of remembering
From the chain rule to autograd
Why It Matters
What we did by hand is exactly what backward() does: record the graph on the forward pass, seed a 1 at the scalar loss, and sweep in reverse, multiplying each local derivative to accumulate every parameter gradient in a single pass.
This output-to-input sweep is reverse-mode automatic differentiation, cheap precisely when there are many parameters and one scalar loss: the deep-learning regime.
We developed the mechanics, including when forward mode is preferable, in the automatic-differentiation section; the full theory (both modes as Jacobian products, and the memory trade-offs they imply) lives in the calculus appendix.
Forward and backward depend on each other
Why It Matters
The two passes are interlocked:
Forward uses the current parameters (set by the last backward step).
Backward reuses the stored intermediates\mathbf{h}, \mathbf{z}, \ldots from the forward pass.
So training alternates them, and must retain every intermediate until the backward pass consumes it.
That stored state is why training needs far more memory than prediction: it grows with depth and batch size, and is the usual cause of out-of-memory errors.
Recap
Wrap-up
Forward propagation computes and stores intermediates, input to output.
Backpropagation sweeps the same graph in reverse, accumulating gradients by the chain rule.
The whole algorithm is one move: add passes the gradient, multiply scales it.
Gradients add at forks: a variable used twice collects both paths’ gradients (+=, never =).
A dead ReLU zeros a gradient row: no signal, no learning.
Autograd reproduced the hand derivation digit for digit.
Retaining intermediates is why training is memory-hungry.
Capstone (exercise 6): build a miniature autograd engine (a scalar Value class with +, *, relu and a topological backward()) and re-derive today’s example with it.