Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sample_generative function #2983

Merged
merged 14 commits into from
Jun 18, 2018

Conversation

ColCarroll
Copy link
Member

This replaces #2876, since @springcoil's recent improvements to draw_values (#2979) makes this somewhat easier. Includes a test, plus two more I had written for draw_values. Both of them actually failed without changes here.

This method is quite fast now.

Copy link
Contributor

@springcoil springcoil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - needs an addition to the RELEASE NOTES though.

@springcoil
Copy link
Contributor

Not for this PR - but a future PR would include some benchmarks I think. Other than that awesome work @ColCarroll

elif is_transformed_name(var_name):
untransformed = get_untransformed_name(var_name)
if untransformed in data:
prior[var_name] = model[untransformed].transformation.forward(data[untransformed]).eval()
Copy link
Member

@junpenglao junpenglao May 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forward_val is the numpy version of the forward function and should be faster.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use .transformation.forward here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops i meant .transformation.forward_val should be used here... Should be slightly faster:
model[untransformed].transformation.forward(data[untransformed]).eval() --> model[untransformed].transformation.forward_val(data[untransformed])

@junpenglao junpenglao added the WIP label May 19, 2018
evaluated[param_idx] = givens[param.name][1]
else:
try: # might evaluate in a bad order,
evaluated[param_idx] = _draw_value(param, point=point, givens=givens.values(), size=size)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the size args is not working yet:

from pymc3.distributions.distribution import draw_values

with pm.Model() as m:
    p = pm.Beta('p', 1., 1.)
draw_values([m['p']], size=10)
[array(0.72159403)]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your previous example is still a little funny too:

X = theano.shared(np.arange(3))
with pm.Model() as m:
    ind = pm.Categorical('i', np.ones(3)/3)
    x = pm.Deterministic('X', X[ind])
    prior=pm.sample_generative()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am really good at discovering edge cases.

@@ -1208,6 +1209,49 @@ def sample_ppc_w(traces, samples=None, models=None, weights=None,
return {k: np.asarray(v) for k, v in ppc.items()}


def sample_generative(samples=500, model=None, vars=None, random_seed=None):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we rename this sample_prior_predictive?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and sample_ppc should then probably be sample_posterior_predictive (it's not really doing a check).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is now actually the generative function (see the test case). The prior predictive would be nice to also implement.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at the test but I don't understand the difference between generative and prior predictive here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh... I think I still don't. Will work out a simple model by hand later to try to help my intuition.

@springcoil
Copy link
Contributor

springcoil commented May 19, 2018 via email

else:
try: # might evaluate in a bad order,
evaluated[param_idx] = _draw_value(param, point=point, givens=givens.values(), size=size)
if isinstance(param, collections.Hashable) and named_nodes_parents.get(param):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that isinstance check should me replaced with checker for name

pm.Normal('x_obs', mu=z, sd=1, observed=observed)
prior = pm.sample_generative()

assert (prior['mu'] < 90).all()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems a bit crude, couldn't we just test for the mean and sd?

@ColCarroll ColCarroll mentioned this pull request Jun 1, 2018
@AllenDowney
Copy link

I am coming to this discussion late, but I was talking with Chris Fonnesbeck about this issue and he pointed me to this PR.

This functionality would be very helpful to me. I am working on a new volume of Think Bayes that will use PyMC, and I plan to demonstrate a development process that uses a pm.Model to generate a prior predictive distribution before running an inference. I think this is useful for both validation (model makes sense) and verification (model specification is correct).

Especially for verification, it would be ideal if generation and inference are as identical as possible. For example, I would love to be able to run

with pm.Model() as model:
    mu = pm.Gamma('mu', alpha, beta)
    goals = pm.Poisson('goals', mu)
    trace = pm.sample(1000)

To generate the prior predictives, and then

with pm.Model() as model:
    mu = pm.Gamma('mu', alpha, beta)
    goals = pm.Poisson('goals', mu, observed=6)
    trace = pm.sample(1000)

If the first one validates, it would be really hard to mess up the second one.

This API would require pm.sample to analyze the model, see that there is no data, and run sample_prior_predictive rather than any of the MCMC samplers. I assume that would not be hard, but I don't really know.

This notebook contains more examples of what I have in mind.

https://github.com/AllenDowney/BayesMadeSimple/blob/master/zigzag.ipynb

In those examples I use pm.sample to generate, but (you will not be surprised to hear) it doesn't work very well. @ColCarroll , since you saw the talk where I presented this notebook, you might have thoughts to add.

Thank you all!

@springcoil
Copy link
Contributor

springcoil commented Jun 1, 2018 via email

@fonnesbeck
Copy link
Member

So I wonder if sample could infer that there are no observed RVs and just do forward sampling. Can anyone think of a reason for doing MCMC without any likelihoods?

@kyleabeauchamp
Copy link
Contributor

kyleabeauchamp commented Jun 1, 2018

I think there are lots of situations where we want to sample from a joint (unobserved) distribution that we describe using the machinery of pymc---pymc allows one to describe models in terms of random variables and functions of them, which is the right level of abstraction for that kind of work.

One example: Monte Carlo based probabilistic sensitivity analysis of an economic model.

@kyleabeauchamp
Copy link
Contributor

kyleabeauchamp commented Jun 1, 2018

Another example: for performance reasons, one may sometimes apply Bayes' rule analytically with a known conjugate prior. E.g., perhaps it's computationally faster to model the posterior RVs directly, rather than include the prior and observations. In such cases, the model won't have observed / likelihood nodes.

@fonnesbeck
Copy link
Member

fonnesbeck commented Jun 1, 2018

Right, but those situations don’t require MCMC, do they? Just forward simulation using independent sampling.

@kyleabeauchamp
Copy link
Contributor

Agreed, but my point is that such folks ought to use pymc rather than write their own model description / independent sampling code.

@fonnesbeck
Copy link
Member

Yeah I think we are talking about the same thing. Following on Allen’s point, we could engineer sample such that it uses simple forward sampling when there are no likelihoods in the model, without the user having to do anything. My question was related to whether you would ever not want to do this.

@ColCarroll
Copy link
Member Author

@AllenDowney I actually started using your notebook as a test case on the train home last night!

I think the issues brought up here with size and shape may be too hard to fix in a single go, and I think making the sort of analysis you did "just work" would be a good start (#2984 is something of a blocker), even if some edge-cases remain.

My goal for the api is more like

with pm.Model() as model:
    mu = pm.Gamma('mu', alpha, beta)
    goals = pm.Poisson('goals', mu, observed=6)

with model:
    prior = pm.sample_prior()

with model:
    trace = pm.sample()

with model:
    posterior_pred = pm.sample_ppc(trace)

which allows for model reuse. There's no reason your approach could not also be supported, though it makes me a little nervous to overload a function like that: in particular, silently ignoring arguments like tune or step.

@AllenDowney
Copy link

@ColCarroll

That makes sense: the parameters of sample_prior, sample, and sample_ppc are different, so it makes sense for them to be different functions. And I like the idea of separating the specification of the model from the sampling functions.

To be completely systematic, you might want four functions:

  1. sample_prior
  2. sample_prior_predictive
  3. sample_posterior (synonym for sample)
  4. sample_posterior_predictive (synonym for sample_ppc)

But maybe (1) is not necessary; if you're going to run the model forward, you might as well generate both the priors and the prior predictives.

If you like shorter names, maybe sample_prior_pred, sample_post, and sample_post_pred.

@twiecki
Copy link
Member

twiecki commented Jun 6, 2018

I like this naming scheme. For completeness we should indeed add pm.sample_posterior and then make pm.sample() an alias for that function for backwards compatibility.

@springcoil
Copy link
Contributor

I'm at a talk by Mike Betancourt tonight. Is this implemented yet.

@ColCarroll
Copy link
Member Author

Have been making surprisingly steady progress. I'll push a more complete description of the changes when it is ready (tomorrow?), but will push something partial tonight.

Has turned into a partial rewrite of all shape handling, which runs through generate_samples. I think what I have is much cleaner now, and ironically most of what is left is cleaning up all the code that deals with bad behavior in the old/current implementation. See, for example, Multinomial._random.

I do have it so that @AllenDowney's notebook runs cleanly without "abusing" PyMC3 😄 !

@AllenDowney
Copy link

AllenDowney commented Jun 14, 2018 via email

@springcoil
Copy link
Contributor

springcoil commented Jun 14, 2018 via email

@ColCarroll ColCarroll force-pushed the sample_generative branch 2 times, most recently from 22147c4 to d03ab9e Compare June 16, 2018 22:51
@ColCarroll
Copy link
Member Author

Oddly, I think this is nearly ready to merge. There were a few unexpected tests failing, and I think a few distributions (notably, Dirchlet) are not tested as well as they should be. In any case, I expect some edge cases to pop up after merging.

Everything is quite fast for .random and .draw_values (see the screen shot below). Indeed, this may now be the most flexible Python library for generating random numbers from a network of variables. I am a little worried that I accidentally slowed down a hot path for actual MCMC sampling, though, so checking the http://pandas.pydata.org/speed/pymc3/ after merging may also be good.

I do have a modestly embarrassing question about nomenclature, and what the prior vs prior predictive is. In the example below, would I be able to define

prior = {k: v for k, v in generated.items() if k != 'obs'}
prior_predictive = {'obs': generated['obs']}

image

@junpenglao
Copy link
Member

junpenglao commented Jun 17, 2018

My understanding is that prior predictive will return:

prior_predictive = {k: v for k, v in generated.items()}

The current output from the function is what I would expect.

@@ -25,7 +26,7 @@
import sys
sys.setrecursionlimit(10000)

__all__ = ['sample', 'iter_sample', 'sample_ppc', 'sample_ppc_w', 'init_nuts']
__all__ = ['sample', 'iter_sample', 'sample_ppc', 'sample_ppc_w', 'init_nuts', 'sample_generative']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sample_generative -> sample_prior_predictive

@twiecki
Copy link
Member

twiecki commented Jun 17, 2018

Found some small things. I'm very excited about having this functionality. It definitely needs a release note as well as an example, though.

@ColCarroll
Copy link
Member Author

Thanks for those! I'd prefer to add an example in a separate PR, if that is ok. This touches so much code in subtle ways...

@AllenDowney
Copy link

AllenDowney commented Jun 28, 2018 via email

@junpenglao
Copy link
Member

It will be in the next release. @fonnesbeck @twiecki maybe we should prepare a release actually, we got quite some new features.

@ColCarroll
Copy link
Member Author

I've been using this function and the graph function daily-ish and ironing out the bugs. Which are shape related, obviously.

@twiecki
Copy link
Member

twiecki commented Jun 28, 2018

Are there any non-shape bugs?

The answer to should we do a new release is always yes.

@fonnesbeck
Copy link
Member

OK, I will get a release ready over the weekend.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants