Single-cell and perturbations
Stable negative-binomial log probability
scVItotalVIMULTIVICPA
Free account with email, Google or GitHub. Your code runs in an isolated PyTorch sandbox.
Your task
Evaluate negative-binomial log probabilities for observed gene counts. Return per-gene log probabilities and the summed negative log likelihood for each cell.
Function signature
negative_binomial_log_prob(
counts: torch.Tensor,
mean: torch.Tensor,
inverse_dispersion: torch.Tensor,
observed_mask: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor]Worked example
Unit mean and dispersion give a geometric distribution
At unit mean and inverse dispersion, counts 0, 1, 2 and 3 have probabilities 1/2,1/4,1/8,1/16. Their log probabilities are -\log2,-2\log2,-3\log2,-4\log2; the cell negative log likelihood is 10\log2\approx6.93147.
import torch
counts = torch.tensor([[0, 1, 2, 3]], dtype=torch.int64)
mean = torch.tensor([[1.0, 1.0, 1.0, 1.0]], dtype=torch.float64, requires_grad=True)
inverse_dispersion = torch.tensor([1.0, 1.0, 1.0, 1.0], dtype=torch.float64, requires_grad=True)
observed_mask = torch.tensor([[True, True, True, True]], dtype=torch.bool)
result = negative_binomial_log_prob(counts=counts, mean=mean, inverse_dispersion=inverse_dispersion, observed_mask=observed_mask)Expected return
(torch.tensor([[-0.6931471805599453, -1.3862943611198906, -2.0794415416798357, -2.772588722239781]], dtype=torch.float64), torch.tensor([6.931471805599452], dtype=torch.float64))Parameters
| Parameter | Shape and meaning |
|---|---|
countstorch.Tensor | [B, G]Discrete nonnegative counts at observed entries; missing finite sentinels are ignored. |
meantorch.Tensor | [B, G]Strictly positive finite NB means; differentiable. |
inverse_dispersiontorch.Tensor | [G] or [B, G]Positive inverse-dispersion parameter; a gene-only tensor is shared across all cells. |
observed_masktorch.Tensor | [B, G]True means measured, including measured zero. |
Return
tuple[torch.Tensor, torch.Tensor]
Return these tensors in the stated order.
Required behavior
- Implement an exact, masked negative-binomial log mass from primitive tensor operations. Cells are axis
0and genes axis1.countshas shape [B,G] and contains nonnegative integer-valued observations; integer or floating count storage is permitted, butcountsare discrete and never differentiated.meanis [B,G]. inverse_dispersionis[G](shared across cells) or [B,G]. Both are finite and strictly positive everywhere.observed_maskis boolean [B,G], withTruemeaning measured. A measured count of zero must contribute its NB log probability;Falseentries are missing and may contain an arbitrary finite count sentinel.B,G \ge 1.- For each observed
(b,g), compute:\log p(x)=\log\Gamma(x+\theta)-\log\Gamma(\theta)-\log\Gamma(x+1)+\theta\log\frac{\theta}{\theta+\mu}+x\log\frac{\mu}{\theta+\mu}. Return zero at missing entries, and\texttt{cell\_nll}[b]=-\sum_g\texttt{log\_prob}[b,g]. An entirely missing cell therefore has\mathrm{cell\_nll}=0. - Reject negative observed
countsand nonpositivemeanor inverse dispersion withValueError. Other declared shapes, finite values and count integrality are caller preconditions. No\epsilonis added: this is the exact positive-parameter NB, not an\epsilon-perturbed source formula. - Use stable log-ratios rather than exponentiating huge logits or forming
\theta+\muwhen avoidable. Ifa=\operatorname{log}(\mu)-\operatorname{log}(\theta),\operatorname{log}(1+\operatorname{exp}(a)) = \operatorname{max}(a,0)+\operatorname{log1p}(\operatorname{exp}(-\left|a\right|)). - Sanitize missing entries before probability arithmetic, then mask the outputs. There are no
options.torch.lgammais the sole additional permitted numerical operation;torch.distributionsis forbidden in candidate code.
Why this matters
The mean controls expected counts; inverse dispersion controls variability beyond a Poisson model. A measured zero is a real observation with its own probability. A missing measurement contributes nothing.
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 negative_binomial_log_prob(
counts: torch.Tensor, # (B, G)
mean: torch.Tensor, # (B, G)
inverse_dispersion: torch.Tensor, # (G,) or (B, G)
observed_mask: torch.Tensor, # (B, G)
) -> tuple[torch.Tensor, torch.Tensor]:
"""Return tuple[torch.Tensor, torch.Tensor]:
log_prob: (B, G).
cell_nll: (B,)."""
# Implement the operation. Inputs and test setup are supplied by BioTorch.
raise NotImplementedError("Implement negative_binomial_log_prob")
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.