explicit log parameterization for Poisson, NegativeBinomial, etc (Stan-style *_log variants) #8389
Replies: 2 comments 10 replies
|
@lucidfrontier45 there's a subtlety in that pymc relies on pytensor which does stabilization rewrites automatically. For Bernoulli we know pytensor can stabilize the expression so that "naive" Bernoulli(p=sigmoid(a)) is stable and we offer Bernoulli(logit_p=a) just as syntactic sugar. In the Poisson case we write the Poisson logp as -mu + logpow(mu, y) - factln(y), where logpow is just y * log(mu) with a guard for 0 * log(0). pymc/pymc/distributions/discrete.py Lines 581 to 597 in bf8abd5 PyTensor will simplify that log(exp(alpha)) into alpha (with a negative alpha nan guard perhaps). The exp term survives for the guard (not a problem) and the -mu term (unavoidable). Do you have a case where the log density or gradient come back at -inf / nan, when you know it could be represented in float64? import pytensor
import pymc as pm
with pm.Model() as m:
alpha = pm.Flat("alpha")
pm.Poisson("y", mu=pm.math.exp(alpha))
logp_expr = m.logp()
# it starts with the expression shown above
logp_expr.dprint()
# └─ Mul [id BX]
# ├─ y [id BG]
# └─ Log [id BY]
# └─ Exp [id BD]
# └─ ···
# excluding fusion just to keep the variable names readable
logp_fn = m.compile_logp(mode=pytensor.get_mode().excluding("fusion"))
# It gets common stabilization, in this case undoes the log(exp(alpha))
logp_fn.dprint()
# └─ Mul [id X]
# ├─ y [id O]
# └─ alpha [id F]
print(logp_fn({"y": 3, "alpha": 600})) # -3.7730203009299397e+260
print(logp_fn({"y": 3, "alpha": 709})) # -8.218407461554972e+307
print(logp_fn({"y": 3, "alpha": 710})) # -inf,The last value is -inf, but the true log density is not expressible in float64. You'd need y around 1e305 for the y * alpha term to offset it, which isn't representable as an int64 to begin with. However we may be missing something for Binomial and NegativeBinomial, those don't seem to be stabilized as of today. import pymc as pm
def probe(build, zs, label):
with pm.Model() as m:
z = pm.Flat("z")
build(z)
lp, dlp = m.compile_logp(), m.compile_dlogp()
for zv in zs:
print(f"{label} z={zv:6} logp={lp({'z': zv}):>22} dlogp={dlp({'z': zv})[0]:>8}")
# mu = exp(z)
probe(lambda z: pm.NegativeBinomial("y", mu=pm.math.exp(z), alpha=2.0, observed=3),
(600, 709, 710, 5000), "NegativeBinomial")
# p = sigmoid(z)
probe(lambda z: pm.Binomial("y", n=10, logit_p=z, observed=1),
(-700, 30, 36, 37, 800), "Binomial ")For your specific case, besides normalizing predictors you can also try pt.softplus, it's positive and becomes linear as x grows |
|
@lucidfrontier45 can you check if the last release makes the NB stable in your use case? https://github.com/pymc-devs/pymc/releases/tag/v6.3.0 |
Uh oh!
There was an error while loading. Please reload this page.
When we use liner model or neural network with Possison/NegativeBinomial/Bernouli/Binomial as output distribution, we generally apply exp to the predictor.$\mu = \exp(z)$
when z is relatively large number, like 700, it explodes and MCMC sampling becomes unstable.
Stan has explicit log parameterization to address this numerical instability.
Currently I have encountered this problem and my workaround is to apply normalization to variables whose scale is relatively large.
However if I can do like this it's better.
All reactions