§6.5.6 General back-propagation — one bprop rule per operation
The MLP algorithms were hand-specialized. Real libraries implement
back-propagation generically: every operation carries its own local
bprop method that knows how to convert a gradient on its output into
a gradient on each of its inputs — a Jacobian-vector product. The
back-propagation algorithm itself then knows no differentiation rules at
all; it only calls each operation’s bprop with the right arguments and
sums the results where paths converge.
Term by term
| the gradient already computed on this operation’s OUTPUT | downstream grad | |
| the operation’s local Jacobian w.r.t. the input X we want — this is the only math the OPERATION must know | local Jacobian | |
| returns the gradient on X. Example — matrix multiply C = AB: bprop for A returns GBᵀ, for B returns AᵀG. The algorithm just wires these together | the local rule |
Aside — bprop must pretend inputs are distinct
A subtle rule: op.bprop should treat all its inputs as distinct even
when they’re the same variable. If mul is handed two copies of
to compute , its bprop returns as the derivative w.r.t.
both inputs; the outer algorithm then sums them to the correct
total . This keeps each operation’s rule purely local.
The full driver (algorithm 6.5) prunes the graph to the relevant nodes and
calls the recursive build_grad (algorithm 6.6) that fills each node’s
gradient by summing over its consumers:
- 1prune to : nodes that are both ancestors of z and descendants of the targets ⓘ
- 2grad_table[z] ; for V in : build_grad(V, …)ⓘ
- 3build_grad(V): if V in grad_table, return it (memoize)ⓘ
- 4for C in consumers(V): op(C).bprop(inputs(C), V, build_grad(C))ⓘ
- 5; grad_table[V] ; return Gⓘ
Cost
Term by term
| worst case: back-prop adds one Jacobian-vector product (O(1) nodes) per edge, and a DAG has ≤ O(n²) edges | general bound | |
| most neural nets are roughly chain-structured → O(n) — vastly better than the exponentially-many paths a naive expansion would traverse | in practice | |
| backprop’s work is the SAME ORDER as the forward pass — the reason training is affordable at all | the punchline |
§6.5.7 Example: back-propagation for MLP training
Put it together on a one-hidden-layer classifier: , unnormalized log-probs , cross-entropy cost , plus weight decay . The cost’s computational graph (fig 6.11) has two backward paths into each weight matrix:
Tracing the two paths
The weight-decay path is trivial: it always adds to weight ‘s gradient. The cross-entropy path is the real work: let be the gradient on the log-probs ; the shorter branch adds to ; the longer branch computes , has the ReLU zero out gradient where its input was negative, then adds to . Cost is dominated by the matrix multiplies — forward and backward alike; memory is to hold the hidden-layer pre-nonlinearity input.
§6.5.8 Complications
The textbook version is simpler than production code, which must also handle: operations with multiple outputs (e.g. returning both a max and its argmax in one pass); memory management (accumulate sums into a single buffer rather than materializing every tensor first); many data types (32-/64-bit float, integers); and genuinely undefined gradients (track and report them). None is insurmountable — this chapter gives the intellectual tools — but the subtleties are real.
Next: how the rest of the world computes derivatives (forward vs reverse mode), the Hessian, and the history — differentiation outside deep learning, and historical notes.
Check yourself — general backprop and MLP training
1.In general back-propagation, where does the differentiation knowledge live?
2.For matrix multiply C = AB with output gradient G, what does bprop return for A and for B?
3.Why must op.bprop pretend its inputs are distinct even when they're the same variable?
4.What is back-propagation's cost, and why does that make training feasible?
5.In the fig 6.11 MLP-training graph, why does each weight matrix have two backward paths?