Skip to content

Commit ea82ebd

Browse files
colintwiecki
colin
authored andcommitted
Refactor Hamiltonian methods into single class
1 parent fc0673b commit ea82ebd

File tree

14 files changed

+474
-406
lines changed

14 files changed

+474
-406
lines changed

pymc3/step_methods/__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from .compound import CompoundStep
22

3-
from .hmc import HamiltonianMC
3+
from .hmc import HamiltonianMC, NUTS
44

55
from .metropolis import Metropolis
66
from .metropolis import BinaryMetropolis
@@ -15,5 +15,3 @@
1515
from .gibbs import ElemwiseCategorical
1616

1717
from .slicer import Slice
18-
19-
from .nuts import NUTS

pymc3/step_methods/arraystep.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from ..blocking import ArrayOrdering, DictToArrayBijection
55
import numpy as np
66
from numpy.random import uniform
7-
from numpy import log, isfinite
87
from enum import IntEnum, unique
98

109
__all__ = ['ArrayStep', 'ArrayStepShared', 'metrop_select', 'SamplerHist',
@@ -90,9 +89,9 @@ class ArrayStep(BlockedStep):
9089
----------
9190
vars : list
9291
List of variables for sampler.
92+
fs: list of logp theano functions
9393
allvars: Boolean (default False)
9494
blocked: Boolean (default True)
95-
fs: logp theano function
9695
"""
9796

9897
def __init__(self, vars, fs, allvars=False, blocked=True):
@@ -114,10 +113,11 @@ def step(self, point):
114113

115114

116115
class ArrayStepShared(BlockedStep):
117-
"""Faster version of ArrayStep that requires the substep method that does not wrap the functions the step method uses.
116+
"""Faster version of ArrayStep that requires the substep method that does not wrap
117+
the functions the step method uses.
118118
119-
Works by setting shared variables before using the step. This eliminates the mapping and unmapping overhead as well
120-
as moving fewer variables around.
119+
Works by setting shared variables before using the step. This eliminates the mapping
120+
and unmapping overhead as well as moving fewer variables around.
121121
"""
122122

123123
def __init__(self, vars, shared, blocked=True):
@@ -144,14 +144,25 @@ def step(self, point):
144144

145145

146146
def metrop_select(mr, q, q0):
147-
# Perform rejection/acceptance step for Metropolis class samplers
147+
"""Perform rejection/acceptance step for Metropolis class samplers.
148148
149+
Returns the new sample q if a uniform random number is less than the
150+
metropolis acceptance rate (`mr`), and the old sample otherwise.
151+
152+
Parameters
153+
----------
154+
mr : float, Metropolis acceptance rate
155+
q : proposed sample
156+
q0 : current sample
157+
158+
Returns
159+
-------
160+
q or q0
161+
"""
149162
# Compare acceptance ratio to uniform random number
150-
if isfinite(mr) and log(uniform()) < mr:
151-
# Accept proposed value
163+
if np.isfinite(mr) and np.log(uniform()) < mr:
152164
return q
153165
else:
154-
# Reject proposed value
155166
return q0
156167

157168

pymc3/step_methods/hmc.py

Lines changed: 0 additions & 127 deletions
This file was deleted.

pymc3/step_methods/hmc/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .hmc import HamiltonianMC
2+
from .nuts import NUTS

pymc3/step_methods/hmc/base_hmc.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from ..arraystep import ArrayStepShared
2+
from .trajectory import get_theano_hamiltonian_functions
3+
4+
from pymc3.tuning import guess_scaling
5+
from pymc3.model import modelcontext, Point
6+
from .quadpotential import quad_potential
7+
from pymc3.theanof import inputvars, make_shared_replacements
8+
9+
10+
class BaseHMC(ArrayStepShared):
11+
default_blocked = True
12+
13+
def __init__(self, vars=None, scaling=None, step_scale=0.25, is_cov=False,
14+
model=None, blocked=True, use_single_leapfrog=False, **theano_kwargs):
15+
"""
16+
Parameters
17+
----------
18+
vars : list of theano variables
19+
scaling : array_like, ndim = {1,2}
20+
Scaling for momentum distribution. 1d arrays interpreted matrix diagonal.
21+
step_scale : float, default=0.25
22+
Size of steps to take, automatically scaled down by 1/n**(1/4)
23+
is_cov : bool, default=False
24+
Treat scaling as a covariance matrix/vector if True, else treat it as a
25+
precision matrix/vector
26+
state
27+
State object
28+
model : pymc3 Model instance. default=Context model
29+
blocked: Boolean, default True
30+
use_single_leapfrog: Boolean, will leapfrog steps take a single step at a time.
31+
default False.
32+
**theano_kwargs: passed to theano functions
33+
"""
34+
model = modelcontext(model)
35+
36+
if vars is None:
37+
vars = model.cont_vars
38+
vars = inputvars(vars)
39+
40+
if scaling is None:
41+
scaling = model.test_point
42+
43+
if isinstance(scaling, dict):
44+
scaling = guess_scaling(Point(scaling, model=model), model=model, vars=vars)
45+
46+
n = scaling.shape[0]
47+
self.step_size = step_scale / (n ** 0.25)
48+
self.potential = quad_potential(scaling, is_cov, as_cov=False)
49+
50+
shared = make_shared_replacements(vars, model)
51+
if theano_kwargs is None:
52+
theano_kwargs = {}
53+
54+
self.H, self.compute_energy, self.leapfrog, self._vars = get_theano_hamiltonian_functions(
55+
vars, shared, model.logpt, self.potential, use_single_leapfrog, **theano_kwargs)
56+
57+
super(BaseHMC, self).__init__(vars, shared, blocked=blocked)

pymc3/step_methods/hmc/hmc.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
'''
2+
Created on Mar 7, 2011
3+
4+
@author: johnsalvatier
5+
'''
6+
from ..arraystep import metrop_select, Competence
7+
from .base_hmc import BaseHMC
8+
from pymc3.vartypes import discrete_types
9+
import numpy as np
10+
11+
12+
__all__ = ['HamiltonianMC']
13+
14+
# TODO:
15+
# add constraint handling via page 37 of Radford's
16+
# http://www.cs.utoronto.ca/~radford/ham-mcmc.abstract.html
17+
18+
19+
def unif(step_size, elow=.85, ehigh=1.15):
20+
return np.random.uniform(elow, ehigh) * step_size
21+
22+
23+
class HamiltonianMC(BaseHMC):
24+
default_blocked = True
25+
26+
def __init__(self, vars=None, path_length=2., step_rand=unif, **kwargs):
27+
"""
28+
Parameters
29+
----------
30+
vars : list of theano variables
31+
path_length : float, default=2
32+
total length to travel
33+
step_rand : function float -> float, default=unif
34+
A function which takes the step size and returns an new one used to
35+
randomize the step size at each iteration.
36+
**kwargs : passed to BaseHMC
37+
"""
38+
super(HamiltonianMC, self).__init__(vars, **kwargs)
39+
self.path_length = path_length
40+
self.step_rand = step_rand
41+
42+
def astep(self, q0):
43+
e = np.array(self.step_rand(self.step_size))
44+
n_steps = np.array(self.path_length / e, dtype='int32')
45+
q = q0
46+
p = self.H.pot.random() # initialize momentum
47+
initial_energy = self.compute_energy(q, p)
48+
q, p, current_energy = self.leapfrog(q, p, e, n_steps)
49+
energy_change = current_energy - initial_energy
50+
return metrop_select(energy_change, q, q0)
51+
52+
@staticmethod
53+
def competence(var):
54+
if var.dtype in discrete_types:
55+
return Competence.INCOMPATIBLE
56+
return Competence.COMPATIBLE

0 commit comments

Comments
 (0)