AI Hints
Home›Computer Vision Tutorial›Data for Vision
Computer Vision · Lesson 65 of 111

Rare Defects

When ninety-seven percent means nothing.

⏱ 2 min read▶ Video at 1:42:25🎓 Free
Watch this lesson in the Computer Vision courseWatch on YouTube ↗Full Computer Vision course

On a real line, only about three apples in a hundred are bruised. So a model that always says, fine, scores ninety seven percent, and it catches nothing at all. Rare events are what factories, hospitals, and security systems are all about. So accuracy is the wrong score.

Rare Defects
From the video: Rare Defects
  • Three in a hundred are bruised
  • 'Always fine' scores 97%
  • Accuracy is the wrong score

Oversampling the rare class

We make a realistic training set, where only about three percent are bruised. We train one plain model. Then we train a second one, where each bruised example is repeated thirty times, so that the classes are balanced. And we count how many bruises each one found. Run it. Both have fantastic looking accuracy. But look at the count. The plain model found twenty one of twenty six bruises. The balanced one found twenty four. The accuracy figure barely moved, yet the real result improved. That's why you have to watch recall.

rare.pypython
import torch
from patch import make, net, train

torch.manual_seed(0)
X, y = make(3000, bruise_rate=0.03)
print("Bruised in training:", y.sum().item(), "of", len(y))

Xt, yt = make(1000, seed=9, bruise_rate=0.03)
plain = train(net(), X, y)

rare = (y == 1).nonzero().ravel()
boost = rare.repeat(30)
balanced = train(net(), torch.cat([X, X[boost]]), torch.cat([y, y[boost]]))

for name, m in [("Plain", plain), ("Oversampled", balanced)]:
    pred = m(Xt).argmax(1)
    found = ((pred == 1) & (yt == 1)).sum().item()
    print(f"{name:<12} accuracy {(pred == yt).float().mean():.3f}, found {found} of {yt.sum().item()}")
Output
Bruised in training: 77 of 3000
Plain        accuracy 0.995, found 21 of 26
Oversampled  accuracy 0.998, found 24 of 26
Also uses patch.py
import numpy as np
import cv2
import torch

def make(n, seed=0, light=1.0, bruise_rate=0.5):
    """Simulated apple photos. Label 1 means bruised."""
    rng = np.random.default_rng(seed)
    X, y = [], []
    for _ in range(n):
        img = np.full((48, 48, 3), (70, 75, 80), np.uint8)
        c = tuple(int(v) for v in rng.integers(19, 30, 2))
        r = int(rng.integers(15, 20))
        cv2.circle(img, c, r, (35, 40, int(rng.integers(150, 225))), -1, cv2.LINE_AA)
        bad = rng.random() < bruise_rate
        if bad:
            at = (c[0] + int(rng.integers(-8, 9)), c[1] + int(rng.integers(-8, 9)))
            cv2.circle(img, at, int(rng.integers(3, 6)), (25, 45, 70), -1, cv2.LINE_AA)
        img = img * light + rng.normal(0, 8, img.shape)
        X.append(img.clip(0, 255) / 255)
        y.append(int(bad))
    X = torch.tensor(np.array(X), dtype=torch.float32).permute(0, 3, 1, 2)
    return X, torch.tensor(y)

def net():
    return torch.nn.Sequential(
        torch.nn.Conv2d(3, 16, 3, padding=1), torch.nn.ReLU(), torch.nn.MaxPool2d(2),
        torch.nn.Conv2d(16, 32, 3, padding=1), torch.nn.ReLU(), torch.nn.MaxPool2d(2),
        torch.nn.Flatten(), torch.nn.Linear(32 * 12 * 12, 2))

def train(model, X, y, epochs=8):
    opt = torch.optim.Adam(model.parameters(), lr=2e-3)
    for _ in range(epochs):
        for i in torch.randperm(len(X)).split(64):
            loss = torch.nn.functional.cross_entropy(model(X[i]), y[i])
            opt.zero_grad(); loss.backward(); opt.step()
    return model

def accuracy(model, X, y):
    return (model(X).argmax(1) == y).float().mean().item()

Other tools for rare defects

You've got more options. Weight the loss, so that rare mistakes count for more. Use focal loss, which focuses on hard examples. Copy and paste real defects onto good images, which is a very effective trick. Generate synthetic defects. And use anomaly detection, which needs no defect examples at all. We'll build that soon.

Weighted lossrare errors cost more
Focal lossfocus on hard cases
Copy-pastereal defects, new images
Anomaly detectionno defect examples

Summary

Watch recall, and rebalance your training. Next, a mistake that can make a broken model look perfect.

Key takeaways
  • Accuracy hides rare failures
  • Oversample or weight
  • Track how many you find
Next up: The Leak in Your Split — why your results look too good