Venn-Abers calibration
Venn-Abers predictors turn any real-valued score for a yes/no question — a raw model probability, a logit, a judge rating — into a calibrated probability interval . They come from the same exchangeability theory as conformal prediction (Vovk, Gammerman & Shafer) and, like it, need no assumption about how good the underlying score is.
CLI uses Venn-Abers for Belief, for the per-option venn_abers
intervals on every Set answer, and inside Judge and Route cascades
to decide when a cheaper stage is not sure enough.
The IVAP construction
Take calibration pairs : a score (higher means “more likely 1”) and an observed label . For a test score :
- For each postulated label , add the point to the calibration set.
- Fit an isotonic (non-decreasing) least-squares regression of label on score to those points, using pool-adjacent-violators.
- Let be the fitted value at .
The Inductive Venn-Abers Predictor (IVAP) outputs , with
by construction. This is exactly what
cli_sdk.stats.venn_abers.ivap.calibrate_and_predict computes: one
isotonic fit per postulated label per test point.
What is guaranteed
Let be the endpoint computed by postulating the true label (so when and when ). If calibration and test data are exchangeable, then
that is, is perfectly calibrated (Vovk & Petej, 2014). You never know which endpoint is for a given item, which is why the output is an interval: the calibrated probability is one of its two endpoints.
The statement is about calibration on average over exchangeable data. It is not a confidence interval for a per-item “true probability”, and a Venn-Abers card has no . A poorly separated score does not break the statement; it widens the interval.
Interval width
measures how much a single extra observation at could
move the isotonic fit. It is small where many calibration scores sit near
and the label rate there is stable, and large where calibration data
is sparse or the relationship is noisy. The width is an ambiguity signal
in its own right, available before any threshold is chosen
(BeliefAnswer.interval_width, venn_abers.interval_width).
import numpy as np
from cli_sdk.stats.venn_abers import interval_width, ivap, merge_to_probability
rng = np.random.default_rng(3)
cal_scores = rng.uniform(size=2000) # any score
cal_labels = (rng.uniform(size=2000) < cal_scores).astype(int) # observed 0/1 labels
test_scores = np.array([0.10, 0.50, 0.90])
p0, p1 = ivap.calibrate_and_predict(cal_scores, cal_labels, test_scores)
# p0 = [0.098 0.497 0.935], p1 = [0.113 0.502 0.946]
q0, q1 = ivap.calibrate_and_predict(cal_scores[:50], cal_labels[:50], test_scores)
# with only 50 examples: q0 = [0. 0.2 0.875], q1 = [0.167 0.706 1.]
print(interval_width(p0, p1), interval_width(q0, q1))
print(merge_to_probability(p0, p1))Merging to one number
When downstream code needs a single probability, merge the interval with
the rule that minimizes worst-case log loss over the two endpoints (Vovk &
Petej). For this gives . The
probability field on a Belief answer is this merged value; the offline
equivalent is venn_abers.merge_to_probability(p0, p1). Prefer the
interval itself whenever the width matters to the decision.
The straddle rule
To act on a threshold (merge two records when the match probability is at least , say), use the whole interval:
| Condition | Decision |
|---|---|
| Act: both endpoints are at or above | |
| Do not act: both endpoints are at or below | |
| Escalate: the interval straddles |
Because is always one of the two endpoints, the first two rows never
act on an item whose calibrated probability could lie on the other side of
, whichever label turns out to be true. The third row is exactly the
case where the calibration data cannot tell you which side you are on.
BeliefAnswer.straddles(t) returns True in that case.
The straddle rule is a decision rule on calibrated probabilities, not a
bound on the error rate of the decisions it takes. If you need a stated
bound on how often acted-on decisions are wrong, use a
Gate, or audit the rule on fresh labelled data. The
entity matching guide walks through both.
Cross Venn-Abers (CVAP)
A single IVAP uses one calibration split. The Cross Venn-Abers Predictor fits an IVAP on each of leave-one-fold-out subsets and combines the intervals, using the calibration data more fully at times the cost:
from cli_sdk.stats.venn_abers import cvap
p0, p1 = cvap.calibrate_and_predict(cal_scores, cal_labels, test_scores, n_folds=5, seed=0)The combination rule for intervals is not uniquely prescribed in the
literature. cvap averages each endpoint in log-odds space. Treat the
result as an aggregate signal in the same spirit as a single IVAP, not as
a strictly stronger guarantee.
Where it appears in the API
| Where | Field | Meaning |
|---|---|---|
BeliefAnswer | venn_abers, probability, interval_width, straddles(t) | The interval, its merged value, its width, the straddle test |
SetAnswer | venn_abers[option] | A calibrated interval for each option being correct |
Judge cascade | per stage | A stage’s verdict is accepted when its interval clears that stage’s calibrated threshold; otherwise the next stage runs |
Route(guarantee="accuracy") | per stage | Escalates when the cheap stage’s interval is too wide to support the bound |
A Belief answer’s guarantee card has type: "calibration" with
method: "IVAP", the profile name, and calibration_n, and no alpha.
CVAP has no finite-sample validity guarantee, so a CVAP answer is
labelled heuristic with method: "CVAP". See Guarantee reference.