Backpropagation.
How one mistake changes a network.
All five chapters, static lab comparisons and worked answers. This edition loads no scripts.
1
One mistake, five parameters
A forward pass turns inputs into a loss. Keep its intermediate values: the backward pass will need them.
Who should change?
A small network predicts 1.5 when the target is 2. The prediction is too low. That tells us the direction of the output error, but it does not yet tell us how to change an earlier weight. That weight’s effect passes through other operations—and may reach the output by more than one route.
Backpropagation computes how sensitive one scalar loss is to each parameter. An optimizer uses those sensitivities to propose new parameters. We will do both operations by hand before letting the lab repeat them.
You need arithmetic and the idea that a model has adjustable numbers. We introduce derivatives and vector shapes when they become useful. If you already know losses, treat the half-squared error below as our fixed measuring rule.
A network you can hold in your head
Imagine a normalized electricity-demand feature and a target . Our numbers are invented and dimensionless. There is no timestamp, real grid data, fitted forecast or physical unit behind them. The demand story motivates inspecting a learning mechanism.
The five adjustable parameters are . Every variable here is a scalar: one number. First form an affine value (multiply, then add); apply a rectified linear unit, or ReLU, to get the hidden activation :
ReLU passes a positive input unchanged and outputs zero for a negative input. The same is used in two branches, and , which rejoin at prediction :
This deliberately redundant readout could be collapsed to . Keeping its branches separate makes one shared value’s two uses visible. These are not two different hidden neurons.
Forward arrows show dependency. Both branches use the same h. Invented values; BP-C09–10.
Start with and . Forward values are . Arrows record dependencies. PyTorch’s autograd mechanics describes how recorded operations form the graph used for differentiation. BP-C01, BP-C09–10
End at one number
Define residual and loss :
Here , so . The factor one-half is a convenience: differentiating cancels the square’s factor two. This is a loss for one example, not a batch mean.
Record intermediate values rather than only the final answer. To differentiate , we will need the values of both and from this forward pass. Backpropagation travels backward through dependencies; it does not run the prediction formula in reverse to recover the input.
2
Local slopes, global consequences
At each operation, multiply the sensitivity arriving from the loss by a derivative of that operation.
What a derivative says
A derivative is a local sensitivity. Writing means that a tiny increase in , with other parameters fixed, changes loss at about −1.5 times that increase. It is neither a weight value nor a command to add −1.5.
For , the first-order estimate is . This approximation concerns sufficiently small changes in a differentiable region; it is not an exact finite-step promise. BP-C15
We use compact notation: . Read “q-bar” as the sensitivity of the loss to q. It is an intermediate derivative, often called an adjoint; it is not the forward value .
Multiply along a route
Suppose , and the loss depends on . The chain rule connects the small operation’s derivative to the loss:
Seed the calculation with : the sensitivity of to itself is one. For , the local derivative is . Thus . Since , its derivative with respect to is one, so .
| Operation | Local slope | Loss sensitivity |
|---|---|---|
| 1 for each input |
At the addition , each local derivative is one. Each branch receives the same incoming sensitivity, −0.5. The forward sum does not divide it between its inputs. At multiplication , the local derivative with respect to is ; with respect to it is .
The official autograd explanation describes reverse-mode differentiation as applying the chain rule over recorded operations. BP-C01–02
Keep the two derivatives separate
The local derivative says how changes with . The loss sensitivity contributed through that branch is . Forgetting the incoming factor confuses an operation’s behavior with its effect on the objective.
There is another issue: also changes the second branch. We have found one contribution, so we are not ready to propagate backward through ReLU yet.
3
When paths meet, add
Multiply along a path. Add across paths. Reusing a value creates another contribution to its sensitivity.
One value, two uses
Through , the contribution to the hidden value is . Changing affects both branches at once:
These arrows follow the backward calculation. The branch contributions add before propagating through the activation. BP-C02, BP-C11.
This is the multivariable chain rule. If a value feeds several direct consumers , accumulate every contribution before propagating farther upstream:
Conceptually, initialize a node’s sensitivity to zero and add contributions. Overwriting −0.5 with −0.25 discards a real dependence. Add first, then continue in reverse dependency order. BP-C02
Through the activation
Our is in ReLU’s positive region, whose local slope is one. Hence . For , the local derivatives are with respect to and one with respect to :
| Parameter | Calculation | Gradient |
|---|---|---|
| w | -1.5 | |
| b | -0.75 | |
| v | -0.5 | |
| u | -0.5 | |
| c | -0.5 |
The gradient collects all five partial derivatives in the declared order . A negative gradient for predicts that a small increase in lowers loss. Gradient descent subtracts that negative gradient. BP-C11, BP-C15
A zero needs a diagnosis
For , ReLU stays zero under sufficiently small perturbations. Its slope is zero, blocking the signal to and for this example. With , gradients of and are also zero. Output bias can still have a nonzero gradient. A blocked hidden route does not imply every parameter stopped learning.
At exactly , ReLU has no ordinary derivative. Our code uses a zero backward rule there, consistent with PyTorch’s minimum-norm subgradient convention for this convex operation. This is an implementation choice, not an ordinary derivative. Autograd’s nondifferentiable-function rules give the convention. BP-C04, BP-C14
Another cause of zero is cancellation. If while , contributions to cancel. The activation is positive. Inspect the routes before diagnosing an inactive activation.
Three kinds of accumulation
The path sum belongs to one graph. If a batch loss is a sum of example losses, its gradient is their sum; if it is a mean over a fixed batch of examples, the gradient includes the same factor. Reduction changes the objective you differentiate.
There is also framework storage behavior: PyTorch accumulates gradients into parameter .grad buffers across backward calls. A usual fresh training step clears them with optimizer.zero_grad() before computing new gradients. That housekeeping does not replace the chain rule’s path sum. The optimization tutorial shows the ordering. BP-C06
4
A gradient is not a step
The backward pass reports sensitivities. A learning rate turns them into a finite proposal—and the next forward pass judges it.
Make the proposal
Write parameters as and their gradient as . Plain gradient descent chooses a nonnegative learning rate :
We use the same single example each time: deterministic gradient descent. This is the no-momentum, no-weight-decay case of PyTorch’s SGD rule. All five deltas use the same old forward pass and gradient, then all parameters change together. Updating one before computing the remaining gradients would be a different procedure. BP-C05
At , the first weight delta is , so . The complete new vector is . A fresh forward pass gives prediction 1.865625 and loss about 0.00902832.
Try the next step
Before revealing a proposal, predict: with the initial example, will rate 0.5 lower the loss? The gradient is still correct; the question is how far its local information should be trusted.
Inspect the update
Synthetic scalar network · one fixed example · simultaneous gradient descent · full precision, rounded display
ReLU is active initially. Both branch contributions reach the early layer.
Changing case or rate restarts at update 0. Predict whether the next loss decreases, stays equal or increases.
Update 0. Current loss 0.125. Predict, then reveal the proposed update.
After 0 updates
Prediction 1.5
Loss 0.125
Hidden h 1
Current paths into h: -0.5 + (-0.25) = -0.75. z is positive: ReLU slope 1.
| Parameter | Value | Gradient |
|---|---|---|
| w | 0.5 | -1.5 |
| b | 0 | -0.75 |
| v | 1 | -0.5 |
| u | 0.5 | -0.5 |
| c | 0 | -0.5 |
Proposed next step
Next prediction 1.865625; next loss 0.009028 (lower).
| Parameter | Δ = −ηg | Next value |
|---|---|---|
| w | 0.075 | 0.575 |
| b | 0.0375 | 0.0375 |
| v | 0.025 | 1.025 |
| u | 0.025 | 0.525 |
| c | 0.025 | 0.025 |
| Update | Loss |
|---|---|
| 0 | 0.125 |
| Rate η | Next w | Prediction | Loss |
|---|---|---|---|
| 0 | 0.5 | 1.5 | 0.125 |
| 0.05 | 0.575 | 1.865625 | 0.009028 |
| 0.1 | 0.65 | 2.25 | 0.03125 |
| 0.5 | 1.25 | 6 | 8 |
| 1 | 2 | 12.375 | 53.820313 |
Initial loss 0.125. The gradient is the same for every row. Larger η changes the proposal; it does not improve the derivative. BP-C12 · deterministic toy calculation.
Closed/corner cases initially give zero gradients for w, b, v and u; c has gradient −2. Cancelling branches give gradients (0, 0, −2, −2, −2) while h stays positive.
At the initial parameters, changing only the rate never changes the gradient. Rate 0.05 lowers this loss; rate 0.5 produces prediction 6 and loss 8. A finite step can cross the target and make the error larger. These are toy consequences, not recommended rates for another network. BP-C12
After an update, compute the next gradient at the new parameters. Reusing the initial gradient for every step would follow a fixed direction. Large rates in this tiny system may later close ReLU and let the output bias alone fit the example; the first loss increase does not prove permanent divergence. In a closed or boundary case, that bias can move while the hidden route stays blocked.
Where autograd ends
This schematic PyTorch sequence explains responsibilities. The complete runnable companion for this guide is the dependency-free JavaScript lab linked in the notes.
optimizer.zero_grad()
prediction = model(x)
loss = 0.5 * (prediction - target).square()
loss.backward()
optimizer.step()backward() computes and accumulates derivatives for a scalar loss. step() applies the optimizer’s rule. Calculating gradients does not itself update model parameters. See Optimizing model parameters. Our browser lab implements the equations directly so you can inspect each term. BP-C03
These updates fit one invented pair. A falling loss here says nothing about a real demand forecast, held-out examples, data leakage or research validity. Those require a different experiment and evidence.
5
Trust the trace. Check the derivative.
Hand arithmetic, small perturbations and shape checks catch different failures.
Perturb one parameter
A numerical check provides another route to a derivative. Perturb only parameter by a small amount in both directions and recompute the complete forward loss:
Here is the coordinate vector that changes only the selected parameter, unrelated to our residual . At the default smooth point, gives agreement with all five analytic gradients within the lab’s absolute tolerance .
That agreement is separate from the hand-computed values. Both matter: a checker can miss a shared bug if its “numerical” loss reuses the analytic derivative. Our finite-difference path calls the forward loss alone. Gradcheck mechanics explains the comparison. BP-C07
import { DEFAULT_PARAMS, DEFAULT_EXAMPLE,
backward, finiteDifference } from './model.mjs';
console.log(backward(DEFAULT_PARAMS,
DEFAULT_EXAMPLE).gradients);
console.log(finiteDifference(DEFAULT_PARAMS,
DEFAULT_EXAMPLE, { epsilon: 1e-6 }));The expected analytic gradient is . Run the full companion for recorded outputs and assertions. Small final-digit differences in the numerical estimate are floating-point arithmetic.
The corner is different
Set with other defaults unchanged. Now . Perturbing in opposite directions samples different sides of ReLU’s corner. The centered estimate approaches −3; our chosen backward gradient at the corner is zero. This disagreement is expected: there is no ordinary derivative for both methods to recover.
Even away from a corner, a large perturbation may cross it. An excessively small perturbation can lose precision through subtraction. PyTorch’s gradcheck reference warns about nondifferentiable points and notes its default settings target double precision. Our tests exclude corners from derivative-agreement assertions and check the convention separately. BP-C04, BP-C08
When numbers become vectors
For column input , weights and bias , an affine layer gives . Let be an -coordinate column of sensitivities.
| Quantity | Derivative | Shape |
|---|---|---|
| Weights W | ||
| Bias b | ||
| Input x |
The transpose collects contributions from every output coordinate into each input coordinate. The outer product gives one derivative for every weight entry. A gradient has the same shape as its parameter. These equations use one column-vector example; batch dimensions and reductions need explicit treatment. BP-C13
Diagnose a new case
Try this without the lab. Keep the architecture but change values: . A colleague reports “the early-layer gradient is zero, so ReLU must be inactive.” Is that justified? Find and all five parameter gradients. Will every parameter stay fixed under a positive-rate step?
Open the worked diagnosis
The forward value is , so ReLU is active. Branches are 4 and −4. Prediction is 1, residual −2 and loss 2. Contributions are and . They cancel: .
The early-layer zero comes from cancellation, not an inactive activation or a perfect prediction. Readout weights and output bias can still change. Omitting one route would produce an erroneous nonzero upstream gradient. BP-C16 · derived transfer case
When a gradient surprises you, inspect saved forward values, local derivatives, all downstream routes and the loss reduction. Then inspect the optimizer separately. These questions make “the model learned” a sequence of checkable operations.
Notation
- Forward value
- The result of an operation at the current parameters: for example .
- Local derivative
- Sensitivity of one operation’s output to one input, with other inputs fixed.
- Adjoint
- Loss sensitivity , accumulated over all relevant downstream routes.
- Gradient
- Partial derivatives collected in the same order and shapes as parameters.
- Learning rate
- Multiplier in this guide’s plain update .
- ReLU at zero
- A nondifferentiable boundary. The lab chooses a zero backward rule there.
Sources
- PyTorch 2.14 · Autograd mechanics
Reverse-mode graph differentiation and nondifferentiable-operation conventions. BP-C01, BP-C04.
- PyTorch · Optimizing model parameters
Separate zero_grad, backward and step operations. BP-C03, BP-C06.
- PyTorch 2.14 · SGD
The update rule; our case omits momentum and weight decay. BP-C05.
- PyTorch 2.14 · Gradcheck mechanics
Centered numerical differences versus analytic derivatives. BP-C07.
- PyTorch 2.14 · gradcheck API
Double precision and nondifferentiable-point caveats. BP-C08.
Reproduce the arithmetic
The canonical model (local copy) provides forward, backward, step, trajectory and finiteDifference functions. It uses JavaScript binary64 arithmetic, no randomness and no network. The companion code-lab/run.mjs generates all output cases using this same model.
# From the project folder
node code-lab/run.mjs
node --test site/model.test.mjs
# Optional, when PyTorch is already installed:
python -B code-lab/verify_pytorch.pyThe build bundles KaTeX 0.18.5 locally and emits accessible MathML. Numerical checks (local copy) cover independent hand values, finite differences, missing-path bugs, boundary conventions and repeated steps. Browser checks and limitations are recorded in the project’s audit report.
Current primary documentation was inspected on 4 September 2026 (version 2.14 where pinned). Local optional framework verification used PyTorch 2.12.0+cpu with float64; documentation version and executed version are separate. No library download or real training dataset was needed.