Code

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:

  1. The file opens as a 28×28 grey (L) picture.
  2. .convert("RGB") — ResNet expects 3 channels. A grey image gets three identical ones.
  3. 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).
  4. unsqueeze(0) — the network expects a batch, so one image becomes a batch of one: [1, 3, 224, 224].
  5. The output is four scores (logits), one per class. softmax turns them into probabilities that sum to 1.
  6. 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.