Run it — and what it really bought
All tests pass. Now — training.
What was added: main
In the panel — two files in our versions: data.py with the RGB branches
filled in and transfer.py with count_trainable and the new main. If yours
pass the tests, keep yours. The important part of main:
torch.manual_seed(42)
train_loader, val_loader, classes = make_loaders(
args.data, batch_size=32, augment=args.augment, size=args.size, rgb=True
)
model = build_resnet(len(classes), args.finetune)
trainable, total = count_trainable(model)
print(f"trainable parameters: {trainable:,} of {total:,}")
_, acc = evaluate(model, val_loader)
print(f"before training: val accuracy {acc:.1%} (new head is random)")
fit(model, train_loader, args.epochs, args.lr, val_loader)
Two small things that matter:
fitdoes not change. The same loop from lesson 5. Training a borrowed network is the same as training your own; the only difference is which parameters haverequires_grad.- We measure accuracy before training. The new head is random, so it must be about chance (4 classes → ~25 %). If you see 80 % here, something leaked.
Run it
The first run downloads ~45 MB of weights. You use standin_data to see our
numbers (for your own photographs — --data photos, see the homework).
python transfer.py --data standin_data --epochs 5 --lr 0.01
python transfer.py --data standin_data --epochs 5 --lr 0.001 --finetune
Our output (frozen features):
classes: ['coat', 'pullover', 'sneaker', 'tshirt']
trainable parameters: 2,052 of 11,178,564
before training: val accuracy 24.4% (new head is random)
epoch 1 loss 0.6503 train acc 75.2% | val loss 0.5663 val acc 80.6%
epoch 2 loss 0.3715 train acc 85.8% | val loss 0.3856 val acc 88.1%
epoch 3 loss 0.3796 train acc 84.1% | val loss 0.4051 val acc 83.8%
epoch 4 loss 0.1550 train acc 95.0% | val loss 0.2603 val acc 91.2%
epoch 5 loss 0.1288 train acc 96.2% | val loss 0.2493 val acc 92.5%
final: val accuracy 92.5%
and with --finetune:
trainable parameters: 8,395,780 of 11,178,564
before training: val accuracy 24.4% (new head is random)
epoch 1 loss 0.3467 train acc 90.6% | val loss 0.3601 val acc 88.1%
epoch 2 loss 0.1313 train acc 94.7% | val loss 0.2295 val acc 91.9%
epoch 3 loss 0.0229 train acc 99.7% | val loss 0.1708 val acc 94.4%
epoch 4 loss 0.0054 train acc 100.0% | val loss 0.1306 val acc 96.2%
epoch 5 loss 0.0022 train acc 100.0% | val loss 0.1387 val acc 95.6%
final: val accuracy 95.6%
Duration: from ~2 to ~4 minutes (ours: 118 s and 251 s, with the CPU busy) — much longer than lesson 9's 45 s, because every picture has to be passed through the whole 224 × 224 network, even though only the head is trained.
What it means — carefully
| model (the same 640 / 160 split) | trainable params | validation |
|---|---|---|
| lesson 9 CNN from scratch | 11,076 | 92.5 % |
| ResNet-18, frozen | 2,052 | 92.5 % |
ResNet-18 + layer4 |
8,395,780 | 95.6 % |
- Frozen features did not beat a small CNN from scratch — the same 92.5 %, and it trained several times slower. 28 × 28 gray clothes are simply far from ImageNet.
- Fine-tuning bought +3 points (92.5 → 95.6). The gain is real, but modest: on 400 new pictures the network had never seen (lesson 11) these figures were 91.5–91.8 % (CNN from scratch), 91.2 % (frozen) and 93.5–94.0 % (fine-tuned; two runs with a different number of epochs).
- Training accuracy when fine-tuning — 100 %. The network has 8 million parameters and 640 pictures; it memorises all of them. Validation (95.6 %) is still good, but remember lesson 7.
Noise you would not expect
The same version of the code, the same seed — but if you remove the "before
training" line (that one evaluate ahead of fit), you get different numbers:
with the evaluate before training |
without it | |
|---|---|---|
| frozen | 92.5 % | 91.2 % |
| fine-tuned | 95.6 % | 93.8 % |
Why? Every DataLoader iterator created while measuring consumes part of the
random number generator, so the batches of later epochs are shuffled
differently. It is one and the same model; the difference is that a different
order "turned out lucky". It reaches 1.3–1.8 points, while in a 160-picture
set one picture is 0.6. When the next lesson compares two models, remember:
differences of that size are noise.