16
16
17
17
import numpy as np
18
18
from scipy .special import logsumexp
19
+ from scipy .stats import multivariate_normal
19
20
from theano import function as theano_function
20
21
import theano .tensor as tt
21
22
@@ -33,7 +34,7 @@ def __init__(
33
34
n_steps = 25 ,
34
35
start = None ,
35
36
tune_steps = True ,
36
- p_acc_rate = 0.99 ,
37
+ p_acc_rate = 0.85 ,
37
38
threshold = 0.5 ,
38
39
save_sim_data = False ,
39
40
model = None ,
@@ -42,7 +43,7 @@ def __init__(
42
43
):
43
44
44
45
self .draws = draws
45
- self .kernel = kernel
46
+ self .kernel = kernel . lower ()
46
47
self .n_steps = n_steps
47
48
self .start = start
48
49
self .tune_steps = tune_steps
@@ -62,10 +63,7 @@ def __init__(
62
63
self .max_steps = n_steps
63
64
self .proposed = draws * n_steps
64
65
self .acc_rate = 1
65
- self .acc_per_chain = np .ones (self .draws )
66
66
self .variables = inputvars (self .model .vars )
67
- self .dimension = sum (v .dsize for v in self .variables )
68
- self .scalings = np .ones (self .draws ) * 2.38 / (self .dimension ) ** 0.5
69
67
self .weights = np .ones (self .draws ) / self .draws
70
68
self .log_marginal_likelihood = 0
71
69
self .sim_data = []
@@ -78,7 +76,9 @@ def initialize_population(self):
78
76
var_info = OrderedDict ()
79
77
if self .start is None :
80
78
init_rnd = sample_prior_predictive (
81
- self .draws , var_names = [v .name for v in self .model .unobserved_RVs ], model = self .model ,
79
+ self .draws ,
80
+ var_names = [v .name for v in self .model .unobserved_RVs ],
81
+ model = self .model ,
82
82
)
83
83
else :
84
84
init_rnd = self .start
@@ -102,7 +102,7 @@ def setup_kernel(self):
102
102
"""
103
103
shared = make_shared_replacements (self .variables , self .model )
104
104
105
- if self .kernel . lower () == "abc" :
105
+ if self .kernel == "abc" :
106
106
factors = [var .logpt for var in self .model .free_RVs ]
107
107
factors += [tt .sum (factor ) for factor in self .model .potentials ]
108
108
self .prior_logp_func = logp_forw ([tt .sum (factors )], self .variables , shared )
@@ -122,7 +122,7 @@ def setup_kernel(self):
122
122
self .draws ,
123
123
self .save_sim_data ,
124
124
)
125
- elif self .kernel . lower () == "metropolis" :
125
+ elif self .kernel == "metropolis" :
126
126
self .prior_logp_func = logp_forw ([self .model .varlogpt ], self .variables , shared )
127
127
self .likelihood_logp_func = logp_forw ([self .model .datalogpt ], self .variables , shared )
128
128
@@ -136,7 +136,7 @@ def initialize_logp(self):
136
136
self .prior_logp = np .array (priors ).squeeze ()
137
137
self .likelihood_logp = np .array (likelihoods ).squeeze ()
138
138
139
- if self .save_sim_data :
139
+ if self .kernel == "abc" and self . save_sim_data :
140
140
self .sim_data = self .likelihood_logp_func .get_data ()
141
141
142
142
def update_weights_beta (self ):
@@ -180,8 +180,6 @@ def resample(self):
180
180
self .prior_logp = self .prior_logp [resampling_indexes ]
181
181
self .likelihood_logp = self .likelihood_logp [resampling_indexes ]
182
182
self .posterior_logp = self .prior_logp + self .likelihood_logp * self .beta
183
- self .acc_per_chain = self .acc_per_chain [resampling_indexes ]
184
- self .scalings = self .scalings [resampling_indexes ]
185
183
if self .save_sim_data :
186
184
self .sim_data = self .sim_data [resampling_indexes ]
187
185
@@ -198,47 +196,48 @@ def update_proposal(self):
198
196
199
197
def tune (self ):
200
198
"""
201
- Tune scaling and n_steps based on the acceptance rate.
199
+ Tune n_steps based on the acceptance rate.
202
200
"""
203
- ave_scaling = np .exp (np .log (self .scalings .mean ()) + (self .acc_per_chain .mean () - 0.234 ))
204
- self .scalings = 0.5 * (
205
- ave_scaling + np .exp (np .log (self .scalings ) + (self .acc_per_chain - 0.234 ))
206
- )
207
-
208
201
if self .tune_steps :
209
202
acc_rate = max (1.0 / self .proposed , self .acc_rate )
210
203
self .n_steps = min (
211
- self .max_steps , max (2 , int (np .log (1 - self .p_acc_rate ) / np .log (1 - acc_rate ))),
204
+ self .max_steps ,
205
+ max (2 , int (np .log (1 - self .p_acc_rate ) / np .log (1 - acc_rate ))),
212
206
)
213
207
214
208
self .proposed = self .draws * self .n_steps
215
209
216
210
def mutate (self ):
217
211
ac_ = np .empty ((self .n_steps , self .draws ))
218
212
219
- proposals = (
220
- np .random .multivariate_normal (
221
- np .zeros (self .dimension ), self .cov , size = (self .n_steps , self .draws )
222
- )
223
- * self .scalings [:, None ]
224
- )
225
213
log_R = np .log (np .random .rand (self .n_steps , self .draws ))
226
214
215
+ # The proposal distribution is a MVNormal, with mean and covariance computed from the previous tempered posterior
216
+ dist = multivariate_normal (self .posterior .mean (axis = 0 ), self .cov )
217
+
227
218
for n_step in range (self .n_steps ):
228
- proposal = floatX (self .posterior + proposals [n_step ])
219
+ # The proposal is independent from the current point.
220
+ # We have to take that into account to compute the Metropolis-Hastings acceptance
221
+ proposal = floatX (dist .rvs (size = self .draws ))
222
+ proposal = proposal .reshape (len (proposal ), - 1 )
223
+ # To do that we compute the logp of moving to a new point
224
+ forward = dist .logpdf (proposal )
225
+ # And to going back from that new point
226
+ backward = multivariate_normal (proposal .mean (axis = 0 ), self .cov ).logpdf (self .posterior )
229
227
ll = np .array ([self .likelihood_logp_func (prop ) for prop in proposal ])
230
228
pl = np .array ([self .prior_logp_func (prop ) for prop in proposal ])
231
229
proposal_logp = pl + ll * self .beta
232
- accepted = log_R [n_step ] < (proposal_logp - self .posterior_logp )
230
+ accepted = log_R [n_step ] < (
231
+ (proposal_logp + backward ) - (self .posterior_logp + forward )
232
+ )
233
233
ac_ [n_step ] = accepted
234
234
self .posterior [accepted ] = proposal [accepted ]
235
235
self .posterior_logp [accepted ] = proposal_logp [accepted ]
236
236
self .prior_logp [accepted ] = pl [accepted ]
237
237
self .likelihood_logp [accepted ] = ll [accepted ]
238
- if self .save_sim_data :
238
+ if self .kernel == "abc" and self . save_sim_data :
239
239
self .sim_data [accepted ] = self .likelihood_logp_func .get_data ()[accepted ]
240
240
241
- self .acc_per_chain = np .mean (ac_ , axis = 0 )
242
241
self .acc_rate = np .mean (ac_ )
243
242
244
243
def posterior_to_trace (self ):
0 commit comments