Frontend Labs
⋯

Chapter 15 — How neural networks learn: backpropagation, gently

In one sentence

Backpropagation is the chain rule from calculus, applied backwards through the network, so every knob learns how much it contributed to the error.

The problem

Gradient descent (Chapter 10) needs to know, for each of possibly billions of knobs, "if I nudge this one, does the loss go up or down?" Computing that separately for each knob would take forever. Backprop computes all of them in one backward sweep, costing about twice a forward pass.

The idea, in plain language: assigning blame

A restaurant serves a bad dish. Who's to blame?

FORWARD (making the dish):
  ingredients --> prep cook --> sauce chef --> plating --> customer: "too salty!"

BACKWARD (assigning blame):
  customer complaint "too salty"
       |
       v  how much did plating contribute to saltiness?  -> a little
  plating
       |
       v  how much did the sauce contribute?             -> a lot
  sauce chef
       |
       v  how much did the prep contribute?              -> some
  prep cook

Each station learns HOW MUCH TO CHANGE, based on
  (how much the next station blamed them)  x  (how sensitive they are)

That multiplication — blame from downstream × local sensitivity — is exactly the chain rule.

How it actually works

The chain rule says: if a affects b, and b affects c, then

   how much a affects c  =  (how much a affects b)  x  (how much b affects c)

In a network, the loss depends on the output, which depends on layer 3, which depends on layer 2, and so on. So you compute the sensitivity at the output and multiply your way backwards.

FORWARD PASS  (compute the answer)
  x --[W1]--> z1 --[relu]--> h1 --[W2]--> z2 --[softmax]--> p --> LOSS
  ============================================================>

BACKWARD PASS (compute all gradients)
  <============================================================
  dW1 <-- dz1 <-- dh1 <-- dW2 <-- dz2 <-- dp <-- dLOSS

  At each step you need only:
     (a) the gradient flowing in from the right
     (b) the values you saved during the forward pass

That's why frameworks store activations during the forward pass — they're needed for the backward pass. (It's also why training uses much more memory than inference.)

The one formula worth remembering — for a linear layer out = W @ in:

gradient for the weights   =  (gradient flowing in)  x  (the layer's input)
gradient to pass leftward  =  W^T  x  (gradient flowing in)

"Gradient for a weight = upstream blame × what that weight was multiplying." That's intuitive: a weight that multiplied a big input has a big effect, so it gets a big gradient.

Worked example: two knobs, all the arithmetic

Model: y = w2 * (w1 * x). Input x = 2, target t = 10, start w1 = 1, w2 = 1. Loss = (y - t)^2.

FORWARD:
  a  = w1 * x = 1 * 2  = 2
  y  = w2 * a = 1 * 2  = 2
  loss = (2 - 10)^2 = 64

BACKWARD:
  dloss/dy  = 2*(y - t) = 2*(2-10) = -16

  dloss/dw2 = dloss/dy * dy/dw2 = -16 * a  = -16 * 2 = -32
  dloss/da  = dloss/dy * dy/da  = -16 * w2 = -16 * 1 = -16
  dloss/dw1 = dloss/da * da/dw1 = -16 * x  = -16 * 2 = -32

UPDATE (learning rate 0.01):
  w2 = 1 - 0.01*(-32) = 1.32
  w1 = 1 - 0.01*(-32) = 1.32

CHECK (forward again):
  a = 1.32*2 = 2.64 ;  y = 1.32*2.64 = 3.48 ;  loss = (3.48-10)^2 = 42.4
  Loss went from 64 to 42.4.  It learned.

Do this once with a pen. It removes all the mystery from backpropagation.

Practice (25 min)

A. Verify gradients numerically. The safest way to know your maths is right:

import numpy as np

def f(w1, w2, x=2.0, t=10.0):
    return (w2 * (w1 * x) - t) ** 2

w1, w2, x, t = 1.0, 1.0, 2.0, 10.0

# analytic gradients (what we derived above)
a = w1 * x
y = w2 * a
g_w2 = 2*(y - t) * a
g_w1 = 2*(y - t) * w2 * x

# numerical gradients: (f(w+eps) - f(w-eps)) / (2*eps)
eps = 1e-6
n_w1 = (f(w1+eps, w2) - f(w1-eps, w2)) / (2*eps)
n_w2 = (f(w1, w2+eps) - f(w1, w2-eps)) / (2*eps)

print(f"w1: analytic {g_w1:.4f}   numeric {n_w1:.4f}")
print(f"w2: analytic {g_w2:.4f}   numeric {n_w2:.4f}")

They should match to ~4 decimal places. This technique will save you hours whenever you hand-write a backward pass.

B. Let PyTorch do it and see that it agrees:

import torch

w1 = torch.tensor(1.0, requires_grad=True)
w2 = torch.tensor(1.0, requires_grad=True)
x, t = torch.tensor(2.0), torch.tensor(10.0)

loss = (w2 * (w1 * x) - t) ** 2
loss.backward()                      # <-- backprop, one line

print("loss:", loss.item())
print("w1.grad:", w1.grad.item())    # should be -32
print("w2.grad:", w2.grad.item())    # should be -32

This is what every deep-learning framework does for you. You never write backprop by hand in real work — but you must know what it's doing to debug anything.

Two problems you will hear about

Vanishing gradients. Multiply many numbers smaller than 1 and you get something microscopic. In a deep network, the gradient reaching layer 1 can be ~0, so early layers never learn. Sigmoid activations made this terrible.

Exploding gradients. The opposite: numbers > 1 multiplied many times explode to infinity, and the loss becomes NaN.

The fixes (all used in LLMs):

Fix What it does
ReLU/GELU activations don't squash the signal for positive inputs
Careful initialisation start weights at a scale that preserves signal size
Normalisation layers (LayerNorm/RMSNorm) rescale activations back to a sane range at each layer
Residual connections y = x + F(x) give the gradient a direct highway past each layer — this is why 100-layer networks are trainable at all
Gradient clipping cap the gradient size so one bad batch can't blow up the weights

Residual connections deserve emphasis: they are in every Transformer block you'll meet in Part 6. Without them, deep Transformers would not train.

Common confusions

  • "Do I need to understand the calculus?" To use deep learning: no. To debug it: understanding the picture (blame flowing backwards, gradients vanishing/exploding) is essential. The formulas, no.
  • "Backprop is the learning algorithm" — not quite. Backprop computes gradients; gradient descent (or Adam) uses them to update. Two separate things.

Check yourself

  1. Explain backpropagation using the restaurant analogy.
  2. Why do frameworks store activations during the forward pass?
  3. What is a vanishing gradient, and name two fixes.
  4. What does a residual connection do for the gradient?

Further reading

  • Video (essential, 2.5 h): Karpathy, "The spelled-out intro to neural networks and backpropagation: building micrograd" — you build a working autodiff engine from scratch. If you watch one long video in this whole guide, watch this one.
  • Video (13 min): 3Blue1Brown, "What is backpropagation really doing?" (Neural Networks ep. 3), and ep. 4 for the calculus.
  • Book (free): Nielsen, Neural Networks and Deep Learning, Chapter 2 — "How the backpropagation algorithm works". Careful and complete.
  • Article: "Yes you should understand backprop" (Karpathy, Medium) — why the abstraction leaks and what bugs it causes.

Generated from real-life-ai-example-v1.md · 8 Oct 2026 16:36