Chapter 13 — How do you know if a model is any good?
In one sentence
Test it on data it has never seen, with a metric that matches what you actually care about — and be paranoid about accidentally cheating.
The golden rule
Split your data BEFORE you do anything else:
+-------------------- all your data --------------------+
| TRAIN (70-80%) | VALIDATION (10%) | TEST (10%)|
+----------------------+-------------------+------------+
fit the knobs choose settings touch ONCE,
(learning rate, at the very
model size, ...) end
If you tune anything based on the test set, your test score becomes a lie.
Metrics: accuracy is usually the wrong one
Suppose 99% of emails are legitimate. A model that says "not spam" to everything gets 99% accuracy and is completely useless. You need:
PREDICTED
spam not spam
+-----------+-----------+
spam | TP | FN | <- FN = spam that got through
ACTUAL | (caught) | (missed) |
+-----------+-----------+
not spam | FP | TN | <- FP = real email in the spam folder
| (false | (correct)|
| alarm) | |
+-----------+-----------+
Precision = TP / (TP + FP) "when I say spam, how often am I right?"
Recall = TP / (TP + FN) "of all the spam, how much did I catch?"
F1 = harmonic mean of precision and recall
Which one matters depends on the cost of each mistake:
| Application | You care most about | Why |
|---|---|---|
| Spam filter | Precision | A lost job offer in the spam folder is far worse than one spam in the inbox |
| Cancer screening | Recall | Missing a cancer is far worse than a false alarm that gets a second test |
| Search results | Precision@10 | Users only look at the first page |
| Fraud detection | Depends on the money at stake — usually a tuned balance |
Always ask "what is the cost of each kind of mistake?" before picking a metric.
Overfitting: the most common failure
error
^
| \ ___
| \ ____/ <- TEST error (what matters)
| \ _____/
| \ ____/
| \______________----/
| \___ <- TRAIN error
| \_____________________________
+---------------------------------------------> training time / model size
underfit GOOD overfit
^
+-- stop here ("early stopping")
- Underfitting: bad on train AND test. Model too simple. Fix: bigger model, better features, train longer.
- Overfitting: great on train, bad on test. The model memorised instead of generalising. Fix: more data, simpler model, regularisation, early stopping.
Memorable analogy: underfitting is a student who didn't study. Overfitting is a student who memorised last year's exam answers word for word and fails when the questions change.
Data leakage: the sneaky killer
Leakage = information sneaking into training that won't exist at prediction time, or the test data influencing training. Symptom: suspiciously good scores that collapse in production.
Real examples:
- Predicting churn using a feature
account_closed_date— which only exists because they churned. - Scaling your features using statistics computed over the whole dataset (including the test set) before splitting.
- The same customer appearing in both train and test.
- Predicting stock prices with a random split instead of a time-based split.
- (LLM era) the benchmark's questions appearing in the pretraining data — called contamination, and a serious ongoing problem in AI evaluation.
A famous real case: a pneumonia detector scored 0.95 AUC in the lab and failed in the clinic. It had learned to recognise the portable X-ray machine marker — used on sicker, bed-bound patients — rather than the lungs. The fix was a hospital-stratified split.
Practice (25 min)
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import classification_report, confusion_matrix
import numpy as np
data = fetch_20newsgroups(subset="all",
categories=["rec.sport.baseball", "sci.space", "talk.politics.misc"],
remove=("headers","footers","quotes"))
X_train, X_test, y_train, y_test = train_test_split(
data.data, data.target, test_size=0.2, stratify=data.target, random_state=0)
vec = TfidfVectorizer(min_df=2)
Xtr = vec.fit_transform(X_train) # fit ONLY on train <-- avoids leakage
Xte = vec.transform(X_test) # transform test with the SAME vocabulary
clf = LogisticRegression(max_iter=1000).fit(Xtr, y_train)
pred = clf.predict(Xte)
print(classification_report(y_test, pred, target_names=data.target_names))
print("Confusion matrix (rows=true, cols=predicted):")
print(confusion_matrix(y_test, pred))
print("\n5-fold CV on training data:",
cross_val_score(clf, Xtr, y_train, cv=5).round(3))
Now deliberately cause leakage and watch the score inflate:
# WRONG: fit the vectorizer on ALL data before splitting
vec_bad = TfidfVectorizer(min_df=2)
X_all = vec_bad.fit_transform(data.data) # <-- sees the test set!
Xtr2, Xte2, ytr2, yte2 = train_test_split(X_all, data.target, test_size=0.2,
stratify=data.target, random_state=0)
clf2 = LogisticRegression(max_iter=1000).fit(Xtr2, ytr2)
print("leaky score: ", clf2.score(Xte2, yte2))
print("honest score:", clf.score(Xte, y_test))
The leaky number will be a bit higher. In this small case the difference is modest; in real projects with feature engineering it can be enormous. Build the habit: split first, always.
Common confusions
- "My model is 99% accurate!" Check the class balance first. Always.
- "Cross-validation instead of a test set?" CV replaces the validation split. Keep a final test set you touch once.
- "Can I look at the test set to debug?" Once you look, it's no longer a clean estimate. Use validation for debugging.
Check yourself
- Why is accuracy a bad metric for rare events?
- Explain precision vs recall using the spam example.
- What is data leakage? Give an example.
- Would you optimise for precision or recall in a cancer screening test? Why?
Further reading
- Video: StatQuest, "Machine Learning Fundamentals: Bias and Variance", "The Confusion Matrix", and "ROC and AUC, Clearly Explained" — three short videos that cover this chapter better than most courses.
- Course: Google ML Crash Course — "Classification" module (accuracy, precision/recall, ROC/AUC), with interactive widgets.
- Book: Hands-On Machine Learning (Géron), Chapter 3 — the best practical treatment of evaluation metrics.
- Article: "Data Leakage in Machine Learning" — search for the Kaggle learn micro-course lesson; short and full of real examples.
End of Part 3 — Milestone check
You should now be able to:
- Explain the six-step ML recipe from Chapter 8 from memory
- Build a working text classifier in <20 lines
- Explain what loss and gradient descent are, with the hillside analogy
- Explain what a language model computes:
p(next word | previous words) - Explain the two reasons n-gram models fail
- Split data correctly and pick a metric that matches the business cost
If any box is unticked, revisit that chapter before moving on — Parts 4–6 build directly on all six.