Skip to content

Commit 82003ed

Browse files
author
Christopher Fonnesbeck
committed
Re-implemented tuning to keep logic in step function
1 parent 644058d commit 82003ed

File tree

5 files changed

+84
-85
lines changed

5 files changed

+84
-85
lines changed

examples/lasso_block_update.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
# <markdowncell>
55

66
# Sometimes, it is very useful to update a set of parameters together. For example, variables that are highly correlated are often good to update together. In PyMC 3 block updating is simple, as example will demonstrate.
7-
#
7+
#
88
# Here we have a LASSO regression model where the two coefficients are strongly correlated. Normally, we would define the coefficient parameters as a single random variable, but here we define them separately to show how to do block updates.
9-
#
9+
#
1010
# First we generate some fake data.
1111

1212
# <codecell>
1313
from matplotlib.pylab import *
14-
from pymc import *
15-
import numpy as np
14+
from pymc import *
15+
import numpy as np
1616

1717
d = np.random.normal(size=(3, 30))
1818
d1 = d[0] + 4
@@ -29,24 +29,24 @@
2929
s = Exponential('s', 1)
3030
m1 = Laplace('m1', 0, 100)
3131
m2 = Laplace('m2', 0, 100)
32-
32+
3333
p = d1*m1 + d2*m2
34-
35-
y = Normal('y', p, s**-2, observed = yd)
34+
35+
y = Normal('y', p, s**-2, observed = yd)
3636

3737
# <markdowncell>
3838

39-
# For most samplers, including Metropolis and HamiltonianMC, simply pass a list of variables to sample as a block. This works with both scalar and array parameters.
39+
# For most samplers, including Metropolis and HamiltonianMC, simply pass a list of variables to sample as a block. This works with both scalar and array parameters.
4040

4141
# <codecell>
4242

43-
with model:
43+
with model:
4444
start = find_MAP()
45-
46-
step1 = Metropolis([m1, m2], approx_hess(start, [m1,m2]))
47-
48-
step2 = Metropolis([s], approx_hess(start, [s]))
49-
45+
46+
step1 = Metropolis([m1, m2])
47+
48+
step2 = Metropolis([s], laplace_proposal)
49+
5050
trace = sample(5000, [step1, step2], start)
5151

5252
# <codecell>

pymc/sample.py

Lines changed: 1 addition & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88

99
__all__ = ['sample', 'psample']
1010

11-
def sample(draws, step, start=None, trace=None, track_progress=True,
12-
tune_interval=100, model=None):
11+
def sample(draws, step, start=None, trace=None, track_progress=True, model=None):
1312
"""
1413
Draw a number of samples using the given step method.
1514
Multiple step methods supported via compound step method
@@ -58,58 +57,11 @@ def sample(draws, step, start=None, trace=None, track_progress=True,
5857
for i in xrange(draws):
5958
point = step.step(point)
6059
trace = trace.record(point)
61-
if i and not (i % tune_interval) and step.tune:
62-
step = tune(step, tune_interval)
6360
if track_progress:
6461
progress.update(i)
6562

6663
return trace
6764

68-
69-
def tune(step, tune_interval):
70-
"""
71-
Tunes the scaling parameter for the proposal distribution
72-
according to the acceptance rate over the last tune_interval:
73-
74-
Rate Variance adaptation
75-
---- -------------------
76-
<0.001 x 0.1
77-
<0.05 x 0.5
78-
<0.2 x 0.9
79-
>0.5 x 1.1
80-
>0.75 x 2
81-
>0.95 x 10
82-
83-
"""
84-
85-
# Calculate acceptance rate
86-
acc_rate = step.accepted / float(tune_interval)
87-
88-
# Switch statement
89-
if acc_rate<0.001:
90-
# reduce by 90 percent
91-
step.scaling *= 0.1
92-
elif acc_rate<0.05:
93-
# reduce by 50 percent
94-
step.scaling *= 0.5
95-
elif acc_rate<0.2:
96-
# reduce by ten percent
97-
step.scaling *= 0.9
98-
elif acc_rate>0.95:
99-
# increase by factor of ten
100-
step.scaling *= 10.0
101-
elif acc_rate>0.75:
102-
# increase by double
103-
step.scaling *= 2.0
104-
elif acc_rate>0.5:
105-
# increase by ten percent
106-
step.scaling *= 1.1
107-
108-
# Re-initialize rejection count
109-
step.accepted = 0
110-
111-
return step
112-
11365
def argsample(args):
11466
""" defined at top level so it can be pickled"""
11567
return sample(*args)

pymc/step_methods/arraystep.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,10 @@
77

88
# TODO Add docstrings to ArrayStep
99
class ArrayStep(object):
10-
def __init__(self, vars, fs, allvars = False, tune=False):
10+
def __init__(self, vars, fs, allvars = False):
1111
self.ordering = ArrayOrdering(vars)
1212
self.fs = fs
1313
self.allvars = allvars
14-
self.tune = tune
1514

1615
def step(self, point):
1716
bij = DictToArrayBijection(self.ordering, point)
@@ -29,10 +28,10 @@ def metrop_select(mr, q, q0):
2928
# Compare acceptance ratio to uniform random number
3029
if isfinite(mr) and log(uniform()) < mr:
3130
# Accept proposed value
32-
return q, True
31+
return q
3332
else:
3433
# Reject proposed value
35-
return q0, False
34+
return q0
3635

3736

3837
class SamplerHist(object):

pymc/step_methods/compound.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
@quickclass(object)
99
def CompoundStep(methods):
1010
methods = list(methods)
11-
tune = False
1211
def step(point):
1312
for method in methods:
1413
point = method.step(point)

pymc/step_methods/metropolis.py

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@
66
from arraystep import *
77
from numpy.random import normal, standard_cauchy, standard_exponential
88

9-
__all__ = ['Metropolis']
9+
__all__ = ['Metropolis', 'normal_proposal', 'cauchy_proposal', 'laplace_proposal']
1010

1111
# Available proposal distributions for Metropolis
12-
def normal_proposal(s, n):
13-
return lambda: normal(scale=s, size=n)
12+
def normal_proposal(s):
13+
return lambda: normal(scale=s)
1414

15-
def cauchy_proposal(s, n):
16-
return lambda: standard_cauchy(size=n) * s
15+
def cauchy_proposal(s):
16+
return lambda: standard_cauchy(size=np.size(s)) * s
1717

18-
def laplace_proposal(s, n):
19-
return lambda: (standard_exponential(size=n) - standard_exponential(size=n)) * s
18+
def laplace_proposal(s):
19+
return lambda: (standard_exponential(size=np.size(s)) - standard_exponential(size=np.size(s))) * s
2020

2121

2222
class Metropolis(ArrayStep):
@@ -40,28 +40,77 @@ class Metropolis(ArrayStep):
4040
Optional model for sampling step. Defaults to None (taken from context).
4141
4242
"""
43-
def __init__(self, vars, S, proposal_dist=quad_potential, scaling=1.,
44-
tune=True, model=None):
43+
def __init__(self, vars, S=None, proposal_dist=normal_proposal, scaling=1.,
44+
tune=True, tune_interval=100, model=None):
4545

4646
model = modelcontext(model)
4747

48-
try:
49-
self.proposal_dist = proposal_dist(S, n=len(vars))
50-
except TypeError:
51-
# quadpotential does not require n
52-
self.proposal_dist = proposal_dist(S)
48+
if S is None:
49+
S = [1]*len(vars)
50+
self.proposal_dist = proposal_dist(S)
5351
self.scaling = scaling
52+
self.tune = tune
53+
self.tune_interval = tune_interval
54+
self.steps_until_tune = tune_interval
5455
self.accepted = 0
55-
super(Metropolis,self).__init__(vars, [model.logpc], tune=tune)
56+
super(Metropolis,self).__init__(vars, [model.logpc])
5657

5758
def astep(self, q0, logp):
5859

60+
if self.tune and not self.steps_until_tune:
61+
# Tune scaling parameter
62+
self.scaling = tune(self.scaling, self.accepted/float(self.tune_interval))
63+
# Reset counter
64+
self.steps_until_tune = self.tune_interval
65+
self.accepted = 0
66+
5967
delta = self.proposal_dist() * self.scaling
6068

6169
q = q0 + delta
6270

63-
q_new, accepted = metrop_select(logp(q) - logp(q0), q, q0)
71+
q_new = metrop_select(logp(q) - logp(q0), q, q0)
72+
73+
if (any(q_new!=q0) or all(q0==q)):
74+
self.accepted += 1
6475

65-
self.accepted += int(accepted)
76+
self.steps_until_tune -= 1
6677

6778
return q_new
79+
80+
def tune(scale, acc_rate):
81+
"""
82+
Tunes the scaling parameter for the proposal distribution
83+
according to the acceptance rate over the last tune_interval:
84+
85+
Rate Variance adaptation
86+
---- -------------------
87+
<0.001 x 0.1
88+
<0.05 x 0.5
89+
<0.2 x 0.9
90+
>0.5 x 1.1
91+
>0.75 x 2
92+
>0.95 x 10
93+
94+
"""
95+
96+
# Switch statement
97+
if acc_rate<0.001:
98+
# reduce by 90 percent
99+
scale *= 0.1
100+
elif acc_rate<0.05:
101+
# reduce by 50 percent
102+
scale *= 0.5
103+
elif acc_rate<0.2:
104+
# reduce by ten percent
105+
scale *= 0.9
106+
elif acc_rate>0.95:
107+
# increase by factor of ten
108+
scale *= 10.0
109+
elif acc_rate>0.75:
110+
# increase by double
111+
scale *= 2.0
112+
elif acc_rate>0.5:
113+
# increase by ten percent
114+
scale *= 1.1
115+
116+
return scale

0 commit comments

Comments
 (0)