Hi, I’m new to PyMC.
I managed to define logp and random for a CustomDist
where t is supported on [0,12] with parameter t_\mathrm{SFR}\in[4,12] for later on posterior inference, check below for its shape.
With
NumericalInversePolynomial
from scipy.stats.sampling
, my code as follows:
import arviz as az
import matplotlib.pyplot as plt
import numpy as np
import pymc as pm
import pytensor.tensor as pt
from scipy.special import expi,erf
from scipy.integrate import quad
from scipy.stats.sampling import NumericalInversePolynomial
print(f"Running on PyMC v{pm.__version__}")
%config InlineBackend.figure_format = 'retina'
az.style.use("arviz-darkgrid")
#------------------------------------------------------------------------
def log_p(t, t_sfr):
return pt.switch(pt.bitwise_and(pt.ge(t, 0.), pt.le(t, 12.)),
-(12 - t)/t_sfr + pt.log(0.159409 * pt.exp(-1.13802 * np.abs(-1.47073 + t)) * t - (0.690966 * (1 - pt.exp(0.0211761 * t)))/(3.80478 + t) + 0.156989 * pt.exp(-0.264495 * t) * pt.erf(0.690966 * t)),
-np.inf)#- np.log(deno(t_sfr))
#--------------------------------------------------------------------------
#define class p for the NumericalInversePolynomial
def int_x_to_12(x,t_sfr):
return -0.690966 * np.exp(-15.8048/t_sfr)*(expi(15.8048/t_sfr)-expi((x+3.80478)/t_sfr)) + 0.690966*np.exp(-0.0805702 - 15.8048/t_sfr)*(expi(15.8048*(0.0211761 + 1/t_sfr))-expi((x+3.80478) * (0.0211761 + 1/t_sfr)))
def deno(t_sfr):
return int_x_to_12(0,t_sfr) + t_sfr * (0.0248331 + 0.615691*np.exp((0.523633 - 12.277 * t_sfr)/t_sfr**2) * (erf(0.191395 - 0.723625/t_sfr)-erf(8.48299 - 0.723625/t_sfr)))/(3.78079 - t_sfr)+(np.exp(-10.5293/t_sfr)*(-0.181027 + np.exp(10.5293/t_sfr)*(9.23477e-6 - 0.000011279*t_sfr)+0.329099*t_sfr)*t_sfr)/(0.878715 - t_sfr)**2 + (np.exp(-22.5293/t_sfr)*t_sfr*(np.exp(12./t_sfr)*(0.181027 + 0.0829266*t_sfr)+0.0230844*np.exp(10.5293/t_sfr)*t_sfr))/(0.878715 +t_sfr)**2
class p:
def __init__(self, t_sfr):
self.t_sfr = t_sfr
def pdf(self, t):
'''
analytical approx. and is not normalized
'''
sfr = np.exp(-(12 - t)/self.t_sfr)
num = 0.159409 * np.exp(-1.13802 * np.abs(-1.47073 + t)) * t - (0.690966 * (1 - np.exp(0.0211761 * t)))/(3.80478 + t) + 0.156989 * np.exp(-0.264495 * t) * erf(0.690966 * t)
return sfr*num#/deno(self.t_sfr)
# def logpdf(self, t):
# return -(12 - t)/self.t_sfr + np.log(0.159409 * np.exp(-1.13802 * np.abs(-1.47073 + t)) * t - (0.690966 * (1 - np.exp(0.0211761 * t)))/(3.80478 + t) + 0.156989 * np.exp(-0.264495 * t) * erf(0.690966 * t)) #- np.log(deno(self.t_sfr))
#-----------------------------------------------------------------------------
def random(t_sfr: float, rng= None, size= None,) -> np.ndarray | float:
dist = p(t_sfr)
p_generator = NumericalInversePolynomial(dist, center=1.47073, domain=(0,12), random_state=rng)
samples = p_generator.rvs(size=size)
return samples
#------------------------------------------------------------------------
Now I have 2 problems:
1. the prior sampling performance
it took 1m 30.33s
to draw 1000 prior samples:
with pm.Model() as m:
t_sfr = 8.
t = pm.CustomDist(
't',
t_sfr,
logp=log_p,
random=random)
idata = pm.sample_prior_predictive(1000)
But if I just call NumericalInversePolynomial
, that’s superfast (78ms
).
dist = p(8.)
urng = np.random.default_rng()
rng = NumericalInversePolynomial(dist, center=1.47073, domain=(0,12),random_state=urng)
obs = rng.rvs(100000);
I wonder why PyMC sampling is slower and if there is a better way to use NumericalInversePolynomial
along with PyMC.
2. Test with MLE
With this CustomDist
, I wanted to test it using MLE, which is Bayesian Inference with uninformative prior (as what I understood):
dist = p(8.) # true t_sfr=8.
urng = np.random.default_rng()
rng = NumericalInversePolynomial(dist, center=1.47073, domain=(0,12),random_state=urng)
obs = rng.rvs(100000);
with pm.Model() as m:
t_sfr = pm.Uniform('t_sfr', lower=4, upper=12)
t = pm.CustomDist(
't',
t_sfr,
logp=log_p,
random=random,
observed=obs)
idata = pm.sample()
Then I got:
which is quite strange (they are almost all
11.999
, but I thought they would be around 8
).
I also tried a flat prior pm.Flat('t_sfr')
but still not working (the code give me Chain 0 reached the maximum tree depth. Increase max_treedepth, increase target_accept or reparameterize.
but I don’t know how to improve my model…
BTW, I verified this “MLE” method with pm.Normal
separately for mu
and sigma
with pm.Flat
prior and both gave plausible results:
obs = np.random.randn(1000)* 8 + 8
with pm.Model() as m:
mu = pm.Flat('mu')
sigma = pm.Flat('sigma')
t = pm.Normal(
't',
mu=mu,
sigma=sigma,
observed=obs)
idata = pm.sample()
Thanks in advance for any help!