ek.qe.calibrate
Calibration: make a raw score mean a probability (the non-optional stage).
A confidence of 0.9 should mean “correct 90% of the time.” Modern models violate
this badly – they are systematically overconfident (Guo et al. 2017), and RLHF
makes LLMs worse. So calibration is non-optional: never gate a raw posterior or a
raw logprob (misc/docs/ek_03 §2, Hard Rule 1). A Calibrator
is fit on a labelled holdout of (raw_score, field_correct?) pairs and maps any
later raw score to a calibrated probability.
Three methods, by what input you have:
PlattCalibrator– logistic fit on any scalar score (no logits needed): the default for aggregated OCR confidence or aggregated logprobs.IsotonicCalibrator– non-parametric monotonic fit; more flexible, needs more data, can overfit small sets.TemperatureCalibrator– a single scalarTon logits; use only when you have logits (it does not change the argmax).
All three are pure-Python (stdlib only) so the calibration stage works with zero
extra dependencies; sklearn_calibrator() / netcal_calibrator() offer the
library-backed equivalents behind the ek[calibration] extra. Measure calibration
with expected_calibration_error() (+ a reliability curve). For per-field-type
validity, wrap per group with GroupCalibrator (Mondrian / class-conditional)
– distribution-free conditional coverage is otherwise impossible (Hard Rule 2).
Calibrate at the granularity of the decision (gate on fields -> calibrate a
“field-correct?” target), and persist the fit (save_calibrator()); calibration
is dataset-specific and decays, so re-fit on drift.
Example
>>> # An overconfident raw signal, calibrated against ground truth.
>>> raw = [0.95, 0.93, 0.92, 0.90, 0.55, 0.52, 0.51, 0.50]
>>> correct = [True, True, False, False, True, False, False, False]
>>> cal = PlattCalibrator().fit(raw, correct)
>>> cal(0.95) < 0.95 # overconfidence pulled down
True
>>> 0.0 <= cal(0.5) <= 1.0
True
- ek.qe.calibrate.DEFAULT_N_BINS = 10
Default number of bins for
expected_calibration_error()/ reliability curves.
- class ek.qe.calibrate.GroupCalibrator(factory: Callable[[], ~typing.Any]=<class 'ek.qe.calibrate.PlattCalibrator'>, by_group: dict = <factory>, pooled: Any = None, kind: str = 'group')[source]
Per-group (Mondrian) calibration: one calibrator per
NodeType/FieldSpec.Distribution-free conditional (per-field-type) coverage is impossible in general (Barber et al. 2019); calibrating separately per group restores it approximately.
fittakes a parallelgroupssequence;__call__routes by group key, falling back to a pooled calibrator for unseen groups.- Parameters:
factory – Zero-arg callable producing a fresh per-group calibrator (default
PlattCalibrator).
- factory
alias of
PlattCalibrator
- fit(scores: Sequence[float], correct: Sequence[bool], *, groups: Sequence[Any]) GroupCalibrator[source]
Fit one calibrator per distinct group key, plus a pooled fallback.
- class ek.qe.calibrate.IsotonicCalibrator(x: List[float] = <factory>, y: List[float] = <factory>, kind: str = 'isotonic')[source]
Isotonic (monotonic non-decreasing) calibration via pool-adjacent-violators.
More flexible than Platt; needs more calibration data and can overfit small sets. Predicts by linear interpolation between fitted points, clipped at the ends.
- fit(scores: Sequence[float], correct: Sequence[bool]) IsotonicCalibrator[source]
Fit the monotonic step function to
(score, correct)pairs.
- class ek.qe.calibrate.PlattCalibrator(a: float = 1.0, b: float = 0.0, max_iter: int = 100, kind: str = 'platt')[source]
Platt scaling:
sigmoid(a * score + b), fit by Newton/IRLS on labels.The default calibrator: works on any scalar (aggregated confidence/logprob), no logits required. Uses Platt’s target smoothing so it does not overfit small calibration sets.
- Parameters:
max_iter – Newton iterations (converges in a handful for a 2-parameter model).
- fit(scores: Sequence[float], correct: Sequence[bool]) PlattCalibrator[source]
Fit
a, bto maximise the likelihood ofcorrectgivenscores.
- class ek.qe.calibrate.TemperatureCalibrator(T: float = 1.0, kind: str = 'temperature', t_min: float = 0.05, t_max: float = 10.0, max_iter: int = 60)[source]
Temperature scaling:
sigmoid(logit / T)with oneTfit on a holdout.Use only when you have logits:
__call__expects a logit, not a probability.T > 1softens overconfidence; the argmax is unchanged.- fit(logits: Sequence[float], correct: Sequence[bool]) TemperatureCalibrator[source]
Fit
Tby minimising NLL with a bounded 1-D search.
- t_min: float = 0.05
Golden-section search bounds and iteration count (config, not magic numbers).
- ek.qe.calibrate.expected_calibration_error(probs: Sequence[float], correct: Sequence[bool], *, n_bins: int = 10) float[source]
Expected Calibration Error: weighted mean gap between confidence and accuracy.
Bins predictions by confidence into
n_binsequal-width bins and averages|mean_confidence - accuracy|weighted by bin population.0is perfect. Non-finite probs are skipped; out-of-range probs are clamped into[0, 1].
- ek.qe.calibrate.load_calibrator(name: str, *, rootdir: str | None = None) Any[source]
Reconstruct a persisted calibrator by name (dispatched on its
kind).Validates the stored record so a malformed or unknown
kindfails with an actionable error rather than a rawKeyErroror a load-then-crash-later.
- ek.qe.calibrate.netcal_ece(probs: Sequence[float], correct: Sequence[bool], *, bins: int = 10) float[source]
ECE via
netcal(behindek[calibration]); D-ECE for localized outputs lives there too.
- ek.qe.calibrate.reliability_curve(probs: Sequence[float], correct: Sequence[bool], *, n_bins: int = 10) List[dict][source]
Per-bin
{confidence, accuracy, count}for a reliability diagram.
- ek.qe.calibrate.save_calibrator(calibrator: Any, name: str, *, rootdir: str | None = None) dict[source]
Persist a fitted calibrator’s parameters to the
calibratorsstore.
- ek.qe.calibrate.sklearn_calibrator(method: str = 'sigmoid')[source]
A calibrator backed by scikit-learn (
method='sigmoid'Platt or'isotonic').Behind
ek[calibration]. Returns an object satisfying theCalibratorprotocol that wraps sklearn’s calibration. The pure-PythonPlattCalibrator/IsotonicCalibratorare the dependency-free defaults; use this for parity with an sklearn-centric stack.