/
metropolis.py
106 lines (84 loc) · 3.17 KB
/
metropolis.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import numpy as np
def metropolis(func, init, Nburnin=0, Nsamp=1, sampleCov=1, args=(), **kwargs):
"""
Parameters
----------
func: callable
function to sample from
this function should take a vector of parameters as first argument
func() returns a two-element vector, the logPrior and logLike (log base
10), the sum of which is taken to be the log of the density function
(i.e. unnormalized posterior).
(If you don't have this separation, just set func to return one of them
as zero.)
init: sequence
the initial values of which are provided by the real vector thetaInit.
Nburnin: int
burning length
Nsamp: int
number of final samples after burning period
sampleCov: sequence
covariance proposal of the sampling
Returns
-------
lnps: sequence(float)
lnp values of the samples
samples: ``ndim, Nsamp``
samples
"""
# set up empty sample holder
ndim = len(init)
samples = np.zeros((ndim, Nsamp), dtype=float)
lnps = np.zeros(Nsamp, dtype=float)
if (np.ndim(sampleCov) == 0) | ((np.ndim(sampleCov) == 1) & (np.size(sampleCov) == ndim)):
sampleCov = np.eye(ndim) * sampleCov
# initialize state and log-likelihood
state = np.array(init).ravel().copy()
Lp = sum(func(state, *args))
accepts = 0
for i in np.arange(0, Nburnin + Nsamp):
prop = np.random.multivariate_normal(state, sampleCov)
Lp_prop = sum(func(prop, *args))
rand = np.random.rand()
if np.log(rand) < (Lp_prop - Lp):
accepts += 1
state = prop.ravel().copy()
Lp = Lp_prop
# store only after burning period
if i >= Nburnin:
lnps[i - Nburnin] = Lp
samples[:, i - Nburnin] = state.copy().ravel()
print('''Metropolis sampler:
burning: {0:d},
sampling: {1:d}
acceptance ratio: {2:0.2f}
initial guess: {5}
best lnp: {3:0.4g}
best params: {4}'''.format(Nburnin, Nsamp,
accepts / (Nburnin + Nsamp),
lnps.max(),
samples[:, lnps.argmax()], init))
return lnps, samples
def test_metropolis(Nsamp=int(1e4), Nburnin=0, sampleSig=5, thetaInit=-5):
from scipy import stats
import pylab as plt
def testfunc(theta):
return (stats.cauchy(-10, 2).pdf(theta)
+ 4 * stats.cauchy(10, 4).pdf(theta))
def lnp(theta):
""" interface to metrop """
return (0, np.log10(testfunc(theta)))
x = np.linspace(-50, 50, 1e4)
y = testfunc(x)
# compute normalization function: used later to put on same as histogram
Zfunc = np.sum(y * x.ptp() / len(x))
plt.plot(x, y / Zfunc, 'k-', lw=2)
plt.xlabel('theta')
plt.ylabel('f')
lnps, samples = metropolis(lnp, [thetaInit], Nburnin=Nburnin, Nsamp=Nsamp,
sampleCov=sampleSig ** 2)
n, b = np.histogram(samples.ravel(), bins=np.arange(-50, 50, 1),
normed=False)
Zhist = (n * np.diff(b)).sum()
plt.step(b[1:], n.astype(float) / Zhist, color='r')
plt.title('test metropolis')