Exercises — Training loops from scratch
Before we start, three symbols we will lean on constantly. We define them once, in plain words, so nothing below is a mystery:
Level 1 — Recognition
Exercise 1.1
State, in order, the five components of one training-loop iteration and give a one-line reason each exists.
Recall Solution
- Forward pass — compute the prediction . Why: we need to know what the current model says before we can judge it.
- Loss computation — compute the scalar error . Why: we need a single number that says "how wrong", so we have something to minimize.
- Backward pass — compute . Why: we need to know which direction each parameter should move.
- Parameter update — . Why: actually apply the improvement.
- Iteration — repeat over all batches and epochs. Why: one nudge is tiny; convergence needs many.
See 3.1.02-Backpropagation-algorithm for step 3's machinery.
Exercise 1.2
In the PyTorch loop, match each line to its from-scratch equivalent:
optimizer.zero_grad(), loss.backward(), optimizer.step().
Recall Solution
optimizer.zero_grad()::: resets stored gradients to zero — the from-scratch equivalent is simply not accumulating: each batch you compute freshdW1, dW2, ...from scratch, overwriting the old ones.loss.backward()::: the manual block computingdloss_dy, dW2, db2, da1, dz1, dW1, db1— the chain-rule pass.optimizer.step()::: the linesW2 -= lr*dW2,b2 -= lr*db2, etc. — the update rule.
See 3.3.01-PyTorch-fundamentals.
Level 2 — Application
Exercise 2.1
A single parameter has value . The averaged gradient is and the learning rate is . Compute .
Recall Solution
The gradient is positive (increasing raises loss), so we step down. Loss should drop.
Exercise 2.2
You have a batch of examples. Their per-example gradients (for one weight) are . With and , find .
Recall Solution
First average (the in our formula): Then update: Why average, not sum? Summing would make the step scale with batch size, so changing would secretly change your effective learning rate. Averaging keeps step size batch-independent — see 3.5.02-Data-loaders-and-batching.
Exercise 2.3
Mean Squared Error for one example is . Given , , compute (a) the loss and (b) .
Recall Solution
(a) . (b) Differentiate: . Why MSE and its derivative matter: the derivative is the seed of backprop — the very first number the backward pass produces, from which every parameter gradient is chained. Positive means "we overshot, pull prediction down".
Level 3 — Analysis
Exercise 3.1
A student trains for 200 steps but forgets optimizer.zero_grad(). At step the correct batch gradient is for every step. Because gradients accumulate, what gradient does the optimizer actually use at step 3? At step ?
Recall Solution
With accumulation, .grad at step holds the running sum of all gradients so far:
- Step 3 uses gradient .
- Step uses gradient .
So the effective step grows linearly: by step 200 you're stepping 200× harder than intended — a runaway (see the figure below). This is why omitting zero_grad() typically explodes the loss.

zero_grad()); the amber line climbs as (accumulated, without it) — visual proof the effective step balloons over time.
Exercise 3.2
Set up the hidden-layer backward pass symbolically. Let be the batch input matrix (one row per example, one column per input feature) and let be the hidden activations after ReLU. In the parent note's from-scratch code the hidden-layer gradient is built as , then , then . ReLU's derivative is if and if . A hidden unit has pre-activation for the entire dataset. What happens to its incoming weights during training, and why?
Recall Solution
Reading the symbols: is the batch input, is "how the loss changes with the hidden activation", is that same signal after passing back through ReLU, and means element-wise multiply. Because the ReLU mask is whenever : The incoming weights get zero gradient, so they never update. The unit is stuck outputting forever — a dead ReLU. It contributes nothing and cannot recover on its own. Why it happens: ReLU flatlines for negatives; a flat region has zero slope, so no learning signal passes back. Small initialization or 3.4.01-Batch-normalization helps keep pre-activations near the "alive" region.
Exercise 3.3
Look at the loss curve. Given the descent picture below, decide: is (too large), (well-chosen), or (too small) more likely to produce a loss that bounces up and down without settling? Explain using the geometry.
Recall Solution
(too large). Look at the bowl-shaped loss surface below: the update takes a step proportional to . If is too big, each step overshoots the bottom, landing on the opposite wall higher up — so the loss zig-zags and can even diverge. Too small () creeps down slowly but smoothly; well-chosen () descends steadily.

See 3.4.03-Learning-rate-schedules and 4.2.01-Gradient-descent-variants.
Level 4 — Synthesis
Exercise 4.1
Write pseudocode for one epoch of a from-scratch loop over a dataset of with batch_size=32. State how many parameter updates occur in that epoch.
Recall Solution
shuffle the N examples # break batch-order correlation
for start = 0, 32, 64, ..., up to N:
batch = examples[start : start+32]
y_pred = forward(batch) # 1. forward
loss = MSE(y_pred, y_batch)# 2. loss
grads = backward(loss) # 3. backward (with 1/B baked in)
params = params - lr * grads # 4. update
Number of updates updates (the last batch has only examples). One epoch ≠ one update — it's many.
Exercise 4.2
Derive the output-layer weight gradient for MSE with a linear output , . Show every chain-rule link.
Recall Solution
We want . Break it by the chain rule (each link is "how the next thing changes when this changes"):
- (MSE derivative with the batch average). Call this quantity the loss-to-prediction signal, written .
- (output is linear).
- (the hidden activations feeding this layer).
In matrix form (summing correctly over the batch): , where is the transpose of the hidden activations and is the loss-to-prediction signal from the first bullet. This is 3.1.02-Backpropagation-algorithm in miniature.
Exercise 4.3
You want a custom loss: Mean Absolute Error, . Give its derivative w.r.t. and note the one place it misbehaves.
Recall Solution
Misbehaves at (the kink) — the derivative jumps and isn't defined exactly at zero. In practice frameworks set it to there (a subgradient). Note MAE's gradient magnitude is constant (), unlike MSE's which shrinks near the target — so MAE keeps pushing at full strength even when close. See 3.3.06-Custom-loss-functions.
Level 5 — Mastery
Exercise 5.1
A loop runs loss.backward() twice before a single optimizer.step(), with per-call gradient and no zero_grad() in between. Then it steps with from . What is , and is this a bug or a feature?
Recall Solution
Two backward calls accumulate: stored grad . Then:
Bug or feature? This is the intended mechanism behind gradient accumulation — deliberately summing gradients over several mini-batches to simulate a larger effective batch on limited memory. It's a feature if you divide appropriately (here you'd want to average by dividing by 2 to keep the effective learning rate honest); a bug if you did it by accident. Same math, different intent — which is exactly why zero_grad() is explicit rather than automatic.
Exercise 5.2
On a bowl-shaped (convex) 1-D loss , the gradient is . Starting at with , compute and by hand. Is it converging toward the minimum at ?
Recall Solution
Update rule .
- .
- .
Distance to the minimum () halves each step: Yes, it converges smoothly — this is the well-behaved regime of Exercise 3.3. (If instead , the factor becomes , and would fling past 2 and diverge.)
Exercise 5.3
Diagnose: training loss steadily falls to near-zero, but validation loss rises after epoch 20. Name the phenomenon, explain the mechanism in one sentence, and give one fix.
Recall Solution
Phenomenon: overfitting. Mechanism (one sentence): the model has enough capacity and epochs to memorize the training set's noise, which keeps driving training loss down while destroying generalization, so validation loss — measured on unseen data — turns back up. One fix: early stopping — halt training at the validation minimum (epoch 20) and keep those weights; alternatives are adding regularization or gathering more data. See 5.1.01-Overfitting-and-underfitting.
Recall Quick self-check ledger
The essential update rule ::: with the batch-averaged gradient. Updates in one epoch ::: . Why divide the batch gradient by ::: keeps effective learning rate independent of batch size. Symptom of missing zero_grad ::: gradients accumulate, effective step grows each iteration, loss explodes. Symptom of a dead ReLU ::: pre-activation always , mask zeroes the gradient, weights frozen. Training loss down but validation up ::: overfitting → early stopping / regularization.