Train the CNN and see what it really bought
All 13 tests pass — so the CNN exists. Time to train it.
What changes in train.py
Almost nothing. fit, evaluate and the loop have no idea about pictures; they
only know "model → logits". All that is needed is for build_model to be able
to create a CNN:
from model import CNN, MLP, LinearClassifier, count_parameters
...
if name == "cnn":
return CNN(dropout=dropout)
...
parser.add_argument("--model", choices=["linear", "mlp", "cnn"], default="mlp")
In the right panel you see three files in our versions: model.py with the
CNN filled in, conv_by_hand.py with the holes filled and pooling written, and
train.py with this change. If yours passes the tests, keep yours — these are
only for comparison.
Run it
python train.py --model cnn --epochs 15 --dropout 0.3
With our random seed (torch.manual_seed(42)) and 10,000 training images we got:
cnn: 20,490 parameters
epoch 1 loss 0.6056 train acc 76.5% | val loss 0.6426 val acc 74.5%
epoch 2 loss 0.4953 train acc 82.4% | val loss 0.5401 val acc 80.8%
epoch 3 loss 0.4497 train acc 83.6% | val loss 0.5035 val acc 81.7%
...
epoch 13 loss 0.2729 train acc 90.8% | val loss 0.3562 val acc 87.5%
epoch 14 loss 0.2728 train acc 90.4% | val loss 0.3651 val acc 87.4%
epoch 15 loss 0.2769 train acc 89.9% | val loss 0.3647 val acc 87.4%
final: train 89.9% val 87.4% gap +2.5%
The duration depends on your CPU: our measurements ranged from ~20 seconds (a quiet CPU) to 66 seconds (with three other runs sharing it).
Your numbers will differ by roughly 1–2 points. The same machine with the same seed reproduces numbers exactly, but another machine or another PyTorch version does not quite. What matters is the order, not the third digit.
What it means
| model | parameters | train | val | gap |
|---|---|---|---|---|
| MLP (256, 256), 30 ep. (lesson 7) | 269,322 | 97.1 % | 86.7 % | +10.4 |
| MLP (256, 256) + dropout + weight decay + early stopping | 269,322 | 89.4 % | 86.0 % | +3.3 |
| CNN (16, 32), dropout 0.3, 15 ep. | 20,490 | 89.9 % | 87.4 % | +2.5 |
Read it carefully, because it is easy to overstate:
- The CNN beat the MLP by 0.7 points in one seed. Over three seeds (0, 1, 2), on average: MLP 86.2 %, MLP with regularisation 86.5 %, CNN 87.5 %; the seed-to-seed spread is ~0.6 points. That is roughly +1 to +1.5 points — real, but not a jump.
- And it did it with 13 times fewer parameters (20,490 vs 269,322).
- The train-to-validation gap is 2.5 points instead of 10.4: a smaller network has less to memorise.
Equal budget — this is where the difference is
The fair comparison is the same parameter count. An MLP with a single hidden layer of 25 neurons has 19,885 parameters, nearly the same as the CNN:
python train.py --model mlp --hidden 25 --epochs 15
python train.py --model cnn --epochs 15
mlp: 19,885 parameters
...
final: train 87.2% val 84.1% gap +3.1%
cnn: 20,490 parameters
...
final: train 91.0% val 87.2% gap +3.8%
(Both without dropout, 15 epochs, seed 42.) That is +3.1 points on validation for the same number of parameters. This is where structure pays off.
What to take away
On this task the CNN is not magic: 28 × 28 grayscale clothes are not where it shines. But it wins when the budget is limited, and it is practically unavoidable when pictures are large (224 × 224 × 3 = 150,528 inputs — a dense layer with 100 neurons would have over 15 million weights). Those are exactly the pictures we move to in lessons 9 and 10.