`LogitNormal` gets no default transform, unlike other unit-interval distributions
Description
pm.LogitNormal is supported on the interval (0, 1), but unlike the other
distributions on that support it is not assigned a default transform. As a
result, NUTS operates on the constrained scale, proposes values outside the
support, and a model using it fails at initialization.
Reproducible example
import pymc as pm
with pm.Model() as model:
mu = pm.Normal("mu", 0, 2)
sigma = pm.HalfNormal("sigma", sigma=1)
xs = pm.LogitNormal("xs", mu=mu, sigma=sigma, shape=13)
pm.Binomial("ks", n=[129] * 13, p=xs, observed=[4] * 13)
idata = pm.sample()Result:
pymc.exceptions.SamplingError: Initial evaluation of model at starting point failed!
Starting values:
{'mu': array(-0.58882144), 'sigma_log__': array(0.91378887), 'xs': array([ 0.19462499, 0.39542672, 1.05327918, -0.03184312, 0.82003634,
-0.61738645, 1.26254703, 0.69715246, 0.60729211, 1.12854095,
0.72072905, -0.44765787, -0.48220885])}
Logp initial evaluation results:
{'mu': np.float64(-1.66), 'sigma': np.float64(-2.42), 'xs': np.float64(-inf), 'ks': np.float64(-inf)}Note that sigma appears as sigma_log__, because it did get its transform,
while xs did not.
Note the negative entries in the starting value for xs. jitter+adapt_diag
perturbs the initial point on the constrained scale, so with 13 components at
least one lands outside (0, 1) essentially every time. I tried 6 seeds and it
failed on all 6.
Comparison with other distributions on (0, 1)
for name, make in [
("Beta", lambda: pm.Beta("v", 2, 2)),
("Kumaraswamy", lambda: pm.Kumaraswamy("v", 2, 2)),
("Uniform(0,1)", lambda: pm.Uniform("v", 0, 1)),
("LogitNormal", lambda: pm.LogitNormal("v", 0, 1)),
]:
with pm.Model() as m:
make()
print(name, m.rvs_to_transforms[m.free_RVs[0]])| distribution | support | default transform | base class |
|---|---|---|---|
Beta |
(0, 1) | LogOddsTransform |
UnitContinuous |
Kumaraswamy |
(0, 1) | LogOddsTransform |
UnitContinuous |
Uniform(0, 1) |
(0, 1) | Interval |
BoundedContinuous |
LogitNormal |
(0, 1) | none | object |
LogitNormal is the only one without a transform, and the only one that is not
a subclass of a continuous distribution base class. It appears to be implemented
as a wrapper that returns a CustomDist, which is presumably why it does not
pick up the default transform that UnitContinuous provides.
For contrast, HalfNormal — also a constrained distribution — does get its
LogTransform automatically, so the inconsistency is specific to this
distribution rather than general.
Workaround
from pymc.distributions.transforms import logodds
xs = pm.LogitNormal("xs", mu=mu, sigma=sigma, shape=13,
default_transform=logodds)With this, the model initializes and samples normally.
(For the hierarchical model above, a non-centered parameterization —
xs = invlogit(mu + sigma * us) with us ~ Normal(0, 1) — both avoids the
issue and samples better, but that is a modeling choice and does not address
the inconsistency in the distribution itself.)
Question
Should LogitNormal be implemented as a proper UnitContinuous distribution,
or at minimum default to the logodds transform? As it stands the failure is
hard to diagnose: the error points at the starting values rather than at the
missing transform, and nothing in the distribution's documentation suggests it
behaves differently from Beta.
Happy to open a PR if there is a preferred approach.
Versions
- PyMC 6.3.1
- PyTensor 3.3.0
- Python 3.12.13
- Linux
Source: pymc-devs/pymc