mixle.models.mixture_density module¶
NeuralConditionalDensity – the adapter that turns ANY torch conditional density into a mixle leaf.
This is the conditional sibling of NeuralDensity. Where that one wraps a
module exposing log_density(x) -> (n,) (an unconditional p(x)), this wraps a module exposing
log_density(x, y) -> (n,) (and sample_given(x) -> (n, d)) and gives you a full five-piece mixle
Distribution over the pair (x, y) – so a flexible conditional density drops into a mixture of experts,
a composite field, or an HMM emission and is fit jointly with classical families by the same
responsibility-weighted-NLL EM M-step (warm-started across iterations, i.e. generalized EM).
Why it matters: NeuralGaussian fixes the conditional law to a single Gaussian,
p(y | x) = N(y; f(x), sigma^2 I) – one mean per x, unimodal and homoscedastic. Many real conditionals
are neither: an inverse problem has several valid y for one x; measurement noise grows with x.
build_mdn() is the ready instance – a mixture density network, p(y | x) = sum_k pi_k(x) N(y; mu_k(x),
sigma_k(x)^2) – whose entire mixture (weights, means, variances) is a function of x, so it is multimodal
and heteroscedastic. Any other conditional density (a conditional flow, an autoregressive head) plugs in the same
way: give it log_density(x, y) and sample_given(x).
build_projection_leaf() is a different kind of ready instance – a contrastive p(y | x) (an InfoNCE
projection between two, typically frozen, embedding spaces) whose log_density is not a calibrated density at
all but still trains and composes through the exact same adapter: the stage-1 “frozen encoder -> projection ->
frozen encoder” pattern, generalized to a family with no domain nouns.
- class NeuralConditionalDensity(module, *, m_steps=60, lr=5e-3, device='cpu', name=None)[source]
Bases:
SequenceEncodableProbabilityDistributionWrap a torch conditional-density
module(module.log_density(x, y) -> (n,)) as a mixle leaf.Observations are pairs
(x, y). The module must also exposesample_given(x) -> (n, d)to drawy.- log_density(xy)[source]
Return
log p(y | x)for one observation pair(x, y).
- seq_log_density(enc)[source]
Return per-row conditional log densities for encoded
(x, y)arrays.
- sampler(seed=None)[source]
Return a conditional sampler for drawing
ygivenx.- Parameters:
seed (int | None)
- Return type:
NeuralConditionalDensitySampler
- estimator(pseudo_count=None)[source]
Return the generalized-EM estimator for weighted conditional-density training.
- Parameters:
pseudo_count (float | None)
- Return type:
NeuralConditionalDensityEstimator
- dist_to_encoder()[source]
Return the encoder for
(x, y)observation pairs.- Return type:
NeuralConditionalDensityEncoder
- to_dict()[source]
Serialize hyperparameters and module bytes for registry-based round trips.
- class NeuralConditionalDensitySampler(dist, seed=None)[source]
Bases:
DistributionSamplerConditional sampler for modules exposing
sample_given(x).- Parameters:
dist (NeuralConditionalDensity)
seed (int | None)
- sample(size=None, *, batched=True)[source]
Raise because the leaf defines
p(y | x)and has no marginalp(x).
- sample_given(x)[source]
Draw one response from
p(y | x)using the wrapped module.
- sample_given_batch(x_batch)[source]
One draw of
y ~ p(y | x)for every row ofx_batch(shape(n, x_dim)), in one batched forward pass – statistically identical to callingsample_given()once per row (same model, same per-draw sampling procedure), just without paying framework/dispatch overhead per row. That per-call overhead dominates a Python loop of hundreds of individualsample_givencalls, which is exactly the shape both a particle-walk step (many different x’s, one draw each) and a per-point coverage check (repeat one x, many draws) reduce to – both call sites use this to speed up the same check/walk rather than shrink it. Repeat a row ofx_batchto draw more than once from the samex.
- class NeuralConditionalDensityEncoder[source]
Bases:
DataSequenceEncoderEncode
(x, y)pairs for vectorized conditional-density scoring and fitting.
- class NeuralConditionalDensityAccumulator[source]
Bases:
SequenceEncodableStatisticAccumulatorBuffers responsibility-weighted
(x, y)pairs for the M-step (the weights are the E-step soft counts).- update(xy, weight, estimate)[source]
Add one weighted observation pair to the accumulator.
- seq_update(enc, weights, estimate)[source]
Add a batch of encoded observation pairs and responsibility weights.
- initialize(xy, weight, rng)[source]
Initialize from one observation using the ordinary update path.
- seq_initialize(enc, weights, rng)[source]
Initialize from an encoded batch using the ordinary batch update path.
- combine(other)[source]
Merge the value tuple from another conditional-density accumulator.
- Parameters:
other (Any)
- Return type:
NeuralConditionalDensityAccumulator
- from_value(value)[source]
Restore accumulator buffers from a value tuple.
- Parameters:
value (tuple)
- Return type:
NeuralConditionalDensityAccumulator
- acc_to_encoder()[source]
Return the encoder expected by this accumulator.
- Return type:
NeuralConditionalDensityEncoder
- class NeuralConditionalDensityAccumulatorFactory[source]
Bases:
StatisticAccumulatorFactoryFactory for conditional-density accumulators.
- make()[source]
Create a fresh accumulator.
- Return type:
NeuralConditionalDensityAccumulator
- class NeuralConditionalDensityEstimator(module, *, m_steps=60, lr=5e-3, device='cpu', name=None)[source]
Bases:
ParameterEstimatorM-step: responsibility-weighted MLE
max sum_i w_i log p(y_i | x_i)by gradient ascent (warm-started).- accumulator_factory()[source]
Return an accumulator factory for weighted conditional-density batches.
- Return type:
NeuralConditionalDensityAccumulatorFactory
- build_mdn(x_dim, y_dim, *, k=5, hidden=32, layers=2)[source]
A mixture density network:
p(y | x) = sum_k pi_k(x) N(y; mu_k(x), diag sigma_k(x)^2)– ready to wrap.A shared MLP body maps
xto three heads – mixing logits, component means, and (log) component scales – so the entire conditional law is a function ofx: multimodal (severalmu_k) and heteroscedastic (input-dependentsigma_k). Exposeslog_density(x, y)(a log-sum-exp over components) andsample_given(x)(pick a component bypi, then a Gaussian), the contract aNeuralConditionalDensityadapts.
- build_conditional_flow(x_dim, y_dim, *, hidden=32, layers=4)[source]
A conditional coupling flow: an exact
p(y | x)whose transform ofyis conditioned onx.The exact-density counterpart to
build_mdn(). Each affine-coupling layer’s shift/scale networks take both the passed-throughycoordinates andx, so the whole invertibley-transform bends with the input – capturing within-``y`` dependence (e.g.y2a nonlinear function ofy1) that a single-GaussianNeuralGaussian(isotropic mean-only) cannot, while keeping an exact log-density rather than a bound. Needsy_dim >= 2for the coupling to be non-trivial. Exposeslog_density(x, y)andsample_given(x)– the contract aNeuralConditionalDensityadapts.
- build_projection_leaf(d_x, d_y, *, encoder_x=None, encoder_y=None, proj_dim=None, hidden=64, freeze_encoders=True, temperature=0.07)[source]
A contrastive (InfoNCE / CLIP-style) conditional
p(y | x)between two embedding spaces – ready to wrap.This is the stage-1 multimodal pattern – frozen encoder -> trainable projection -> frozen encoder – stated with no domain nouns.
encoder_x/encoder_yare any torch module mapping a raw item to ad_x/d_yembedding; both default tonn.Identity(), sox/ymay already BE the embeddings (pass precomputed vectors straight in, no backbone required). Encoders are frozen by default (freeze_encoders=True): their parameters getrequires_grad_(False)and the module is pinned ineval()regardless of the outertrain()/eval()calls the M-step makes, so no dropout/batchnorm noise leaks into a “frozen” backbone and no gradient ever reaches it. The only trainable piece is a small projection head per side (d_x/d_y->hidden->proj_dim, defaultproj_dim = min(d_x, d_y)) mapping BOTH embeddings into one shared, L2-normalized space – the CLIP design (two projections into a shared space), not a single asymmetricx -> yregression – so the same leaf answers “which y matches this x” and “which x matches this y”.log_density(x, y)returns, per row, the (negative) SYMMETRIC INFONCE loss for a batch ofnpaired embeddings: every row’s projected pair is scored against every OTHER row in the batch as a negative, in both directions (x -> yandy -> x), log-softmax-normalized over the batch dimension, then averaged. That is exactly what the sharedNeuralConditionalDensityM-step already does withlog_density– weight it and sum it – so no separate loss path is needed: the M-step’s responsibility-weighted-NLL gradient ascent onlog_densityis InfoNCE training, “for free” from the adapter’s existing contract. As withbuild_vae()’s ELBO, this is an honest score against itself (or another leaf scored the same batch-relative way) rather than a calibratedlog p(y | x)– there is no way to integrate a softmax-over-the-current-batch score to 1 over ally. A batch of a single row has no negatives to contrast against, solog_densityreturns0for it rather than raising.sample_givenis not defined – a contrastive leaf is discriminative (it scores/ranks pairs); it has no generativep(y | x)to draw from. Retrieve a matchingyby comparingmodule.embed_x(x)againstmodule.embed_y(candidates)(cosine similarity in the shared space) instead.
- build_conditional_autoregressive_categorical(x_dim, y_dim, n_categories, *, hidden=64)[source]
An autoregressive categorical conditioned on
x: exactp(y | x)over discretey in {0..C-1}^y_dim.The conditional sibling of
build_autoregressive_categorical()and the discrete counterpart tobuild_conditional_flow(). It factorizesp(y | x) = prod_i p(y_i | y_{<i}, x)with a MADE-masked net overyinto whichxis injected unmasked (degree 0, so every coordinate may depend onx). Each per-coordinate softmax is exactly a conditional, so the density is exactly normalized and comparable to other exact discrete conditional leaves. Exposeslog_density(x, y)andsample_given(x)– the contract aNeuralConditionalDensityadapts.