Code

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:

  • fit does 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 have requires_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 %
  1. 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.
  2. 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).
  3. 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.