Code

The model as an nn.Module — write it yourself

The training loop works with any nn.Module. Now you write your own model and the function that wires everything together. This time with no gaps: empty skeletons.

fit in the panel still shows pass where your two lines are — the panel shows the skeleton, not your file. In your copy they are already filled in; leave them.

nn.Module — how it is written

A model is a class that inherits from nn.Module and has two methods:

class Something(nn.Module):
    def __init__(self, ...):
        super().__init__()          # first — always
        self.layer = nn.Linear(...) # layers live as attributes

    def forward(self, x):
        return ...                  # what the model does with its input

Three rules worth knowing before you write:

  1. super().__init__() is the first line. Without it Python raises AttributeError: cannot assign module before Module.__init__() call.
  2. Assign the layer to self.something = .... Only then does it show up in model.parameters() — and so in the optimizer. A layer that lives only in a local variable will not be trained.
  3. forward must return. Without it the model returns None and the loss complains: argument 'input' (position 1) must be Tensor, not NoneType.

You call a model like a function: model(x) calls forward(x) inside.

Why the model does not return probabilities

In lesson 4 forward ended with softmax. Here it does not. The model returns raw scores (logits) and nn.CrossEntropyLoss applies softmax itself, inside. If you applied softmax twice there would be no error — just somewhat worse learning. You will see the numbers in the next step, "When the loop lies".

Your job

1. model.py (a new file) — the class LinearClassifier. The contract is in the comments. It must be the same 10 neurons from lesson 4, now as one object: scores = x @ W + b.

  • in_features pixels in, num_classes scores out;
  • one nn.Linear inside — the default model owns exactly 7 850 numbers;
  • forward returns raw scores.

2. train.py has grown: import lines and main() — an empty skeleton. Its docstring spells out exactly what to do and in what order (seed 42, 10 000 images, batch 64, 10 epochs, lr=0.1).

main has no unit test — its "test" is the next step: you run it and see whether you get what lesson 4 led you to expect.

3. test_train.py has grown: a TestLinearClassifier class. It checks the shape [N, 784] → [N, 10], 7 850 numbers, that it is an nn.Module, that raw scores come back (not probabilities), other sizes (LinearClassifier(2, 3)), and that fit trains this model.

Run python -m unittest. While you write, the test tells you what is wrong — the same kind of messages as in the previous step. Until the model is written you will see NotImplementedError.