Code

The MLP — fill in two holes

In the last step you saw that two Linear layers with nothing between them are one Linear, and that ReLU breaks that. Now you write the same thing as a class — and fill in two missing pieces.

The network in words

An MLP (multi-layer perceptron) is this chain:

input → Linear → ReLU → Linear → ReLU → … → Linear → scores
  • hidden=(128,) means one hidden layer of 128 neurons: 784 → 128 → ReLU → 10.
  • hidden=(128, 64) — two: 784 → 128 → ReLU → 64 → ReLU → 10.
  • hidden=() — no hidden layer, the same as LinearClassifier.

The last layer has no ReLU. The output scores may be negative; ReLU would cut them off and CrossEntropyLoss would have nothing to compare. That is why the loop adds [Linear, ReLU] pairs, and the final Linear is appended separately.

nn.Sequential takes a list of layers and runs them one after another.

Your job

The model.py file has grown: LinearClassifier stays as it was (your file already has it), and an MLP class with a count_parameters function was added. Also create test_model.py — this time it is the one you pass.

Two holes:

  • HOLE 1 — a nn.ReLU() must follow every hidden Linear. Change the list so it holds both.
  • HOLE 2 — count_parameters must sum numbers, not tensors. The current line returns the number of tensors — it looks right but is wrong.

Run the test before filling anything in

$ python -m unittest test_model
FAIL: test_linear_classifier_has_7850_numbers
AssertionError: 2 != 7850
FAIL: test_mlp_128_has_101770_numbers
AssertionError: 4 != 101770
FAIL: test_two_hidden_layers
AssertionError: 6 != 109386
FAIL: test_doubling_the_input_does_not_double_the_output
AssertionError: True is not false
FAIL: test_learns_rings_which_a_single_layer_cannot
AssertionError: 0.5333333611488342 not greater than 0.95 : mlp reached 53.3%; linear got 53.3%
FAIL: test_one_relu_per_hidden_layer
AssertionError: 0 != 1 : hidden=(128,)
FAIL: test_no_hidden_layers_is_just_a_linear_classifier
AssertionError: 2 != 7850
Ran 11 tests in 1.2s

FAILED (failures=7)

(I kept only the FAIL and AssertionError lines; the real output also has a Traceback for each.)

That is the starting point. 2 != 7850: count_parameters counted two tensors (weight and bias) instead of 7 850 numbers. And without ReLU the network is linear: it does not learn the two rings (53.3 % — the same as a single layer).

Fill both holes and run again: OK, 11 tests.

Two numbers the test checks and that are worth understanding, not just passing:

784·128 + 128   =  100 480   hidden layer: weights + biases
128·10  + 10    =    1 290   output layer
                 ─────────
                  101 770

When it does not pass

  • TypeError: torch.nn.modules.activation.ReLU is not a Module subclass — you wrote nn.ReLU without parentheses. The list needs an object, not a class: nn.ReLU().
  • not greater than 0.95 : mlp reached 54.7% and 0 != 1 — you put in another activation (e.g. nn.Sigmoid()). The test insists on ReLU: it counts them. With sigmoid the network does learn, but not enough within those 400 steps.
  • 20 != 7850, 276 != 101770 — you sum len(p), that is each tensor's first dimension. You need all elements: that is what p.numel() is for.
  • 2 != 7850, but the ReLU tests pass — count_parameters is not changed yet.