Evaluation & Metrics
Precision, recall, ROC/AUC, calibration, and honest model evaluation and validation.
Start here — a model that is 99% accurate and completely useless
A hospital wants to screen for a rare disease. About 1 person in 100 actually has it. They ask two teams to build a model, and they test both on the same 10,000 people.
Team A ships a real model that reads scans and flags patients at risk.
Team B ships one line of code:
def predict(patient):
return "healthy" # always
Team B's model never looks at anything. It just says "healthy" every single time. And it is 99% accurate — because 9,900 of the 10,000 people really are healthy, and it got every one of them right.
Team A's real model scores 95.6%.
The model that detects nothing scored higher than the model that actually works. If the hospital picks the winner by accuracy, it ships the model that misses every single sick patient — and it will look like a great decision on the dashboard.
This is not a trick question or an edge case. It is the single most common way real machine-learning projects fail, and it is why this whole lesson exists.
is not wrong, exactly. It answers a question — how often is the model right? — perfectly well. The problem is that it is almost never the question you actually care about.
A classifier can fail in two completely different ways: it can raise a false alarm on a healthy person, or it can miss a sick one. Those mistakes cost wildly different things. A false alarm costs a follow-up test and a bad afternoon; a miss can cost a life. Accuracy blends both into one number and then throws away which was which. Everything in this lesson is about not doing that.
An alarm that is silent 365 days a year is correct on 364 of them. Rate it on "days it was right" and it scores 99.7% — better than a working alarm that chirps once when you burn toast. But you do not buy a smoke alarm to be right on quiet days. You buy it for the one day that matters, and that day never appears in the average.
A fraud detector is 99.9% accurate on a dataset where 0.1% of transactions are fraud. What is the cheapest way to get that score, and does it catch any fraud?
Hint: What does the model that always predicts the majority class score?
It teaches from first principles: every metric arrives as a plain-language question first, then a worked example with real numbers, and only then the formula. Flip the Depth switch at the top for the formal definitions and the derivations — nothing is hidden for good, and the deeper panels open automatically once you have the prerequisites.
The four boxes — the confusion matrix
Since one number hides which mistake happened, let us stop compressing and just count all four outcomes separately.
Every prediction on a yes/no problem lands in exactly one of four boxes: the model said yes or no, and the truth was yes or no. That two-by-two table is the , and every metric in the rest of this lesson is built out of it.
- — flagged, and genuinely sick. A correct catch.
- — flagged, but actually healthy. A false alarm.
- — cleared, but actually sick. A miss.
- — cleared, and genuinely healthy. A correct all-clear.
Here is what Team A's real model actually produced on those 10,000 people. Look at the picture before the numbers:
Read the table the way a doctor would, one true group at a time.
The 100 people who really were sick. The model flagged 75 of them (TP) and cleared 25 (FN). So it caught three out of four, and 25 sick people were sent home believing they were fine.
The 9,900 people who really were healthy. The model cleared 9,489 (TN) and flagged 411 (FP). So 411 healthy people got a frightening phone call and an unnecessary follow-up test.
Now the accuracy. Correct predictions are the two boxes on the diagonal:
And there is the whole problem in one line. The number 9,489 dominates that fraction so completely that the 75 catches and the 25 misses barely move it. Accuracy is not lying — it is answering a question about the 9,900 healthy people, when the entire point of the model is the 100 sick ones.
Run the counts yourself. This cell hard-codes Team A's four boxes and Team B's one-line model, and puts their scores side by side:
Of the four boxes, which one is a miss and which one is a false alarm?
Hint: Positive means the model said yes.
Precision and recall — two different questions
Rather than blending the four boxes into one score, ask two sharp questions and answer each separately.
Question 1 — When the model raises the alarm, should I believe it? Of everyone it flagged, how many were really sick? That is precision. It is a question about the model's outputs.
Question 2 — Of the people who need finding, how many did it find? Of everyone who really was sick, how many did it flag? That is recall. It is a question about the truth.
They are different questions with different denominators, and a model can ace one while failing the other.
asks: how trustworthy is an alarm?
asks: how complete is the search?
Cast a net into a lake and haul it in.
Precision is: of everything in your net, what fraction is actually fish? Drag up a net full of boots and weeds and your precision is terrible, however many fish you also got.
Recall is: of all the fish in the lake, what fraction ended up in your net? Leave most of the lake unfished and your recall is terrible, however clean your catch.
And now the tension that runs through this entire lesson: you can trivially max out either one. Want perfect recall? Drag a net across the whole lake — you catch every fish, plus every boot (precision collapses). Want perfect precision? Reach in and grab one fish you can already see — one for one, flawless, and you left the lake full (recall collapses). Neither number means anything on its own. They only mean something together.
Team A's counts again: , , , .
Precision — of everyone flagged, how many were sick? The model flagged people in total, and 75 of them were genuinely sick:
15.4%. When this model raises the alarm, it is wrong more than five times out of six. Roughly 85 of every 100 people it calls back are perfectly healthy.
Recall — of everyone sick, how many were flagged? There were sick people, and it found 75:
75%. It catches three sick patients in four, and misses the fourth.
Notice how much more this tells you than "95.6% accurate". Two numbers, two clear sentences: it finds three quarters of the disease, and five sixths of its alarms are false. Whether that is a good model now depends on something no formula knows — how much a missed diagnosis costs compared to an unnecessary follow-up test. For screening, where a miss can be fatal and a false alarm just means another blood draw, this trade might be exactly right. For an automated system that starts chemotherapy, it would be indefensible. The metric cannot make that call for you. It can only make the trade-off visible.
A spam filter flags 200 emails. 180 of them really are spam. There were 400 spam emails in the inbox in total. What are its precision and recall?
F1 — when you really do need one number
Sometimes you have to rank fifty models on a leaderboard, and two numbers per model will not do. The usual compromise is the .
The obvious move — average precision and recall — fails immediately. Flag every single person and you get recall 1.0 with precision near 0.01; the plain average is about 0.50, which sounds respectable for a model that is useless. F1 uses a different kind of average, one that is dragged down hard by the smaller of the two. On that same useless model F1 is about 0.02. The rule of thumb: F1 is only high when both are high.
The threshold is a dial, not a fact
Here is the thing almost every beginner misses. Team A's model does not output "sick" or "healthy". Like nearly every classifier, it outputs a score between 0 and 1 — a confidence, say 0.83. Something has to turn that score into a decision, and that something is a cut-off you choose:
flag = score >= 0.5 # 0.5 is a DEFAULT, not a law of nature
That 0.5 is the , and it is where the precision-recall trade-off actually lives.
Everything you computed above — the 75, the 411, the precision of 0.154 — describes the threshold, not the model. Lower the threshold to 0.3 and the model flags far more people: recall climbs, precision falls. Raise it to 0.8 and it flags only the most obvious cases: precision climbs, recall falls. The trained model never changed. Only the dial did.
This is genuinely liberating once it clicks: you do not need to retrain a model to change its precision and recall. You need to move one number, which takes about a second.
Turn the sensitivity up and the detector beeps at every bottle cap, buried nail, and coin — you will not walk past the buried treasure (high recall), but you will be digging holes all afternoon (low precision). Turn it down and it only beeps for something big and metallic — every hole you dig is worth it (high precision), but you will stroll straight past smaller finds (low recall). Same detector, same beach. One dial.
Watch it happen. This is Team A's exact model, swept across every threshold from 0.05 to 0.95:
If you take one practical habit away, take this: after training, sweep the threshold and look at this curve before you ship anything. It costs one loop over your validation set, it never requires retraining, and it routinely delivers a bigger improvement in the metric you actually care about than a week of architecture tuning.
Do the sweep yourself on a set small enough to check by eye — twelve patients, six sick and six healthy, sorted by the model's score:
Your model must never miss a fraudulent transaction, even at the cost of many false alarms. Which way do you move the threshold, and what happens to precision?
Hint: Never miss means recall must be as close to 1 as possible.
ROC and AUC — grading the model, not the threshold
The threshold sweep raises an awkward question. If precision and recall depend entirely on a dial you chose, how do you compare two models without both teams tuning their own dial and declaring victory?
The answer: judge the model across every threshold at once. That is what an does. (The strange name is wartime radar jargon — it is about "receiver operators" reading radar screens, and it tells you nothing useful about the method.)
It plots two of the rates you already know against each other:
- The (TPR) on the y-axis. This is just recall, wearing a different hat: of the sick, how many did we catch?
- The (FPR) on the x-axis: of the healthy, how many did we wrongly alarm?
Each point on the curve is one threshold. Sweep the threshold from 1 down to 0 and you trace the curve from the bottom-left corner (flag nobody: no catches, no false alarms) to the top-right (flag everybody: all catches, all false alarms).
Up and to the left is good. The top-left corner is perfection: you caught every sick person (TPR = 1) and alarmed no healthy person (FPR = 0). The diagonal line is the coin flip — a model with no information at all, where catching 30% more of the sick costs you exactly 30% more false alarms. The further your curve bulges toward that top-left corner, the better the model separates the two groups, at every threshold simultaneously.
That area has a name: .
- AUC = 1.0 — perfect. Every sick person scores above every healthy person.
- AUC = 0.5 — the diagonal. No information whatsoever.
- AUC below 0.5 — worse than chance, which almost always means your labels are flipped somewhere. (Invert the predictions and you have a good model.)
Pick one sick person and one healthy person at random. AUC is the probability that the model gives the sick one a higher score. That is the entire meaning — and notice what it is not about: it never mentions a threshold, or how many people are sick, or what any score literally means. AUC grades ranking quality only. A model that scores every sick person at 0.9001 and every healthy one at 0.9000 has a perfect AUC of 1.0 while being wildly overconfident nonsense.
Compute an AUC from scratch — no library, just counting which pairs the model ranked correctly:
The trap: AUC looks fantastic on imbalanced data
Team A's AUC is 0.965. That is a number you would put on a slide. And you already know that at its actual operating point, 85% of its alarms are false.
Both facts are true. The ROC curve is not lying — it is answering a question about ranking, and the model genuinely ranks well. The problem is what the x-axis does with a large negative class.
FPR divides false alarms by 9,900 healthy people. Going from 0 to 411 false alarms moves the x-axis by only 0.04 — a sliver you can barely see. But precision divides those same 411 false alarms by the 486 flags actually raised, where they are overwhelming. Same errors, same model. One denominator makes them look negligible; the other shows them for what they are.
The rule: when positives are rare, plot precision against recall instead. The precision-recall curve has no term that grows with the size of the negative class, so it cannot flatter you the same way.
- Always report the no-skill baseline alongside the score. AUC's baseline is always 0.5; average precision's baseline is the prevalence — 0.01 here. A PR-AUC of 0.5 is spectacular at 1% prevalence and mediocre at 45%.
- Never compare PR curves across datasets with different class balance. Their y-axes are on different scales. ROC is the one that survives resampling.
- Report the operating point, not just the curve. A curve is a menu of options; a deployed model is one dish. State the threshold and the precision and recall it delivers.
A model reports AUC 0.98 on a dataset where 0.5% of examples are positive. In production, 96% of its alerts turn out to be false alarms. Which statement is correct?
Calibration — do the probabilities mean anything?
Everything so far has graded the model on ordering — did it put the right examples above the others? But a score of 0.7 looks like it is claiming something stronger: a 70% chance this person is sick. Is it?
Usually, no.
Collect every day a forecaster said 70% chance of rain. If it rained on about 70% of those days, the forecaster is calibrated — the number means what it says. If it rained on only 20% of them, the forecaster is overconfident: still possibly great at ranking rainy days above dry ones (fine AUC!), but the number itself is not a probability. It is just a score that happens to live between 0 and 1.
is that property, and you check it with a : bucket predictions by confidence, then plot what the model claimed against what actually happened.
Here is Team A's model under that test:
Team A's model has an AUC of 0.965 and an expected calibration error of 0.22. Nothing is contradictory about that. AUC only ever compares scores to other scores; it is completely unchanged if you squash every prediction through any function that preserves order. Calibration asks the different question of whether a score matches a frequency in the world. A model can be superb at one and hopeless at the other, and most are.
Imagine a doctor who can rank any waiting room perfectly from most to least ill — but who tells every mildly unwell patient they have a 60% chance of something serious. Her rankings are flawless; her numbers are alarmist. Send her patients for tests in her order and you would do very well. Let a hospital use her percentages to allocate ICU beds and you would waste a great many beds. That is the difference AUC cannot see.
Why does calibration matter if the model already ranks correctly?
Hint: Think about what happens after the prediction, when a decision has to be made.
Honest evaluation — the part that decides whether any of this is real
Every metric above assumes one thing: that the numbers were measured on data the model had never seen. Break that assumption and every score on this page becomes fiction — and the failure is silent, because a leaked model looks better, not worse.
A large model can simply memorise its training set. Score it on that same data and you measure memory, not understanding — like grading students on the exact practice problems they had the answer key to. Everyone scores 100%, and you learn nothing about who can handle a new question.
The standard defence is three splits, with three distinct jobs:
- Training set — the model fits its parameters on this. It sees it thousands of times.
- Validation set — you use this to choose hyperparameters, architectures, and the decision threshold. The model never trains on it, but you effectively do.
- Test set — touched once, at the very end, to produce the number you report. Every look costs you a little of its honesty.
Here is the subtlety that catches experienced people. Try 200 architectures and pick the best on validation, and that winning score is optimistically biased — with 200 tries, some model got lucky on that particular split. You have not overfitted the model to the validation data; you have overfitted, through your choices. This is why the untouched test set exists, and why re-tuning after peeking at test results quietly turns your test set into a second validation set.
Cross-validation — when one split is not enough
A single train/test split has its own problem: you only measured once. On a small dataset, which examples happened to land in the test set can swing the score enormously.
fixes this by measuring times and averaging.
One grader might be harsh, or might happen to get the easy papers. Five independent graders give you both a better central estimate and — just as importantly — a sense of how much graders disagree. If the five marks are 61, 62, 60, 63, 61 you can trust the average. If they are 40, 85, 55, 70, 50 you have learned that a single mark means very little, which is itself the most valuable thing the exercise told you.
You normalise your features using the mean and standard deviation of the entire dataset, then split into train and test. What is wrong?
Metrics for other kinds of problems
The four boxes only exist for yes/no predictions. Two other families come up constantly.
Regression — when the model predicts a number, not a class:
- MAE () — the average size of the error, in the units of whatever you are predicting.
$4,200 off on averageis a sentence anyone can act on. Treats all errors proportionally, so it shrugs off outliers. - RMSE () — squares each error before averaging, then square-roots back into the original units. The squaring means one enormous miss counts for much more than several small ones. Use it when big errors are disproportionately costly; use MAE when they are not.
- R² — the fraction of the variance in the target that the model explains. is perfect, means you did no better than always predicting the mean, and negative values are possible and mean you did worse than that.
Here is the truth for example , (read "y-hat") is the model's prediction, and the hat is the standard mark for an estimated quantity.
Object detection — where a prediction is a box, so "correct" is a matter of degree. measures how well two boxes overlap, and mAP averages precision across recall levels and classes. Both are built directly on the precision and recall you learned above — see Object Detection for the full treatment.
Choosing the metric — the actual decision
| Your situation | Report this | Because |
|---|---|---|
| Balanced classes, equal error costs | Accuracy | It genuinely means what it looks like here |
| Rare positive class | Precision, recall, PR-AUC | Accuracy and ROC both flatter you |
| Missing a positive is expensive | Recall, or | Explicitly weights misses above false alarms |
| A false alarm is expensive | Precision, or | Explicitly weights false alarms above misses |
| Comparing models before a threshold exists | ROC-AUC | Threshold-free, and stable across class balance |
| The probability feeds a cost calculation | ECE, Brier score | Ranking is not enough when the number gets multiplied |
| Small dataset | Cross-validated mean and std | A single split has too much variance to trust |
| Predicting a number | MAE or RMSE | MAE for robustness, RMSE when big errors hurt more |
Notice that every row is decided by what a mistake costs, not by anything about the model. So write down the two costs first — what does a false alarm cost us, and what does a miss cost us? — and the right metric follows almost mechanically. Teams that skip this step end up optimising accuracy by default, which is how the hospital ships Team B.
Explain to a friend why a model can be 99% accurate and still useless, then explain precision and recall using the fishing-net picture — no formulas. Finish by saying what happens to each one when you lower the decision threshold. If you stall on any part, that is the exact section to reread.
- Accuracy hides which mistake happened, and on imbalanced data the always-predict-the-common-class model sets a very high floor. Compute that no-skill baseline before believing any score.
- The confusion matrix (TP, FP, FN, TN) is the raw material; every classification metric is a ratio of some of those four counts to some others.
- Precision = of what you flagged, how much was right. Recall = of what was there, how much you found. Same numerator, different denominators — which is why they pull against each other. F1 is their harmonic mean, high only when both are.
- The decision threshold is a dial you choose after training, not a property of the model. Sweeping it moves precision and recall a long way for free.
- ROC-AUC grades ranking across all thresholds and equals the probability a random positive outscores a random negative — but it flatters models on rare positives, where a precision-recall curve tells the truth.
- Calibration asks whether a 0.7 really means 70%. It is independent of ranking quality: measure it with ECE or the Brier score, and fix it cheaply with temperature scaling.
- Numbers are only real if measured on untouched data. Use train/validation/test, use cross-validation with its standard deviation on small data, and hunt for leakage whenever a result looks too good.
Practice — and how to make it stick
Three research-backed habits, built into this platform:
• Retrieval practice: answer the questions below from memory before scrolling back up — pulling an answer out beats re-reading the page that contains it.
• Spaced repetition: mark this topic complete and it joins your Review queue, resurfacing right before you would have forgotten it.
• Interleaving: mix these with problems from Machine Learning and Probability & Statistics rather than grinding metrics in one block — messier practice, sturdier memory.
- By hand: a model flags 50 items; 30 are correct. There were 120 true positives in the data. Compute precision, recall, and F1. Now say in one sentence what this model is good and bad at.
- Find the baseline: for a dataset that is 3% positive, compute the accuracy of the always-negative model, and the average precision of a random-scoring model. Any result you report must beat both.
- Sweep it: take any classifier you have trained, compute precision and recall at 20 thresholds, and pick the one that satisfies a constraint you invent — say, precision at least 0.9. Notice you never retrained anything.
- Break it deliberately: normalise features using the whole dataset before splitting, and see how much the score inflates versus doing it correctly. Seeing leakage inflate a number yourself is the fastest way to learn to smell it.
Try it right here — edit and run the code, and if you get stuck or hit an error, ask Ada on the right: she can see your code and terminal output.
Next, put these metrics to work choosing between real models in Ensembles & Boosting, or go back and strengthen the foundations in Probability & Statistics.