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 asLinearClassifier.
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 hiddenLinear. Change the list so it holds both. - HOLE 2 —
count_parametersmust 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 wrotenn.ReLUwithout parentheses. The list needs an object, not a class:nn.ReLU().not greater than 0.95 : mlp reached 54.7%and0 != 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 sumlen(p), that is each tensor's first dimension. You need all elements: that is whatp.numel()is for.2 != 7850, but the ReLU tests pass —count_parametersis not changed yet.