Backpropagation

Backpropagation is reverse-mode automatic differentiation applied to a network’s computational graph. A forward pass records intermediate values; the backward pass reuses them to push an error signal from the loss function through parameters, nonlinear activation functions, and earlier layers. It is the gradient-computing stage of the training loop introduced in neural network fundamentals: backpropagation supplies the gradients, and optimizers decide how parameters move.

How it works

Backpropagation computes every parameter’s gradient in two passes over the network’s computation graph:

  1. Forward pass. Run the input through the layers to produce the prediction and the loss, caching each layer’s intermediate activations along the way.
  2. Backward pass. Starting from the loss, send an error signal backward through the graph. At each layer, combine the incoming error with the cached activations to produce both the gradient for that layer’s parameters and the error signal to pass further back.

The efficiency comes from reuse: each activation is computed once in the forward pass and reused in the backward pass, so a network with millions of parameters still needs only one forward and one backward sweep — not one pass per parameter. The forward pass (solid) caches activations; the backward pass (dotted) reuses them to send the error signal back into each parameter:

flowchart TD
  X[Input x] --> H[Hidden activation h]
  H --> Yhat[Output y hat]
  Yhat --> L[Loss L]
  L -.-> dY[Error signal at the output]
  dY -.-> dW2[Gradient for W2 reuses h]
  dY -.-> dH[Propagate through W2 and the activation derivative]
  dH -.-> dW1[Gradient for W1 reuses x]

The chain rule, organized

Take the smallest network that still shows every step: a small multilayer perceptron with one hidden layer of width . Fix the remaining sizes so every object below has a definite shape — an input row , weights and , nonlinearity , a scalar output , and target . The forward pass is

where is the hidden activation and the prediction. Backpropagation now works from the loss backward, one layer at a time.

One rule keeps the shapes straight: because the loss is a single scalar, the gradient of with respect to any quantity has exactly the same shape as that quantity. A gradient is never a new kind of object — is a matrix like , is a row vector like , and is a scalar like . The forward objects and their shapes are:

SymbolMeaningShapeObject
inputrow vector
first-layer weightsmatrix
hidden pre-activationrow vector
hidden activationrow vector
second-layer weightscolumn vector
predictionscalar
lossscalar

Final layer. The loss reaches only through , so differentiate the loss with respect to the prediction, then push that error through the linear layer:

The weight gradient lands in , matching : the layer input is exactly what scales the incoming scalar error.

Hidden layer. Reaching takes one more step back — through , then through the nonlinearity:

Multiplying by carries the output error back across the linear layer; the elementwise product with applies the activation ( is elementwise, so it preserves the shape). The weight gradient again takes the “input transposed, times arriving error” form:

which matches . Substituting the two intermediate gradients collapses the chain into one closed-form expression:

This is the chain rule from gradients, organized so each intermediate gradient — , then , then — is computed once and reused, instead of re-expanding every path from back to .

Beyond one hidden layer: deep networks and CNNs

The same two moves — pull the error back through the next layer’s weights, then through the local activation derivative — repeat at every depth. Write layer with pre-activation and activation , starting from . Backpropagation carries the pre-activation gradient backward through the stack, starting from at the output layer:

The first equation is the hidden-layer step from above, applied recursively; the second is the same “layer input transposed, times arriving error” rule. The shapes carry over too: has the shape of the pre-activation , and has the shape of . Because each is built from , one backward sweep computes every gradient no matter how deep the network is.

Convolutional networks obey the identical recursion, with two changes that follow from replacing the dense weight matrix with a shared, sliding kernel:

  • Convolution replaces the matrix product. Pulling the error back across a convolutional layer (the multiply-by- step) becomes a convolution of the incoming error with the spatially flipped kernel — a transposed convolution.
  • Weight sharing sums the gradient. Because one kernel is reused at every spatial location, its gradient is the sum of the contributions from all those locations, not a single term. Pooling layers carry no weights: max-pooling routes the error only to the position that was the maximum, and average-pooling spreads it evenly over the window.

Worked example: MLP gradients in PyTorch

This snippet lets PyTorch differentiate the same one-hidden-layer network (here ) and compares selected gradients with a manual backpropagation calculation.

import torch
 
torch.manual_seed(0)
x = torch.tensor([[0.2, -0.4]])
y = torch.tensor([[1.0]])
W1 = torch.randn(2, 3, requires_grad=True)
W2 = torch.randn(3, 1, requires_grad=True)
h = torch.tanh(x @ W1)
pred = h @ W2
loss = ((pred - y) ** 2).mean()
loss.backward()
d_pred = 2 * (pred.detach() - y)
dW2_manual = h.detach().T @ d_pred
dh = d_pred @ W2.detach().T
dW1_manual = x.T @ (dh * (1 - h.detach() ** 2))
print("loss", round(loss.item(), 6))
print("W2_grad", torch.round(W2.grad.flatten(), decimals=6).tolist())
print("manual_W2_grad", torch.round(dW2_manual.flatten(), decimals=6).tolist())
print("max_abs_W1_diff", (W1.grad - dW1_manual).abs().max().item())

Observed output:

loss 0.570892
W2_grad [-0.12187600135803223, -0.5416929721832275, -0.18595199286937714]
manual_W2_grad [-0.12187600135803223, -0.5416929721832275, -0.18595199286937714]
max_abs_W1_diff 0.0

Autograd and the manual chain-rule calculation agree exactly for this tiny graph. The important detail is that the hidden activation h is reused in both the final-layer gradient and the earlier-layer gradient.

Worked example: a convolution by hand

The two CNN rules are easiest to trust on numbers. Take a input and a single kernel , with a valid (stride-1, no-padding) convolution — the cross-correlation used by deep-learning frameworks — which produces a output :

Each output pixel is , giving

Suppose a squared-error loss against targets produces the upstream gradient , a matrix (the shape of ):

Kernel gradient — weight sharing sums over positions. The same four kernel weights are applied at all four output positions, so each weight’s gradient adds up a contribution from every position:

For the top-left weight, summing over the four output positions,

and repeating for the other three weights gives a matrix, the shape of :

This is itself a cross-correlation of the input with the upstream gradient .

Input gradient — a transposed convolution. Each input pixel fed every output position it touched, so its error is gathered back through the flipped kernel:

treating as zero outside its range. For the center pixel, only two terms survive,

and over the whole grid the input gradient is a matrix, the shape of :

A framework’s autograd computes exactly these two arrays. The lesson is that convolution obeys the same backward rule as the dense layers: pull the error back through the layer (a transposed convolution in place of ) and form the weight gradient from the layer input (here summed over every position the shared kernel visited). Pooling layers, which have no weights, only route : max-pooling sends it to the position that was the maximum, average-pooling spreads it evenly.

Caveats

Backpropagation through many repeated transformations can make gradients vanish or explode, which is why initialization, normalization, gating, and residual connections matter. In-place tensor edits can overwrite values needed for the backward pass. The backward pass also stores activations, so memory often scales with depth and batch size rather than parameter count alone.

References