A model you can reload, and new images
You know how to build the matrix. But it only becomes useful once you have something to put into it: a model that can be loaded from disk, and images nobody has seen. This step makes two small changes — both written for you, because they are plumbing, not an exercise.
1. data.py: a folder of "new images"
make_loaders from lesson 9 splits a folder into training and validation. A fresh test/ folder needs no splitting: it needs every image, untouched, in the same order.
def make_eval_loader(
root: str, batch_size: int = 32, size: int = 224, rgb: bool = True
) -> tuple[DataLoader, list[str]]:
It calls the same build_transform (same size, same normalisation, no augmentation) and a DataLoader without shuffling. The only new thing is that nothing is split.
2. transfer.py: a model you can rebuild
So far transfer.py saved nothing after training — the model vanished with the process. Three changes:
build_resnet(..., pretrained=True)— a new parameter. When you turn it off, a fresh ResNet-18 is created without ImageNet weights and nothing is downloaded. You will need it when we rebuild the architecture only to fill it with saved weights.--out model.pt— where to save.torch.save({...})at the end.
Look at what we save:
torch.save(
{"state_dict": model.state_dict(), "classes": classes, "size": args.size},
args.out,
)
Not just the weights. The class list and the size the model was trained with travel along. If you kept only the state_dict you would lose two things:
- The class names.
ImageFoldersorts classes in alphabetical order, not in the order you had in your head. With a hand-typed list the same picture gets another name:
right class list : ['coat', 'pullover', 'sneaker', 'tshirt'] -> sneaker
a list typed by hand: ['tshirt', 'sneaker', 'pullover', 'coat'] -> pullover
The same image — a sneaker. The model did not err, but every prediction carries the wrong name, and nothing reports it.
- The size. A prediction for one image must prepare it the same way as in training: the same size. If you do not save it, you have to guess.
A bare state_dict (122 keys for ResNet-18) has no class list in it.
The tests grew
test_evaluate.py gained two new blocks: make_eval_loader (every image, order unchanged, three channels, the right size) and build_resnet(pretrained=False) (works offline, the head has as many outputs as there are classes). The earlier seven tests are untouched.
$ python -m unittest
...........
Ran 11 tests in 0.361s
OK
Training now saves the model
If you do not have a model.pt yet, train — the same command as in lesson 10, with --finetune, which now writes the file:
python transfer.py --data data --epochs 5 --lr 0.001 --finetune
It takes a few minutes (on our CPU, in two runs — 148 and 199 seconds). model.pt comes out at ~45 MB (ours: 44,785,163 bytes): ResNet-18 with 11.2 million weights.
data.pyandtransfer.pyshow the full files in the panel with the changed lines highlighted. The rest stays exactly as you had it in lesson 10.