Code

The Adam optimizer and build_model — write it yourself

The model is ready. Now it has to be trained — and here plain SGD starts to fail for the first time.

Why we change the optimizer

The same MLP(128), the same slice of data, the same seed, 10 epochs, batch 64. Only the optimizer and lr change. All of this is my experiment, nothing from a "textbook":

sgd   lr 0.01   loss 0.6689  train acc 76.2%
sgd   lr 0.1    loss 0.9140  train acc 68.5%
sgd   lr 0.5    loss 0.7285  train acc 74.1%
adam  lr 0.001  loss 0.3622  train acc 86.3%
adam  lr 0.01   loss 0.3219  train acc 86.8%
adam  lr 0.1    loss 1.2670  train acc 45.6%

Three SGD lr values — and none comes close to what Adam gives on the first try. I am not saying SGD is "bad": I tried only three values and 10 epochs; with more epochs or momentum it would catch up. I am saying that here, with these settings, Adam hits first, and that its lr matters differently: 0.1 is already too much for it.

What Adam does: for every number separately it remembers how large and how consistent its latest gradients were, and picks that number's step accordingly. So:

  • the first step moves every number by roughly lr, whatever its gradient — the test checks exactly that;
  • numbers with noisy gradients move more carefully.

In your code this is one word: SGD → Adam. optimizer.zero_grad(), backward() and step() stay the same.

Your job

1. train.py: change torch.optim.SGD to torch.optim.Adam. Do not change anything else in fit. The file also grew in other ways: argparse (command-line parameters) and main() are provided — they just wire the parts together.

2. build_model(name, hidden) — an empty skeleton. The contract is in the docstring: "linear" returns LinearClassifier(), anything else MLP(hidden=hidden).

3. test_train.py: TestBuildModel tests were added and one test was replaced. Lesson 5's test that checked "exactly SGD's arithmetic" no longer applies — fit is no longer SGD. It was replaced by another: "after one step every number moved by about lr". That is not forgotten; it is a deliberate retirement.

Does fit in the panel have Adam? Yes — the change is already visible there. build_model still has NotImplementedError.

Run it

$ python -m unittest

While build_model is unwritten: two NotImplementedError errors. While fit still uses SGD, only one test fails:

FAIL: test_first_step_moves_every_number_by_about_lr_as_adam_does
AssertionError: False is not true : weight moved by [0.005079716444015503, 0.004353374242782593, 0.005079731345176697, 0.004353374242782593], want about 0.01 each

(SGD with lr=0.01 moves numbers not by 0.01 but by lr · gradient — here ~0.005.) When both are done: OK, 23 tests.

When it does not pass

  • AssertionError: 101770 != 109386 — build_model ignores hidden (you wrote MLP() instead of MLP(hidden=hidden)).
  • weight moved by [...], numbers not ~0.01 — the optimizer is not Adam (you probably left SGD).