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.Sequential refinement (rounds 2..R). Resolved decision (was open in the design note): eager, via an optional
y_obskeyword to THIS function. Wheny_obsis given, each subsequent round drawstheta ~ q(theta | y_obs)from the CURRENT round’s student, re-runs the simulator, and retrains warm-started from the previous round’s module weights (same object, sooptimizecontinues training it in place – the same warm-start patternGradLeaf’s own M-step uses across EM iterations).rounds > 1withouty_obshas no observation to sharpen toward, so it raisesValueErrorrather than silently doing nothing – round 1 alone (unconditional pair generation) is valid withouty_obs.Optional exactness stage.
reweight=Truewithtrue_log_likelihood(theta, y_obs) -> float(a LOG likelihood) treats the final round’sq(theta | y_obs)as a self-normalized- importance-sampling proposal:log w_j = log p(theta_j) + true_log_likelihood(theta_j, y_obs) - log q(theta_j | y_obs), normalized by log-sum-exp (the same constructionmixle.inference.condition’s SIR fallback uses), withESS = 1 / sum(w_norm^2)reported so a low ESS visibly says “don’t trust this reweighted posterior” instead of silently returning a degenerate one.Calibration receipts (always computed). - SBC. Resolved decision (was open): a chi-square uniformity test on binned ranks (Talts et
al.),
bins = min(20, n_sbc_replications // 5)(clamped to >= 2), one chi-square statistic perthetadimension SUMMED (valid under the standard independence-across-dimensions simplification – a sum of independent chi-square variables is chi-square with summed degrees of freedom), against thresholdp-value > 0.01(no rejection of uniformity).Coverage. Per-dimension, per-replication credible interval containment vs nominal level, averaged; pass when within +/-5% of nominal.
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)[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, sharpness_by_round=<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 > 1(SNPE-style sequential refinement toward a SPECIFIC observation) requiresy_obs: round 1 alone (unconditional pair generation) is the only round that has meaning without an observation to sharpen against.