Unit 06.06: Project step: small CNN image classifier
A complete image classifier, small enough to train on a CPU in seconds and structured like one you would build for real.
Building on a task with a known answer
The images are synthetic โ horizontal versus vertical stripes โ so the correct answer is known and any failure is a bug in the pipeline rather than an ambiguity in the data.
The architecture is the standard shape from Unit 06.02: two convolution-and-pool blocks, then flatten and classify.
import json
import torch
from torch import nn
from torch.utils.data import DataLoader, TensorDataset
torch.manual_seed(0)
# Synthetic 16x16 images: horizontal stripes (0) vs vertical stripes (1).
def make(n, vertical):
imgs = torch.rand(n, 1, 16, 16) * 0.3
for i in range(n):
for k in range(0, 16, 4):
if vertical:
imgs[i, 0, :, k] += 1.0
else:
imgs[i, 0, k, :] += 1.0
return imgs
X = torch.cat([make(200, False), make(200, True)])
y = torch.cat([torch.zeros(200), torch.ones(200)]).unsqueeze(1)
perm = torch.randperm(len(X))
X, y = X[perm], y[perm]
Xtr, ytr, Xva, yva = X[:320], y[:320], X[320:], y[320:]
model = nn.Sequential(
nn.Conv2d(1, 8, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
nn.Conv2d(8, 16, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
nn.Flatten(), nn.Linear(16 * 4 * 4, 1),
)
opt = torch.optim.Adam(model.parameters(), lr=0.01)
loader = DataLoader(TensorDataset(Xtr, ytr), batch_size=32, shuffle=True)
for epoch in range(8):
model.train()
for xb, yb in loader:
opt.zero_grad()
nn.BCEWithLogitsLoss()(model(xb), yb).backward()
opt.step()
model.eval()
with torch.no_grad():
acc = ((torch.sigmoid(model(Xva)) > 0.5).float() == yva).float().mean().item()
print(json.dumps({
"seed": 0,
"architecture": "conv8 -> pool -> conv16 -> pool -> linear",
"parameters": sum(p.numel() for p in model.parameters()),
"train_images": len(Xtr), "val_images": len(Xva),
"val_accuracy": round(acc, 3),
"majority_baseline": round(max(yva.mean().item(), 1 - yva.mean().item()), 3),
"limitation": "synthetic stripes; real images vary in lighting, scale and background",
}, indent=2))
Accuracy should land near 1.0 against a 0.50 baseline โ the task is easy by design, which is exactly what you want when validating a pipeline.
The report records architecture, parameter count, image counts, and the limitation. That last line matters most: real images vary in lighting, scale and background, and this result says nothing about those.
The mistake this prevents
Validating a pipeline on real, messy data. When the accuracy is poor you cannot tell whether the code is wrong or the problem is hard. Debug on data whose answer you already know.
Takeaway
Prove the pipeline on a solvable task first. Then point it at the real problem.
