Measuring a model · AI Foundations
Your model is 95% accurate. Is it any good?
Course contentsWhy you need it
A single score can hide a useless model. If only a few cases are the ones you care about, such as fraud or a disease, a model that always says "no" looks excellent and catches nothing. Choosing the right measure, and checking it on unseen data, is how you tell a real model from one that only looks good.
The intuition
Imagine 5 frauds in 100 transactions. A model that always predicts "not fraud" is right 95 times: accuracy 95%, frauds caught 0.
Split the results into four boxes, the confusion matrix (1 = fraud, the "positive" class):
| predicted 1 | predicted 0 | |
|---|---|---|
| actual 1 | true positive (TP) | false negative (FN) |
| actual 0 | false positive (FP) | true negative (TN) |
Two questions then matter:
- Precision = TP / (TP + FP): of the cases we flagged, how many were real?
- Recall = TP / (TP + FN): of the real cases, how many did we catch?
They pull against each other. Flag everything and recall is perfect but precision is terrible. F1 = 2 × P × R / (P + R) combines them into one number that's only high when both are.
Finally, overfitting: a model can learn its training rows by heart, noise and all. The sign is a big gap, such as 99% on training data and 60% on the test set.
Definition
Accuracy
The share of all predictions that were right. Misleading when one class is rare.
Precision
Of the cases predicted positive, the share that really were: TP / (TP + FP).
Recall
Of the real positive cases, the share the model found: TP / (TP + FN).
Overfitting
Learning the training data so closely, noise included, that the model does worse on new data.
Examples
The accuracy trap
Try it yourselfAlways saying "no" scores 95% and catches nothing.
actual = [0] * 95 + [1] * 5
always_no = [0] * 100
correct = sum(1 for a, p in zip(actual, always_no) if a == p)
print("accuracy:", correct / len(actual))
caught = sum(1 for a, p in zip(actual, always_no) if a == 1 and p == 1)
print("fraud caught:", caught, "of", actual.count(1))accuracy: 0.95 fraud caught: 0 of 5
- L195 normal transactions, 5 frauds (made-up data).
- L495 of 100 right.
accuracy: 0.95 - L6Recall is 0: not one fraud found.
fraud caught: 0 of 5
Confusion matrix
Try it yourselfCount the four boxes, then precision and recall.
actual = [1, 1, 1, 0, 0, 0, 0, 1, 0, 0]
predicted = [1, 0, 1, 1, 0, 0, 0, 1, 0, 1]
pairs = list(zip(actual, predicted))
tp = pairs.count((1, 1))
fp = pairs.count((0, 1))
fn = pairs.count((1, 0))
tn = pairs.count((0, 0))
print("TP", tp, "FP", fp, "FN", fn, "TN", tn)
print("precision", tp / (tp + fp))
print("recall", tp / (tp + fn))TP 3 FP 2 FN 1 TN 4 precision 0.6 recall 0.75
- L3Each pair is (actual, predicted).
- L5(0, 1): we said yes, it was no.
- L93 of the 5 we flagged were real.
precision 0.6 - L10We caught 3 of the 4 real ones.
recall 0.75
A model that memorises
Try it yourselfPerfect on its training rows, a coin flip on new ones.
train = {(1, 1): "red", (2, 1): "red", (8, 8): "blue", (9, 8): "blue"}
test = {(1, 2): "red", (8, 9): "blue"}
def memoriser(point):
return train.get(point, "red")
def accuracy(model, data):
return sum(model(p) == label for p, label in data.items()) / len(data)
print("train", accuracy(memoriser, train))
print("test", accuracy(memoriser, test))train 1.0 test 0.5
- L4Look the point up; if it's new, just guess red.
- L10Every training point is in the table.
train 1.0 - L11(8, 9) is clearly blue, but it's not in the table.
test 0.5
Common mistakes
Reporting only accuracy
With a rare class, accuracy is high even for a useless model. Report precision and recall for the class you care about.
Dividing by zero
A model that never predicts positive has TP + FP = 0. Decide what precision means then (usually 0) instead of crashing.
Tuning on the test set
If you try 50 settings and keep the best test score, the test set has become training data. Use a separate validation set for tuning.
Quick quiz
1. What does this print?
actual = [1, 0, 1, 1]
predicted = [1, 1, 0, 1]
tp = sum(1 for a, p in zip(actual, predicted) if a == 1 and p == 1)
fp = sum(1 for a, p in zip(actual, predicted) if a == 0 and p == 1)
print(round(tp / (tp + fp), 2))Show the answer
Answer: A
TP = 2, FP = 1: precision 2 / 3 ≈ 0.67.
2. A spam filter must almost never send a real email to spam. Which number matters most?
Show the answer
Answer: A
Precision asks: of the emails we called spam, how many really were? A false positive is a lost real email.
3. Training accuracy is 99%, test accuracy is 60%. What's going on?
Show the answer
Answer: A
The model learned the training rows, not the pattern.
Practice
3 exercises in three steps: predict what a program prints, change a program to meet a new requirement, then write one from scratch. Your code runs in the browser and is checked the moment you submit.
- PredictCount the boxes
Type exactly what this prints.
- ModifyFix recall
recalluses the wrong box. Fix it to TP / (TP + FN). When there are no real positives at all (TP + FN is 0), return0.0. - WriteA full report
Write
report(actual, predicted)(lists of 0 and 1) that returns a dictionary with"precision","recall"and"f1", each rounded to 3 decimals.- any measure whose bottom part is 0 is
0.0 - F1 = 2 × P × R / (P + R), from the unrounded P and R
report([1, 1, 1, 0, 0, 0, 0, 1, 0, 0], [1, 0, 1, 1, 0, 0, 0, 1, 0, 1]) # {'precision': 0.6, 'recall': 0.75, 'f1': 0.667} - any measure whose bottom part is 0 is
Practise this lesson
Open the interactive lesson to edit and run these examples and solve the exercises with instant feedback. Free to start, no sign-up.
Interactive lessons open in order. If you have not done the earlier ones yet, you start at the course map.
Terms in this lesson
| Term | Chinese | Definition |
|---|---|---|
| Accuracy | 准确率 | The share of predictions that were right. |
| Confusion matrix | 混淆矩阵 | Counts of TP, FP, FN and TN. |
| Precision | 精确率 | TP / (TP + FP): how many flagged cases were real. |
| Recall | 召回率 | TP / (TP + FN): how many real cases were found. |
| Overfitting | 过拟合 | Doing well on training data and badly on new data. |