mixle.task.inverse module¶
learn_inverse – amortized posteriors q(theta | y) for a simulator, with calibration receipts.
Simulation-based inference done the mixle way: given a forward simulator g: theta -> y (a bare
Python callable – no mixle.task.imagine/M2 program required) and a prior p(theta) (any
fitted mixle Model), learn_inverse() trains a torch CONDITIONAL density student
q(theta | y) on simulated (theta, y) pairs, then ships it wrapped as an
Posterior (M0’s type) so downstream condition/do
composition and B7 treat a learned inverse exactly like an exactly-conditioned one – except its
.receipt carries an explicit amortization warning plus a pointer to InverseReceipts,
because a trained student is an APPROXIMATION and the whole point of this module is to ship the
numbers that say whether to trust it.
Convention (matches build_mdn/build_conditional_flow’s own log_density(x, y)/
sample_given(x) contract): theta – the quantity being inferred – is the student’s
y-ARGUMENT, and the observed data y is its x-argument. So q(theta | y_obs) is
module.sample_given(y_obs) -> theta and its density is module.log_density(y_obs, theta) –
the inverse of the simulator’s own arrow.
The student is trained through the vendored GradLeaf (a bare torch
module IS the model), not the simpler NeuralConditionalDensity
adapter – see notes/designs/M3.md for why: sequential refinement (below) re-scores the module
against freshly generated round data before the next fit commits, which wants the generic
seq_log_density path GradLeaf gives any bare module (warm-started across optimize()
calls via the SAME underlying nn.Module object) rather than a second bespoke accumulator.
NeuralConditionalDensity remains the simpler documented alternative for callers who don’t need
round-conditioned rescoring.
Algorithm (notes/designs/M3.md):
Pair generation (round 1).
theta_i ~ p(theta)via the prior’s own sampler;y_i = simulator(theta_i)in a plain Python loop (no batching assumed onsimulator).Student.
build_conditional_flow/build_mdnwrapped inGradLeaf, fit viaoptimize(list(zip(y_pairs, theta_pairs)), leaf, ...)– A4.4’s tuple-default-loss fix is what lets the bare module’s two-arglog_density(x, y)score straight off tuple observations.Proposal-corrected sequential refinement (rounds 2..R). Each round draws from the frozen current proposal
q_r(theta | y_obs), recordsp(theta) / q_r(theta | y_obs)importance weights, and refits on ALL retained rounds. Round 1 prior draws keep global support; every later block is self-normalized to equal round mass. Thus every round’s weighted conditional-density objective targets the original prior jointp(theta)p(y|theta)– never the proposal joint.Optional finite-particle target correction.
reweight=Truewithtrue_log_likelihood(theta, y_obs)turns the final neural posterior into a self-normalized importance proposal. The returned model stores those particles and normalized weights, andposterior(y_obs)samples and computes means from that weighted empirical posterior. It is an importance-resampled approximation, not an exact continuous density; ESS and a warning make that contract explicit.Calibration receipts (always computed).
SBC. Ties are randomized and every discrete rank cell is jittered into a continuous uniform variate. Each parameter dimension gets an equal-probability-bin chi-square test; a Bonferroni-adjusted minimum p-value supplies a valid global test under arbitrary cross-dimension dependence.
Coverage. Per-dimension empirical coverage ships with simultaneous 99% Wilson intervals; a level passes only when its nominal probability lies inside every dimension’s interval.
Prior-predictive. Empirical mean/std of the round-1 simulated
y_i’s, plus (wheny_obsis given) its per-dimension z-score against that empirical distribution – a caller-facing warning, not a gate.
- class InverseModel(*, module, prior, simulator, family, theta_dim, y_dim, receipts, seed=None, reweighted_y=None, reweighted_particles=None, reweighted_weights=None)[source]
Bases:
objectA fitted amortized posterior
q(theta | y)plus itsInverseReceipts.- Parameters:
- posterior(y)[source]
Wrap
q(theta | y)as an M0Posterior:sample(n)/log_density(theta)/mean(field)/.receipt– so downstream condition/do composition treats a learned inverse like an exactly-conditioned one, modulo the amortization warning on.receiptand theInverseReceiptspointer at.receipt.inverse_receipts.- Parameters:
y (Any)
- Return type:
Posterior
- class InverseReceipts(sbc_statistic, sbc_pvalue, sbc_bins, sbc_replications, sbc_pass, coverage, coverage_pass, prior_predictive, rounds_trained, sbc_pvalues_by_dimension=<factory>, sbc_method='randomized_rank_bonferroni', coverage_by_dimension=<factory>, coverage_intervals=<factory>, coverage_method='simultaneous_99pct_wilson', sharpness_by_round=<factory>, round_training=<factory>, ess=None, ess_ratio=None, warnings=<factory>)[source]
Bases:
objectThe calibration report that ships with every
InverseModel– tells the caller whether to trustq(theta | y), not just a point estimate.
- learn_inverse(simulator, prior, *, family='flow', n_sims=2000, rounds=1, n_sbc_replications=200, coverage_levels=(0.5, 0.9), reweight=False, true_log_likelihood=None, y_obs=None, seed=None, m_steps=200, lr=5e-3, max_its=1, hidden=32, n_posterior_samples=200, n_reweight_samples=500)[source]
Learn an amortized posterior
q(theta | y)for simulatorg: theta -> yunder priorp(theta). See the module docstring for the full algorithm and the calibration receipts computed unconditionally.family="flow"(build_conditional_flow) requirestheta(the quantity being inferred, the student’sy-argument) to be >= 2-dimensional –build_conditional_flowneedsy_dim >= 2for its coupling layers to be non-trivial (see its own docstring). A 1-Dtheta(e.g. a scalar-parameter inverse problem) must usefamily="mdn", which has no such restriction (a mixture of per-component Gaussians is well-defined for scalarthetatoo, and is the more direct fit for asserting multimodality component-by-component).rounds > 1performs proposal-corrected SNPE toward a specific observation. Round-one prior simulations are retained forever. Every later proposal block is weighted byp(theta)/q_round(theta|y_obs)and the accumulated weighted objective continues to target the declared prior posterior rather than the narrower proposal posterior.reweight=Truerequiresy_obsand returns an observation-bound, finite-particle importance-resampled posterior. It improves the target measure when its ESS is healthy but does not claim an exact continuous density.