x = torch.arange(4.0)
xDive into Deep Learning · §1.5
From the chain rule to backpropagation
automatic differentiation for a complete network.
Motivation
Deriving a million-parameter network’s gradient by hand is impractical. Instead, the framework records each operation during the forward pass, then replays it in reverse, systematically applying the chain rule from the calculus section to obtain the gradient with respect to every input at once.
Training steps in this book generally include a forward pass and a backward pass over this graph.
01
The mechanics
record forward · sweep backward
Mechanics
Mechanics
First tell the framework to track x (reserve a slot for its gradient), then run the forward pass; y is now the root of a recorded graph:
tensor(28., grad_fn=<MulBackward0>)
Mechanics
One call sweeps the graph in reverse, and the result equals the promised 4\mathbf{x}, at every coordinate:
tensor([ 0., 4., 8., 12.])
tensor([True, True, True, True])
That reverse sweep is the calculus section’s chain rule, run from output to input.
02
Working with gradients
accumulation · non-scalar outputs · detaching · inference
Gradients
PyTorch adds each new gradient into x.grad rather than replacing it (handy for summing losses). So zero it before a fresh computation:
tensor([1., 1., 1., 1.])
Forgetting .zero_() between iterations causes unintended gradient accumulation.
Gradients
Gradients are defined for a scalar loss. For a vector y, the engine differentiates the sum of its components (a vector–Jacobian product), exactly what a per-example batch loss needs:
tensor([0., 2., 4., 6.])
Gradients
Gradients
When we only need the value (prediction, evaluation, manual updates), we turn recording off and avoid its bookkeeping cost. This is the default mode for inference throughout the book:
False
03
Dynamic graphs
the graph is whatever actually ran
Dynamic graphs
In eager execution, autograd records the operations executed by if and while statements. This function’s loop count and branch both depend on its input (compiled transformations may impose additional constraints):
Dynamic graphs
Each call realizes a concrete graph that backward can walk. Whichever branch ran, f scaled its input by some constant, so f(a) = k\,a and the gradient must equal f(a)/a. It does:
tensor(True)
Beyond
A counting argument settles which way to sweep. With n inputs and m outputs, the full derivative matrix costs m reverse sweeps or n forward sweeps, each sweep priced at roughly one function evaluation.
A training loss has m = 1 and n in the millions: one reverse sweep delivers every parameter’s gradient, for the cost of about one extra forward pass. Forward mode wins the opposite regime (few inputs, many outputs) and Hessian–vector products.
Beyond
The gradient is itself a function on the graph, so we can differentiate it. For f(x) = x^3 at x = 2: f'(2) = 3x^2 = 12 and f''(2) = 6x = 12: the values coincide at this point, and autograd computes both:
(tensor(12., grad_fn=<MulBackward0>), tensor(12.))
Wrap-up
detach / no-grad keep values out of the graph.Backpropagation through real networks gets its full treatment in the backpropagation section; forward vs. reverse mode is derived in the matrix-calculus-and-automatic-differentiation section.