Skip to content

Commit 8e9d33e

Browse files
ColCarrollspringcoil
authored andcommitted
Refactor assign step methods (pymc-devs#1347)
* Refactor step tests * Refactor assign_step_methods and test_step
1 parent dcfcf65 commit 8e9d33e

File tree

3 files changed

+114
-141
lines changed

3 files changed

+114
-141
lines changed

pymc3/sampling.py

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22
from .backends.base import merge_traces, BaseTrace, MultiTrace
33
from .backends.ndarray import NDArray
44
from joblib import Parallel, delayed
5-
from time import time
65
from .model import modelcontext, Point
76
from .step_methods import (NUTS, HamiltonianMC, Metropolis, BinaryMetropolis,
87
BinaryGibbsMetropolis, Slice, ElemwiseCategorical, CompoundStep)
98
from .progressbar import progress_bar
109
from numpy.random import randint, seed
11-
from numpy import shape, append, asarray
10+
from numpy import shape, asarray
1211
from collections import defaultdict
1312

1413
import sys
@@ -17,9 +16,9 @@
1716
__all__ = ['sample', 'iter_sample', 'sample_ppc']
1817

1918

20-
def assign_step_methods(model, step=None,
21-
methods=(NUTS, HamiltonianMC, Metropolis, BinaryMetropolis, BinaryGibbsMetropolis,
22-
Slice, ElemwiseCategorical)):
19+
def assign_step_methods(model, step=None, methods=(NUTS, HamiltonianMC, Metropolis,
20+
BinaryMetropolis, BinaryGibbsMetropolis,
21+
Slice, ElemwiseCategorical)):
2322
'''
2423
Assign model variables to appropriate step methods. Passing a specified
2524
model will auto-assign its constituent stochastic variables to step methods
@@ -50,31 +49,29 @@ def assign_step_methods(model, step=None,
5049
steps = []
5150
assigned_vars = set()
5251
if step is not None:
53-
steps = append(steps, step).tolist()
54-
for s in steps:
52+
try:
53+
steps += list(step)
54+
except TypeError:
55+
steps.append(step)
56+
for step in steps:
5557
try:
56-
assigned_vars = assigned_vars | set(s.vars)
58+
assigned_vars = assigned_vars.union(set(step.vars))
5759
except AttributeError:
58-
for m in s.methods:
59-
assigned_vars = assigned_vars | set(m.vars)
60+
for method in step.methods:
61+
assigned_vars = assigned_vars.union(set(method.vars))
6062

6163
# Use competence classmethods to select step methods for remaining
6264
# variables
6365
selected_steps = defaultdict(list)
6466
for var in model.free_RVs:
65-
if not var in assigned_vars:
66-
67-
competences = {s: s._competence(var) for s in methods}
68-
69-
selected = max(competences.keys(), key=(lambda k: competences[k]))
70-
67+
if var not in assigned_vars:
68+
selected = max(methods, key=lambda method: method._competence(var))
7169
if model.verbose:
7270
print('Assigned {0} to {1}'.format(selected.__name__, var))
7371
selected_steps[selected].append(var)
7472

7573
# Instantiate all selected step methods
76-
steps += [s(vars=selected_steps[s])
77-
for s in selected_steps if selected_steps[s]]
74+
steps += [step(vars=selected_steps[step]) for step in selected_steps if selected_steps[step]]
7875

7976
if len(steps) == 1:
8077
steps = steps[0]

pymc3/step_methods/arraystep.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ def __init__(self, vars, fs, allvars=False, blocked=True):
105105
def step(self, point):
106106
bij = DictToArrayBijection(self.ordering, point)
107107

108-
inputs = list(map(bij.mapf, self.fs))
108+
inputs = [bij.mapf(x) for x in self.fs]
109109
if self.allvars:
110-
inputs += [point]
110+
inputs.append(point)
111111

112112
apoint = self.astep(bij.map(point), *inputs)
113113
return bij.rmap(apoint)

pymc3/tests/test_step.py

Lines changed: 97 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,139 +1,115 @@
1+
import unittest
2+
13
from .checks import close_to
2-
from .models import simple_model, mv_simple, mv_simple_discrete, simple_2model
3-
from ..step_methods import MultivariateNormalProposal
4-
from theano.tensor import constant
5-
from scipy.stats.mstats import moment
4+
from .models import mv_simple, mv_simple_discrete, simple_2model
65
from pymc3.sampling import assign_step_methods, sample
76
from pymc3.model import Model
8-
from pymc3.step_methods import NUTS, BinaryMetropolis, BinaryGibbsMetropolis, Metropolis, Constant, ElemwiseCategorical, Slice, CompoundStep, MultivariateNormalProposal, HamiltonianMC
7+
from pymc3.step_methods import (NUTS, BinaryGibbsMetropolis, Metropolis, Constant, Slice,
8+
CompoundStep, MultivariateNormalProposal, HamiltonianMC)
99
from pymc3.distributions import Binomial, Normal, Bernoulli, Categorical
1010
from numpy.testing import assert_almost_equal
1111
import numpy as np
1212

1313

14-
def check_stat(name, trace, var, stat, value, bound):
15-
s = stat(trace[var][2000:], axis=0)
16-
close_to(s, value, bound)
17-
18-
19-
def test_step_continuous():
20-
start, model, (mu, C) = mv_simple()
21-
22-
with model:
23-
mh = Metropolis()
24-
slicer = Slice()
25-
hmc = HamiltonianMC(scaling=C, is_cov=True, blocked=False)
26-
nuts = NUTS(scaling=C, is_cov=True, blocked=False)
27-
28-
mh_blocked = Metropolis(S=C,
29-
proposal_dist=MultivariateNormalProposal,
30-
blocked=True)
31-
slicer_blocked = Slice(blocked=True)
32-
hmc_blocked = HamiltonianMC(scaling=C, is_cov=True)
33-
nuts_blocked = NUTS(scaling=C, is_cov=True)
34-
35-
compound = CompoundStep([hmc_blocked, mh_blocked])
36-
37-
steps = [slicer, hmc, nuts, mh_blocked, hmc_blocked,
38-
slicer_blocked, nuts_blocked, compound]
39-
40-
unc = np.diag(C) ** .5
41-
check = [('x', np.mean, mu, unc / 10.),
42-
('x', np.std, unc, unc / 10.)]
43-
44-
for st in steps:
45-
h = sample(8000, st, start, model=model, random_seed=1)
46-
for (var, stat, val, bound) in check:
47-
yield check_stat, repr(st), h, var, stat, val, bound
48-
49-
50-
def test_non_blocked():
51-
"""Test that samplers correctly create non-blocked compound steps.
52-
"""
53-
54-
start, model = simple_2model()
55-
56-
with model:
57-
# Metropolis and Slice are non-blocked by default
58-
mh = Metropolis()
59-
assert isinstance(mh, CompoundStep)
60-
slicer = Slice()
61-
assert isinstance(slicer, CompoundStep)
62-
hmc = HamiltonianMC(blocked=False)
63-
assert isinstance(hmc, CompoundStep)
64-
nuts = NUTS(blocked=False)
65-
assert isinstance(nuts, CompoundStep)
66-
67-
mh_blocked = Metropolis(blocked=True)
68-
assert isinstance(mh_blocked, Metropolis)
69-
slicer_blocked = Slice(blocked=True)
70-
assert isinstance(slicer_blocked, Slice)
71-
hmc_blocked = HamiltonianMC()
72-
assert isinstance(hmc_blocked, HamiltonianMC)
73-
nuts_blocked = NUTS()
74-
assert isinstance(nuts_blocked, NUTS)
75-
76-
compound = CompoundStep([hmc_blocked, mh_blocked])
77-
78-
79-
def test_step_discrete():
80-
start, model, (mu, C) = mv_simple_discrete()
81-
82-
with model:
83-
mh = Metropolis(S=C,
84-
proposal_dist=MultivariateNormalProposal)
85-
slicer = Slice()
86-
87-
steps = [mh]
88-
89-
unc = np.diag(C) ** .5
90-
check = [('x', np.mean, mu, unc / 10.),
91-
('x', np.std, unc, unc / 10.)]
92-
93-
for st in steps:
94-
h = sample(20000, st, start, model=model, random_seed=1)
95-
96-
for (var, stat, val, bound) in check:
97-
yield check_stat, repr(st), h, var, stat, val, bound
98-
99-
100-
def test_constant_step():
101-
102-
with Model() as model:
103-
x = Normal('x', 0, 1)
104-
start = {'x': -1}
105-
tr = sample(10, step=Constant([x]), start=start)
14+
class TestStepMethods(object): # yield test doesn't work subclassing unittest.TestCase
15+
def check_stat(self, check, trace):
16+
for (var, stat, value, bound) in check:
17+
s = stat(trace[var][2000:], axis=0)
18+
close_to(s, value, bound)
19+
20+
def test_step_continuous(self):
21+
start, model, (mu, C) = mv_simple()
22+
unc = np.diag(C) ** .5
23+
check = (('x', np.mean, mu, unc / 10.),
24+
('x', np.std, unc, unc / 10.))
25+
with model:
26+
steps = (
27+
Slice(),
28+
HamiltonianMC(scaling=C, is_cov=True, blocked=False),
29+
NUTS(scaling=C, is_cov=True, blocked=False),
30+
Metropolis(S=C, proposal_dist=MultivariateNormalProposal, blocked=True),
31+
Slice(blocked=True),
32+
HamiltonianMC(scaling=C, is_cov=True),
33+
NUTS(scaling=C, is_cov=True),
34+
CompoundStep([
35+
HamiltonianMC(scaling=C, is_cov=True),
36+
HamiltonianMC(scaling=C, is_cov=True, blocked=False)]),
37+
)
38+
for step in steps:
39+
trace = sample(8000, step=step, start=start, model=model, random_seed=1)
40+
yield self.check_stat, check, trace
41+
42+
def test_step_discrete(self):
43+
start, model, (mu, C) = mv_simple_discrete()
44+
unc = np.diag(C) ** .5
45+
check = (('x', np.mean, mu, unc / 10.),
46+
('x', np.std, unc, unc / 10.))
47+
with model:
48+
steps = (
49+
Metropolis(S=C, proposal_dist=MultivariateNormalProposal),
50+
)
51+
for step in steps:
52+
trace = sample(20000, step=step, start=start, model=model, random_seed=1)
53+
self.check_stat(check, trace)
54+
55+
def test_constant_step(self):
56+
with Model():
57+
x = Normal('x', 0, 1)
58+
start = {'x': -1}
59+
tr = sample(10, step=Constant([x]), start=start)
10660
assert_almost_equal(tr['x'], start['x'], decimal=10)
10761

10862

109-
def test_assign_step_methods():
110-
111-
with Model() as model:
112-
x = Bernoulli('x', 0.5)
113-
steps = assign_step_methods(model, [])
114-
115-
assert isinstance(steps, BinaryGibbsMetropolis)
116-
117-
with Model() as model:
118-
x = Normal('x', 0, 1)
119-
steps = assign_step_methods(model, [])
120-
121-
assert isinstance(steps, NUTS)
122-
123-
with Model() as model:
124-
x = Categorical('x', np.array([0.25, 0.75]))
125-
steps = assign_step_methods(model, [])
126-
127-
assert isinstance(steps, BinaryGibbsMetropolis)
63+
class TestCompoundStep(unittest.TestCase):
64+
samplers = (Metropolis, Slice, HamiltonianMC, NUTS)
65+
66+
def test_non_blocked(self):
67+
"""Test that samplers correctly create non-blocked compound steps."""
68+
_, model = simple_2model()
69+
with model:
70+
for sampler in self.samplers:
71+
self.assertIsInstance(sampler(blocked=False), CompoundStep)
72+
73+
def test_blocked(self):
74+
_, model = simple_2model()
75+
with model:
76+
for sampler in self.samplers:
77+
sampler_instance = sampler(blocked=True)
78+
self.assertNotIsInstance(sampler_instance, CompoundStep)
79+
self.assertIsInstance(sampler_instance, sampler)
80+
81+
82+
class TestAssignStepMethods(unittest.TestCase):
83+
def test_bernoulli(self):
84+
"""Test bernoulli distribution is assigned binary gibbs metropolis method"""
85+
with Model() as model:
86+
Bernoulli('x', 0.5)
87+
steps = assign_step_methods(model, [])
88+
self.assertIsInstance(steps, BinaryGibbsMetropolis)
89+
90+
def test_normal(self):
91+
"""Test normal distribution is assigned NUTS method"""
92+
with Model() as model:
93+
Normal('x', 0, 1)
94+
steps = assign_step_methods(model, [])
95+
self.assertIsInstance(steps, NUTS)
96+
97+
def test_categorical(self):
98+
"""Test categorical distribution is assigned binary gibbs metropolis method"""
99+
with Model() as model:
100+
Categorical('x', np.array([0.25, 0.75]))
101+
steps = assign_step_methods(model, [])
102+
self.assertIsInstance(steps, BinaryGibbsMetropolis)
128103

129104
# with Model() as model:
130105
# x = Categorical('x', np.array([0.25, 0.70, 0.05]))
131106
# steps = assign_step_methods(model, [])
132107
#
133108
# assert isinstance(steps, ElemwiseCategoricalStep)
134109

135-
with Model() as model:
136-
x = Binomial('x', 10, 0.5)
137-
steps = assign_step_methods(model, [])
138-
139-
assert isinstance(steps, Metropolis)
110+
def test_binomial(self):
111+
"""Test binomial distribution is assigned metropolis method."""
112+
with Model() as model:
113+
Binomial('x', 10, 0.5)
114+
steps = assign_step_methods(model, [])
115+
self.assertIsInstance(steps, Metropolis)

0 commit comments

Comments
 (0)