-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Sampling Bounded Variables #1068
Comments
Thanks! This looks good on first sight but would love to get @mwibrow's take on this who wrote the initial |
Sounds good; especially since I wasn't too sure about the backend sampling framework (see my comment in the pull request). Also, the i.i.d. assumption doesn't seem very generalizable. The code doesn't explicitly check that the results from |
Something definitely isn't right with what I did, and PR #1069 seems to address it, but if the following is a legitimate use-case (i.e., different import numpy as np
import pymc3 as pm
x_shape = (2,1)
with pm.Model() as model:
BoundedNormal = pm.Bound(pm.Normal, lower=0)
x_var = BoundedNormal('x', mu=np.array([[50],[500]]),
sd=np.ones(shape=x_shape), shape=x_shape)
s = x_var.random(size=(1,)) PR #1069 produces |
@mwibrow, I don't seem able to reproduce that result with the given code; the second term in the sample is ~500, and the mean appears correct (via In [20]: !git branch -vv
* bound-shape-fix b50c566 [origin/bound-shape-fix] quick solution to Bounded sampling of arbitrary shapes
master 2924910 [origin/master] DOC Add link to pymc3 paper and citation.
njobs-import-fix e9883ec [origin/njobs-import-fix] added missing import alias for njobs=None condition
In [15]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:import numpy as np
:import pymc3 as pm
:
:x_shape = (2,1)
:
:with pm.Model() as model:
: BoundedNormal = pm.Bound(pm.Normal, lower=0)
: x_var = BoundedNormal('x', mu=np.array([[50],[500]]),
: sd=np.ones(shape=x_shape), shape=x_shape)
: s = x_var.random(size=(1000,))
: s.mean(axis=0)
:--
Out[15]:
array([[ 49.98727624],
[ 499.97657311]]) Any recommendations (different settings, etc.)? |
Actually, @mwibrow, it looks like I can produce your result by setting |
OK, so that example is really just an instance of the i.i.d. assumption being broken. When one of the samples is rejected, the code that follows is no longer consistent. We can't just push the remaining accepted samples on the FYI: I've updated the sampler in that branch to take an |
To answer this problem somewhat thoroughly, we need to know if the
I don't see how those are possible through the object/class structure of PyMC3 distributions alone, but there are some hackish approaches that might work. For instance, using introspection, we could ask if the distribution object is in the What do you folks say to a class structure that specifies the dimension (at least uni/multi-variate) of the support clearly and the addition of a |
Could we add a new baseclass |
That's what I was thinking. |
Great, can you add that here? |
Yep, and what about adding a |
Just to make sure:
Is different from what I proposed with the base-class. A bit undecided on |
Yes, I was just mentioning a "hack". Regarding the |
I just tried specifying some def test_multi_shape(reps_shape=(), dim=2):
supp_shape = (dim,)
test_shape_mu = reps_shape + supp_shape
test_shape_tau = reps_shape + supp_shape + supp_shape
with pm.Model():
x = pm.MvNormal('x'
, mu=np.zeros(shape=test_shape_mu)
, tau=np.ones(shape=test_shape_tau)
, shape=test_shape_mu)
assert x.random().shape == test_shape_mu
test_multi_shape((), 3) Now, when I change the replications shape Anyway, am I simply mistaken in these assumptions? |
Here's an example of a potential fix. I've written it to highlight the elements mentioned above. def mvnorm_logf(mu, tau, value):
mu = T.as_tensor_variable(mu)
tau = T.as_tensor_variable(tau)
reps_shape_T = tau.shape[:-2]
reps_shape_prod = T.prod(reps_shape_T, keepdims=True)
dist_shape_T = mu.shape[-1:]
# Collapse reps dimensions
flat_supp_shape = T.concatenate([reps_shape_prod, dist_shape_T])
# TODO: This could be made more generic. If we had a collection of params,
# we could simply loop through those.
mus_collapsed=mu.reshape(flat_supp_shape, ndim = 2)
taus_collapsed = tau.reshape(T.concatenate([reps_shape_prod,
dist_shape_T, dist_shape_T]), ndim=3)
params_collapsed = [mus_collapsed, taus_collapsed]
# Force value to conform to reps_shape
value_reshape = T.ones_like(mu) * value
values_collapsed = value_reshape.reshape(flat_supp_shape, ndim=2)
def single_logl(_mu, _tau, _value, k):
delta = _value - _mu
result = k * T.log(2 * np.pi) + T.log(det(_tau))
result += T.square(delta.dot(_tau)).sum(axis=-1)
return -result/2
res, _ = theano.scan(fn=single_logl
, sequences=params_collapsed + [values_collapsed]
, non_sequences=[dist_shape_T]
, strict=True
)
return res.sum() My note about the params, and the use of a |
Thanks @brandonwillard. I think fixing the shape issue is one of the last items in the way of a major release. Do you think you could help applying this to the rest of the code-base so that we have a good standard? |
I get errors when sampling a
Bound
'ed univariate scalar random variable with--effectively--non-scalar shape, e.g.The errors look like this.
First, is there anything fundamentally wrong about what's going on in that example?
Regardless, I've hacked up a quick solution (assuming that the underlying bounded [tensor] variable is a collection of i.i.d. variables) and I'm sending a pull request.
The text was updated successfully, but these errors were encountered: