Temperature Scaling and ECE: Calibrate Model Confidence Step by Step
Work through softmax, fit a temperature on six predictions, compute ECE, and see why a better likelihood can still produce a worse binned calibration score.
Six predictions can be correct four times out of six and still tell two very different probability stories. In the worked example below, the winning class never changes. Dividing every logit by a fitted temperature only lowers or raises the confidence attached to each winner.
That small adjustment improves mean negative log-likelihood from to . Then something awkward happens: expected calibration error, computed with four equal-width bins, rises from to .
The arithmetic is not a failure of temperature scaling. It exposes a more useful lesson: calibration is a relationship between predicted probabilities and observed outcomes, while ECE is one sample-dependent, bin-dependent estimate of that relationship. By the end, you will be able to fit the temperature, calculate ECE, reproduce both results in Python, and explain why neither number can travel unchanged into a new deployment population.
Current evaluation work makes the distinction practical. A Google DeepMind pilot published August 27 kept proprietary model weights and external test prompts hidden from each other, while MLCommons supplied a reserved AILuminate subset. Those controls protect the test material. They do not decide which population a probability should describe. NIST AI 800-3 makes the parallel point for accuracy: performance on fixed benchmark questions and performance across a broader universe of similar questions are different measurement targets.
A probability forecast has a testable promise
Suppose a classifier predicts class with confidence on many independent examples. A calibrated confidence means that about of those predictions are correct. More formally, if is the predicted class and its reported confidence, perfect top-label calibration asks for
This definition does not say the classifier is accurate. A model that predicts at confidence and succeeds of the time is calibrated even though it is wrong often. A model can also rank every example correctly while reporting for cases that succeed only of the time. Accuracy asks who won; calibration checks whether the attached probability kept its promise.
The event and population need names. “Correct” might mean exact class match, task completion, or another scored outcome. The examples might be customer support requests in one language, benchmark questions, or tomorrow’s production traffic. Change either side and the probability statement changes with it. For the wider evaluation pipeline behind that choice, see how evaluations shape AI products.
Temperature turns the confidence dial
A classifier often emits logits: unrestricted scores for classes. Softmax converts them into probabilities:
Temperature scaling introduces one positive number :
When , the gaps between scaled logits shrink and the probability vector becomes less concentrated. When , the gaps widen and the vector becomes sharper. leaves the original probabilities unchanged.
For the first example, the logits are . Subtracting the largest logit before exponentiating gives the same softmax without handling unnecessarily large numbers:
At , the vector becomes approximately . Class still wins. In fact, every positive temperature preserves the ordering of the logits, so temperature scaling cannot change the predicted class or the classification accuracy. It changes only the probability concentration.
Fit one scalar against held-out labels
Guo and colleagues’ peer-reviewed calibration paper fits by minimizing negative log-likelihood (NLL) on a held-out validation set while keeping the network parameters fixed. For labeled examples, the objective is
where is the scaled probability assigned to the true class of example . A confident mistake receives a large penalty because the true class probability is small.
Here is the complete synthetic validation set. The labels , , and stand for three arbitrary classes; no private or high-stakes predictions are involved.
| Example | Logits | True class | Predicted class | Correct? | Confidence at | Confidence at |
|---|---|---|---|---|---|---|
| 1 | A | A | Yes | 0.844 | 0.779 | |
| 2 | A | A | Yes | 0.766 | 0.701 | |
| 3 | B | A | No | 0.883 | 0.821 | |
| 4 | B | B | Yes | 0.574 | 0.537 | |
| 5 | C | C | Yes | 0.629 | 0.575 | |
| 6 | A | B | No | 0.518 | 0.496 |
Searching positive temperatures gives . The mean NLL falls from at to at the fitted value. Accuracy remains , exactly as the preserved class ordering predicts.
This set is intentionally tiny enough to calculate. It is not large enough to approve a deployed calibrator, and using the same six rows both to fit and to report performance would give an optimistic estimate of generalization.
ECE compresses a reliability diagram
A reliability diagram, also called a calibration curve, groups predictions by confidence. For each bin , calculate
and
The indicator is for a correct prediction and otherwise. A bin sits on the ideal diagonal when its accuracy equals its mean confidence.
Expected calibration error takes a sample-weighted average of the absolute bin gaps:
The calculation below uses four fixed, equal-width confidence intervals: , , , and . Empty bins contribute nothing.
| Temperature | Confidence interval | Examples | Mean confidence | Accuracy |
|---|---|---|---|---|
| 1.00 | [0.50, 0.75) | 3 | 0.574 | 0.667 |
| 1.00 | [0.75, 1.00] | 3 | 0.831 | 0.667 |
| 1.23 | [0.25, 0.50) | 1 | 0.496 | 0.000 |
| 1.23 | [0.50, 0.75) | 3 | 0.604 | 1.000 |
| 1.23 | [0.75, 1.00] | 2 | 0.800 | 0.500 |
Before scaling, the three predictions in contribute
and the three in contribute about . Their sum is , or .
After scaling, one prediction crosses below and the other examples form groups of three and two. The three bin contributions are approximately , , and , giving , or .
Better likelihood, worse ECE
The fitted temperature optimized NLL, not four-bin ECE. NLL evaluates the full probability assigned to every true class and changes smoothly with . ECE throws the predictions into intervals, discards within-bin detail, and changes abruptly when a confidence crosses an edge.
Six examples amplify that instability. One mistake in a one-example bin has accuracy; one correct result would make the same bin accurate. Moving the edge or choosing a different number of bins can produce a different ECE without changing a single prediction. Guo et al. explicitly note that ECE is a binned approximation and that results can be affected by the binning scheme. scikit-learn’s calibration guide likewise pairs the curve with a histogram because a point without its sample count is easy to overread.
So the example supports three separate statements:
- the selected improves NLL on these six rows;
- temperature scaling leaves their predicted classes and accuracy unchanged;
- four-bin ECE gets worse on the same rows.
None implies that the calibrator will improve probabilities for a fresh sample. That question needs untouched evaluation data drawn from the population the deployment decision concerns.
Reproduce every number with NumPy
The following program performs a dense search over one positive scalar. The grid is deliberately transparent; production code can use a bounded scalar optimizer and should retain an independent final evaluation set.
import numpy as np
logits = np.array([
[3.0, 1.0, 0.0],
[2.5, 1.0, 0.0],
[3.0, 0.5, 0.0],
[1.5, 2.0, 0.0],
[0.5, 0.0, 1.5],
[2.0, 2.2, 0.0],
])
labels = np.array([0, 0, 1, 1, 2, 0])
def probabilities(temperature):
scaled = logits / temperature
scaled -= scaled.max(axis=1, keepdims=True)
exp = np.exp(scaled)
return exp / exp.sum(axis=1, keepdims=True)
def nll(temperature):
p = probabilities(temperature)
return -np.log(p[np.arange(len(labels)), labels]).mean()
temperatures = np.linspace(0.25, 4.0, 15_001)
temperature = temperatures[np.argmin([nll(t) for t in temperatures])]
def ece(temperature, edges=np.linspace(0.0, 1.0, 5)):
p = probabilities(temperature)
predictions = p.argmax(axis=1)
confidence = p.max(axis=1)
correct = predictions == labels
total = 0.0
for lower, upper in zip(edges[:-1], edges[1:]):
in_bin = (confidence >= lower) & (confidence < upper)
if upper == 1.0:
in_bin = (confidence >= lower) & (confidence <= upper)
if in_bin.any():
total += in_bin.mean() * abs(
correct[in_bin].mean() - confidence[in_bin].mean()
)
return total
print(f"T={temperature:.5f}")
print(f"NLL: {nll(1.0):.5f} -> {nll(temperature):.5f}")
print(f"ECE: {ece(1.0):.3f} -> {ece(temperature):.3f}")
The expected output is approximately:
T=1.22975
NLL: 0.82278 -> 0.81243
ECE: 0.129 -> 0.380
A calibrator belongs to one measurement contract
A defensible calibration record should name the model checkpoint, unscaled logits, outcome definition, population, data windows, split method, fitted , optimization objective, reliability-bin edges, bin counts, and at least one proper scoring rule such as NLL. Fit the calibrator on examples that were not used to train the classifier, then assess it on untouched data.
Distribution shift is the decisive boundary case. A temperature fitted on clean benchmark questions may be wrong for short prompts, another language, a new class mix, or inputs collected after the product changes. Temperature scaling cannot repair a bad class ranking, detect an unseen domain, or prove a system safe. It only rescales the logits under the validation assumptions used to fit one number.
That is why a deployment review should keep the reliability curve and sample counts beside ECE rather than treating the scalar as a certificate. If a new cohort moves those points away from the diagonal, the next task is not to defend the old ECE. It is to identify which population changed, collect enough labels to measure it, and decide whether the calibration contract still describes the system in use.