Code

The training loop — fill in two holes

In the last step you saw the parts one at a time. Now they are put together into a training loop — and you fill in the two missing pieces.

The loop in words

For each epoch, for each batch:

1. loss      ← ask the model how wrong it was on this batch
2. clear       the gradients left over from the previous batch
3. backward    loss.backward()  — fills in the gradients
4. step        move every weight against its gradient

After each epoch the loop measures and prints the loss and the accuracy — the evaluate function for that is already written.

Notice the order: gradients are cleared before backward(), not after it. After backward() they are needed — the step is made from them.

Your job

Create train.py and test_train.py (see the panel on the right).

train.py compiles and runs, but learns nothing. The helpers make_loader and evaluate are already written (nothing new in them), and fit has two gaps marked HOLE 1 and HOLE 2:

  • HOLE 1 — clearing the gradients. The optimizer is already built in the line optimizer = torch.optim.SGD(...); you need one command of it.
  • HOLE 2 — the step itself. Again one optimizer command.

Replace each pass with a real line. Nothing else needs to change.

What does fit do when the model is not a LinearClassifier but a plain nn.Linear? Exactly the same. fit does not care what it is given as long as it is an nn.Module. That is why the test uses nn.Linear(2, 2) and two clouds of points — you write the model class in the next step.

Run the test before filling anything in

First as it is. In the project folder:

$ python -m unittest
.FF..
======================================================================
FAIL: test_accuracy_rises_on_separable_data (test_train.TestFit...)
----------------------------------------------------------------------
AssertionError: 0.5 not greater than 0.9 : accuracy 50.0% -> 50.0%

======================================================================
FAIL: test_one_epoch_is_exactly_the_manual_update_of_lesson_4 (test_train.TestFit...)
----------------------------------------------------------------------
AssertionError: False is not true : weight differs from the manual loop by up to 0.1795

----------------------------------------------------------------------
Ran 5 tests in 1.2s

FAILED (failures=2)

(The real output has a Traceback with file paths before each AssertionError — I kept only the essence.)

This is not an error but a starting point. The skeleton learns nothing: the accuracy stays at exactly 50.0 % (two classes, a coin toss), and the weights never move — so they differ from the manual loop. The make_loader and evaluate tests pass: those functions are already correct.

Fill both holes and run again — you should see OK.

How does the test know? The second test replays lesson 4's manual loop (p -= 0.1 * p.grad, p.grad.zero_()) from the same starting model over the same 4 batches, then compares the weights with what your fit produced. So it is not just "does accuracy rise" — it is "is this the same computation".

When it does not pass

  • weight differs from the manual loop by up to 0.2776, but the accuracy test passes — you skipped zero_grad() (or wrote just optimizer.zero_grad without parentheses, which does nothing and raises no error). It still learns, but the gradients pile up: every batch pushes the weights by the leftovers of old batches too.
  • accuracy 50.0% -> 50.0% and up to 0.1795 — no step was made. The usual cause: optimizer.step without () — Python raises nothing, it just does not run it.
  • both tests fail although step() and zero_grad() are both there — most likely swapped. A step before backward() would use gradients that are not filled in yet.