Run a Model First
Load a pretrained network, classify a photo you took, and see the whole thing work before any theory.
By the end of this session you will be able to:
- Load a pretrained ResNet-50 with TorchVision and get a labeled top-5 prediction for a photo you took yourself
- State the shape and dtype of the data at every step between the JPEG on disk and the answer
- Name the two lines that change the prediction without ever raising an error
A model that already knows a thousand things
Someone has already spent the GPU hours. ResNet-50 was trained on ImageNet, 1.28 million photos sorted into 1000 categories, and the result is a 100MB file of numbers you can download in under a minute. Nothing in this session trains anything. You are borrowing finished weights and watching a photo go through them.
This runs on a laptop CPU. One image through ResNet-50 takes well under a second, so you do not need a GPU for anything in this session.
The course is pinned to PyTorch 2.13 and TorchVision 0.28, which are versioned together:
pip install "torch==2.13.0" "torchvision==0.28.0"The part of TorchVision that matters here is the weights enum. Every pretrained model ships its checkpoint and the exact preprocessing recipe that checkpoint was trained with, bundled in one object. That pairing is the whole design. A model trained on images resized to 232 pixels and normalized with ImageNet's channel means will produce confident nonsense if you hand it a raw JPEG scaled some other way. Because the recipe travels with the weights, you never have to look those numbers up.
Four steps and a photo
Put a photo next to your notebook. Any JPEG or PNG of a single obvious object: a cat, a coffee mug, a bicycle. Then:
import torch
from torchvision.io import decode_image
from torchvision.models import resnet50, ResNet50_Weights
weights = ResNet50_Weights.IMAGENET1K_V2
model = resnet50(weights=weights)
model.eval()
preprocess = weights.transforms()
img = decode_image("my_photo.jpg")
batch = preprocess(img).unsqueeze(0)
with torch.inference_mode():
logits = model(batch)
probs = logits.squeeze(0).softmax(0)
top5 = probs.topk(5)
for score, idx in zip(top5.values.tolist(), top5.indices.tolist()):
print(f"{weights.meta['categories'][idx]:28s} {score:.3f}")The first run downloads the checkpoint to ~/.cache/torch/hub/checkpoints and prints a progress bar. Every run after that reads the cached file.
ResNet50_Weights.DEFAULT also works and is what the official docs use. It is an alias for the best currently available weights, which today means IMAGENET1K_V2. Pin the explicit name instead. DEFAULT is allowed to point somewhere else in a future TorchVision release, and a notebook whose accuracy quietly changes when you upgrade a library is a bad notebook.
weights.transforms() returns a callable preprocessing pipeline. weights.meta is a plain dictionary holding, among other things, categories, the list of 1000 ImageNet class names in the order the model outputs them.
Every shape between the file and the answer
Add this and read the output before you read the explanation:
processed = preprocess(img)
print("decoded ", tuple(img.shape), img.dtype)
print("preprocessed", tuple(processed.shape), processed.dtype)
print("batched ", tuple(batch.shape))
print("logits ", tuple(logits.shape))
print("categories ", len(weights.meta["categories"]))Four shapes, in order:
- Decoded:
(3, 4032, 3024),torch.uint8. Your photo's own resolution, and channels first. PyTorch puts the color channel before height and width. If you have used NumPy or PIL, you are used to the opposite order, and this is the first place that bites. - Preprocessed:
(3, 224, 224),torch.float32. The pipeline resized the short side to 232, cropped the center 224 by 224, converted the 0 to 255 integers to floats in 0 to 1, and subtracted the ImageNet channel means. Note the crop: anything at the edge of your photo is now gone. - Batched:
(1, 3, 224, 224).unsqueeze(0)adds a leading dimension of size 1. The model is built to process a stack of images at once, and a stack of one is still a stack. Skip this call and you get a shape error from somewhere deep inside the network rather than at the input, which is exactly why it reads as confusing the first time. - Logits:
(1, 1000). One raw score per ImageNet category, for the one image in the batch. These are not probabilities. They are unbounded real numbers, and yours will include negatives.softmaxturns the 1000 scores into positive numbers that sum to 1.
That is the entire pipeline: a file becomes integers, integers become normalized floats at a fixed size, floats become 1000 scores, and one index into a list of names becomes an answer.
The two lines that change the answer
model.eval() and torch.inference_mode() are both easy to skip, and skipping either one raises no error at all.
model.eval() flips the model out of training behavior. ResNet-50 has a BatchNorm layer after nearly every convolution, and BatchNorm behaves differently depending on the mode. In training mode it normalizes using the statistics of the current batch. Your batch is one image, so it would normalize that image using only its own numbers instead of the ImageNet-wide statistics baked into the checkpoint. You still get a prediction. It is just a measurably worse one, produced silently. Try it both ways and watch your top-1 score move.
torch.inference_mode() tells PyTorch not to record the operations it would need to compute gradients. You are not training, so that bookkeeping is wasted memory and time. This one costs you speed rather than correctness, and the machinery it disables is the subject of a later session.
Try it
Take five photos with your phone right now. Five different everyday objects, one clear subject each, and copy them next to your notebook.
Run the classifier on all five and print the top-5 for each. Then, for the photo the model got most wrong, print the four shapes from the section above and write one sentence naming which step lost the information the model needed. The center crop is the usual culprit.
You are done when at least three of the five have a plausible label in their top-5, and you can say out loud what (1, 3, 224, 224) means, dimension by dimension, without looking.
If nothing scores above 0.10 on any photo, you have a bug rather than a hard photo. Check model.eval() first.
Common mistakes
- Copying
resnet50(pretrained=True)from an old tutorial. It still runs and still loads weights, but it prints two deprecation warnings and gives you no way to ask for the matching preprocessing. Useweights=soweights.transforms()is available. - Writing your own Resize and Normalize. Hardcoded ImageNet means copied off a blog post are the single most common cause of a pretrained model that predicts sensible-looking garbage.
weights.transforms()is correct by construction. - Feeding an iPhone HEIC file to
decode_image. It handles JPEG, PNG, WEBP and GIF, not HEIC. Convert to JPEG when you export, or you will spend twenty minutes debugging a decoder error that has nothing to do with deep learning. - Calling
argmaxon the logits and reporting that number as a confidence. The index is right, the value is not a probability. Runsoftmaxfirst. - Expecting ImageNet to know your object. There is no class for "laptop charger" or "my friend Omar". There are 120 dog breeds. Check
weights.meta["categories"]before you conclude the model is broken.
Where this goes next
You ran a model before writing a line of Python of your own, which was the point. The next session, Just Enough Python, covers the functions, loops, lists and imports that every notebook from here on opens with, so that the code above stops being something you copy and starts being something you change.