Mixture Density Networks (MDN)¶
Mixture Density Networks model the conditional distribution \(p(y \mid x)\) as a mixture of Gaussian components, capturing multimodality, heteroscedastic noise, and complex distributional shapes.
When to use
When the output distribution has multiple modes — e.g., inverse problems with multiple solutions, financial regimes, or multi-path trajectory prediction.
Noisy Inputs
If your input features (\(X\)) are noisy, standard MDNs may produce biased results. Use InputNoiseMDNLoss to marginalize over the input noise for more robust predictions.
Mathematical Background¶
| Symbol | Predicted by model | Constraint |
|---|---|---|
| \(\pi_k\) | Mixture weights | \(\pi_k > 0\), \(\sum_k \pi_k = 1\) (softmax) |
| \(\mu_k\) | Component means | Unconstrained |
| \(\Sigma_k\) | Component covariance | PD (softplus + min_std) |
The NLL is:
computed via log-sum-exp for numerical stability.
See the MixtureDensityLoss API for constructor parameters and the MixtureDensityLoss alias.
Usage¶
from torchregress.losses import MixtureDensityLoss
loss_fn = MixtureDensityLoss(
n_components=3, # K = number of Gaussian components
n_features=2, # d = dimensionality of targets
covariance_type="diagonal", # or "full"
min_std=1e-3,
)
| Parameter | Type | Default | Description |
|---|---|---|---|
n_components |
int |
— | Number of mixture components \(K\) |
n_features |
int |
— | Target dimensionality \(d\) |
covariance_type |
str |
"diagonal" |
"diagonal" or "full" |
full_parameterization |
str |
"precision" |
For "full": the triangular factor parameterises the precision or the covariance (see below) |
min_std |
float |
1e-3 |
Minimum std for stability |
Model Output Size¶
The last block parameterises lower-triangular factors \(T_k\) (positive diagonal). By default they are Cholesky factors of the precision, \(\Sigma_k^{-1} = T_k T_k^\top\); with full_parameterization="covariance", \(\Sigma_k = T_k T_k^\top\). The layout is the same for both.
Full covariance: precision or covariance factor¶
With covariance_type="full" the network outputs, per component, a lower-triangular \(T_k\). full_parameterization sets what it means:
full_parameterization |
Meaning of \(T_k\) | Component log-density |
|---|---|---|
"precision" (default since 0.3.0, as in sbi's MDN) |
\(P_k = \Sigma_k^{-1} = T_k T_k^\top\) | \(-\tfrac12 \lVert T_k^\top (y-\mu_k)\rVert^2 + \sum_i \log T_{k,ii} - \tfrac d2 \log 2\pi\) |
"covariance" (pre-0.3.0 behaviour) |
\(\Sigma_k = T_k T_k^\top\) | \(-\tfrac12 \lVert T_k^{-1} (y-\mu_k)\rVert^2 - \sum_i \log T_{k,ii} - \tfrac d2 \log 2\pi\) |
The precision form is linear in \(T_k\) and needs no triangular solve, which makes it much easier to optimise when there are many targets. It is not better everywhere. Harness run #6 (density_parity, test NLL, mean of 3 seeds, lower is better) against run #5 (covariance form), with sbi's MDN on the same splits:
| Dataset | Targets \(d\) | "covariance" (run #5) |
"precision" (run #6) |
sbi MDN |
|---|---|---|---|---|
| mt_scm20d | 16 | 3.11 | 0.33 | -0.01 |
| mt_jura | 3 | 2.53 | 1.95 | 1.92 |
| energy_efficiency | 2 | -1.32 | -0.76 | -1.33 |
| mt_enb | 2 | -1.18 | -0.85 | -1.24 |
Which parameterisation to use
Keep the default "precision": it wins clearly on many targets (scm20d: 3.11 → 0.33). For low-dimensional targets (\(d \le 2\) here) the covariance form did better, so also train with full_parameterization="covariance" and keep whichever has the lower validation NLL.
Sampling from a precision factor
In precision mode _extract_distribution_parameters returns the precision factor \(T\) (\(P = T T^\top\)), not a covariance Cholesky factor. A covariance square root is \(T^{-\top}\): a draw is \(\mu + T^{-\top}\varepsilon\), not \(\mu + T\varepsilon\). Use loss_fn.sample(...) (and predict_interval), which handle both parameterisations, rather than sampling from the extracted factors yourself. A harness suite that used \(T\) as if it were \(L\) got 0.00 interval coverage.
Training¶
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
for epoch in range(100):
params = model(x_train) # (batch, output_dim)
loss = loss_fn(params, y_train) # MDN NLL
optimizer.zero_grad()
loss.backward()
optimizer.step()
Inference Methods¶
predict_mean_std¶
Gaussian-approximation summary (mean and variance of the mixture):
Multimodal caveat
For genuinely multimodal distributions, the mean may fall between modes where no data exists. Use sample or predict_interval instead. Also note that predict_interval using MC sampling requires n_samples large enough to resolve all modes — for \(K\) well-separated components, 10,000+ samples may be needed for stable interval boundaries.
predict_interval¶
MC-based prediction intervals that respect multimodality:
sample¶
Draw samples from the learned mixture:
samples = loss_fn.sample(model(x_test), n_samples=1000)
# samples shape: [n_samples, batch, n_features]
Practical Tips¶
Avoiding component collapse
Monitor component usage during training:
Initialisation
Initialise the final layer with small weights to prevent early commitment to one component.
Start simple
Begin with \(K = 3\) diagonal components. Increase \(K\) only if validation NLL improves.
Limitations & Optimization Challenges¶
While MDNs are highly expressive, they present several training and optimization challenges:
-
Numerical Instability & Log-Sum-Exp: Computing the NLL directly via \(p(y \mid x) = \sum_k \pi_k \mathcal{N}(y \mid \mu_k, \sigma_k^2)\) involves exponentials that can easily underflow or overflow. torchregress internally utilizes the
log-sum-exptrick: $\(\log p(y \mid x) = \text{log-sum-exp}\left(\log \pi_k + \log \mathcal{N}(y \mid \mu_k, \sigma_k^2)\right)\)$ Always use a non-zeromin_std(e.g., \(10^{-3}\)) to prevent variance collapse (\(\sigma_k^2 \rightarrow 0\)), which causes division by zero. -
Computational Scaling: The forward pass cost is \(\mathcal{O}(K \cdot d)\) for diagonal covariance and \(\mathcal{O}(K \cdot d^2)\) for full covariance per sample. NLL evaluation adds determinant and solve operations that scale as \(\mathcal{O}(d^3)\) for full covariance. For large \(K\) or high-dimensional targets, prefer diagonal covariance or consider flow-based alternatives.
-
Mode Collapse: The network may collapse into using only a single mixture component, effectively ignoring the others (\(\pi_k \approx 0\) for \(k > 1\)). To monitor this, check active components during validation: $\(\text{active\_fraction} = \frac{1}{B} \sum_{i=1}^B \sum_{k=1}^K \mathbf{1}_{\pi_k^{(i)} > 0.05}\)$ Mode collapse is especially common with large \(K\) (> 10) or strong regularization.
-
Initialization Sensitivity: If initialized poorly, components can "claim" the same regions of target space. Initialize the final output layer weight matrices with small random values to diversify components early in training.
-
Dtype Sensitivity: The log-sum-exp trick is numerically sensitive in
float16orbfloat16. Usefloat32for the NLL computation and only cast down after the loss is computed. Mixed-precision training with MDNs requires careful gradient scaling. -
Covariance Type Constraints: With
"full"covariance the head has \(K \cdot d(d+1)/2\) factor outputs and memory grows as \(\mathcal{O}(K \cdot d^2)\) per sample. The default precision parameterisation trains on 16 targets (scm20d above); for very large \(d\) prefer diagonal components or a flow.
Next steps¶
- Normalizing Flows — more flexible distributions for complex targets
- EIV + MDN — marginalize over input noise with MDN heads
- MDN Ensembles — mixture-of-mixtures for decomposed uncertainty
- Multimodal comparison — benchmark MDN vs flows vs SLS
References¶
| # | Reference |
|---|---|
| 1 | C.M. Bishop. "Mixture Density Networks." Neural Computing Research Group Report NCRG/94/004, 1994. |
| 2 | C.M. Bishop. Pattern Recognition and Machine Learning, §5.6. Springer, 2006. |