Current: Week 4

0%

Week 4

Week 4 — Backpropagation: learning across all the network's parameters

Key questionHow do we calculate each weight's effect on the final loss?

Learning objectives

  • Understand and apply Week 4 — Backpropagation: Calculate Gradients Throughout the Network

55 min estimated reading time

Week progress: 0 of 21 sections (0%)Course progress: 0 of 321 sections (0%)

Week 3 calculated a network's prediction. This week's question is: with only one final loss value, how can we determine the local effect of each parameter? Instead of repeatedly trying changes to every parameter, we reuse the forward-pass values and combine local sensitivities.

Scroll horizontally to view all columns.

Course data table
Study sessionsWhat you will produce
1: Record dependenciesFix x=2, y=5, w1=w2=1, b1=b2=0; obtain z=h=prediction=2 and loss=9.
2: Propagate gradientsObtain dw2=−12 and db2=−6, then propagate backward to obtain dw1=−12 and db1=−6.
3: Branches and updatesExplain multiplication along paths and addition of branch contributions; update all parameters, then predict again.
4: Check independentlyRun course_examples/week04_gradient_check.py and examine the incorrect early-update variant.

Prerequisites: Week 2's local rates of change and Week 3's ReLU. We use only one hidden neuron to reduce arithmetic. This is a new, smaller example of the same principles—not a silent change to last week's two-hidden-unit network.

Week 4 learning goals

Week 2 explained how gradient descent adjusts a parameter.

Week 3 organized many weights and biases into neurons and layers, leaving four questions:

How much should w₁ change?

Should w₂ increase or decrease?

How sensitive is the final loss to a particular weight?

How does a weight near the beginning receive a learning signal from loss?

Backpropagation provides the gradients needed to answer these questions.

This week focuses on four ideas:

  1. Computational Graph;
  2. Local Derivative;
  3. Chain Rule;
  4. How gradients propagate backward from loss to each parameter.

The central idea:

Backpropagation starts at loss and follows computational dependencies backward, using the chain rule to calculate how each parameter affects loss locally.

1. Backpropagation is not a separate training method

The complete training sequence remains:

Forward Pass

Prediction

Loss

Backpropagation

Gradients

Optimizer / Gradient Descent

Updated Parameters

Within that sequence:

  • Backpropagation calculates gradients;
  • Gradient descent or another optimizer uses gradients to update parameters.

Keep these two jobs separate.

2. Computational Graph

Consider a very small network:

z=w1x+b1z = w_1x+b_1
h=ReLU(z)h = \operatorname{ReLU}(z)
y^=w2h+b2\hat y=w_2h+b_2
L=(y^y)2L=(\hat y-y)^2

Its dependencies are:

x, w₁, b₁

z

↓ReLU

h

↓with w₂, b₂

ŷ

↓compare with y

L

This is a computational graph: split a large calculation into small operations and record which results depend on which inputs.

For a programmer, it resembles a runtime dependency graph.

Why split the formula into a graph?

In principle, we could differentiate a huge expression for the whole network, but maintaining it would be difficult. A tensor may feed several layers, and a parameter may affect loss through several paths. A computational graph breaks this into repeated small questions:

Which inputs does this operation receive?

What output does it produce?

Which later operations use that output?

What are its local derivatives?

This resembles a compiler's intermediate representation or a service dependency graph. Each node describes its own behavior, while the graph records the dependencies. The same chain-rule traversal principle applies as the number of operations grows.

What is actually saved?

The graph records more than call order. Backward calculation also needs context from the forward pass. For multiplication:

c=abc=ab

Backward needs the values of a and b used during forward, because:

ca=b,cb=a\frac{\partial c}{\partial a}=b,\qquad \frac{\partial c}{\partial b}=a

Retaining required intermediate values trades memory for the information needed by backward. This is one reason training usually requires more memory than inference without gradient tracking.

3. Forward pass: record the intermediate values

Start a new, smaller network with one hidden neuron so that we can follow every edge. Do not reuse Week 3's two-hidden-unit values. Record x, w₁, b₁, w₂, b₂, z₁, a₁, prediction, and loss. Use this same old state until the parameter update in Section 11.

Set:

x = 2

y = 5

w₁ = 1

b₁ = 0

w₂ = 1

b₂ = 0

Forward:

z=w1x+b1=1×2+0=2z=w_1x+b_1=1\times2+0=2
h=ReLU(2)=2h=\operatorname{ReLU}(2)=2
y^=w2h+b2=1×2+0=2\hat y=w_2h+b_2=1\times2+0=2
L=(25)2=9L=(2-5)^2=9

Saved values include z, h, and ŷ, which are used again when evaluating local derivatives during backward.

A training forward pass does more than predict

During training, the forward computation does two jobs:

  1. Calculate the prediction and loss;
  2. Record dependencies and retain intermediate values required for backward.

Inference normally needs predictions, not a subsequent backward pass, so gradient tracking can be disabled. For training, the required intermediate information must remain available or be recomputed: backward needs to know whether ReLU's input was positive or negative and which forward values belong in a multiplication derivative.

4. Local derivatives: each operation handles its own calculation

Backpropagation does not differentiate the whole expression in one leap. Each node answers:

If one input changes slightly, how quickly does my output change?

More precisely, a local derivative answers:

Within this operation, how sensitive is the output to a small change in one input, with other inputs held fixed?

“Local” means the operation does not need to understand the entire network or know the final loss value. Multiplication handles multiplication; ReLU handles ReLU. Combining these local relationships along the graph gives the sensitivity of the final loss to a parameter.

Think of each node as a component implementing a common interface:

forward(inputs) → output

backward(upstream_gradient) → input_gradients

Forward produces values. Backward receives a gradient from later computation, combines it with local derivatives, and propagates contributions to the inputs.

Some common local derivatives:

Addition

c=a+bc=a+b
ca=1,cb=1\frac{\partial c}{\partial a}=1,\qquad \frac{\partial c}{\partial b}=1

Why are both derivatives 1? For c=a+b with b fixed, increasing a by 0.01 increases c by exactly 0.01. The change multiplier is 1. An addition node passes the incoming gradient unchanged to each input; it does not split it in half.

Multiplication

c=abc=ab
ca=b,cb=a\frac{\partial c}{\partial a}=b,\qquad \frac{\partial c}{\partial b}=a

For c=a×b, suppose forward used a=3 and b=4. Hold b fixed and increase a by 0.01:

Originally c=3×4

= 12

Now c=3.01×4

= 12.04

a increases by 0.01 and c increases by 0.04. The multiplier is 4, which is the other input b. This explains the multiplication derivative instead of leaving it as a memorized rule.

Squared error

L=(y^y)2L=(\hat y-y)^2
Ly^=2(y^y)\frac{\partial L}{\partial \hat y}=2(\hat y-y)

The derivative with respect to prediction encodes both direction and scale:

  • If prediction exceeds the target, ŷ−y and the derivative are positive;
  • If prediction is below the target, ŷ−y and the derivative are negative;
  • The farther the prediction is from the target, the larger the derivative's absolute value.

Squared error scores the discrepancy. Its derivative tells us which local change in the prediction would reduce that score. Parameter gradients also need the remaining chain-rule factors.

ReLU

ReLU(z)=max(0,z)\operatorname{ReLU}(z)=\max(0,z)
ReLU(z)={1,z>00,z<0\operatorname{ReLU}'(z)=\begin{cases}1,&z>0\\0,&z<0\end{cases}

At z=0, ReLU has no unique ordinary derivative; an implementation chooses a convention for backward. Our worked example avoids this point.

ReLU's local derivative acts like a gradient gate:

Concept sequence
  1. Forward input z>0
  2. Gate open
  3. Multiply the incoming gradient by 1 and pass it through
Concept sequence
  1. Forward input z<0
  2. Gate closed
  3. Multiply by 0; this path contributes zero

ReLU does not create a gradient from nothing. Its forward input determines how much of the incoming gradient passes through this path.

How a local derivative participates in backward

The core scalar rule for a node is:

input gradient=upstream gradient×local derivative\mathit{input\ gradient}=\mathit{upstream\ gradient}\times\mathit{local\ derivative}

For example, the multiplication node c=ab receives:

upstream gradient = ∂L/∂c = -6

Forward used a=3

Forward used b=4

The gradients it sends to its two inputs are:

La=Lcca=(6)(4)=24\frac{\partial L}{\partial a}=\frac{\partial L}{\partial c}\frac{\partial c}{\partial a}=(-6)(4)=-24
Lb=Lccb=(6)(3)=18\frac{\partial L}{\partial b}=\frac{\partial L}{\partial c}\frac{\partial c}{\partial b}=(-6)(3)=-18

This is what the local derivative formulas are for: they are not isolated entries in a table. They are the multipliers each node uses to propagate the sensitivity of loss backward to its inputs.

5. Chain rule: connect local sensitivities

This section temporarily changes a local multiplier to isolate the multiplication-along-a-path idea. That does not update the main experiment's saved parameters from Section 3. Return to that old state in Section 6. The chain rule calculates sensitivities; the optimizer separately determines parameter updates.

Lw1=Ly^y^hhzzw1\frac{\partial L}{\partial w_1}=\frac{\partial L}{\partial \hat y}\frac{\partial \hat y}{\partial h}\frac{\partial h}{\partial z}\frac{\partial z}{\partial w_1}

Each factor is a local sensitivity between adjacent nodes.

Why multiply? Because:

Change w₁ slightly

That changes z

The change in z changes h

The change in h changes prediction

The change in prediction changes loss

Along this single path, the final local sensitivity is the product of the local sensitivities.

Why multiply rather than add?

Suppose a small change passes through three stages:

Increase w₁ by 0.01

z changes by approximately 2×0.01

h changes by approximately 1×the change in z

ŷ changes by approximately 3×the change in h

Finally:

Δy^0.01×2×1×3=0.06\Delta\hat y\approx0.01\times2\times1\times3=0.06

Each stage scales the change from the previous stage, so the multipliers multiply. The chain rule expresses this composition precisely in terms of derivatives.

A familiar dependency-chain analogy

If:

Tax = Tax(Subtotal(Price(quantity, unitPrice)))

A change in unitPrice affects Tax through Price and Subtotal. Along this path, a zero local sensitivity at any stage makes its first-order contribution to the final change zero. Neural-network backward calculations follow the same dependency idea, with more nodes and branches. The gradients needed for our training loop are first derivatives.

Backpropagation here calculates first derivatives of loss with respect to parameters. It does not first need second derivatives. A nonzero first-order gradient identifies the local direction of increase; moving in the opposite direction with a sufficiently small step can decrease a differentiable loss. An arbitrary finite step is not guaranteed to do so.

Second derivatives describe curvature. Some optimization methods use them, but a full neural-network Hessian can be extremely large. GPT training commonly uses first-order methods such as SGD or AdamW without explicitly constructing it.

6. Start backward from loss

The forward pass gives:

ŷ = 2

y = 5

L = 9

The logical starting point for backward is:

LL=1\frac{\partial L}{\partial L}=1

The derivative of a value with respect to itself is 1. Seed the loss node with 1, then propagate backward through the local derivatives.

Why usually reduce loss to a scalar? It specifies a single objective and allows a seed of 1. For a vector output, a backward calculation needs a supplied vector of weights to specify a vector–Jacobian product, or an explicitly defined scalar objective. Summing or averaging batch losses gives the scalar objective we use in training.

Next, calculate how loss changes with prediction.

First step:

Ly^=2(y^y)=2(25)=6\frac{\partial L}{\partial \hat y}=2(\hat y-y)=2(2-5)=-6

This −6 is the gradient passed from loss to the prediction node.

It means a sufficiently small increase in prediction decreases loss at the current point.

7. Output-layer gradients

Output:

y^=w2h+b2\hat y=w_2h+b_2

For w₂:

y^w2=h=2\frac{\partial \hat y}{\partial w_2}=h=2

Chain Rule:

Lw2=Ly^y^w2=(6)(2)=12\frac{\partial L}{\partial w_2}=\frac{\partial L}{\partial \hat y}\frac{\partial \hat y}{\partial w_2}=(-6)(2)=-12

For b₂:

y^b2=1\frac{\partial \hat y}{\partial b_2}=1

So:

Lb2=(6)(1)=6\frac{\partial L}{\partial b_2}=(-6)(1)=-6

We now have local loss sensitivities for both output-layer parameters. The update rule will use them later.

8. Continue backward to the hidden layer

To continue, calculate the loss sensitivity to h:

y^h=w2=1\frac{\partial \hat y}{\partial h}=w_2=1

So:

Lh=Ly^y^h=(6)(1)=6\frac{\partial L}{\partial h}=\frac{\partial L}{\partial \hat y}\frac{\partial \hat y}{\partial h}=(-6)(1)=-6

This is a gradient with respect to an intermediate activation, not a parameter. It carries the sensitivity needed to reach earlier parameters.

9. Pass the gradient through ReLU

During forward:

z = 2 > 0

Therefore:

hz=1\frac{\partial h}{\partial z}=1

So:

Lz=Lhhz=(6)(1)=6\frac{\partial L}{\partial z}=\frac{\partial L}{\partial h}\frac{\partial h}{\partial z}=(-6)(1)=-6

If forward used z<0, ReLU's local derivative is 0, so this path contributes zero gradient.

10. Hidden-layer gradients

Hidden linear calculation:

z=w1x+b1z=w_1x+b_1

For w₁:

zw1=x=2\frac{\partial z}{\partial w_1}=x=2

So:

Lw1=Lzzw1=(6)(2)=12\frac{\partial L}{\partial w_1}=\frac{\partial L}{\partial z}\frac{\partial z}{\partial w_1}=(-6)(2)=-12

For b₁:

zb1=1\frac{\partial z}{\partial b_1}=1
Lb1=(6)(1)=6\frac{\partial L}{\partial b_1}=(-6)(1)=-6

Finally:

gradient_w₁ = -12

gradient_b₁ = -6

gradient_w₂ = -12

gradient_b₂ = -6

We now have gradients for all parameters with respect to the same loss.

11. Finish all gradients before updating parameters

No parameter has changed yet. Every gradient describes the loss from the same old forward pass. Set the learning rate to 0.01; only now apply “old parameter minus learning rate times gradient.”

Scroll horizontally to view all columns.

Course data table
ParameterOld valueGradient at the old valuesUpdated value
w11−121.12
b10−60.06
w21−121.12
b20−60.06

Why not update w2 as soon as dw2 is available? The old forward pass gives dL/dprediction=−6, and propagating to h requires the old w2=1, so dL/dh=−6. Using the new w2=1.12 instead gives −6.72 and then the incorrect dw1=−13.44 after multiplication by input 2. The correct value is −12. The incorrect calculation mixes two parameter states.

θnew=θoldαL(θold)\theta_{\mathrm{new}}=\theta_{\mathrm{old}}-\alpha\nabla L(\theta_{\mathrm{old}})

θ denotes the four parameters collectively; ∇L is their corresponding gradient vector. Backward calculates those gradients. The optimizer changes parameters according to the chosen update rule. These are separate operations.

The next section recomputes forward: z=1.12×2+0.06=2.30, h=2.30, prediction=1.12×2.30+0.06=2.636, and loss=(2.636−5)²=5.588496. The old variable holding loss=9 does not update itself; we must calculate again.

Knowledge check

EX04: If hidden z<0 makes this ReLU path's gradient zero, must the output bias b2 also have zero gradient?

12. Run forward again

New parameters:

z=(1.12)(2)+0.06=2.30z=(1.12)(2)+0.06=2.30
h=ReLU(2.30)=2.30h=\operatorname{ReLU}(2.30)=2.30
y^=(1.12)(2.30)+0.06=2.636\hat y=(1.12)(2.30)+0.06=2.636
L=(2.6365)25.59L=(2.636-5)^2\approx5.59

Compare:

Before: Loss = 9.00

After: Loss ≈5.59

After this backward calculation and update, the prediction has moved toward the target in our example.

13. Match the full calculation to code

python
x = 2.0
y = 5.0
w1, b1 = 1.0, 0.0
w2, b2 = 1.0, 0.0
learning_rate = 0.01

# Forward
z = w1 * x + b1
h = max(0.0, z)
prediction = w2 * h + b2
loss = (prediction - y) ** 2

# Backward: Loss → prediction
d_prediction = 2 * (prediction - y)

# Output layer
dw2 = d_prediction * h
db2 = d_prediction
d_h = d_prediction * w2

# ReLU
d_z = d_h * (1.0 if z > 0 else 0.0)

# Hidden layer
dw1 = d_z * x
db1 = d_z

# Update
w1 -= learning_rate * dw1
b1 -= learning_rate * db1
w2 -= learning_rate * dw2
b2 -= learning_rate * db2

Each gradient calculation corresponds to an edge or combination of paths in the graph.

You can check the result without another symbolic derivation: perturb one parameter by +ε and −ε, hold all other parameters fixed, and calculate both losses. The central difference [L(θ+ε)−L(θ−ε)]/(2ε) should be close to the hand-derived gradient. This is a small-example diagnostic, not an efficient way to train a large model parameter by parameter. Avoid ReLU's nondifferentiable zero point. The runnable example is course_examples/week04_gradient_check.py.

14. What if a parameter has multiple paths to loss?

A parameter or intermediate value can affect loss through several paths.

The rule:

Add gradient contributions from different paths reaching the same variable.

For example:

L=L1+L2L=L_1+L_2

Then:

Lw=L1w+L2w\frac{\partial L}{\partial w}=\frac{\partial L_1}{\partial w}+\frac{\partial L_2}{\partial w}

Think of one shared dependency used by several callers: its total effect must include all relevant paths.

Why add here? Each path contributes to the local change. Omitting a path can give the wrong total. Contributions can reinforce each other or cancel because gradients have signs.

Remember two graph rules:

Along one continuous path: multiply local derivatives

When paths lead back to the same variable: add their gradient contributions

“Multiply along paths, add contributions at branches” captures the basic scalar structure of backpropagation.

Knowledge check

Two branches contribute gradients −3 and +1 to the same parameter. What is the total?

15. Why combine gradients across a batch?

A batch contains multiple examples:

L=1ni=1nLiL=\frac{1}{n}\sum_{i=1}^{n}L_i

For a mean loss, the parameter gradient is the mean of the per-example contributions:

Lw=1ni=1nLiw\frac{\partial L}{\partial w}=\frac{1}{n}\sum_{i=1}^{n}\frac{\partial L_i}{\partial w}

The gradient reduction follows the loss reduction. This is why averaging batch losses also averages their gradient contributions.

16. What does Autograd automate?

PyTorch Autograd automates three jobs:

  1. Record differentiable operations and required intermediate values during forward;
  2. Traverse dependencies backward from the scalar loss;
  3. Accumulate gradients using local derivatives and the chain rule.

It automates repeated calculations without changing the underlying mathematics.

In its usual eager execution mode, PyTorch builds the differentiation graph from the tensor operations actually executed during each forward pass. Python conditionals, loops, and function calls can therefore determine which operations appear. Ordinary backward normally releases saved intermediate values needed for differentiation; the next training step builds a new graph. Retaining a Python reference to an output is not the same as retaining all saved values needed to run backward again.

This explains several common behaviors:

  • Input-dependent control flow can change the executed graph;
  • Inference without gradients can use torch.no_grad() to avoid recording a backward graph and reduce memory use;
  • Unnecessarily retaining graphs or graph-connected tensors across steps can increase memory use.

17. Vanishing and exploding gradients: an introduction

Along a path through many layers, local derivative factors multiply.

If many factors have absolute values below 1, a path's contribution can become very small: a vanishing gradient.

If many factors have absolute values above 1, a path's contribution can become very large: an exploding gradient.

Later we discuss choices such as ReLU, residual connections, LayerNorm, and gradient clipping that can help particular aspects of training stability. None is a universal guarantee, and detailed proofs are not required here.

A small numerical illustration: passing through twenty local factors of 0.5 gives a total multiplier of:

0.5200.000000950.5^{20}\approx0.00000095

The contribution becomes tiny. Passing through twenty factors of 2 instead gives:

220=1,048,5762^{20}=1{,}048{,}576

The issue is not depth alone, but how local sensitivities compose. Repeated products can become extremely small or large. Later architectural and optimization choices address aspects of these effects; a full network can also have multiple paths whose contributions combine.

Derivatives, gradients, and parameter gradients

These terms are easy to mix up in code:

  • A derivative describes the local rate of change of an output with respect to an input;
  • A local derivative refers to that sensitivity within one operation;
  • A gradient collects the partial derivatives of a scalar objective with respect to a set of variables;
  • A parameter gradient is the part with respect to trainable parameters, such as w.grad.

Intermediate tensors also have mathematical gradients, although a framework need not retain them in each tensor's .grad field by default. An optimizer normally updates registered trainable parameters, not activations. Backpropagation combines local derivatives to calculate parameter gradients.

18. Six essential Week 4 ideas

  1. A training forward pass calculates prediction and loss and retains required intermediate information.
  2. Backward starts from loss and follows dependencies in reverse.
  3. Each operation supplies its local derivatives.
  4. The chain rule multiplies local sensitivities along a path.
  5. Add contributions from multiple paths to the same variable.
  6. Backpropagation calculates gradients; the optimizer uses them to update parameters.

19. Check your understanding

Knowledge check

Given: gradient from loss to prediction = −4 prediction = w × h h = 3 Then:

Review the relevant lesson
predictionw=h=3\frac{\partial\mathit{prediction}}{\partial w}=h=3

Therefore:

Lossw=(4)(3)=12\frac{\partial\mathit{Loss}}{\partial w}=(-4)(3)=-12

If the learning rate is 0.01:

w_new = w - 0.01 × (-12)

= w + 0.12

20. Week 4 → Week 5

We can now calculate a small network's forward pass, backward pass, and parameter update by hand.

Real networks can contain millions or billions of parameters, making manual maintenance of all derivatives impractical.

Week 5 hands the same mathematics to PyTorch:

Python numbers

Tensor

Autograd

nn.Module

Optimizer