One image: predict.py
Until now the model evaluated folders. Now somebody brings one photo. The road from file to answer is short, but every step can go wrong — and the mistake will not announce itself.
One image, step by step
This is that road with the real model and one test/ image (real output):
PIL image: (28, 28) L
after convert: (28, 28) RGB
tensor: (3, 224, 224) min -2.12 max 2.62
with batch: (1, 3, 224, 224)
logits: (1, 4) [1.34, 5.91, -3.7, -4.63]
probs: [0.01, 0.99, 0.0, 0.0] sum 1.0
classes: ['coat', 'pullover', 'sneaker', 'tshirt']
Read it in order:
- The file opens as a 28×28 grey (
L) picture. .convert("RGB")— ResNet expects 3 channels. A grey image gets three identical ones.- The same transforms as in training: size 224×224 and ImageNet normalisation (which is why the values run from −2.12 to 2.62, not 0…1).
unsqueeze(0)— the network expects a batch, so one image becomes a batch of one:[1, 3, 224, 224].- The output is four scores (logits), one per class.
softmaxturns them into probabilities that sum to 1. - The class names come from the same
model.pt, where we saved the list.
Your job
Create predict.py and test_predict.py (on the right). Both are complete — there are no holes, because this file is glue, not an exercise. Look at one line:
tf = build_transform(False, IMAGENET_MEAN, IMAGENET_STD, size, rgb=True)
It is the same function that make_loaders and make_eval_loader use to prepare images. Not "similar" — the same one. That is how a difference between the two paths (evaluation and prediction) becomes impossible.
Try it:
python predict.py test/coat/0000.png test/sneaker/0003.png test/tshirt/0050.png
test/coat/0000.png: pullover 99%, coat 1%, sneaker 0%
test/sneaker/0003.png: sneaker 100%, coat 0%, pullover 0%
test/tshirt/0050.png: tshirt 100%, coat 0%, pullover 0%
The first image is a coat, and the model says pullover and is 99 % sure. It is one of those 11 coats the model called a pullover. The softmax probability only shows the ratio of the scores, not the real probability of being right: 99 % is no protection against a mistake.
Three ways to get it wrong that nobody will tell you about
test_predict.py holds the most important test: agrees_with_the_evaluation_loader. It takes the same files and sends them two ways: through make_eval_loader and through predict_image. If the paths differ by even one step, the numbers disagree.
That catches all three:
1. Normalize forgotten.
AssertionError: False is not true : …/ant/0.png: predict_image gives [0.501939594745636, 0.498060405254364], evaluation gives [0.5009315013885498, 0.4990684986114502]
The difference is small (the test uses a randomly initialised network), but real. On the real model: the same coat image gets pullover 99 %, coat 1 % with normalisation and pullover 93 %, coat 4 % without; and over 100 test/ images (25 per class) the accuracy without Normalize is 25.0 %. That is exactly the level of random guessing for four classes.
2. Resize forgotten (28 px straight into the network).
AssertionError: False is not true : …/ant/0.png: predict_image gives [0.30359408259391785, 0.6964059472084045], evaluation gives [0.5009315013885498, 0.4990684986114502]
On the real model that is 25.0 % on the same 100 images, too.
3. .convert("RGB") forgotten.
RuntimeError: output with shape [1, 64, 64] doesn't match the broadcast shape [3, 64, 64]
This one at least shouts. (Without Normalize as well, the error would be a different but equally loud one: expected input[1, 1, 224, 224] to have 3 channels, but got 1 channels instead.)
Two of the three mistakes are silent. That is why the test does not compare with a "right answer" (a new image may not have one) but with another path you have already verified.
A practical rule: if accuracy suddenly becomes exactly 1 / number of classes (here 25 %), suspect the preparation — size, normalisation, channels — before you suspect the model.