Threshold Tuning

0.5 is a default, not a decision. Set what each kind of mistake costs and watch the cheapest threshold move away from it.

Overview

Where 0.5 comes from

It comes from the library. predict() calls predict_proba() and cuts at 0.5, because a default has to be a thing and 0.5 is the least any-looking choice available.

It's best under two assumptions that are rarely true together: the classes are weighed, and the two kinds of mistake cost the same. Change either and 0.5 stops being the right cut.

Threshold Tuning

This module needs JavaScript: the numbers are computed in the page rather than recorded.

Worth knowing

0.5 is where predict() happens to cut. It is not derived from your problem.
The optimal threshold depends on the cost of each error and on the class balance, neither of which the model knows.
Tune the threshold on validation data, not on the test set, or you have tuned on the thing you are reporting.
A model whose scores are not calibrated still ranks correctly, so threshold tuning works even when the probabilities are wrong.

Threshold Tuning

Where 0.5 came from, why it's almost never right. How to find the number that is.

Put a price on each mistake

The two sliders set what a false minus and a false plus cost. The curve is the total cost across each threshold, found over 1,500 cases with 20% positives. The grey line marks 0.5. The orange line marks the cheapest threshold.

Start with a miss costing 20 and a false alarm costing 1 — a screening problem, where investigating a healthy person is cheap and letting a sick one through isn't. The min sits well below 0.5. The model should flag generously, because the flags are cheap and the misses aren't.

Now reverse them: a miss at 1 and a false alarm at 50 — an automated account suspension, where a wrong suspension is costly and a missed offender is dealt with later. The min moves above 0.5.

Set them equal and, with these class proportions, the min lands near 0.5 but not just on it, because the balance is 20/80 rather than 50/50.

The arithmetic

The expected cost of predicting plus on a case with predicted chance *p* is (1 - p) x cost_FP. Predicting minus costs p x cost_FN. Flagging is worth it when

(1 - p) * cost_FP  <  p * cost_FN

which rearranges to

p  >  cost_FP / (cost_FP + cost_FN)

With equal costs that is 0.5. This Is where the default comes from. With a miss ten times as costly as a false alarm it's 1/11, about 0.09 — and a default of 0.5 is then five times too strict.

That formula is worth carrying around. It gives a defensible starting threshold from two numbers a domain expert can often supply, without any tuning at all.

When you cannot price the errors

Often no one can put a number on a miss. Three usable fallbacks:

Raise F1, or F-beta if one error matters more. This is the least opinionated option and it's what most people do.

Fix an operating constraint. "The team can investigate 200 cases a day" sets the threshold right: take the top 200 scores. "Precision must be at least 80%" does the same from the other side. These are often more honest than a metric, because they show what will really happen.

Choose from the curve. Plot precision and recall against threshold and pick the point where the trade-off turns. It's a judgement, but an informed one.

Two things that must be right

Tune on validation data. Choosing the threshold on the test set and then reporting test speed is the same category of error as tuning settings there — the reported number includes the choice you made to raise it.

Don't confuse this with calibration. Threshold tuning takes the scores as given and picks a cut. Calibration changes the scores so that a score of 0.8 really means 80% of such cases are plus.

They're free. A badly calibrated model can rank just right, and if the ranking is right, a tuned threshold works fine. But if the threshold is being derived from the cost formula above, the *p* in it must mean an actual chance — and then calibration matters. Platt scaling and isotonic regression are the usual tools.

The threshold the costs imply

The best cut isn't a matter of taste. It drops out of the two error costs. Flagging a case with predicted chance p is worth it when (1 - p) · cost_FP < p · cost_FN. This Rearranges to a threshold of cost_FP / (cost_FP + cost_FN):

a miss costsa false alarm costsoptimal threshold
201 (screening)0.048
11 (symmetric)0.500
150 (auto-suspension)0.980

The familiar 0.5 is just the middle row. The special case where the two mistakes cost the same. Move to a screening problem. Here Missing a sick patient is twenty times worse than a needless second look, and the right cut falls to 0.048: flag almost any of it. Flags are cheap and misses aren't, and the default of 0.5 is then more than ten times too strict. Reverse the costs and it climbs to 0.98. The formula needs only two numbers a domain expert can often supply, and it gives a defensible threshold with no tuning at all — but the p in it must be an actual chance. This Is why a cost-derived threshold is just the case where calibration stops being optional.

When no one can price the errors, three fallbacks are honest. Raise F1 (or F-beta if one error matters more). This Is the least opinionated option. Fix an operating constraint — "the team can investigate 200 cases a day" sets the threshold right as the top 200 scores, and often shows reality better than any metric. Or read the precision-recall trade-off off the curve and pick the knee. Whichever you use, tune it on validation data, never the test set: choosing the threshold to raise a number and then reporting that number folds the choice into the result, just as tuning settings on the test set does.

Where it goes wrong with Threshold Tuning

Reporting metrics at 0.5 and stopping. The most common test mistake after using fit.

Tuning on the test set. Optimistic by just the amount you gained.

Retuning per fold and blending. The thresholds aren't close across folds. Tune once on a held-out validation set.

Losing it drifts. The best threshold depends on the class balance, and that moves in production. It needs rechecking, not setting once.

Questions people ask about Threshold Tuning

Why is 0.5 not always the right threshold? It assumes both kinds of error cost the same and the classes are balanced. Neither is usually true.

How do I pick a threshold? Assign a cost to false positives and false negatives and choose the threshold that minimises expected cost. If you cannot price them, set a recall or precision target.

On which data should I tune the threshold? On validation data or cross-validated predictions, not the test set. Then report the final result on test data once.

Does threshold tuning change the model? No. It changes only how scores become labels, so ranking metrics like AUC stay the same.

Do I need calibrated probabilities? For thresholds derived from costs, yes. A cost-based threshold assumes the probabilities are real, so calibrate first.

What happens when class balance shifts in production? The best threshold moves. Monitor the positive rate and re-tune when the distribution changes.

0.5 is a default, not a decision

The threshold is the one setting you can change after training, in production, in a config file. Here is what it costs and what it buys.

example_01.pyscikit-learn
Output

F1 is one way to pick a threshold, but a cost matrix is more honest. The next block shows how the same probabilities produce three different optimal thresholds depending on the price of each mistake.

example_02.pyscikit-learn
Output

Check yourself

0 of 3

Answer without scrolling back up.

  1. Where does the 0.5 default threshold come from?

  2. If a miss costs ten times a false alarm, roughly what threshold does the cost formula give?

  3. How does threshold tuning differ from calibration?

Cheat sheet

Threshold Tuning

Slide the classification threshold from zero to one and watch precision, recall and cost shift. Find the cutoff that fits your problem.

MACHINE LEARNING · vizlearn.in/machine_learning/threshold_tuning.html

About the author

Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.