08

Backpropagation

How is the blame for an error shared among thousands of weights?

9 min

Backpropagation

With voice. You can pause and jump to any step.

Lesson complete

Subtitles

We know how to fix a weight: subtract its slope. But a network has thousands of weights, in layers, and almost none of them touch the output. How does each one know how much of the error is its fault?

Voice

No voice: the lesson advances on its own with subtitles.

The voice for this lesson is being recorded: for now it advances on its own with subtitles.

0:00 / 0:00

What you will achieve

  • Compute, step by step and with real numbers, the forward pass, the loss, and the blames (gradients) of a small network.
  • Explain the chain rule as multiplying the influence of each link, and why one layer’s blame is reused going backward.
  • Tell apart what backpropagation computes (the gradient) from what gradient descent does (moves the weights).

Key ideas

  1. A network’s final error is shared among thousands of weights hidden in layers. Knowing how much blame each one carries is the credit assignment problem.
  2. The chain rule says that a weight’s total influence on the error is the product of the influence of each link along the path.
  3. Backpropagation sweeps the network from output to input, computes each neuron’s blame, and reuses it for the previous layer: it gets the whole gradient in a single trip.
  4. Backpropagation only computes the gradient. The one that corrects the weights is gradient descent: w ← w − η · blame.
  5. It is cheap (in large networks, on the order of a couple of forward passes), but it needs the values of the forward pass to be stored, and the blame can fade as it crosses many layers.

What many people believe

Common belief Backpropagation is the mechanism by which the network corrects its weights.

What is true In current usage, it only computes how much blame each weight carries (the gradient), with one trip backward. Moving the weights is the job of gradient descent (or another optimizer), which uses those numbers.

Check yourself

Four questions. No grade, no account: they just show whether the idea stuck.

ConceptA colleague says: “backpropagation corrects the network’s weights.” What is more accurate?
ConceptWhy is backpropagation much cheaper than probing the weights one by one?
CalculationThe output neuron’s blame is −0.2. A weight of 0.5 connects a hidden neuron to the output, and the slope of that hidden neuron’s sigmoid is 0.25. What is the hidden neuron’s blame?
ApplicationA team notices that training a model takes much more GPU memory than using it, at the same size. What part is due to backpropagation?

Under the hoodFor those who want the math and the code. You do not need it to follow the course.

The network’s equations

The lesson’s network has inputs x1,x2x_1, x_2, two hidden neurons, one output neuron, a sigmoid activation σ(z)=1/(1+e−z)\sigma(z)=1/(1+e^{-z}), and no biases:

z1=w1x1+w2x2,z2=w3x1+w4x2,aj=σ(zj)z_1 = w_1x_1 + w_2x_2,\quad z_2 = w_3x_1 + w_4x_2,\quad a_j=\sigma(z_j)
z=w5a1+w6a2,y^=σ(z),L=12(y^−t)2z = w_5a_1 + w_6a_2,\quad \hat y=\sigma(z),\quad L=\tfrac12(\hat y-t)^2

With x=(1, 0.5)\mathbf x=(1,\,0.5), t=1t=1, and w=(0.6, −0.3, 0.7, 0.5, 0.8, −0.9)\mathbf w=(0.6,\,-0.3,\,0.7,\,0.5,\,0.8,\,-0.9) we get y^≈0.460\hat y\approx 0.460 and L≈0.146L\approx 0.146. The starting weights were chosen so that every calculation shown with three decimals can be repeated by hand, using the already-rounded figures, without discrepancies (the lesson’s verify-data.ts script checks it, and compares the gradient with finite differences).

The chain rule, step by step

If LL depends on y^\hat y, which depends on zz, which depends on w5w_5, the derivative of LL with respect to w5w_5 is the product of the derivatives of each link:

∂L∂w5=∂L∂y^⋅∂y^∂z⋅∂z∂w5=(y^−t)⋅y^(1−y^)⋅a1\frac{\partial L}{\partial w_5}=\frac{\partial L}{\partial \hat y}\cdot\frac{\partial \hat y}{\partial z}\cdot\frac{\partial z}{\partial w_5}=(\hat y-t)\cdot\hat y(1-\hat y)\cdot a_1

The sigmoid’s derivative is σ′(z)=σ(z)(1−σ(z))\sigma'(z)=\sigma(z)(1-\sigma(z)), so it is computed from the activation that was already stored. The first two factors form the neuron’s blame δ=∂L/∂z\delta=\partial L/\partial z. Going backward, layer by layer:

δy=(y^−t) y^(1−y^),∂L∂w4+j=δy aj\delta_y=(\hat y-t)\,\hat y(1-\hat y),\qquad \frac{\partial L}{\partial w_{4+j}}=\delta_y\,a_j
δj=δy w4+j aj(1−aj),∂L∂w=δj xi\delta_j=\delta_y\,w_{4+j}\,a_j(1-a_j),\qquad \frac{\partial L}{\partial w}=\delta_j\,x_i

With the lesson’s numbers: δy=−0.540⋅0.248≈−0.134\delta_y=-0.540\cdot0.248\approx-0.134; ∂L/∂w5=−0.134⋅0.611≈−0.082\partial L/\partial w_5=-0.134\cdot0.611\approx-0.082; δ1=−0.134⋅0.8⋅0.238≈−0.026\delta_1=-0.134\cdot0.8\cdot0.238\approx-0.026 and ∂L/∂w1=δ1 x1≈−0.026\partial L/\partial w_1=\delta_1\,x_1\approx-0.026. Since w6<0w_6<0, δ2\delta_2 comes out with the opposite sign (+0.024+0.024). Note that the blames ∂L/∂w5\partial L/\partial w_5 and ∂L/∂w6\partial L/\partial w_6 do not add up to δy\delta_y: each weight gets the neuron’s blame multiplied by its own signal, not a share of it.

In layers and in matrix form

With W(l)W^{(l)} the matrix that takes a(l−1)\mathbf a^{(l-1)} to z(l)\mathbf z^{(l)}, the same ideas are written for any number of layers:

δ(l)=(W(l+1)⊤δ(l+1))⊙σ′(z(l)),∂L∂W(l)=δ(l) a(l−1)⊤,∂L∂b(l)=δ(l)\boldsymbol\delta^{(l)}=\big(W^{(l+1)\top}\boldsymbol\delta^{(l+1)}\big)\odot\sigma'(\mathbf z^{(l)}),\qquad \frac{\partial L}{\partial W^{(l)}}=\boldsymbol\delta^{(l)}\,\mathbf a^{(l-1)\top},\qquad \frac{\partial L}{\partial \mathbf b^{(l)}}=\boldsymbol\delta^{(l)}

Each δ(l)\boldsymbol\delta^{(l)} is computed once from the next layer: that is what makes the method cheap. With ReLU, σ′\sigma' is replaced by 11 if z>0z>0 and 00 otherwise.

Why it is cheap

For a scalar loss and NN weights, the reverse mode of automatic differentiation (the techniques for differentiating programs exactly; backpropagation is its special case for networks) delivers all NN derivatives in a single backward sweep. The forward mode, or probing with finite differences, needs one sweep per weight. The whole gradient costs a small constant times the cost of evaluating the function: Baydin et al. (2018), citing Griewank and Walther (2008), give c<6c<6 and typically between 2 and 3, and Griewank (2012) calls it the “cheap gradient principle”.

In a large transformer, Kaplan et al. (2020) count about 2N2N FLOPs per token going forward, with NN the parameters excluding embeddings, and note that the backward pass costs approximately twice as much: hence their ≈6N\approx 6N FLOPs per training token. In exchange, the activations of the forward pass must be stored until they are used: the 1986 paper itself points out that the outputs of the intermediate layers in the forward pass are needed for the backward pass.

A limit: the blame can fade

Since σ′(z)≤14\sigma'(z)\le \tfrac14, each layer of sigmoids contributes a factor of at most 14\tfrac14 to the blame: (14)n(\tfrac14)^n after nn layers. The weights also multiply at every layer and could compensate, but with moderate weights the product shrinks exponentially. That is why deep networks favor other activations, residual connections, and variants such as the LSTM, topics of lessons 12 and 13.

Where it comes from, carefully

The name and the fame date from 1986, but the idea is older and had several discoverers:

  • Linnainmaa. His master’s thesis (University of Helsinki, 1970, in Finnish; results in English in BIT, 1976) studies how the rounding error of an algorithm accumulates and proposes what is now called reverse mode. According to Griewank (2012), he did not present it as a way to get gradients cheaply.
  • Werbos. His doctoral thesis in statistics (Harvard, August 1974) develops “ordered derivatives” and “dynamic feedback” for prediction models. He himself says (Werbos, 2006) that he wrote only a few words about neural networks, in chapter 5; Goodfellow et al. (2016) date to 1981 his proposal to apply it to training neural networks.
  • Rumelhart, Hinton, and Williams (Nature, 1986). They use the same mathematics as this lesson: error 12∑(y−d)2\tfrac12\sum(y-d)^2, sigmoid, one forward pass and one backward pass that “propagates derivatives”. They acknowledge that David Parker and Yann LeCun discovered variants independently. In the paper, “back-propagation” names the whole learning procedure (with the update Δw=−ε ∂E/∂w\Delta w=-\varepsilon\,\partial E/\partial w and a variant with an acceleration term, what is now called momentum); today’s distinction, backpropagation for the gradient and another algorithm to learn with it, is the one Goodfellow et al. (2016) explain. The authors close by saying that the procedure, “in its current form, is not a plausible model of learning in brains”.

In code

import math

def sigma(z):
    return 1 / (1 + math.exp(-z))

X, T = (1.0, 0.5), 1.0

def forward(w):
    a1 = sigma(w[0] * X[0] + w[1] * X[1])
    a2 = sigma(w[2] * X[0] + w[3] * X[1])
    y = sigma(w[4] * a1 + w[5] * a2)
    return a1, a2, y, 0.5 * (y - T) ** 2

def backward(w):
    a1, a2, y, loss = forward(w)
    d_y = (y - T) * y * (1 - y)            # blame of the output neuron
    d_1 = d_y * w[4] * a1 * (1 - a1)       # blame of hidden 1
    d_2 = d_y * w[5] * a2 * (1 - a2)       # blame of hidden 2
    grad = [d_1 * X[0], d_1 * X[1], d_2 * X[0], d_2 * X[1], d_y * a1, d_y * a2]
    return loss, grad

w = [0.6, -0.3, 0.7, 0.5, 0.8, -0.9]
loss, grad = backward(w)
print(round(loss, 3), [round(g, 3) for g in grad])
# 0.146 [-0.026, -0.013, 0.024, 0.012, -0.082, -0.097]

# check: probe each weight (one extra forward pass per weight)
eps = 1e-6
check = []
for k in range(6):
    w2 = w.copy()
    w2[k] += eps
    check.append((forward(w2)[3] - loss) / eps)
print([round(c, 3) for c in check])
# [-0.026, -0.013, 0.024, 0.012, -0.082, -0.097]

# one gradient descent step: gradient descent does this, not backpropagation
eta = 1.0
w = [wk - eta * g for wk, g in zip(w, grad)]
print(round(forward(w)[3], 3))
# 0.129

What comes next

With real data the gradient is computed on a batch of examples rather than on a single one, and optimizers such as momentum or Adam transform that gradient before moving the weights. All of them receive the gradient from backpropagation. That is the topic of lesson 9.

Decision lens

What this idea means if your job is to decide, not to code.

  • One training step costs about three forward passes: one forward and one backward that costs twice as much. Ask for the training cost per token, not just the cost of use.
  • Training requires storing the intermediate values of the forward pass: GPU memory, not just compute, limits which model fits. Ask how much memory training needs compared with using the model.
  • Only what is differentiable can be trained by backpropagation. A hard rule, a database lookup, or a discrete decision in the middle of the system cuts the flow of blame: other methods are needed there.
  • Almost nobody codes backpropagation by hand: PyTorch and TensorFlow derive it automatically. The useful questions are which parts are trained and which are frozen, because freezing layers saves compute and gradient memory.

Terms in this lesson

Full transcript
CommentsThis lesson’s public conversation: questions, facts to check, ideas.

Comments live in GitHub Discussions, in the course’s public repository; writing one requires a GitHub account. They load from giscus.app when you open this section. The course reviewers read them, and if a change is accepted, it is applied in both languages. Open the thread on GitHub

Sources