Skip to main content
Milestone #06Not StartedPhase 3Intermediate10–14 hours · 1–2 weeks12 required tasks

Image Classifier

Train, evaluate, checkpoint, and diagnose an image classification pipeline in PyTorch

PyTorchNumPyMatplotlib
Build Progress0 / 12 tasks (0%)
0%(0/12 steps)Not Started
Project Specification & Architecture

What You Will Build

Having built automatic differentiation (Project 4) and a NumPy neural network from scratch (Project 5), you now have the conceptual foundation to use PyTorch with complete understanding of what happens inside `loss.backward()` and `optimizer.step()`. In this project, you will build a reproducible computer vision training and evaluation pipeline on a classic benchmark such as MNIST or Fashion-MNIST. You will explore class distributions, normalize pixel intensities, wrap splits in `DataLoader` batches, compare a flattened MLP against a Convolutional Neural Network (`Conv2d`), track training and validation metrics, plot a 10×10 confusion matrix, inspect the highest-confidence misclassifications, and serialize/reload model checkpoints for standalone inference.

Why This Project Matters in the AI/LLM Stack

This project establishes the canonical PyTorch engineering workflow (`Dataset` -> `DataLoader` -> `nn.Module` -> `train/eval` loop -> `state_dict` checkpointing) that you will reuse for language modeling, Transformers, and LLM pretraining in Phases 4–8.

Learning Objectives

  • Load, inspect, and normalize an image dataset and construct batched PyTorch `DataLoader` pipelines.
  • Implement a clean `nn.Module` architecture (MLP and/or CNN) and separate `train_one_epoch` and `evaluate` functions.
  • Generate a 10×10 confusion matrix and visually inspect misclassified images to diagnose failure patterns.
  • Save model weights and metadata with `torch.save(state_dict)` and reload them into a fresh model instance for deterministic inference.

Prerequisites

  • Project 5 (Neural Network From Scratch) and Phase 3 PyTorch fundamentals
  • Understanding of `torch.Tensor`, `nn.Module`, `DataLoader`, and `model.train()` vs `model.eval()`

Required Mathematical Concepts

  • Channel-wise pixel standardization: x_norm = (x - μ) / σ
  • 2D spatial convolution and receptive fields: H_out = ⌊(H + 2P - K)/S⌋ + 1
  • Multi-class Cross-Entropy loss over C = 10 class logits
  • 10×10 Confusion Matrix where entry M_{i,j} counts true class i predicted as class j
Interactive Engineering Checklist

Step-by-Step Implementation Tasks (12 Steps)

Check off each engineering task as your code passes its acceptance criteria. Progress is saved automatically in your browser.

Stage 1 of 40/4 tasks complete

Dataset Exploration, Normalization & DataLoaders

Load MNIST/Fashion-MNIST, inspect class balance and sample grids, normalize pixel tensors, and create DataLoaders.

Core

MNIST (`28×28` grayscale handwritten digits `0–9`) or Fashion-MNIST provides 60,000 training images and 10,000 test images that train rapidly on CPU or GPU.

Implementation: Load MNIST (or Fashion-MNIST) and split the 60,000 training images into 50,000 training and 10,000 validation examples, keeping the 10,000 test set strictly held out.
Expected Outcome: Three distinct dataset splits: `train_ds` (50k), `val_ds` (10k), and `test_ds` (10k).
Acceptance Check: Fixed random seed (`torch.manual_seed(42)`) ensures reproducible train/val splitting.
Core

Checking label counts and plotting a grid of raw images ensures labels align with visual content.

Implementation: Count samples per digit class `0–9` and save a `4×8` Matplotlib grid displaying sample images with their ground-truth labels.
Expected Outcome: Class distribution summary and sample visualization grid.
Acceptance Check: Verifies image tensor shape `(1, 28, 28)` and pixel range.
Core

Scaling uint8 pixels `[0, 255]` to zero-mean, unit-variance float32 tensors stabilizes gradient magnitudes in the first layer.

Implementation: Convert images to `float32` in `[0.0, 1.0]` and normalize using the training set's mean (`0.1307` for MNIST) and standard deviation (`0.3081`).
Expected Outcome: Normalized input tensors have mean `~0.0` and std `~1.0` across the training split.
Acceptance Check: Validation and test splits use the exact same normalization constants.
Core

PyTorch `DataLoader` handles mini-batching and per-epoch shuffling automatically.

Implementation: Wrap `train_ds`, `val_ds`, and `test_ds` in `DataLoader` instances (`batch_size=64` or `128`), enabling `shuffle=True` for `train_loader` and `shuffle=False` for `val_loader` and `test_loader`.
Expected Outcome: Iterating `next(iter(train_loader))` yields `(images, labels)` of shapes `(B, 1, 28, 28)` and `(B,)`.
Acceptance Check: Validation and test loaders do not shuffle examples.
Stage 2 of 40/3 tasks complete

Network Architecture, Training Loop & Validation

Define the `nn.Module` classifier and implement reproducible training and `torch.no_grad()` validation loops.

Core

Subclassing `torch.nn.Module` lets you define either a flattened MLP or a spatial CNN (`Conv2d -> ReLU -> MaxPool2d -> Linear`) that outputs 10 raw class logits.

Implementation: Implement `DigitClassifier(nn.Module)` in `vision/model.py`. Return raw unnormalized logits of shape `(B, 10)` from `forward(x)` (do not apply `Softmax` inside `forward` since `nn.CrossEntropyLoss` expects raw logits).
Expected Outcome: Passing a dummy tensor `torch.randn(8, 1, 28, 28)` outputs logits of shape `(8, 10)`.
Acceptance Check: Includes a helper function counting total trainable parameters (`sum(p.numel() for p in model.parameters() if p.requires_grad)`).
Core

Each training batch follows the five-step PyTorch rhythm: `model.train()`, forward pass, loss calculation, `optimizer.zero_grad()`, `loss.backward()`, and `optimizer.step()`.

Implementation: Implement `train_one_epoch(model, loader, criterion, optimizer, device)` that computes average batch loss and training accuracy across the epoch.
Expected Outcome: Returns `(avg_train_loss, train_accuracy)` for each epoch.
Acceptance Check: Explicitly sets `model.train()` at the start of the function.
Core

During validation, we disable gradient tracking (`with torch.no_grad():`) and set `model.eval()` so Dropout and BatchNorm use evaluation behavior.

Implementation: Implement `evaluate(model, loader, criterion, device)` decorated with `@torch.no_grad()` and calling `model.eval()`.
Expected Outcome: Fast, memory-efficient evaluation returning `(val_loss, val_accuracy)`.
Acceptance Check: No gradients are allocated during validation.
Stage 3 of 40/3 tasks complete

Accuracy Evaluation, Confusion Matrix & Error Analysis

Compute test accuracy, build a 10×10 confusion matrix, and visualize misclassified examples.

Core

After selecting the best model checkpoint based on validation accuracy, final performance is measured once on the held-out 10,000-image test set.

Implementation: Train for 5–10 epochs, log train/val loss and accuracy curves, and evaluate overall test accuracy and per-class accuracy on `test_loader`.
Expected Outcome: Test accuracy `>= 97.5%` (MLP) or `>= 99.0%` (CNN) on MNIST.
Acceptance Check: Reports both overall test accuracy and accuracy for each digit `0–9`.
Core

A 10×10 confusion matrix reveals which digit pairs are most frequently confused (such as `4` vs `9` or `3` vs `5`).

Implementation: Compute a `10×10` integer confusion matrix `cm[true_label, pred_label]` over the test set and render it as an annotated heatmap using Matplotlib.
Expected Outcome: Saved `plots/confusion_matrix.png` showing diagonal dominance and off-diagonal confusions.
Acceptance Check: Sum of all entries in the confusion matrix equals `len(test_ds)` (`10,000`).
Core

Visualizing the model's top mistakes alongside their predicted softmax probabilities reveals ambiguous handwriting or systematic model blind spots.

Implementation: Collect misclassified test samples, sort them by highest wrong-class confidence, and plot the top 12 errors showing `True: y | Pred: ŷ (p=...)`.
Expected Outcome: Saved figure `plots/top_misclassifications.png` and written failure analysis.
Acceptance Check: Displays the actual image, true label, predicted label, and softmax confidence.
Stage 4 of 40/2 tasks complete

Checkpoint Serialization & Standalone Inference

Save best checkpoints and verify reloading into a fresh model for single-image inference.

Core

Saving `model.state_dict()` along with configuration metadata allows resuming training or deploying the model without retraining.

Implementation: Whenever validation accuracy improves, save a checkpoint dictionary `{'model_state_dict': model.state_dict(), 'val_acc': val_acc, 'epoch': epoch, 'config': config}` to `checkpoints/best_model.pt`.
Expected Outcome: Persisted `.pt` checkpoint file containing weights and validation metadata.
Acceptance Check: Uses `state_dict()` rather than pickling the entire class instance.
Core

An inference function instantiates a fresh model, loads `state_dict`, sets `model.eval()`, and returns the predicted class and probability distribution for a single image.

Implementation: Implement `predict_single_image(checkpoint_path, image_tensor)` and write a test verifying that the reloaded model produces identical logits (`torch.allclose`) to the original trained model.
Expected Outcome: Reloaded model outputs exact same predictions and probabilities as before saving.
Acceptance Check: Automated test verifies `torch.allclose(logits_before, logits_after)`.
Reference Architecture & Verification Code

Starter & Verification Code Examples

PyTorch Evaluation Loop & Checkpoint Save/Reload Pattern

Demonstrates the standard `@torch.no_grad()` evaluation function and `state_dict` checkpointing.

import torch
import torch.nn as nn
from torch.utils.data import DataLoader

@torch.no_grad()
def evaluate(
    model: nn.Module,
    loader: DataLoader,
    criterion: nn.Module,
    device: torch.device,
) -> tuple[float, float, torch.Tensor]:
    model.eval()
    total_loss = 0.0
    correct = 0
    total = 0
    confusion = torch.zeros(10, 10, dtype=torch.int64)

    for images, labels in loader:
        images, labels = images.to(device), labels.to(device)
        logits = model(images)
        loss = criterion(logits, labels)

        total_loss += loss.item() * images.size(0)
        preds = logits.argmax(dim=1)
        correct += int((preds == labels).sum().item())
        total += images.size(0)

        for t, p in zip(labels.view(-1), preds.view(-1)):
            confusion[int(t), int(p)] += 1

    return total_loss / total, correct / total, confusion
Verification & Acceptance Suite

How to Test Your Implementation

Test forward output shapes, single-batch overfitting sanity check, and checkpoint save/load determinism.

$ pytest tests/test_image_classifier.py -vTest Runner

Single-Batch Overfit Sanity Check

Verify that the model and optimizer can drive training loss near zero on a single mini-batch of 16 images.

Steps: Take 1 batch of 16 images, run 50 optimizer steps, and check final accuracy on that batch.

Pass Criteria: Batch accuracy reaches 100% and loss drops below 0.02.

Checkpoint Save & Reload Logit Equivalence

Confirm that `state_dict` serialization preserves exact model behavior in `eval()` mode.

Steps: Save `model.state_dict()` to a temporary path, instantiate a fresh `DigitClassifier`, call `load_state_dict`, and compare outputs on a test batch.

Pass Criteria: `torch.allclose(out1, out2, atol=1e-6)` is True.

Overall Project Acceptance Criteria

  • Dataset normalized using training statistics and wrapped in train/val/test DataLoaders.
  • Training and `torch.no_grad()` validation loops implemented with `model.train()` and `model.eval()`.
  • 10×10 confusion matrix and visual analysis of misclassified examples generated.
  • Model checkpoint saved via `state_dict` and verified to produce identical inference outputs when reloaded.
Progressive Spoiler-Free Guidance

Expandable Hints & Debugging Tips

Try implementing each stage on your own first. Expand a hint below if you want to verify tensor shapes, mathematical identities, or common edge cases.

Optional Bonus Extensions

Optional Stretch Challenges

Optional · Does Not Block Completion

MLP vs. CNN Parameter-Efficiency Benchmark

Compare a 2-layer MLP against a 2-layer CNN with fewer total parameters, measuring test accuracy and robustness to 2-pixel image shifts.

What You Learn: Why translation equivariance and weight sharing in convolutions outperform dense layers on spatial grids.

First-Layer Convolutional Filter & Feature Map Visualization

Plot the learned `3×3` or `5×5` convolutional kernels of `conv1` and the resulting activation maps on a test digit.

What You Learn: How early CNN layers learn edge, stroke, and corner detectors.

Expected Deliverable

What Your Completed Repository Should Contain

A trained image classifier and reproducible evaluation report.

Deliverable Artifacts

  • PyTorch image classification pipeline (`vision/model.py`, `vision/train.py`)
  • 10×10 confusion matrix and top-misclassifications diagnostic plots
  • Saved model checkpoint and standalone inference verification test

After Completing This Project, You Should Be Able To...

  • Build end-to-end PyTorch data pipelines with normalization and batched `DataLoader` splits.
  • Train and validate deep neural networks using proper `model.train()`, `model.eval()`, and `torch.no_grad()` semantics.
  • Diagnose classification performance using per-class accuracy, confusion matrices, and visual error inspection.
  • Serialize and reload trained model checkpoints (`state_dict`) for reproducible inference.
Portfolio & Repository Tracking

GitHub Repository Submission

Link your public or private GitHub repository URL to keep a structured portfolio of your from-scratch AI builds. Stored locally in your browser (self-reported; no GitHub tokens or remote code execution).

Status: Not Linked

Suggested Repository: pytorch-image-classifier

Recommended Directory Layout:

vision/model.py
vision/train.py
vision/infer.py
plots/confusion_matrix.png
tests/test_image_classifier.py
README.md

Recommended README Documentation Checklist

  • Architecture summary and trainable parameter count
  • Training/validation curves and 10×10 confusion matrix
  • Misclassification analysis and inference usage instructions
Local Engineering Notebook

Personal Implementation Notes