Commit 0ae72c57 authored by Robert P. Goldman's avatar Robert P. Goldman Committed by Robert P. Goldman

Merge from master.

parent b8a67fea
[DEFAULT]
pylint_rcfile = .pylintrc
\ No newline at end of file
......@@ -538,7 +538,7 @@ def draw_values(params, point=None, size=None):
# The following check intercepts and redirects calls to
# draw_values in the context of sample_posterior_predictive
ppc_sampler = vectorized_ppc.get(None)
if ppc_sampler:
if ppc_sampler is not None:
# this is being done inside new, vectorized sample_posterior_predictive
return ppc_sampler(params, trace=point, samples=size)
......
......@@ -310,6 +310,7 @@ class _PosteriorPredictiveSampler(AbstractContextManager):
givens = {p.name: (p, v) for (p, samples), v in drawn.items()
if getattr(p, 'name', None) is not None}
stack = list(self.leaf_nodes.values()) # A queue would be more appropriate
while stack:
next_ = stack.pop(0)
if (next_, samples) in drawn:
......@@ -364,6 +365,7 @@ class _PosteriorPredictiveSampler(AbstractContextManager):
# The remaining params that must be drawn are all hashable
to_eval = set() # type: Set[int]
missing_inputs = set([j for j, p in self.symbolic_params]) # type: Set[int]
while to_eval or missing_inputs:
if to_eval == missing_inputs:
raise ValueError('Cannot resolve inputs for {}'.format([str(trace.varnames[j]) for j in to_eval]))
......@@ -479,8 +481,6 @@ class _PosteriorPredictiveSampler(AbstractContextManager):
samples = self.samples
def random_sample(meth: Callable[..., np.ndarray], param, point: _TraceDict, size: int, shape: Tuple[int, ...]) -> np.ndarray:
# if hasattr(param, 'name') and param.name == 'obs':
# import pdb; pdb.set_trace()
val = meth(point=point, size=size)
if size == 1:
val = np.expand_dims(val, axis=0)
......@@ -575,8 +575,8 @@ class _PosteriorPredictiveSampler(AbstractContextManager):
if not input_vars:
assert input_vals == [] # AFAICT if there are now vars, there can't be vals
output = func(*input_vals)
if hasattr(output, 'shape', None):
val = np.ndarray(output * samples)
if hasattr(output, 'shape'):
val = np.repeat(np.expand_dims(output, 0), samples, axis=0)
else:
val = np.full(samples, output)
......
......@@ -1475,52 +1475,54 @@ class _DefaultTrace:
self.trace_dict[k][idx, :] = v
def sample_posterior_predictive(trace,
samples: Optional[int]=None,
model: Optional[Model]=None,
vars: Optional[TIterable[Tensor]]=None,
var_names: Optional[List[str]]=None,
size: Optional[int]=None,
keep_size: Optional[bool]=False,
random_seed=None,
progressbar: bool=True) -> Dict[str, np.ndarray]:
def sample_posterior_predictive(
trace,
samples: Optional[int] = None,
model: Optional[Model] = None,
vars: Optional[TIterable[Tensor]] = None,
var_names: Optional[List[str]] = None,
size: Optional[int] = None,
keep_size: Optional[bool] = False,
random_seed=None,
progressbar: bool = True,
) -> Dict[str, np.ndarray]:
"""Generate posterior predictive samples from a model given a trace.
Parameters
----------
trace : backend, list, or MultiTrace
trace: backend, list, or MultiTrace
Trace generated from MCMC sampling. Or a list containing dicts from
find_MAP() or points
samples : int
samples: int
Number of posterior predictive samples to generate. Defaults to one posterior predictive
sample per posterior sample, that is, the number of draws times the number of chains. It
is not recommended to modify this value; when modified, some chains may not be represented
in the posterior predictive sample.
model : Model (optional if in ``with`` context)
model: Model (optional if in ``with`` context)
Model used to generate ``trace``
vars : iterable
vars: iterable
Variables for which to compute the posterior predictive samples.
Deprecated: please use ``var_names`` instead.
var_names : Iterable[str]
var_names: Iterable[str]
Alternative way to specify vars to sample, to make this function orthogonal with
others.
size : int
size: int
The number of random draws from the distribution specified by the parameters in each
sample of the trace. Not recommended unless more than ndraws times nchains posterior
predictive samples are needed.
keep_size : bool, optional
keep_size: bool, optional
Force posterior predictive sample to have the same shape as posterior and sample stats
data: ``(nchains, ndraws, ...)``. Overrides samples and size parameters.
random_seed : int
random_seed: int
Seed for the random number generator.
progressbar : bool
progressbar: bool
Whether or not to display a progress bar in the command line. The bar shows the percentage
of completion, the sampling speed in samples per second (SPS), and the estimated remaining
time until completion ("expected time of arrival"; ETA).
Returns
-------
samples : dict
samples: dict
Dictionary with the variable names as keys, and values numpy arrays containing
posterior predictive samples.
"""
......@@ -1539,9 +1541,11 @@ def sample_posterior_predictive(trace,
samples = sum(len(v) for v in trace._straces.values())
if samples < len_trace * nchain:
warnings.warn("samples parameter is smaller than nchains times ndraws, some draws "
"and/or chains may not be represented in the returned posterior "
"predictive sample")
warnings.warn(
"samples parameter is smaller than nchains times ndraws, some draws "
"and/or chains may not be represented in the returned posterior "
"predictive sample"
)
model = modelcontext(model)
......@@ -1561,9 +1565,8 @@ def sample_posterior_predictive(trace,
indices = np.arange(samples)
if progressbar:
indices = tqdm(indices, total=samples)
indices = progress_bar(indices, total=samples, display=progressbar)
ppc_trace_t = _DefaultTrace(samples)
try:
......@@ -1581,10 +1584,6 @@ def sample_posterior_predictive(trace,
except KeyboardInterrupt:
pass
finally:
if progressbar:
indices.close()
ppc_trace = ppc_trace_t.trace_dict
if keep_size:
for k, ary in ppc_trace.items():
......
......@@ -32,10 +32,10 @@ class TestData(SeededTest):
x_pred = np.linspace(-3, 3, 200, dtype='float32')
with pm.Model() as model:
with pm.Model():
x_shared = pm.Data('x_shared', x)
b = pm.Normal('b', 0., 10.)
obsvar = pm.Normal('obs', b * x_shared, np.sqrt(1e-2), observed=y)
pm.Normal('obs', b * x_shared, np.sqrt(1e-2), observed=y)
prior_trace0 = pm.sample_prior_predictive(1000)
trace = pm.sample(1000, init=None, tune=1000, chains=1)
......@@ -53,7 +53,7 @@ class TestData(SeededTest):
assert pp_trace0['obs'].shape == (1000, 100)
assert pp_trace01['obs'].shape == (1000, 100)
np.testing.assert_allclose(x, pp_trace0['obs'].mean(axis=0), atol=1e-1)
np.testing.assert_allclose(x, pp_trace01['obs'].mean(axis=0), atol=1e-1)
......@@ -108,7 +108,6 @@ class TestData(SeededTest):
atol=1e-1)
np.testing.assert_allclose(new_y, pp_tracef['obs'].mean(axis=0),
atol=1e-1)
def test_creation_of_data_outside_model_context(self):
with pytest.raises((IndexError, TypeError)) as error:
......
......@@ -948,14 +948,62 @@ def test_mixture_random_shape():
assert ppc['like2'].shape == (200, 20)
assert ppc['like3'].shape == (200, 20)
@pytest.mark.xfail
def test_mixture_random_shape_fast():
# test the shape broadcasting in mixture random
y = np.concatenate([nr.poisson(5, size=10),
nr.poisson(9, size=10)])
with pm.Model() as m:
comp0 = pm.Poisson.dist(mu=np.ones(2))
w0 = pm.Dirichlet('w0', a=np.ones(2))
like0 = pm.Mixture('like0',
w=w0,
comp_dists=comp0,
observed=y)
comp1 = pm.Poisson.dist(mu=np.ones((20, 2)),
shape=(20, 2))
w1 = pm.Dirichlet('w1', a=np.ones(2))
like1 = pm.Mixture('like1',
w=w1,
comp_dists=comp1,
observed=y)
comp2 = pm.Poisson.dist(mu=np.ones(2))
w2 = pm.Dirichlet('w2',
a=np.ones(2),
shape=(20, 2))
like2 = pm.Mixture('like2',
w=w2,
comp_dists=comp2,
observed=y)
comp3 = pm.Poisson.dist(mu=np.ones(2),
shape=(20, 2))
w3 = pm.Dirichlet('w3',
a=np.ones(2),
shape=(20, 2))
like3 = pm.Mixture('like3',
w=w3,
comp_dists=comp3,
observed=y)
rand0, rand1, rand2, rand3 = draw_values([like0, like1, like2, like3],
point=m.test_point,
size=100)
assert rand0.shape == (100, 20)
assert rand1.shape == (100, 20)
assert rand2.shape == (100, 20)
assert rand3.shape == (100, 20)
# I *think* that the mixture means that this is not going to work,
# but I could be wrong. [2019/08/22:rpg]
# with m:
# ppc = pm.fast_sample_posterior_predictive([m.test_point], samples=200)
# assert ppc['like0'].shape == (200, 20)
# assert ppc['like1'].shape == (200, 20)
# assert ppc['like2'].shape == (200, 20)
# assert ppc['like3'].shape == (200, 20)
with m:
ppc = pm.fast_sample_posterior_predictive([m.test_point], samples=200)
assert ppc['like0'].shape == (200, 20)
assert ppc['like1'].shape == (200, 20)
assert ppc['like2'].shape == (200, 20)
assert ppc['like3'].shape == (200, 20)
......@@ -1050,8 +1098,26 @@ class TestDensityDist():
ppc = pm.sample_posterior_predictive(trace, samples=samples, model=model, size=size)
assert ppc['density_dist'].shape == (samples, size) + obs.distribution.shape
# ppc = pm.fast_sample_posterior_predictive(trace, samples=samples, model=model, size=size)
# assert ppc['density_dist'].shape == (samples, size) + obs.distribution.shape
@pytest.mark.xfail
def test_density_dist_with_random_sampleable_handcrafted_success_fast(self):
with pm.Model() as model:
mu = pm.Normal('mu', 0, 1)
normal_dist = pm.Normal.dist(mu, 1)
rvs = pm.Normal.dist(mu, 1, shape=100).random
obs = pm.DensityDist(
'density_dist',
normal_dist.logp,
observed=np.random.randn(100),
random=rvs,
wrap_random_with_dist_shape=False
)
trace = pm.sample(100)
samples = 500
size = 100
ppc = pm.fast_sample_posterior_predictive(trace, samples=samples, model=model, size=size)
assert ppc['density_dist'].shape == (samples, size) + obs.distribution.shape
def test_density_dist_without_random_not_sampleable(self):
......
......@@ -18,6 +18,8 @@ from ..distributions.timeseries import EulerMaruyama, AR1, AR, GARCH11
from ..sampling import sample, sample_posterior_predictive, fast_sample_posterior_predictive
from ..theanof import floatX
import numpy as np
import pytest
from .helpers import select_by_precision
pytestmark = pytest.mark.usefixtures('seeded_test')
......
import threading
from pymc3 import Model, Normal
class TestModelContext:
def test_thread_safety(self):
""" Regression test for issue #1552: Thread safety of model context manager
This test creates two threads that attempt to construct two
unrelated models at the same time.
For repeatable testing, the two threads are syncronised such
that thread A enters the context manager first, then B,
then A attempts to declare a variable while B is still in the context manager.
"""
aInCtxt,bInCtxt,aDone = [threading.Event() for _ in range(3)]
modelA = Model()
modelB = Model()
def make_model_a():
with modelA:
aInCtxt.set()
bInCtxt.wait()
Normal('a',0,1)
aDone.set()
def make_model_b():
aInCtxt.wait()
with modelB:
bInCtxt.set()
aDone.wait()
Normal('b', 0, 1)
threadA = threading.Thread(target=make_model_a)
threadB = threading.Thread(target=make_model_b)
threadA.start()
threadB.start()
threadA.join()
threadB.join()
# now let's see which model got which variable
# previous to #1555, the variables would be swapped:
# - B enters it's model context after A, but before a is declared -> a goes into B
# - A leaves it's model context before B attempts to declare b. A's context manager
# takes B from the stack, such that b ends up in model A
assert (
list(modelA.named_vars),
list(modelB.named_vars),
) == (['a'],['b'])
import threading
from pytest import raises
from pymc3 import Model, Normal
from pymc3.distributions.distribution import _DrawValuesContext, _DrawValuesContextBlocker
from pymc3.model import modelcontext
class TestModelContext:
def test_thread_safety(self):
""" Regression test for issue #1552: Thread safety of model context manager
This test creates two threads that attempt to construct two
unrelated models at the same time.
For repeatable testing, the two threads are syncronised such
that thread A enters the context manager first, then B,
then A attempts to declare a variable while B is still in the context manager.
"""
aInCtxt,bInCtxt,aDone = [threading.Event() for _ in range(3)]
modelA = Model()
modelB = Model()
def make_model_a():
with modelA:
aInCtxt.set()
bInCtxt.wait()
Normal('a',0,1)
aDone.set()
def make_model_b():
aInCtxt.wait()
with modelB:
bInCtxt.set()
aDone.wait()
Normal('b', 0, 1)
threadA = threading.Thread(target=make_model_a)
threadB = threading.Thread(target=make_model_b)
threadA.start()
threadB.start()
threadA.join()
threadB.join()
# now let's see which model got which variable
# previous to #1555, the variables would be swapped:
# - B enters it's model context after A, but before a is declared -> a goes into B
# - A leaves it's model context before B attempts to declare b. A's context manager
# takes B from the stack, such that b ends up in model A
assert (
list(modelA.named_vars),
list(modelB.named_vars),
) == (['a'],['b'])
def test_mixed_contexts():
modelA = Model()
modelB = Model()
with raises((ValueError, TypeError)):
modelcontext(None)
with modelA:
with modelB:
assert Model.get_context() == modelB
assert modelcontext(None) == modelB
dvc = _DrawValuesContext()
with dvc:
assert Model.get_context() == modelB
assert modelcontext(None) == modelB
assert _DrawValuesContext.get_context() == dvc
dvcb = _DrawValuesContextBlocker()
with dvcb:
assert _DrawValuesContext.get_context() == dvcb
assert _DrawValuesContextBlocker.get_context() == dvcb
assert _DrawValuesContext.get_context() == dvc
assert _DrawValuesContextBlocker.get_context() is dvc
assert Model.get_context() == modelB
assert modelcontext(None) == modelB
assert _DrawValuesContext.get_context(error_if_none=False) is None
with raises(TypeError):
_DrawValuesContext.get_context()
assert Model.get_context() == modelB
assert modelcontext(None) == modelB
assert Model.get_context() == modelA
assert modelcontext(None) == modelA
assert Model.get_context(error_if_none=False) is None
with raises(TypeError):
Model.get_context(error_if_none=True)
with raises((ValueError, TypeError)):
modelcontext(None)
import threading
from pytest import raises
from pymc3 import Model, Normal
from pymc3.distributions.distribution import _DrawValuesContext, _DrawValuesContextBlocker
from pymc3.model import modelcontext
class TestModelContext:
def test_thread_safety(self):
""" Regression test for issue #1552: Thread safety of model context manager
This test creates two threads that attempt to construct two
unrelated models at the same time.
For repeatable testing, the two threads are syncronised such
that thread A enters the context manager first, then B,
then A attempts to declare a variable while B is still in the context manager.
"""
aInCtxt,bInCtxt,aDone = [threading.Event() for _ in range(3)]
modelA = Model()
modelB = Model()
def make_model_a():
with modelA:
aInCtxt.set()
bInCtxt.wait()
Normal('a',0,1)
aDone.set()
def make_model_b():
aInCtxt.wait()
with modelB:
bInCtxt.set()
aDone.wait()
Normal('b', 0, 1)
threadA = threading.Thread(target=make_model_a)
threadB = threading.Thread(target=make_model_b)
threadA.start()
threadB.start()
threadA.join()
threadB.join()
# now let's see which model got which variable
# previous to #1555, the variables would be swapped:
# - B enters it's model context after A, but before a is declared -> a goes into B
# - A leaves it's model context before B attempts to declare b. A's context manager
# takes B from the stack, such that b ends up in model A
assert (
list(modelA.named_vars),
list(modelB.named_vars),
) == (['a'],['b'])
def test_mixed_contexts():
modelA = Model()
modelB = Model()
with raises((ValueError, TypeError)):
modelcontext(None)
with modelA:
with modelB:
assert Model.get_context() == modelB
assert modelcontext(None) == modelB
dvc = _DrawValuesContext()
with dvc:
assert Model.get_context() == modelB
assert modelcontext(None) == modelB
assert _DrawValuesContext.get_context() == dvc
dvcb = _DrawValuesContextBlocker()
with dvcb:
assert _DrawValuesContext.get_context() == dvcb
assert _DrawValuesContextBlocker.get_context() == dvcb
assert _DrawValuesContext.get_context() == dvc
assert _DrawValuesContextBlocker.get_context() is None
assert Model.get_context() == modelB
assert modelcontext(None) == modelB
assert _DrawValuesContext.get_context() is None
assert Model.get_context() == modelB
assert modelcontext(None) == modelB
assert Model.get_context() == modelA
assert modelcontext(None) == modelA
assert Model.get_context() is None
with raises((ValueError, TypeError)):
modelcontext(None)
import pymc3 as pm
import pytest
from pymc3.distributions.posterior_predictive import _TraceDict
import numpy as np
......
......@@ -16,4 +16,3 @@ recommonmark>=0.4.0
seaborn>=0.8.1
sphinx-autobuild==0.7.1
sphinx>=1.5.5
typing-extensions>=3.7.4
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment