Port the Prior
class from pymc-marketing
into the pymc
repo
#7416
Replies: 9 comments 33 replies
-
We already have a way to define variables outside of a model, what if we try to use that? x = pm.Normal.dist(0, 1)
with pm.Model() as m:
m.register_rv(x, name="x") Pros: allows as much expressivity as PyMC models and uses almost same API. Cons:
Alternative what about lambda functions / callables / functools.partial? Also may be worth checking the approach @jessegrabowski took with Statspace module, which is kind of "user defines a model that fills in the blanks, with defaults for things that are not specified". |
Beta Was this translation helpful? Give feedback.
-
I like the |
Beta Was this translation helpful? Give feedback.
-
Just a point on this:
An additional way is not necessarily a plus. Users have one more thing to learn / try to understand when they come across examples. This assumes an overlap between pymc users and those other libraries It's also limited in different ways that may lead us to more complexity or reinvent pymc v2 with strings: e.g
Re: the centered/non-centered is a specific thing that may make more sense as a |
Beta Was this translation helpful? Give feedback.
-
Also curious what @tomicapretto is doing in over bambi, and what are the pain points |
Beta Was this translation helpful? Give feedback.
-
Is this something like prior? class Prior:
def __init__(self, dist, name, *args):
... # save
@staticmethod
def _eval(arg):
if isinstance(arg, Prior):
return arg.dist(
arg.name,
*(_eval(arg) for arg in args),
**dict(kwarg=_eval(arg) for kwarg, arg in self.kwargs)
)
else:
return arg
def eval(self, coords=None, model=UNSET):
# Unset makes nested models
# None makes standalone models
with pm.Model(coords=coords, model=model) as m:
return _eval(self)
def prior(self, draws=1000):
return pm.draw(self.eval(model=None))
def plot(self, draws=1000):
return pm.plots.plot_dist(
self.prior(draws=draws)
) |
Beta Was this translation helpful? Give feedback.
-
To add very little to this covo, |
Beta Was this translation helpful? Give feedback.
-
Another thing worth discussing. Do we want this to allow defining intermediate graphs. Say library offers model with components A -> B -> C and implements A and C and user can customize B. I don't think any of the existing suggestions would work for this use case, but can perhaps be tweaked for it. Stuff like prior and plotting wouldn't be possible for B objects obviously |
Beta Was this translation helpful? Give feedback.
-
Btw I think we definitely want to figure out something like this. Since there are 3 distinct libraries that would depend on this and there's probably some room for tweaking I would suggest adding it first to experimental and once all 3 libraries are happy with it and haven't felt the need to tweak it further we move to pymc? |
Beta Was this translation helpful? Give feedback.
-
I had something like this in the past. What I came up with is creating a closure that is provided by user and executed inside the model context from functools import partial
def priors_fn(**config) -> dict[str, pt.TensorVariable]:
...
class PriorsCls(pydantic.BaseModel):
...
def __call__(self) -> dict[str, pt.TensorVariable]:
...
def lazy_get(cfg: dict[K_t, V_t], key: str, on_empty: Callable[[], V_t], on_value: Callable[[V_t], V_t]=lambda x: x) -> V_t:
value = cfg.get(key)
if key is None:
value = on_empty()
else:
value = on_value(value)
return value
def lazy_get_deterministic(cfg: dict[str, pt.TensorValue], key: str, on_empty: Callable[[], pt.TensorValue], possible_dims: list[str]) -> pt.TensorValue:
return lazy_get(cfg, key, on_empty, lambda v: pm.Deterministic(key, v, dims=infer_dims(v, possible_dims=possible_dims)))
def model_builder(priors_fn):
with pm.Model(coords=...) as model:
with pm.Model("user_priors"):
priors = priors_fn()
cfg = partial(lazy_get, priors)
cfg_deterministic = partial(lazy_get_deterministic, priors)
b = cfg("b",
lambda: pm.Normal("b", dims="c"),
lambda v: pm.Deterministic("b", v, dims=infer_dims(v, possible_dims=["a", "b", "c"])) # infer_dims might be needed if dim number might be overriden
)
c = cfg_deterministic("g", lambda: pm.Normal("g", dims="c"), possible_dims=["a", "b", "c"]) Effectively, the user facing protocol is the parametrized lambda function, with the only assumption to be called under a model context.
I believe some nice api over cfg("b",
lambda: pm.Normal("b", dims="c"),
lambda v: pm.Deterministic("b", v, dims=infer_dims(v, possible_dims=["a", "b", "c"]))
) can be also proposed, but the nature still seems single use inside the model and that is not a requirement to make use of it. What about def infer_dims(value, possible_dims: list[str]):
if value.ndim:
# slice from the end
return possible_dims[-value.ndim:]
else:
# we have scalar
return None |
Beta Was this translation helpful? Give feedback.
-
I propose that we port the excellent
Prior
class by @wd60622 from thepymc-marketing
repo.What is it & what does it do?
The
Prior
class is basically a wrapper around pymc distributions. It has a number of useful properties:pymc
, it is a way of allowing users to define custom priors in pre-build models, or model factory functions/classes. This supersedes a previous way of doing it based on parsing dicts. This is exactly what it's doing inpymc-marketing
. I would definitely bring it intoCausalPy
. And in theory it could also be used in packages likebambi
- though I have no idea if there's a desire to do that.Bringing it in to
pymc
would allow an additional way to specify priors. This would be most appealing in hierarchical modeling situations, for example.More info
Prior
api docs in pymc-marketing: https://www.pymc-marketing.io/en/latest/api/generated/pymc_marketing.prior.html👆Please do check out these pages to get properly up to speed
Options
I see at least 4 possible options, and it would be great to get some input from folks.
pymc-marketing
. This would then remain out of reach of other packages built on top of pymc.pymc-experimental
with potential to move it intopymc
in the futurepymc
now.(Personally, I like options 4, 2, and 3, in that order)
Beta Was this translation helpful? Give feedback.
All reactions