Single-cell and perturbations
Posterior predictive moments and total variance
scVIscANVItotalVI
Free account with email, Google or GitHub. Your code runs in an isolated PyTorch sandbox.
Your task
Average supplied posterior count predictions using valid sample weights. Return the predictive mean, total variance and its within/between-sample components, zero-count probability, and effective sample size.
Function signature
posterior_predictive_moments(
mean: torch.Tensor,
inverse_dispersion: torch.Tensor,
sample_weights: torch.Tensor,
sample_mask: torch.Tensor,
gene_mask: torch.Tensor,
zero_inflation_logits: torch.Tensor | None = None,
likelihood: str = 'zinb',
) -> dict[str, torch.Tensor]Worked example
Analytic two component predictive mixture
With zero-inflation probability 1/2, NB means 1 and 3, and dispersion 1, conditional means are 0.5 and 1.5. Conditional variance is (1/2)(mu+mu²)+(1/4)mu², giving 1.25 and 8.25. Equal weights give predictive mean 1, within-sample variance (1.25+8.25)/2=4.75, and between-sample variance ((0.5-1)²+(1.5-1)²)/2=0.25, so total variance is 5. Zero-count probability is (3/4+5/8)/2=11/16; effective sample size is 2.
import torch
mean = torch.tensor([[[1.0]], [[3.0]]], dtype=torch.float64, requires_grad=True)
inverse_dispersion = torch.tensor([1.0], dtype=torch.float64, requires_grad=True)
zero_inflation_logits = torch.tensor([[[0.0]], [[0.0]]], dtype=torch.float64, requires_grad=True)
sample_weights = torch.tensor([[1.0], [1.0]], dtype=torch.float64, requires_grad=True)
sample_mask = torch.tensor([[True], [True]], dtype=torch.bool)
gene_mask = torch.tensor([[True]], dtype=torch.bool)
likelihood = 'zinb'
result = posterior_predictive_moments(mean=mean, inverse_dispersion=inverse_dispersion, zero_inflation_logits=zero_inflation_logits, sample_weights=sample_weights, sample_mask=sample_mask, gene_mask=gene_mask, likelihood=likelihood)Expected return
{
'mean': torch.tensor([[1.0]], dtype=torch.float64),
'variance': torch.tensor([[5.0]], dtype=torch.float64),
'likelihood_variance': torch.tensor([[4.75]], dtype=torch.float64),
'latent_variance': torch.tensor([[0.25]], dtype=torch.float64),
'zero_probability': torch.tensor([[0.6875]], dtype=torch.float64),
'effective_sample_size': torch.tensor([2.0], dtype=torch.float64),
'normalized_weights': torch.tensor([[0.5], [0.5]], dtype=torch.float64),
}Parameters
| Parameter | Shape and meaning |
|---|---|
meantorch.Tensor | [S, B, G]Supplied nonnegative NB component count means; differentiable. |
inverse_dispersiontorch.Tensor | [G] or [S, B, G]Positive negative-binomial inverse dispersion, shared across samples/cells when one value per gene is supplied. |
zero_inflation_logitstorch.Tensor | NoneDefault None | [S, B, G]Structural-zero logits; differentiable in ZINB mode and ignored in NB mode. |
sample_weightstorch.Tensor | [S, B]Supplied nonnegative posterior/importance weights on included samples; differentiable. |
sample_masktorch.Tensor | [S, B]True includes this posterior sample for this cell. |
gene_masktorch.Tensor | [B, G]True includes the gene's requested marginal moments. |
likelihoodstrDefault 'zinb' | Choose "nb" for negative-binomial predictions or "zinb" to include the supplied structural-zero probabilities in all predictive moments. |
Return
dict[str, torch.Tensor]
Return exactly the named fields applicable to this operation.
Required behavior
- Marginalize supplied NB or ZINB decoder distributions across posterior samples, applying the law of total
variance.meanandzero_inflation_logitsare [S,B,G], with posterior sample, cell, gene axes.inverse_dispersionis[G]or [S,B,G].sample_weightsand boolsample_maskare [S,B]; boolgene_maskis [B,G]. - All dimensions are
\ge 1.Truemasks include entries.\mathrm{Active}=\mathrm{sample\_mask}[:,:,\mathrm{None}] \land \mathrm{gene\_mask}[\mathrm{None},:,:]. Means are nonnegative and\thetastrictly positive on active entries. Active sample weights are nonnegative. - All inputs are finite; finite invalid sentinels in masked positions are ignored. All tested products/ratios are representable. Set
likelihoodto'zinb'(default) or'nb'. - In ZINB mode
\pi=\operatorname{sigmoid}(\mathrm{zero\_inflation\_logits})andq=1-\pi, computed stably. - In NB mode
q=1,\pi=0and supplied logits are ignored. Each active conditional distribution hasm_{s}=q_{s}\cdot \mu_{s},v_s=q_s\left(\mu_s+\frac{\mu_s^2}{\theta_s}\right)+q_s\pi_s\mu_s^2,p_{0,s}=\pi_s+q_s\exp\left[-\theta_s\log\left(1+\frac{\mu_s}{\theta_s}\right)\right]. - Here
meaninputs are NB component count means, not normalized gene compositions. At\mu=0the count distribution is exactly zero-valued, so itsmean/varianceare zero and\mathrm{p0}=1. - For each cell,
w_{s}=\operatorname{where}(\mathrm{sample\_mask},\mathrm{sample\_weights},0),W=\sum_{s} w_{s}and\alpha_{s}=w_{s}/W. Return\mathrm{normalized\_weights}=\alpha. IfW=0,\alpha, all predictive moments andeffective_sample_sizeare zero. - Otherwise
\operatorname{mean}=\sum_{s} \alpha_{s}\cdot m_{s};\mathrm{likelihood\_variance}=\sum_{s} \alpha_{s}\cdot v_{s};\mathrm{latent\_variance}=\sum_{s} \alpha_{s}\cdot (m_{s}-\operatorname{mean})^{2};\mathrm{variance}=\mathrm{likelihood\_variance}+\mathrm{latent\_variance};\mathrm{zero\_probability}=\sum_{s} \alpha_{s}\cdot \mathrm{p0\_s};\texttt{effective\_sample\_size}=\frac{W^2}{\sum_s w_s^2}. - The
varianceis that of the finite weighted predictive mixture, not an unbiasedS-1estimate. A single active sample has zero latentvarianceand\mathrm{ESS}=1. Masked genes return zero moments/probability; ESS and normalized weights remain per-cell sample diagnostics independent ofgene_mask. - Reject an unknown likelihood, negative active means, nonpositive active
\theta, or negative active weights withValueError. Other stated conditions are preconditions. No sampling, model loading, full encoder/decoder execution, gene-gene covariance, posterior interval estimation, or calibration on experimental data occurs here. - The supplied decoder/posterior samples already encode the desired library and batch-conditioning choices. Compose sigmoid and
variancearithmetic from raw torch; do not use distributions or built-invariance/softmax functions.
Why this matters
Predictive uncertainty has two parts: count variability within each decoder distribution and disagreement between posterior samples. Averaging only sample means would omit the first part.
Starter code
You write the body of this function in the BioTorch workspace. Your code runs in an isolated PyTorch sandbox; Run checks the public examples and Submit grades the full test suite, including gradients.
import torch
def posterior_predictive_moments(
mean: torch.Tensor, # (S, B, G)
inverse_dispersion: torch.Tensor, # (G,) or (S, B, G)
sample_weights: torch.Tensor, # (S, B)
sample_mask: torch.Tensor, # (S, B)
gene_mask: torch.Tensor, # (B, G)
zero_inflation_logits: torch.Tensor | None = None, # (S, B, G)
likelihood: str = 'zinb',
) -> dict[str, torch.Tensor]:
"""Return dict[str, torch.Tensor]:
mean: (B, G).
variance: (B, G). (B,G) total predictive variance, not only between-sample variance.
likelihood_variance: (B, G).
latent_variance: (B, G).
zero_probability: (B, G).
effective_sample_size: (B,).
normalized_weights: (S, B)."""
# Implement the operation. Inputs and test setup are supplied by BioTorch.
raise NotImplementedError("Implement posterior_predictive_moments")
Ready to implement it?
3 progressive hints, a solution walkthrough, and grading that checks values, shapes and gradients are waiting in the workspace.
Free account with email, Google or GitHub. Your code runs in an isolated PyTorch sandbox.
Sources and further reading
A primitive exercise, not an end-to-end reproduction of the named models. No affiliation or endorsement is implied.