Prediction-Powered Inference (PPI)¶
← Semi-Supervised | Visualization →
PPI is a statistical framework for performing valid inference using a combination of a small set of gold-standard labels and a large set of machine learning predictions. It produces confidence intervals that are (1) narrower than using gold labels alone and (2) statistically valid even if the ML model is arbitrarily biased.
Why this matters
In many domains — healthcare, remote sensing, social science, NLP evaluation — obtaining accurate labels is expensive, but cheap ML predictions are abundant. PPI lets you exploit this asymmetry for rigorous statistical analysis.
Background and Motivation¶
The Problem¶
Suppose you want to estimate a population parameter \(\theta\) (a mean, a quantile, or a regression coefficient). You have two data sources:
| Source | Size | Quality | Cost |
|---|---|---|---|
| Gold-standard labels \(Y_i\) | \(n\) (small) | Accurate | Expensive (expert annotation, spectroscopy, laboratory assay) |
| ML predictions \(\hat{f}(X_i)\) | \(N \gg n\) (large) | Potentially biased | Cheap (automated model) |
Three naive approaches all have critical flaws:
| Approach | Problem |
|---|---|
| Use only gold labels | Valid but imprecise — CIs are wide because \(n\) is small |
| Use only predictions | Invalid — systematic model bias contaminates the estimate |
| Mix them ad hoc | No coverage guarantee — unclear how to combine |
The PPI Solution¶
PPI uses the large prediction set for precision and the small gold set for bias correction. The rectified estimator is:
The first term uses all \(N\) samples for precision. The bias correction (middle two terms) cancels the model's systematic error, estimated on the labelled subset.
Validity guarantee
The resulting CIs are valid regardless of model quality. If the model is accurate (\(R^2 \approx 1\)), CIs are much tighter. If the model is poor (\(R^2 \approx 0\)), PPI gracefully degrades to gold-label-only inference, never making things worse (PPI++).
Core Assumptions¶
PPI's validity rests on a small number of assumptions:
| Assumption | Description | Diagnostic |
|---|---|---|
| Exchangeability | Labelled and unlabelled samples drawn from the same population (MCAR) | Compare covariate distributions |
| External model | The prediction model \(\hat{f}\) must be trained on data separate from the internal dataset | Prevents overfitting bias |
| Full covariates | All features needed by \(\hat{f}\) are observed for every unit (labelled and unlabelled) | Check for missingness |
When PPI breaks
If labelling is not random (e.g., only easy-to-classify examples get labels), the bias correction is invalid. This is the Missing Not At Random (MNAR) setting; extensions exist but require additional modelling [5].
Available Estimators¶
| Function | Estimand | Typical Application |
|---|---|---|
ppi_mean_ci |
Population mean \(\mathbb{E}[Y]\) | Average treatment effect, prevalence estimation |
ppi_calibrated_mean_ci |
Same, with affine post-hoc calibration of the score | Mis-scaled but informative scores (OLS on labeled pairs) |
ppi_quantile_ci |
Population quantile \(Q_\tau\) | Median income, tail risk |
ppi_ols_ci |
OLS coefficients \(\beta\) | Regression with proxy labels |
ppi_diagnostics |
— | Prediction quality assessment |
Population Mean¶
from torchregress.inference import PPIConfig, ppi_mean_ci
ci = ppi_mean_ci(
y_labeled=y_gold,
pred_labeled=f_hat_gold,
pred_unlabeled=f_hat_all,
config=PPIConfig(alpha=0.05),
)
print(f"95% CI: [{ci['ci_lower']:.4f}, {ci['ci_upper']:.4f}]")
Population Quantile¶
from torchregress.inference import PPIConfig, ppi_quantile_ci
ci = ppi_quantile_ci(
y_labeled=y_gold,
pred_labeled=f_hat_gold,
pred_unlabeled=f_hat_all,
q=0.5, # median
config=PPIConfig(alpha=0.05),
)
The quantile estimator inverts the rectified CDF
\(\hat F(\theta) = \frac{1}{N}\sum_j 1\{\tilde f_j \le \theta\} + \frac{1}{n}\sum_i \big(1\{Y_i \le \theta\} - 1\{f_i \le \theta\}\big)\):
the estimate is \(\inf\{\theta : \hat F(\theta) \ge q\}\) and the CI collects the \(\theta\) whose
\(|\hat F(\theta) - q|\) is within \(z_{1-\alpha/2}\) standard errors [1]. The
interval is analytic, so n_boot and seed are not used.
OLS Coefficients¶
from torchregress.inference import PPIConfig, ppi_ols_ci
ci = ppi_ols_ci(
x_labeled=x_gold, y_labeled=y_gold,
x_unlabeled=x_all, pred_labeled=f_hat_gold, pred_unlabeled=f_hat_all,
config=PPIConfig(alpha=0.05),
)
Calibrated PPI for the mean¶
Chen et al. (Calibeating Prediction-Powered Inference, arXiv:2604.21260) study post-hoc calibration of a fixed scalar score \(m(X)\) on the labeled sample before semisupervised mean estimation. Torchregress implements their linearly calibrated mean (Section 3.3): fit an affine map by ordinary least squares on labeled pairs,
Then apply the same rectified mean as ppi_mean_ci, but with \(m_n^\star\) in place of \(m\):
Bootstrap percentile intervals refit \((\hat a, \hat b)\) on each labeled bootstrap replicate (and draw independent unlabeled bootstrap rows), which accounts for calibration uncertainty. The paper also analyzes isotonic calibration theoretically; we omit it here in favor of the lightweight affine map (pure PyTorch), which they relate to prognostic-score style adjustment and to PPI++ at first order.
from torchregress.inference import PPIConfig, ppi_calibrated_mean_ci
ci_cal = ppi_calibrated_mean_ci(
y_labeled=y_gold,
pred_labeled=f_hat_gold,
pred_unlabeled=f_hat_all,
config=PPIConfig(alpha=0.05, n_boot=2000, seed=0),
)
When this helps
Use calibrated PPI when the score tracks \(Y\) but has the wrong slope or intercept (common for models trained on another population or loss). If the score is already conditionally unbiased, gains over ppi_mean_ci may be modest.
Inference vs prediction (and “better on all metrics”)¶
Earlier guidance distinguished two kinds of follow-up work:
| Track | What it meant | Role |
|---|---|---|
| (b) Code | Wire existing pieces together (e.g. SplitConformal + PPI) in a runnable workflow |
Shows a defensible pipeline, not a single magic estimator |
| © Docs | Explain which metric each method optimizes and where tradeoffs live | Sets expectations so “better everywhere” is not promised |
There is no single procedure that simultaneously maximizes every metric (interval width vs coverage vs bias vs sharpness vs conditional validity). What you can do is separate estimands and use the right tool per question:
| Goal | Typical tool in torchregress | Contract (informal) |
|---|---|---|
| Uncertainty for a summary (e.g. \(\mathbb{E}[Y]\), OLS \(\beta\)) | ppi_mean_ci, ppi_calibrated_mean_ci, ppi_ols_ci, … |
Uses labeled + unlabeled; bias-aware rectification |
| Finite-sample predictive bands for individual \(Y\) | torchregress.losses.SplitConformal, CQR, … |
Exchangeability / split assumptions; calibrate scores on a held-out labeled fold |
Conformalizing in the strict sense means building nonconformity scores on labeled data and extrapolating intervals to new predictions — it does not replace PPI’s job for \(\mathbb{E}[Y]\), but it complements it when you also care about per-unit coverage.
Recommended composition¶
- Split the labeled data (at least two folds): one fold fits affine calibration (or any post-hoc map), another supports PPI bias correction together with unlabeled scores, and optionally a third (or reuse a fold carefully) feeds split conformal calibration.
- Use
ppi_calibrated_mean_ciwhen you are comfortable refitting \((\hat a,\hat b)\) on every labeled bootstrap replicate (often good variance when \(n\) is not tiny). - Use
SplitConformalon residuals \(|Y - \hat Y|\) where \(\hat Y\) is the same post-calibrated score you will deploy at test time, calibrated only on data not used to cherry-pick that map if you need clean marginal guarantees.
A minimal end-to-end sketch is in examples/ppi_mean_plus_split_conformal.py.
Efficiency and When to Expect Gains¶
The precision gain from PPI depends on the predictive quality of the model:
| Model quality | \(R^2\) | Effective sample size multiplier | CI width reduction |
|---|---|---|---|
| Excellent | 0.95 | \(\sim 20\times\) | \(\sim 78\%\) |
| Good | 0.80 | \(\sim 5\times\) | \(\sim 55\%\) |
| Moderate | 0.50 | \(\sim 2\times\) | \(\sim 30\%\) |
| Poor | 0.10 | \(\sim 1.1\times\) | \(\sim 5\%\) |
| Useless | 0.00 | \(1\times\) (no gain) | \(0\%\) |
Rule of thumb
PPI is most valuable when \(R^2 > 0.5\) and \(N/n > 10\). Use ppi_diagnostics to assess before committing.
Complete Example¶
import torch
from torchregress.inference import ppi_mean_ci, ppi_diagnostics
# Setting: estimating mean patient recovery time
# Gold labels: expert-assessed recovery (expensive, n = 150)
# ML predictions: automated scoring (cheap, N = 10,000)
torch.manual_seed(42)
# Gold-standard labels
y_gold = 14.0 + 3.0 * torch.randn(150) # days
# ML predictions on gold set (correlated but biased)
f_hat_gold = y_gold + 1.5 + 0.8 * torch.randn(150) # bias = +1.5 days
# ML predictions on full population
f_hat_all = 15.5 + 3.0 * torch.randn(10_000)
# --- Gold-only CI (wide) ---
gold_mean = y_gold.mean()
gold_se = y_gold.std() / (150 ** 0.5)
print(f"Gold-only 95% CI: [{gold_mean - 1.96*gold_se:.2f}, {gold_mean + 1.96*gold_se:.2f}]")
print(f" Width: {2 * 1.96 * gold_se:.2f} days")
# --- PPI CI (narrower) ---
from torchregress.inference import PPIConfig
ci = ppi_mean_ci(y_gold, f_hat_gold, f_hat_all, config=PPIConfig(alpha=0.05))
print(f"PPI 95% CI: [{ci['ci_lower']:.2f}, {ci['ci_upper']:.2f}]")
print(f" Width: {ci['ci_upper'] - ci['ci_lower']:.2f} days")
# Diagnostics
diag = ppi_diagnostics(y_gold, f_hat_gold, f_hat_all)
Comparison with Related Approaches¶
| Method | Uses Unlabelled | Valid CIs | Corrects Bias | Assumptions |
|---|---|---|---|---|
| Gold-only | ❌ | ✅ | N/A | None |
| Prediction-only | ✅ | ❌ | ❌ | Model is correct |
| Semi-supervised | ✅ | ❌ (heuristic) | Partially | Model class |
| PPI | ✅ | ✅ | ✅ | Exchangeability |
| PPI++ | ✅ | ✅ | ✅ | Same + tuned \(\lambda\) |
| Calibrated PPI (mean) | ✅ | ✅ (bootstrap) | ✅ | Post-hoc \(f \circ m\) on labeled data [6] |
Strengths and Limitations¶
Strengths
- Model-agnostic — valid even when \(\hat{f}\) is arbitrarily wrong
- Never hurts (PPI++) — asymptotically never increases variance over gold-only
- Modular — works for means, quantiles, regression, classification
- Broad applicability — biomedicine, remote sensing, social science, NLP
Limitations
- Requires MCAR labelling — if labels are obtained non-randomly, extensions are needed
- No gain when predictions are uninformative (\(R^2 \approx 0\))
- Model must be trained on external data (not the same dataset)
- The proliferation of PPI variants (PPI, PPI++, Cross-PPI, Cross-PPBoot) creates a usability gap [5]
References¶
| # | Reference |
|---|---|
| 1 | A. Angelopoulos et al. "Prediction-Powered Inference." Science, 382(6671):669–674, 2023. |
| 2 | A. Angelopoulos, S. Bates, T. Zrnic. "PPI++: Fast Valid Inference via Tuned Cross-Prediction." arXiv:2311.01453, 2023. |
| 3 | T. Zrnic, E. Candès. "Cross-Prediction-Powered Inference." PNAS, 2024. |
| 4 | J. Gronsbell et al. "Efficient and Robust Semi-Supervised Estimation of ATE." JASA, 2024. |
| 5 | J. Miao et al. "Demystifying Prediction Powered Inference." arXiv:2601.20819, 2025. |
| 6 | Y. Chen et al. "Calibeating Prediction-Powered Inference." arXiv:2604.21260, 2026. |
| 7 | M. P. Stoica et al. "In-Context Parametric Inference: Point or Distribution Estimators?" arXiv:2502.11617, 2025 — compares amortized point (MLE/MAP) and posterior (NPE) estimators; background for choosing NRE/NPE in this guide. |
Likelihood-Ratio Posterior Reweighting (NRE)¶
The same package also provides utilities for a different inference pattern: a model estimates the likelihood ratio between hypotheses rather than a population parameter.
Let \(r(x,\theta) = p(x \mid \theta)/p_{\mathrm{ref}}(x)\), for example the output of
NeuralRatioLoss or of a contrastive flow. For a finite candidate bank
\(\{\theta_k\}_{k=1}^{K}\) drawn from a proposal \(q(\theta)\):
When the candidates are drawn from the target prior (\(q=p\)) the corrections cancel and the posterior weights are the normalized ratios. The maximum-likelihood estimate is \(\arg\max_k \log p(x \mid \theta_k)\); adding \(\log p(\theta_k)\) gives the MAP estimate, and the MLE itself does not depend on the training label/simulation prior (the \(p(x)\) factor is constant in \(\theta\)).
| Utility | Use |
|---|---|
posterior_log_weights |
normalize candidate log ratios with prior/proposal corrections |
effective_sample_size |
Kish ESS of the candidate bank; gate posterior means/quantiles on it |
grid_log_posterior |
the same for likelihood scans on a parameter grid |
maximum_likelihood_estimate |
MLE/MAP point estimate and winning index |
Caveats
- A ratio model is only as good as its reference distribution: scores are relative to the proposal/negative-sampling distribution, not absolute posteriors.
- Finite banks give self-normalized importance-sampling estimates: check the ESS, the grid
truncation, and unequal cell volumes (
log_reference). - A maximum-likelihood point is not a posterior mean; use the weights for minimum-MSE summaries and SBC/TARP to validate posterior calibration.
See the NRE loss guide for training the ratio model itself.
Limitations¶
- MCAR labelling required: PPI assumes labelled samples are Missing Completely At Random. If labelling is non-random (e.g., only easy examples get labels), the bias correction is invalid. Extensions for MNAR exist but require additional modelling.
- No gain with uninformative predictions: If the ML model has \(R^2 \approx 0\), PPI provides no precision gain over gold-only inference. Use
ppi_diagnosticsto assess predictive quality before committing. - External model requirement: The prediction model must be trained on data separate from the internal dataset used for inference. Training and evaluating on the same data introduces overfitting bias that invalidates confidence intervals.
- Bootstrap cost: Bootstrap-based CIs (
ppi_calibrated_mean_ci, PPI++) refit nuisance parameters on each bootstrap replicate. For large \(n\) or many bootstraps, this adds non-trivial runtime. - Not a drop-in replacement for conformal prediction: PPI targets population-level estimands (means, quantiles, regression coefficients). For per-unit prediction intervals, use the Conformal prediction tools.
Recommendations¶
- Check \(R^2\) first: PPI is most valuable when \(R^2 > 0.5\) and \(N/n > 10\). Run
ppi_diagnosticsto estimate the expected CI width reduction before investing in a full PPI pipeline. - Use
ppi_calibrated_mean_cifor mismatched scores: When the ML score tracks \(Y\) but has the wrong slope or intercept (common for models trained on a different population or loss function), calibrated PPI often improves over plainppi_mean_ci. - Split your data carefully: Hold out one fold for affine calibration, another for PPI bias correction, and optionally a third for split-conformal calibration. Never reuse the same fold for multiple stages.
- Complement with conformal prediction: Use PPI for population-level inference (e.g., "what is the mean recovery time?") and SplitConformal for per-unit prediction intervals ("what is the recovery time for this patient?"). See
examples/ppi_mean_plus_split_conformal.py. - Degrade gracefully with PPI++: When model quality is uncertain, use PPI++ (via
power=1.0) — it asymptotically never increases variance over gold-only inference.
Next Steps¶
- Calibration Metrics — evaluate your model's predictive quality for PPI
- Causal Inference — doubly-robust estimation (related methodology)