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

for Bounded RV: 'HalfNormal' object has no attribute 'median' #3399

Closed
aakhmetz opened this issue Mar 9, 2019 · 5 comments
Closed

for Bounded RV: 'HalfNormal' object has no attribute 'median' #3399

aakhmetz opened this issue Mar 9, 2019 · 5 comments

Comments

@aakhmetz
Copy link

aakhmetz commented Mar 9, 2019

I use a simple code:

with pm.Model() as model:
    D = pm.HalfNormal('D',1)
    S = pm.Bound(pm.HalfNormal, upper=D)('S', 1)

Two half normals, one of which is bounded, then the default parameter value in S is not set to sd or tau as one may expect. The code above gives an error:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-30-1331a459e0b2> in <module>
      1 with pm.Model() as model:
      2     D = pm.HalfNormal('D',1)
----> 3     S = pm.Bound(pm.HalfNormal, upper=D)('S', 1)

~/anaconda/lib/python3.7/site-packages/pymc3/distributions/bound.py in __call__(self, name, *args, **kwargs)
    217         if issubclass(self.distribution, Continuous):
    218             return _ContinuousBounded(name, self.distribution,
--> 219                                       self.lower, self.upper, *args, **kwargs)
    220         elif issubclass(self.distribution, Discrete):
    221             return _DiscreteBounded(name, self.distribution,

~/anaconda/lib/python3.7/site-packages/pymc3/distributions/distribution.py in __new__(cls, name, *args, **kwargs)
     39                 raise TypeError("observed needs to be data but got: {}".format(type(data)))
     40             total_size = kwargs.pop('total_size', None)
---> 41             dist = cls.dist(*args, **kwargs)
     42             return model.Var(name, dist, data, total_size)
     43         else:

~/anaconda/lib/python3.7/site-packages/pymc3/distributions/distribution.py in dist(cls, *args, **kwargs)
     50     def dist(cls, *args, **kwargs):
     51         dist = object.__new__(cls)
---> 52         dist.__init__(*args, **kwargs)
     53         return dist
     54 

~/anaconda/lib/python3.7/site-packages/pymc3/distributions/bound.py in __init__(self, distribution, lower, upper, transform, *args, **kwargs)
    153         super().__init__(
    154             distribution=distribution, lower=lower, upper=upper,
--> 155             transform=transform, default=default, *args, **kwargs)
    156 
    157 

~/anaconda/lib/python3.7/site-packages/pymc3/distributions/bound.py in __init__(self, distribution, lower, upper, default, *args, **kwargs)
     22             defaults = self._wrapped.defaults
     23             for name in defaults:
---> 24                 setattr(self, name, getattr(self._wrapped, name))
     25         else:
     26             defaults = ('_default',)

AttributeError: 'HalfNormal' object has no attribute 'median'

The following code works as expected:

with pm.Model() as model:
    D = pm.HalfNormal('D',1)
    S = pm.Bound(pm.HalfNormal, upper=D)('S', sd=1)

Please provide any additional information below.

It would be nice to have some consistency: if both D = pm.HalfNormal('D',1) and D = pm.HalfNormal('D',sd=1) work, then the variant with S = pm.Bound(pm.HalfNormal, upper=D)('S', 1) would work as well.

Versions and main components

  • PyMC3 Version: 3.6
  • Theano Version: 1.0.3
  • Python Version: 3.7.2
  • Operating system: archlinux
  • How did you install PyMC3: pip with latest version on github (as of March 08)
@sshekh
Copy link
Contributor

sshekh commented Apr 2, 2019

Hi @aakhmetz , I tried to look deeper into this. It's not just a problem with HalfNormal, but other bounded variables too. For example - the following example will work

with pm.Model() as model: 
     n = pm.Normal('n', 1.0, 1.0) 
     par1 = pm.Bound(pm.Normal, upper=n)('par1', mu=2.0, sd=1.0)

while the following will throw an error

with pm.Model() as model:
    n = pm.Normal('n', 1.0, 1.0)
    par1 = pm.Bound(pm.Normal, upper=n)('par1', 2.0, 1.0)

Bound calls _ContinuousBounded(name, self.distribution, self.lower, self.upper, *args, **kwargs) here which in turn calls Distribution.dist(*args, **kwargs). At this point the two sets of arguments (one to Bound: self.distribution, self.lower, self.upper and the other to Normal: - 2.0, 1.0 are mixed together in *args.

    def dist(cls, *args, **kwargs):
        dist = object.__new__(cls)
        dist.__init__(*args, **kwargs)
        return dist

At dist.init() thus:

When written as

pm.Bound(pm.Normal, upper=n)('par1', mu=2.0, sd=1.0)

args = (<class 'pymc3.distributions.continuous.Normal'>, None, n) and kwargs = {'mu': 2.0, 'sd': 1.0}
so it works as expected.

However, when written as

pm.Bound(pm.Normal, upper=n)('par1', 2.0, 1.0)

args = (<class 'pymc3.distributions.continuous.Normal'>, None, n, 2.0, 1.0) and kwargs = {}
So, Bound gets unexpected arguments while Normal gets none.

@twiecki , @ColCarroll, @canyon289 do you have any idea to proceed with this? I am trying to think of something which would stop the different sets of arguments from merging into one or always require parameter names when creating Bound objects.

I am new to pymc3 and trying to contribute thanks in advance 😅

@ColCarroll
Copy link
Member

Wow -- this is tricky: thank you for the helpful writeup. I think I see what you're saying.

There is a lot of shuffling arguments around to internal functions -- do you think Bound, _ContinuousBounded and _Bounded can make sure that the arguments they use are keyword arguments? Specifically, replacing calls like

_ContinuousBounded(name, self.distribution, self.lower, self.upper, *args, **kwargs)

with something like

_ContinuousBounded(*args, name=name, dist=self.distribution, lower=self.lower, upper=self.upper,  **kwargs)

That seems like it might be straightforward, and not change any of the public API, right?

@sshekh
Copy link
Contributor

sshekh commented Apr 3, 2019

@ColCarroll thanks for the response! I will look into it and update soon 🔜

@sshekh
Copy link
Contributor

sshekh commented Apr 8, 2019

@ColCarroll I initially tried to convert all arguments to keyword arguments but dist.init will put the positional arguments *args (meant for Bound.distribution) before the keyword arguments no matter what, thus mixing them. So I converted all calls to _ContinuousBounded to use only (and all) positional arguments. PR #3446

@lucianopaz
Copy link
Contributor

Closed by #3446

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

No branches or pull requests

4 participants