Skip to content

Commit 1e8790c

Browse files
Add more basic PyMC3 distributions and conversion dispatches
1 parent 55d33c2 commit 1e8790c

File tree

5 files changed

+254
-27
lines changed

5 files changed

+254
-27
lines changed

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def get_long_description():
3232
author_email=AUTHOR_EMAIL,
3333
url=URL,
3434
install_requires=[
35-
"scipy>=1.2.0",
35+
"scipy>=1.4.0",
3636
"Theano>=1.0.4",
3737
"tf-estimator-nightly==2.1.0.dev2020012309",
3838
"tf-nightly==2.2.0.dev20200201",

symbolic_pymc/relations/theano/conjugates.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,24 @@ def _create_normal_normal_goals():
3232
#
3333
# Create the pattern/form of the prior normal distribution
3434
#
35-
beta_name_lv = var("beta_name")
36-
beta_size_lv = var("beta_size")
37-
beta_rng_lv = var("beta_rng")
38-
a_lv = var("a")
39-
R_lv = var("R")
35+
beta_name_lv = var()
36+
beta_size_lv = var()
37+
beta_rng_lv = var()
38+
a_lv = var()
39+
R_lv = var()
4040
beta_prior_mt = mt.MvNormalRV(a_lv, R_lv, size=beta_size_lv, rng=beta_rng_lv, name=beta_name_lv)
4141

42-
y_name_lv = var("y_name")
43-
y_size_lv = var("y_size")
44-
y_rng_lv = var("y_rng")
45-
F_t_lv = var("f")
46-
V_lv = var("V")
42+
y_name_lv = var()
43+
y_size_lv = var()
44+
y_rng_lv = var()
45+
F_t_lv = var()
46+
V_lv = var()
4747
E_y_mt = mt.dot(F_t_lv, beta_prior_mt)
4848
Y_mt = mt.MvNormalRV(E_y_mt, V_lv, size=y_size_lv, rng=y_rng_lv, name=y_name_lv)
4949

5050
# The variable specifying the fixed sample value of the random variable
5151
# given by `Y_mt`
52-
obs_sample_mt = var("obs_sample")
52+
obs_sample_mt = var()
5353

5454
Y_obs_mt = mt.observed(obs_sample_mt, Y_mt)
5555

@@ -80,21 +80,21 @@ def _create_normal_normal_goals():
8080
def _create_normal_wishart_goals(): # pragma: no cover
8181
"""TODO."""
8282
# Create the pattern/form of the prior normal distribution
83-
Sigma_name_lv = var("Sigma_name")
84-
Sigma_size_lv = var("Sigma_size")
85-
Sigma_rng_lv = var("Sigma_rng")
86-
V_lv = var("V")
87-
n_lv = var("n")
83+
Sigma_name_lv = var()
84+
Sigma_size_lv = var()
85+
Sigma_rng_lv = var()
86+
V_lv = var()
87+
n_lv = var()
8888
Sigma_prior_mt = mt.WishartRV(V_lv, n_lv, Sigma_size_lv, Sigma_rng_lv, name=Sigma_name_lv)
8989

90-
y_name_lv = var("y_name")
91-
y_size_lv = var("y_size")
92-
y_rng_lv = var("y_rng")
93-
V_lv = var("V")
94-
f_mt = var("f")
90+
y_name_lv = var()
91+
y_size_lv = var()
92+
y_rng_lv = var()
93+
V_lv = var()
94+
f_mt = var()
9595
Y_mt = mt.MvNormalRV(f_mt, V_lv, y_size_lv, y_rng_lv, name=y_name_lv)
9696

97-
y_mt = var("y")
97+
y_mt = var()
9898
Y_obs_mt = mt.observed(y_mt, Y_mt)
9999

100100
n_post_mt = etuple(mt.add, n_lv, etuple(mt.Shape, Y_obs_mt))

symbolic_pymc/theano/pymc3.py

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,22 @@
3737
CauchyRVType,
3838
HalfCauchyRV,
3939
HalfCauchyRVType,
40+
BetaRV,
41+
BetaRVType,
42+
BinomialRV,
43+
BinomialRVType,
44+
PoissonRV,
45+
PoissonRVType,
46+
DirichletRV,
47+
DirichletRVType,
48+
BernoulliRV,
49+
BernoulliRVType,
50+
BetaBinomialRV,
51+
BetaBinomialRVType,
52+
CategoricalRV,
53+
CategoricalRVType,
54+
MultinomialRV,
55+
MultinomialRVType,
4056
)
4157
from .opt import FunctionGraph
4258
from .ops import RandomVariable
@@ -197,6 +213,110 @@ def _convert_rv_to_dist_HalfCauchy(op, rv):
197213
return pm.HalfCauchy, params
198214

199215

216+
@convert_dist_to_rv.register(pm.Beta, object)
217+
def convert_dist_to_rv_Beta(dist, rng):
218+
size = dist.shape.astype(int)[BetaRV.ndim_supp :]
219+
res = BetaRV(dist.alpha, dist.beta, size=size, rng=rng)
220+
return res
221+
222+
223+
@_convert_rv_to_dist.register(BetaRVType, Apply)
224+
def _convert_rv_to_dist_Beta(op, rv):
225+
params = {"alpha": rv.inputs[0], "beta": rv.inputs[1]}
226+
return pm.Beta, params
227+
228+
229+
@convert_dist_to_rv.register(pm.Binomial, object)
230+
def convert_dist_to_rv_Binomial(dist, rng):
231+
size = dist.shape.astype(int)[BinomialRV.ndim_supp :]
232+
res = BinomialRV(dist.n, dist.p, size=size, rng=rng)
233+
return res
234+
235+
236+
@_convert_rv_to_dist.register(BinomialRVType, Apply)
237+
def _convert_rv_to_dist_Binomial(op, rv):
238+
params = {"n": rv.inputs[0], "p": rv.inputs[1]}
239+
return pm.Binomial, params
240+
241+
242+
@convert_dist_to_rv.register(pm.Poisson, object)
243+
def convert_dist_to_rv_Poisson(dist, rng):
244+
size = dist.shape.astype(int)[PoissonRV.ndim_supp :]
245+
res = PoissonRV(dist.mu, size=size, rng=rng)
246+
return res
247+
248+
249+
@_convert_rv_to_dist.register(PoissonRVType, Apply)
250+
def _convert_rv_to_dist_Poisson(op, rv):
251+
params = {"mu": rv.inputs[0]}
252+
return pm.Poisson, params
253+
254+
255+
@convert_dist_to_rv.register(pm.Dirichlet, object)
256+
def convert_dist_to_rv_Dirichlet(dist, rng):
257+
size = dist.shape.astype(int)[DirichletRV.ndim_supp :]
258+
res = DirichletRV(dist.a, size=size, rng=rng)
259+
return res
260+
261+
262+
@_convert_rv_to_dist.register(DirichletRVType, Apply)
263+
def _convert_rv_to_dist_Dirichlet(op, rv):
264+
params = {"a": rv.inputs[0]}
265+
return pm.Dirichlet, params
266+
267+
268+
@convert_dist_to_rv.register(pm.Bernoulli, object)
269+
def convert_dist_to_rv_Bernoulli(dist, rng):
270+
size = dist.shape.astype(int)[BernoulliRV.ndim_supp :]
271+
res = BernoulliRV(dist.p, size=size, rng=rng)
272+
return res
273+
274+
275+
@_convert_rv_to_dist.register(BernoulliRVType, Apply)
276+
def _convert_rv_to_dist_Bernoulli(op, rv):
277+
params = {"p": rv.inputs[0]}
278+
return pm.Bernoulli, params
279+
280+
281+
@convert_dist_to_rv.register(pm.BetaBinomial, object)
282+
def convert_dist_to_rv_BetaBinomial(dist, rng):
283+
size = dist.shape.astype(int)[BetaBinomialRV.ndim_supp :]
284+
res = BetaBinomialRV(dist.n, dist.alpha, dist.beta, size=size, rng=rng)
285+
return res
286+
287+
288+
@_convert_rv_to_dist.register(BetaBinomialRVType, Apply)
289+
def _convert_rv_to_dist_BetaBinomial(op, rv):
290+
params = {"n": rv.inputs[0], "alpha": rv.inputs[1], "beta": rv.inputs[2]}
291+
return pm.BetaBinomial, params
292+
293+
294+
@convert_dist_to_rv.register(pm.Categorical, object)
295+
def convert_dist_to_rv_Categorical(dist, rng):
296+
size = dist.shape.astype(int)[CategoricalRV.ndim_supp :]
297+
res = CategoricalRV(dist.p, size=size, rng=rng)
298+
return res
299+
300+
301+
@_convert_rv_to_dist.register(CategoricalRVType, Apply)
302+
def _convert_rv_to_dist_Categorical(op, rv):
303+
params = {"p": rv.inputs[0]}
304+
return pm.Categorical, params
305+
306+
307+
@convert_dist_to_rv.register(pm.Multinomial, object)
308+
def convert_dist_to_rv_Multinomial(dist, rng):
309+
size = dist.shape.astype(int)[MultinomialRV.ndim_supp :]
310+
res = MultinomialRV(dist.n, dist.p, size=size, rng=rng)
311+
return res
312+
313+
314+
@_convert_rv_to_dist.register(MultinomialRVType, Apply)
315+
def _convert_rv_to_dist_Multinomial(op, rv):
316+
params = {"n": rv.inputs[0], "p": rv.inputs[1]}
317+
return pm.Multinomial, params
318+
319+
200320
# TODO: More RV conversions!
201321

202322

@@ -207,9 +327,17 @@ def pymc3_var_to_rv(pm_var, rand_state=None):
207327
new_rv.name = pm_var.name
208328

209329
if isinstance(pm_var, pm.model.ObservedRV):
210-
obs = tt.as_tensor_variable(pm_var.observations)
330+
obs = pm_var.observations
331+
# For some reason, the observations can be float when the RV's dtype is
332+
# not.
333+
if obs.dtype != pm_var.dtype:
334+
obs = obs.astype(pm_var.dtype)
335+
336+
obs = tt.as_tensor_variable(obs)
337+
211338
if getattr(obs, "cached", False):
212339
obs = obs.clone()
340+
213341
new_rv = observed(obs, new_rv)
214342

215343
# Let's attempt to fix the PyMC3 broadcastable dims "oracle" issue,

symbolic_pymc/theano/random_variables.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ def make_node(self, lower, upper, size=None, rng=None, name=None):
2121
UniformRV = UniformRVType()
2222

2323

24+
class BetaRVType(RandomVariable):
25+
print_name = ("Beta", "\\operatorname{Beta}")
26+
27+
def __init__(self):
28+
super().__init__("beta", theano.config.floatX, 0, [0, 0], "beta", inplace=True)
29+
30+
def make_node(self, alpha, beta, size=None, rng=None, name=None):
31+
return super().make_node(alpha, beta, size=size, rng=rng, name=name)
32+
33+
34+
BetaRV = BetaRVType()
35+
36+
2437
class NormalRVType(RandomVariable):
2538
print_name = ("N", "\\operatorname{N}")
2639

@@ -209,6 +222,61 @@ def make_node(self, b, loc=0.0, scale=1.0, size=None, rng=None, name=None):
209222
TruncExponentialRV = TruncExponentialRVType()
210223

211224

225+
class BernoulliRVType(RandomVariable):
226+
print_name = ("Bern", "\\operatorname{Bern}")
227+
228+
def __init__(self):
229+
super().__init__(
230+
"bernoulli",
231+
"int64",
232+
0,
233+
[0],
234+
lambda rng, *args: scipy.stats.bernoulli.rvs(args[0], size=args[1], random_state=rng),
235+
inplace=True,
236+
)
237+
238+
def make_node(self, p, size=None, rng=None, name=None):
239+
return super().make_node(p, size=size, rng=rng, name=name)
240+
241+
242+
BernoulliRV = BernoulliRVType()
243+
244+
245+
class BinomialRVType(RandomVariable):
246+
print_name = ("Binom", "\\operatorname{Binom}")
247+
248+
def __init__(self):
249+
super().__init__("binomial", "int64", 0, [0, 0], "binomial", inplace=True)
250+
251+
def make_node(self, n, p, size=None, rng=None, name=None):
252+
return super().make_node(n, p, size=size, rng=rng, name=name)
253+
254+
255+
BinomialRV = BinomialRVType()
256+
257+
258+
class BetaBinomialRVType(RandomVariable):
259+
print_name = ("BetaBinom", "\\operatorname{BetaBinom}")
260+
261+
def __init__(self):
262+
super().__init__(
263+
"beta_binomial",
264+
"int64",
265+
0,
266+
[0, 0, 0],
267+
lambda rng, *args: scipy.stats.betabinom.rvs(
268+
*args[:-1], size=args[-1], random_state=rng
269+
),
270+
inplace=True,
271+
)
272+
273+
def make_node(self, n, a, b, size=None, rng=None, name=None):
274+
return super().make_node(n, a, b, size=size, rng=rng, name=name)
275+
276+
277+
BetaBinomialRV = BetaBinomialRVType()
278+
279+
212280
# Support shape is determined by the first dimension in the *second* parameter
213281
# (i.e. the probabilities vector)
214282
class MultinomialRVType(RandomVariable):
@@ -232,6 +300,28 @@ def make_node(self, n, pvals, size=None, rng=None, name=None):
232300
MultinomialRV = MultinomialRVType()
233301

234302

303+
class CategoricalRVType(RandomVariable):
304+
print_name = ("Cat", "\\operatorname{Cat}")
305+
306+
def __init__(self):
307+
super().__init__(
308+
"categorical",
309+
"int64",
310+
0,
311+
[1],
312+
lambda rng, *args: scipy.stats.rv_discrete(values=(range(len(args[0])), args[0])).rvs(
313+
size=args[1], random_state=rng
314+
),
315+
inplace=True,
316+
)
317+
318+
def make_node(self, pvals, size=None, rng=None, name=None):
319+
return super().make_node(pvals, size=size, rng=rng, name=name)
320+
321+
322+
CategoricalRV = CategoricalRVType()
323+
324+
235325
class Observed(tt.Op):
236326
"""An `Op` that represents an observed random variable.
237327

tests/theano/test_pymc3.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323

2424
@pytest.mark.usefixtures("run_with_theano")
25-
def test_pymc_convert_dists():
25+
def test_pymc3_convert_dists():
2626
"""Just a basic check that all PyMC3 RVs will convert to and from Theano RVs."""
2727
tt.config.compute_test_value = "ignore"
2828
theano.config.cxx = ""
@@ -36,6 +36,15 @@ def test_pymc_convert_dists():
3636
gamma_rv = pm.Gamma("gamma_rv", 1.0, 1.0, observed=1.0)
3737
invgamma_rv = pm.InverseGamma("invgamma_rv", 1.0, 1.0, observed=1.0)
3838
exp_rv = pm.Exponential("exp_rv", 1.0, observed=1.0)
39+
halfnormal_rv = pm.HalfNormal("halfnormal_rv", 1.0, observed=1.0)
40+
beta_rv = pm.Beta("beta_rv", 2.0, 2.0, observed=1.0)
41+
binomial_rv = pm.Binomial("binomial_rv", 10, 0.5, observed=5)
42+
dirichlet_rv = pm.Dirichlet("dirichlet_rv", np.r_[0.1, 0.1], observed=np.r_[0.1, 0.1])
43+
poisson_rv = pm.Poisson("poisson_rv", 10, observed=5)
44+
bernoulli_rv = pm.Bernoulli("bernoulli_rv", 0.5, observed=0)
45+
betabinomial_rv = pm.BetaBinomial("betabinomial_rv", 0.1, 0.1, 10, observed=5)
46+
categorical_rv = pm.Categorical("categorical_rv", np.r_[0.5, 0.5], observed=1)
47+
multinomial_rv = pm.Multinomial("multinomial_rv", 5, np.r_[0.5, 0.5], observed=np.r_[2])
3948

4049
# Convert to a Theano `FunctionGraph`
4150
fgraph = model_graph(model)
@@ -53,7 +62,7 @@ def test_pymc_convert_dists():
5362

5463

5564
@pytest.mark.usefixtures("run_with_theano")
56-
def test_pymc_normal_model():
65+
def test_pymc3_normal_model():
5766
"""Conduct a more in-depth test of PyMC3/Theano conversions for a specific model."""
5867
tt.config.compute_test_value = "ignore"
5968

@@ -199,7 +208,7 @@ def _check_model(model):
199208

200209

201210
@pytest.mark.usefixtures("run_with_theano")
202-
def test_pymc_broadcastable():
211+
def test_pymc3_broadcastable():
203212
"""Test PyMC3 to Theano conversion amid array broadcasting."""
204213
tt.config.compute_test_value = "ignore"
205214

0 commit comments

Comments
 (0)