Problem
A model predicts that a house will sell for $612,000. Is that ±$5,000 or ±$200,000? A triage model says a scan is benign. Did it see thousands of scans like this one, or is this a scanner it has never met? A point prediction hides both questions, and the cost of an error usually depends on them: you can act on a confident prediction, but an uncertain one should go to a person, trigger more measurement, or be refused.
Calibration makes a classifier's probabilities honest on average. It does not say which inputs are unfamiliar, it does not give regression models an interval, and it is itself an estimate from a finite validation set. Uncertainty quantification fills those gaps.
Intuition
Imagine predicting tomorrow's temperature. Even a perfect forecaster cannot say it will be exactly 18.3 °C: the weather is noisy. That irreducible spread is aleatoric uncertainty (Latin alea, dice). Now imagine forecasting for a town you have no records for. The extra doubt comes from your own ignorance, and data from that town would remove it. That is epistemic uncertainty (Greek episteme, knowledge).
A practical test for epistemic uncertainty: train several reasonable models and see whether they agree. Where the training data were dense, they agree; where there were none, each fills the gap its own way and they disagree. The spread of their predictions measures what the data failed to pin down. Aleatoric uncertainty shows up instead as the noise each model expects around its own prediction.
Conformal prediction takes a different, almost accountant-like view. It does not ask why the model is wrong. It holds back some labelled data, looks at how wrong the model was on it, and widens every future prediction by the error size that covered, say, 90% of those held-back cases. If future data look like the held-back data, about 90% of future intervals will contain the truth, whatever the model is.
Mechanics
Two kinds of uncertainty
Write the model's parameters as θ and the training data as D. A Bayesian predictive distribution averages over every plausible θ (see Bayes' rule):
p(y | x, D) = ∫ p(y | x, θ) p(θ | D) dθ
If each θ gives a Gaussian with mean μθ(x) and variance σ²θ(x), the law of total variance splits the predictive variance in two:
Var[y | x] = Eθ[σ²θ(x)] + Varθ[μθ(x)] = aleatoric + epistemic
The first term is the noise the models expect; the second is how much they disagree. For classification the same split uses entropy: the entropy of the averaged probabilities equals the average entropy of each model (aleatoric) plus the mutual information between the label and θ (epistemic). The exact posterior p(θ | D) is out of reach for neural networks, so the practical methods below each approximate it with a handful of samples.
Bootstrap confidence intervals
The bootstrap (Efron, 1979) estimates the sampling distribution of any statistic by resampling the data n times with replacement, recomputing the statistic on each resample, and reading off quantiles. The percentile interval takes the 2.5th and 97.5th percentiles of the resampled statistics. It answers "how precisely do I know this number?", for example a test-set metric, which matters whenever two models are compared on a few hundred examples. Each resample leaves out about 1/e ≈ 37% of the points, which is where the random forest's out-of-bag estimate comes from.
Ensembles and bagging
Deep ensembles (Lakshminarayanan, Pritzel and Blundell, 2017) train M networks (5 is common)
from different random initializations, each with a mean and a variance head trained by Gaussian negative log
likelihood, and combine them as a mixture with the decomposition above. They are simple, parallel, and in large
comparisons are among the most reliable methods under dataset shift (Ovadia et al., 2019), at M times the
training and serving cost. Bagging gets diversity from bootstrap resamples instead.
scikit-learn's BaggingRegressor draws each member's rows with
_generate_indices (randint(0, n, n)
when bootstrap=True), and its
predict returns only the mean
(sum(all_y_hat) / n_estimators); to get the spread, loop over estimators_ yourself.
Monte Carlo dropout
Dropout zeroes each activation with probability p during training and scales the
survivors by 1/(1 − p).
nn.Dropout.forward passes
self.training to F.dropout, so in
eval() mode it is the identity. MC dropout (Gal and Ghahramani, 2016) leaves dropout on at test
time, runs T stochastic forward passes, and treats their mean and spread as an approximate posterior
predictive: each mask is one sample of a thinned network. It costs T forward passes but no retraining. The spread
depends on p, which was chosen for regularization, not for calibrated uncertainty, so check it on held-out data.
Conformal prediction
Split conformal prediction (Vovk, Gammerman and Shafer, 2005; Angelopoulos and Bates, 2021) wraps any trained model:
- Hold out n labelled calibration points the model did not train on.
- Compute a nonconformity score on each, for regression si = |yi − ŷ(xi)|.
- Let q̂ be the k-th smallest score with k = ⌈(n + 1)(1 − α)⌉.
- For a new x, predict the interval [ŷ(x) − q̂, ŷ(x) + q̂].
If the calibration and test points are exchangeable (for example i.i.d.), the new score is equally likely to fall in any of the n + 1 rank positions, so
1 − α ≤ P(ynew ∈ C(xnew)) ≤ 1 − α + 1/(n + 1)
(the upper bound holds when scores have no ties). The "+1" in the rank is the finite-sample correction that makes this exact. The guarantee is marginal: averaged over all x, not for each x. A constant-width interval over-covers easy inputs and under-covers hard ones. Dividing the score by an estimated spread, s = |y − ŷ| / σ̂(x), gives intervals ±q̂·σ̂(x) that adapt to the input; conformalized quantile regression (Romano, Patterson and Candès, 2019) does the same with predicted quantiles. For classification, the set of classes whose score 1 − p̂(y | x) is at most q̂ covers the true label with the same guarantee.
| Component | Shape/Params | Meaning | Source |
|---|---|---|---|
_generate_indices | bootstrap=True → randint(0, n_population, n_samples) | Draws one bootstrap resample of row indices. | _bagging.py 51–61 |
BaseBagging.__init__ | n_estimators=10, max_samples=None, bootstrap=True, oob_score=False | Shared settings for bagging classifiers and regressors. | 294–321 |
BaggingRegressor | estimators_: list of fitted members | Bagged regressors; members are available for spread estimates. | 1192–1480 |
BaggingRegressor.predict | X [n, d] → [n] | Mean of member predictions only. | 1384–1446 |
oob_prediction_ | [n] | Each training row predicted by members that never saw it. | 1448–1474 |
nn.Dropout | p=0.5, inplace=False; any shape | Zero with probability p, scale by 1/(1 − p), identity in eval(). | dropout.py 17–73 |
F.dropout | p, training, inplace | The functional form; training=True forces MC dropout. | functional.py 1473–1498 |
| conformal quantile | rank k = ⌈(n+1)(1−α)⌉ | Interval half-width covering 1 − α of future points. | Angelopoulos and Bates (2021) |
Worked example
Data: x uniform on [-3.0, 1.0], y = sin(x) + Gaussian noise whose standard deviation grows linearly from
0.1 at the left edge to 0.5 at the right. There are 200 training, 200 calibration and
2000 test points, all from the same distribution. Every number comes from tested helpers in
ml_models_docs.py with fixed seeds.
Aleatoric and epistemic, from an ensemble
5 members, each fitted to its own bootstrap resample of the training set. A member is a cubic least-squares fit for the mean plus a straight-line fit to its absolute residuals for the noise scale (dividing by √(2/π) turns a mean absolute residual into a standard deviation). At five inputs, two of them beyond the training range:
| x | Member means | Ensemble mean | True sin(x) | Aleatoric σ² | Epistemic σ² | Total σ |
|---|---|---|---|---|---|---|
| -2.5 | -0.66, -0.64, -0.64, -0.66, -0.65 | -0.648 | -0.598 | 0.0196 | 0.0001 | 0.140 |
| -1.0 | -0.70, -0.78, -0.70, -0.74, -0.70 | -0.723 | -0.841 | 0.0844 | 0.0010 | 0.292 |
| 0.5 | 0.29, 0.39, 0.30, 0.32, 0.36 | 0.331 | 0.479 | 0.1955 | 0.0014 | 0.444 |
| 2.0 | -0.24, 0.50, 0.17, 0.44, 0.10 | 0.195 | 0.909 | 0.3529 | 0.0699 | 0.650 |
| 3.0 | -2.70, -1.16, -1.59, -1.04, -2.00 | -1.700 | 0.141 | 0.4836 | 0.3657 | 0.922 |
Inside the data the members agree to within about 0.1, and almost all the variance is aleatoric: at x = -2.5 the estimated noise σ is 0.140 (the true value is 0.150), at x = 0.5 it is 0.442 (true 0.450). More data would not shrink these. Beyond the training range the member means scatter from -2.70 to -1.04 at x = 3.0, and epistemic variance rises from 0.0014 to 0.3657. Data in that region would shrink it.
It still is not enough. At x = 3.0 the truth, 0.14, is 2.0 total standard deviations from the ensemble mean: all five cubics share the same wrong assumption about what lies beyond the data. Ensembles detect disagreement, not a shared bias, and five samples are a coarse picture of the posterior. The same blind spot shows inside the data: at x = 0.5 the cubic's mean, 0.33, misses sin(0.5) = 0.48 by more than the members' spread.
A bootstrap interval for a test metric
The single cubic model's mean absolute error on the first 40 test points is 0.270. Resampling those 40 errors 2000 times with replacement and taking the 2.5th and 97.5th percentiles of the resampled means gives the 95% interval [0.195, 0.345]. The bootstrap standard error is 0.0381, close to the formula s/√n = 0.0397, and the normal interval mean ± 1.96·s/√n = [0.192, 0.348] nearly matches. With only 40 test points the metric is known to about ±28%, so a competing model would need to beat it by more than that to be clearly better. The bootstrap earns its keep for statistics without a simple formula: a median, an AUC, a difference of F1 scores.
Monte Carlo dropout on one layer
A hidden layer outputs h = [0.9, -0.4, 1.3, 0.2, -1.1, 0.7, 0.5, -0.8] and the output weights are
w = [0.6, 0.3, -0.5, 0.8, 0.4, -0.7, 0.9, 0.2]. In eval() mode the output is w·h =
-0.710. With dropout p = 0.2 left on, each pass draws a mask and scales survivors by
1/(1 − 0.2) = 1.25:
| Pass | Mask | Output |
|---|---|---|
| 1 | 1 0 1 0 0 1 1 1 | -0.3875 |
| 2 | 1 1 1 1 0 0 1 1 | 0.2750 |
| 3 | 1 1 1 0 1 1 1 1 | -1.0875 |
| Passes T | MC mean | MC standard deviation |
|---|---|---|
| 10 | -0.507 | 0.563 |
| 100 | -0.725 | 0.559 |
| 1000 | -0.706 | 0.586 |
The 1/(1 − p) scaling makes each pass unbiased, so the MC mean converges to the eval-mode output -0.710. For a linear read-out the variance is exactly p/(1 − p)·Σ(wjhj)², a standard deviation of 0.595, which the 1000-pass estimate matches. Ten passes give a noisy mean (-0.507); the MC estimate's own error falls like 1/√T. With nonlinear layers after the dropout, the MC mean no longer equals the eval-mode output.
Conformal coverage, by hand and at scale
By hand first, with the first 9 calibration scores and α = 0.2. The scores |y − ŷ| sorted are 0.055, 0.062, 0.091, 0.113, 0.128, 0.200, 0.335, 0.358, 0.636. The rank is ⌈(9 + 1)(1 − 0.2)⌉ = ⌈8.0⌉ = 8, so q̂ is the 8th smallest, 0.358, and every interval is ŷ ± 0.358. Because (9 + 1)(1 − α) is a whole number here, the lower and upper bounds meet: averaged over calibration sets, coverage is exactly 8/10 = 80% when scores have no ties. Note the naive choice, the 80th percentile of 9 scores, would sit lower and under-cover.
Now all 200 calibration points with α = 0.1: rank ⌈201 × 0.9⌉ = 181, so q̂ = 0.520. On the 2000 test points:
| Score | q̂ | Coverage, all | x < -1.0 (quiet) | x ≥ -1.0 (noisy) |
|---|---|---|---|---|
| |y − ŷ| | 0.520 | 89.4% | 97.2% | 81.5% |
| |y − ŷ| / σ̂(x) | 1.680 | 90.3% | 88.5% | 92.2% |
Both rows deliver about 90% overall. The constant ±0.52 interval does it by over-covering the quiet region (97.2%) and under-covering the noisy one (81.5%). Normalizing by the model's own noise estimate σ̂(x) makes the interval narrow where the data are quiet and wide where they are noisy, and brings the two regions to 88.5% and 92.2%. The guarantee is the same; the intervals are more useful.
The guarantee is about the procedure, averaged over calibration sets. Repeating the whole thing 200 times with fresh calibration and test draws (500 test points each), coverage averaged 89.88%, against the theoretical expectation 181/201 = 90.05%. Any single run varied: the middle 90% of runs fell between 85.2% and 93.4%, and 83 of 200 runs landed below 90%. A larger calibration set tightens that spread; the coverage of one fixed calibration set follows a Beta(181, 20) distribution.
Implementation
Split conformal around any scikit-learn regressor, then MC dropout in PyTorch:
import numpy as np
from sklearn.ensemble import BaggingRegressor
model = BaggingRegressor(n_estimators=50, random_state=0).fit(X_train, y_train)
# ensemble spread: predict() only returns the mean
member_preds = np.stack([m.predict(X_test[:, f])
for m, f in zip(model.estimators_, model.estimators_features_)])
epistemic_sd = member_preds.std(axis=0)
# split conformal, alpha = 0.1
scores = np.abs(y_cal - model.predict(X_cal))
n = len(scores)
k = int(np.ceil((n + 1) * (1 - 0.1)))
q = np.sort(scores)[k - 1] if k <= n else np.inf
lower, upper = model.predict(X_test) - q, model.predict(X_test) + q
# MC dropout: dropout layers stay stochastic, everything else in eval mode
import torch
net.eval()
for m in net.modules():
if isinstance(m, torch.nn.Dropout):
m.train()
with torch.no_grad():
samples = torch.stack([net(x) for _ in range(30)]) # [T, b, out]
mean, sd = samples.mean(0), samples.std(0)
Keep normalization layers in eval() while switching only the dropout modules back to training mode: putting the whole net in
train() would also make BatchNorm use batch statistics. scikit-learn ships no conformal predictor; MAPIE
is the usual library. For classification the analogous conformal step uses 1 - predict_proba[range(n), y_cal] as the score.
Tradeoffs
- Conformal is cheap, model-agnostic and the only method here with a finite-sample guarantee, but the guarantee is marginal and needs exchangeability: under distribution shift or in time series it breaks (weighted and adaptive variants exist). It says nothing about why an interval is wide.
- Ensembles are the strongest practical estimate of epistemic uncertainty and improve accuracy too, but cost M× in training, memory and latency. Distillation or sharing a backbone reduces the serving cost.
- MC dropout is nearly free to add to an existing model but tends to under-estimate uncertainty, especially far from the data, and its spread depends on a dropout rate chosen for other reasons.
- The bootstrap quantifies uncertainty in an estimate (a metric, a coefficient), not in individual predictions, and the percentile interval can be poor for small n or skewed statistics (BCa intervals correct this).
- Aleatoric versus epistemic is a modeling distinction: noise from a feature you did not measure looks aleatoric to this model and would become explainable with that feature.
- None of these methods reliably flags inputs that are far outside the training distribution; out-of-distribution detection is a separate problem, and shared biases (like all five cubics bending the same way) stay invisible.
You have 99 calibration scores and want 95% coverage. Which sorted score is q̂?
k = ⌈100 × 0.95⌉ = 95, so the 95th smallest of the 99. Coverage is then between 95% and 95/100 = 95%: exactly 95% when scores have no ties.
Doubling the training data shrinks which kind of uncertainty?
Epistemic. The noise around the true function, the aleatoric part, stays the same; the models' disagreement shrinks, most where new data land.
Why does MC dropout average to the eval-mode output only for a linear layer after the dropout?
Each scaled mask has expectation 1, so E[w·(m⊙h)/(1−p)] = w·h by linearity. A nonlinearity f in between breaks it: E[f(z)] ≠ f(E[z]) in general.
Connections
Read first
- Calibration core
- Bayes' rule and likelihood core
Read next
No page builds on this one yet.
References
- Efron. Bootstrap Methods: Another Look at the Jackknife. Annals of Statistics, 1979.
- Breiman. Bagging Predictors. Machine Learning, 1996.
- Lakshminarayanan, Pritzel and Blundell. Simple and Scalable Predictive Uncertainty Estimation using Deep Ensembles. NeurIPS 2017. arXiv:1612.01474
- Gal and Ghahramani. Dropout as a Bayesian Approximation: Representing Model Uncertainty in Deep Learning. ICML 2016. arXiv:1506.02142
- Kendall and Gal. What Uncertainties Do We Need in Bayesian Deep Learning for Computer Vision? NeurIPS 2017. arXiv:1703.04977
- Ovadia et al. Can You Trust Your Model's Uncertainty? Evaluating Predictive Uncertainty Under Dataset Shift. NeurIPS 2019. arXiv:1906.02530
- Vovk, Gammerman and Shafer. Algorithmic Learning in a Random World. Springer, 2005.
- Angelopoulos and Bates. A Gentle Introduction to Conformal Prediction and Distribution-Free Uncertainty Quantification. 2021. arXiv:2107.07511
- Romano, Patterson and Candès. Conformalized Quantile Regression. NeurIPS 2019. arXiv:1905.03222
- scikit-learn 1.9.1:
BaggingRegressor, bootstrap indices. PyTorch v2.15.0:nn.Dropout,F.dropout.