Code

Loading and predicting — write it yourself

evaluate.py can already build a matrix from two tensors: the truth and the predictions. Now it needs to get those tensors from a real model. Two functions, no holes this time: empty skeletons that you fill in completely.

Pseudocode

load_model — turns model.pt back into a working network:

ckpt  ← load the file (onto the CPU)
model ← a new ResNet-18 with as many outputs as ckpt has classes, WITHOUT ImageNet weights
fill model with the weights from ckpt["state_dict"]
switch model to evaluation mode
return model, ckpt["classes"], ckpt["size"]

predict_all — runs every batch through the model:

preds, targets ← two empty lists
for each batch (xb, yb) in loader:
    logits  ← model(xb)
    preds   ← append the INDEX of the highest-scoring class (one per image)
    targets ← append yb
return preds and targets, each glued into one long tensor

A couple of things the pseudocode does not say:

  • Evaluation mode is not a detail. ResNet-18 has BatchNorm layers; in training mode they normalise by the current batch's statistics and change themselves. In the next step you will see what that does to accuracy.
  • predict_all is already marked @torch.no_grad() — it needs no gradients.
  • Every image must appear exactly once, in the loader's order. The matrix and the list of mistakes with file names rely on it.

Your job

evaluate.py (on the right) has grown: load_model and predict_all skeletons with raise NotImplementedError were added, plus image_paths and main, which are ready-made glue (you only read them).

confusion_matrix and class_scores still appear with holes in the panel — the panel shows the skeleton, not your file. In your copy they are already filled in, and you leave them that way.

test_evaluate.py has grown, too. It builds a tiny model (build_resnet(3, pretrained=False) — no internet needed), saves it to a temporary file, and checks that load_model brings it back: the classes, the size, evaluation mode, the number of head outputs, and the same weights.

Run before you write

$ python -m unittest
...
ERROR: test_returns_the_classes_and_the_size_that_were_saved
NotImplementedError
ERROR: test_the_head_has_one_output_per_class
NotImplementedError
ERROR: test_the_model_is_in_evaluation_mode
NotImplementedError
ERROR: test_the_weights_are_the_saved_ones
NotImplementedError
ERROR: test_matches_argmax_in_loader_order
NotImplementedError
Ran 16 tests in 1.116s
FAILED (errors=5)

The eleven old tests pass (they do not depend on the new functions); the five new ones are waiting for you.

When it does not pass

  • AssertionError: True is not false in the …evaluation_mode test — you did not switch the model to eval(). model.training is still True.
  • RuntimeError: Error(s) in loading state_dict for ResNet with a line size mismatch for fc.weight: copying a param with shape torch.Size([3, 512]) from checkpoint, the shape in current model is torch.Size([10, 512]) — you rebuilt the model with the wrong number of classes. Take the number from ckpt["classes"], not from your head.
  • AttributeError: 'list' object has no attribute 'shape' — you returned a list of tensors, not one tensor. Glue them together (torch.cat).
  • AssertionError: Tuples differ: (10, 3) != (10,) — preds holds the score of every class (3 numbers per image), but you need one number: the class index. Pick the highest score along the class axis (argmax(dim=1)).