Skip to content

Allow arrays in pm.Bound #2277

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

Merged
merged 6 commits into from
Jun 29, 2017
Merged

Allow arrays in pm.Bound #2277

merged 6 commits into from
Jun 29, 2017

Conversation

aseyboldt
Copy link
Member

pm.Bound doesn't support array valued bounds at the moment. This is fixed in this PR. I also refactored the Bounded.__init__ function a bit.


Parameters
----------
distribution : pymc3 distribution
Distribution to be transformed into a bounded distribution
lower : float (optional)
lower : float, optional
Copy link
Member

Choose a reason for hiding this comment

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

should it be tensor (float or 1D vector)?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, arbitrary shape actually.

@junpenglao
Copy link
Member

LGTM. The broken example garch_example.py is also fixed with this PR. #2254

@junpenglao junpenglao mentioned this pull request Jun 6, 2017
The resulting distribution is not normalized anymore. This
is usually fine if the bounds are constants. If you need
truncated distributions, use `Bound` in combination with
a `pm.Potential` with the cumulative probability function.
Copy link
Member

Choose a reason for hiding this comment

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

We probably want an example of this. Maybe just in the examples folder, rather than a full-blown notebook.

Copy link
Member

Choose a reason for hiding this comment

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

We can edit the example censored_data.py

@junpenglao
Copy link
Member

There seems to be some problem with the implementation, I tested on the Imputed censored model in censored_data.py:
1, Got ValueError: Bad initial energy: inf. The model might be misspecified. using ADVI init
2, Estimation is off (with init=None):
Before:
image
After:
image

@aseyboldt
Copy link
Member Author

By "before/after" do you mean before and after applying this patch?

@ferrine
Copy link
Member

ferrine commented Jun 6, 2017

Is it possible to make Bound to be a subclass of distribution?

@aseyboldt
Copy link
Member Author

Calling a Bound object produces a Bounded object, which is a distribution subclass. (It's basically a distribution factory). So I'm not sure what you mean. Do you want to change the API for Bound? @ferrine

@junpenglao
Copy link
Member

By "before/after" do you mean before and after applying this patch?

@aseyboldt Yep

@aseyboldt
Copy link
Member Author

That was a smart one. I forgot to pass the transformation to the wrapped distribution....

@ferrine
Copy link
Member

ferrine commented Jun 6, 2017

@aseyboldt issubclass(Bound(Normal, -1, 1), Distribution)

@aseyboldt
Copy link
Member Author

aseyboldt commented Jun 6, 2017

@ferrine No, that shouldn't be true. If anything it should be
issubclass(Bound(Normal, -1, 1), Distribution), but that would mean that we create types on the fly and that often clashes with pickle. We have
isinstance(Bound(Normal, -1, 1).dist(mu=0, sd=1), Distribution) . (not tested, I have other stuff running on a different branch)

@ferrine
Copy link
Member

ferrine commented Jun 6, 2017

You can, just use __reduce__ (https://docs.python.org/3/library/pickle.html#object.__reduce__)
It can pickle arbitrary things created on the fly.

@aseyboldt
Copy link
Member Author

aseyboldt commented Jun 6, 2017

I didn't say it can't be done. But I think it is trouble and doesn't do anything useful.

@ferrine
Copy link
Member

ferrine commented Jun 6, 2017

Sorry if I misunderstood you. It adds consistency in the structure. It is confusing to have failing subclass check for implementation stuff that works with distributions

@aseyboldt
Copy link
Member Author

aseyboldt commented Jun 6, 2017

I think we shouldn't depend on any subclass checks. That is typically bad practice, too. If we need to find out more about the distribution/RV, we could add a property kind or similar, that could be 'discrete' or 'continuous'. Or maybe better domain, which could be reals, integers...

@junpenglao junpenglao mentioned this pull request Jun 9, 2017
@twiecki twiecki changed the base branch from master to 3.2_dev June 23, 2017 10:04
@twiecki
Copy link
Member

twiecki commented Jun 23, 2017

@aseyboldt can you remove the conflicts? would like to merge this into the 3.2 branch.

@junpenglao
Copy link
Member

There is failed test as well.

@aseyboldt aseyboldt force-pushed the fix-bound branch 4 times, most recently from 1c7abb0 to 2bbc610 Compare June 25, 2017 19:43
@aseyboldt aseyboldt changed the base branch from 3.2_dev to master June 25, 2017 19:45
@aseyboldt aseyboldt force-pushed the fix-bound branch 2 times, most recently from 0b2196a to e6a7a36 Compare June 27, 2017 15:57
@aseyboldt
Copy link
Member Author

I think this is ready to merge.
@ferrine I did end up making sure the inheritance with Discrete and Continuous checks out right, I think you were right about that.

@junpenglao
Copy link
Member

@aseyboldt pm.Bound now also accept tensor as bound right? Could you also write a test for that?

'par3', mu=0.0, sd=1.0, testval=1.0)
"""

def __init__(self, distribution, lower=None, upper=None):
Copy link
Member

Choose a reason for hiding this comment

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

How do you feel about returning dynamicly created class that can be ofc pickled with __reduce__? I saw code above and that can be not so difficult and take little coding

Copy link
Member

Choose a reason for hiding this comment

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

I feel worried about this extra Bound class that does nothing and is not Distribution itself but proposed to be the one

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'd rather not make this any more complicated than it already is.
Sadly, we don't distinguish properly between random variables and distributions, but use the constructor of our distribution objects to build random variables (observed and otherwise). This leads to the somewhat strange behaviour

issubclass(pm.Normal, pm.Distribution)  # -> True
isinstance(pm.Normal('a'), pm.Distribution) # -> False
isinstance(pm.Normal('a'), pm.Normal) # -> False

As we really don't want to second to be true, I think the first actually shouldn't be true in the first place. We shouldn't think of our distribution classes as distributions, but as factory functions for random variables. The distributions are pm.Normal.dist.
So I kind of don't see the point of adding another non-standard thing on top of the metaclasses and overriden __new__ that's already in there, to make a subclass check true, that shouldn't be true in the first place. Writing it isn't the problem, it just makes the code ever harder to read.

We do have the following now, which I think is the important part:

BoundNormal = pm.Bound(pm.Normal)
isinstance(BoundNormal.dist(), pm.Continuous) # -> True

The original design of pm.Bound is I guess a bit unfortunate, we wouldn't have that problem in the first place if it took a distribution as parameter and returned a random variable:

dist = pm.Normal.dist(mu=5, sd=3)
pm.Bound("a", dist, lower=3)

Copy link
Member

Choose a reason for hiding this comment

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

Sounds reasonable. Hope no one will meet the problem of that kind

Copy link
Member

Choose a reason for hiding this comment

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

@aseyboldt for the thing you mention above:

dist = pm.Normal.dist(mu=5, sd=3)
pm.Bound("a", dist, lower=3)

Could we not use tt.clip to do the same thing?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, clip would be more like censoring, as the values below lower are just set to lower. Bound mostly sets an appropriate transformation.

@aseyboldt
Copy link
Member Author

@junpenglao I just pushed a test for tensor bounds.

@junpenglao junpenglao merged commit 1e78da4 into pymc-devs:master Jun 29, 2017
@junpenglao
Copy link
Member

Thanks @aseyboldt

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.

5 participants