Chapter 14 of 25
Backpropagation
How a network learns from its own mistakes
Motivation
Suppose a group project gets a failing grade. The professor doesn't just shrug — they need to figure out exactly which teammate's section pulled the score down, and by how much, so each person knows precisely what to fix next time. Do it fairly and efficiently, and the whole group improves fast. Do it by guessing, and you're stuck.
A multi-layer perceptron faces the exact same problem after every single prediction. The network gets a loss — a single number saying "you were this wrong" — and it needs to know how much every individual weight, in every layer contributed to that wrongness. With potentially millions of weights spread across many layers, this is called the credit assignment problem, and backpropagation is the algorithm that solves it, efficiently and exactly, using nothing more exotic than the chain rule from calculus.
A Real-Life Scenario
Think of a factory assembly line producing a final product, where the final inspector rejects it for being 2cm too long. That error didn't originate at the final station — it could have started at station 1, been slightly amplified at station 2, and slightly corrected at station 3. To fix the process, you need to trace the error back through every station and figure out each one's share of the blame.
Backpropagation does exactly this inside a neural network. It starts with the error at the output (the loss), and walks backward through every layer, computing exactly how much each layer — and each weight within that layer — is responsible for the final mistake.
Building the Intuition
Here's the picture to hold in your head before any formulas:
- Forward pass: information flows left to right — input → layer 1 → layer 2 → ... → output → loss.
- Backward pass: blame flows right to left — loss → output layer → ... → layer 2 → layer 1.
- At every layer, backprop computes one central quantity: how sensitive is the final loss to a tiny change in this layer's raw output? That sensitivity is called the layer's error signal, written .
- Once you know a layer's error signal, computing that layer's weight gradients is simple arithmetic — no calculus left to do. All the hard work is in propagating backward correctly.
1.Forward pass
Input flows through every layer, caching z and a at each step, ending in a prediction and a loss.
2.Output error
Compute δ at the last layer: how the loss reacts to the network's raw output.
3.Backward recursion
Propagate δ backward, layer by layer, reusing the weights and cached activations from the forward pass.
4.Parameter gradients
At every layer, turn δ into concrete weight and bias gradients — ready for gradient descent.
The Math Behind It
We reuse the forward-propagation notation: , , and a per-example loss that can be any differentiable loss function.
This is the one quantity backpropagation computes at every layer, working from the output back to the input. Everything else follows from it.
Step 1 — The Output Layer Error
By the chain rule, the error at the very last layer splits into two ingredients:
Where: is the gradient of the loss with respect to the network's raw output, and is the element-wise (Hadamard) product, since the activation is applied element-wise.
Intuition: the error combines how wrong the prediction is with how sensitive the activation function is right now. A big loss gradient paired with a steep activation slope produces a big error signal — and either one being near zero shrinks it.
For two very common pairings — sigmoid activation with binary cross-entropy, and softmax with categorical cross-entropy — this entire expression collapses to . Chapter 16 derives this explicitly; it's one of the most frequently tested results in deep learning, precisely because it's so clean: the gradient is just prediction minus target.
Step 2 — Propagating the Error Backward
This is the heart of the algorithm: relating layer 's error to the error one layer ahead of it.
Where it comes from: layer 's pre-activation only affects the loss through layer . Summing over every neuron in layer that a given neuron feeds into (the multivariate chain rule) gives:
which, written across every neuron at once, is exactly the matrix recursion above.
To compute you need , which needs , and so on — all the way from the output. That dependency chain is why error signals are computed starting at the output and working backward, the exact mirror image of the forward pass.
Step 3 — Turning Error Signals Into Gradients
Once every is known, the actual trainable parameters fall out directly — no more chain rule required.
Derivation sketch: since , we get and . Applying the chain rule and writing it in matrix form gives the outer product above.

Worked Example: Backpropagation Through a 2-Layer Network
We continue the network from Chapter 13's worked example: input , sigmoid activations throughout, with the forward pass already computed as , , , and . The true label is , and we use binary cross-entropy with a sigmoid output — so, using the simplification above:
Layer 2 gradients:
Backward recursion into layer 1, using :
Layer 1 gradients:
Every gradient needed for one step of gradient descent is now available — computed with only local quantities and a single backward pass.
"Derive backpropagation" is arguably the single most common deep learning interview question. Be ready to reproduce, from memory: (1) the definition of , (2) the output layer formula, (3) the backward recursion and its chain-rule derivation, and (4) the weight/bias gradient formulas. Working through the numeric example above without looking is excellent practice.
Implementing It
import numpy as np
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def sigmoid_deriv(a):
return a * (1 - a) # takes the ACTIVATION a = sigmoid(z), not z
x = np.array([1.0, 0.5]); y = 1.0
W1 = np.array([[0.3, -0.2], [0.5, 0.1]]); b1 = np.array([0.1, -0.1])
W2 = np.array([[0.8, -0.6]]); b2 = np.array([0.2])
# --- Forward pass ---
z1 = W1 @ x + b1
a1 = sigmoid(z1)
z2 = W2 @ a1 + b2
a2 = sigmoid(z2) # y_hat
# --- Backward pass ---
delta2 = a2 - y # BCE + sigmoid simplification
dW2 = np.outer(delta2, a1)
db2 = delta2
delta1 = (W2.T @ delta2) * sigmoid_deriv(a1)
dW1 = np.outer(delta1, x)
db1 = delta1The comment on sigmoid_deriv matters more than it looks: that function takes the activation , not the pre-activation , because can be written directly in terms of the already-computed . This is exactly why the forward pass caches every and — backprop reuses them instead of recomputing anything.
It's easy to assume that computing gradients for millions of parameters must be far more expensive than a single forward pass. In reality, backpropagation costs only a small constant factor more — typically 2-3x — because it reuses cached forward-pass values instead of recomputing anything from scratch. The alternative, estimating each parameter's gradient independently via finite differences, would scale with the number of parameters, which is computationally hopeless at modern scale.
Key Takeaways
- Backpropagation solves the credit assignment problem: computing for every layer, efficiently.
- The error signal is computed recursively, from the output layer backward.
- The backward recursion follows directly from the multivariate chain rule: .
- Weight and bias gradients are simple outer products / direct copies of the error signal once is known.
- Backpropagation costs roughly the same order of computation as one forward pass — the reason training deep networks is feasible at all.
Common Mistakes
is a gradient with respect to the pre-activation, not the weight. It's an intermediate quantity — you still need one more step (the outer product with ) to get .
is written in terms of the activation . Plugging in the raw pre-activation instead of the cached activation is a common off-by-one-step bug when implementing this from scratch.
Interview Corner
Where This Shows Up in Practice
Every deep learning framework — PyTorch, TensorFlow, JAX — is built around an automatic differentiation engine that is, at its core, backpropagation generalized to arbitrary computation graphs. Understanding the derivation in this chapter by hand is what makes concepts like vanishing gradients (Chapter 15), gradient clipping, and modern optimizers (Chapter 17) make sense — they're all strategies for keeping this exact backward flow of error signals well-behaved.