import arviz as az
import pymc as pm
from pymc.math import log, sqr
%load_ext lab_black
10. The Zero Trick and Custom Likelihoods*#
Zero-trick Jeremy#
This introduces the “zero trick”, which is a method for specifying custom likelihoods in BUGS. For a more detailed treatment of these methods, see Ntzoufras [2009], page 276, which is where I got this explanation. These tricks are unnecessary in PyMC since we can just define custom distributions directly, but they do seem to work.
Adapted from Unit 6: zerotrickjeremy.odc.
Here’s the model we’re using:
Of course, BUGS can handle this model just fine. But, let’s pretend for a moment that there is no built-in normal distribution. The zero trick takes advantages of some properties of the Poisson distribution to recreate an arbitrary likelihood.
Given a log-likelihood of the form \(l_i = \log f(y; \theta)\),
–Ntzoufras [2009] page 276.
But the rate, \(\lambda\), can’t be negative. So we need to add a constant, C, to keep that from happening.
The normal log-likelihood is:
But the constant terms won’t affect the posterior.
Here’s the model in PyMC:
y = 98
μ = 110
σ = 80**0.5
τ = 120**0.5
C = 10000
inits = {"θ": 100}
with pm.Model() as m:
θ = pm.Flat("θ")
λ1 = pm.Deterministic("λ1", log(σ) + 0.5 * sqr(((y - θ) / σ)) + C)
λ2 = pm.Deterministic("λ2", log(τ) + 0.5 * sqr(((θ - μ) / τ)) + C)
z1 = pm.Poisson("z1", λ1, observed=0)
z2 = pm.Poisson("z2", λ2, observed=0)
trace = pm.sample(5000, tune=1000, initvals=inits, target_accept=0.88)
Show code cell output
Auto-assigning NUTS sampler...
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [θ]
Sampling 4 chains for 1_000 tune and 5_000 draw iterations (4_000 + 20_000 draws total) took 1 seconds.
az.summary(trace, hdi_prob=0.95, var_names="θ", kind="stats")
mean | sd | hdi_2.5% | hdi_97.5% | |
---|---|---|---|---|
θ | 102.852 | 6.972 | 89.6 | 116.956 |
Custom likelihoods in PyMC#
PyMC is a lot more flexible. For one, many more built-in distributions are available, including mixtures and zero-inflated models, which is where I’ve seen the zero trick used most often (Karlis and Ntzoufras [2008], Ntzoufras [2009] page 288).
If you need a custom likelihood, take a look at the pm.Potential or pm.CustomDist classes.
%load_ext watermark
%watermark -n -u -v -iv -p pytensor
Last updated: Sun Aug 06 2023
Python implementation: CPython
Python version : 3.11.4
IPython version : 8.14.0
pytensor: 2.14.2
pymc : 5.7.1
arviz: 0.16.1