5
5
import theano
6
6
import pymc3 as pm
7
7
from tqdm import tqdm
8
+ import multiprocessing as mp
8
9
9
10
from .arraystep import metrop_select
10
11
from .metropolis import MultivariateNormalProposal
@@ -43,6 +44,9 @@ class SMC:
43
44
Determines the change of beta from stage to stage, i.e.indirectly the number of stages,
44
45
the higher the value of `threshold` the higher the number of stages. Defaults to 0.5.
45
46
It should be between 0 and 1.
47
+ parallel : bool
48
+ Distribute computations across cores if the number of cores is larger than 1
49
+ (see pm.sample() for details). Defaults to True.
46
50
model : :class:`pymc3.Model`
47
51
Optional model for sampling step. Defaults to None (taken from context).
48
52
@@ -68,6 +72,7 @@ def __init__(
68
72
tune_scaling = True ,
69
73
tune_steps = True ,
70
74
threshold = 0.5 ,
75
+ parallel = True ,
71
76
):
72
77
73
78
self .n_steps = n_steps
@@ -77,9 +82,10 @@ def __init__(
77
82
self .tune_scaling = tune_scaling
78
83
self .tune_steps = tune_steps
79
84
self .threshold = threshold
85
+ self .parallel = parallel
80
86
81
87
82
- def sample_smc (draws = 5000 , step = None , progressbar = False , model = None , random_seed = - 1 ):
88
+ def sample_smc (draws = 5000 , step = None , cores = None , progressbar = False , model = None , random_seed = - 1 ):
83
89
"""
84
90
Sequential Monte Carlo sampling
85
91
@@ -90,6 +96,8 @@ def sample_smc(draws=5000, step=None, progressbar=False, model=None, random_seed
90
96
independent Markov Chains. Defaults to 5000.
91
97
step : :class:`SMC`
92
98
SMC initialization object
99
+ cores : int
100
+ The number of chains to run in parallel.
93
101
progressbar : bool
94
102
Flag for displaying a progress bar
95
103
model : pymc3 Model
@@ -102,9 +110,10 @@ def sample_smc(draws=5000, step=None, progressbar=False, model=None, random_seed
102
110
if random_seed != - 1 :
103
111
np .random .seed (random_seed )
104
112
105
- beta = 0.
113
+ beta = 0.0
106
114
stage = 0
107
- acc_rate = 1.
115
+ accepted = 0
116
+ acc_rate = 1.0
108
117
proposed = draws * step .n_steps
109
118
model .marginal_likelihood = 1
110
119
variables = inputvars (model .vars )
@@ -138,53 +147,95 @@ def sample_smc(draws=5000, step=None, progressbar=False, model=None, random_seed
138
147
if step .tune_scaling :
139
148
step .scaling = _tune (acc_rate )
140
149
if step .tune_steps :
141
- acc_rate = max (1. / proposed , acc_rate )
150
+ acc_rate = max (1.0 / proposed , acc_rate )
142
151
step .n_steps = min (
143
152
step .max_steps , 1 + int (np .log (step .p_acc_rate ) / np .log (1 - acc_rate ))
144
153
)
145
154
146
- pm ._log .info (
147
- "Stage: {:d} Beta: {:f} Steps: {:d}" .format (stage , beta , step .n_steps , acc_rate )
148
- )
155
+ pm ._log .info ("Stage: {:d} Beta: {:.3f} Steps: {:d}" .format (stage , beta , step .n_steps ))
149
156
# Apply Metropolis kernel (mutation)
150
157
proposed = draws * step .n_steps
151
- accepted = 0.
152
158
priors = np .array ([prior_logp (sample ) for sample in posterior ]).squeeze ()
153
- tempered_post = priors + likelihoods * beta
154
- for draw in tqdm (range (draws ), disable = not progressbar ):
155
- old_tempered_post = tempered_post [draw ]
156
- q_old = posterior [draw ]
157
- deltas = np .squeeze (proposal (step .n_steps ) * step .scaling )
158
- for n_step in range (step .n_steps ):
159
- delta = deltas [n_step ]
160
-
161
- if any_discrete :
162
- if all_discrete :
163
- delta = np .round (delta , 0 ).astype ("int64" )
164
- q_old = q_old .astype ("int64" )
165
- q_new = (q_old + delta ).astype ("int64" )
166
- else :
167
- delta [discrete ] = np .round (delta [discrete ], 0 )
168
- q_new = floatX (q_old + delta )
169
- else :
170
- q_new = floatX (q_old + delta )
171
-
172
- new_tempered_post = prior_logp (q_new ) + likelihood_logp (q_new )[0 ] * beta
173
-
174
- q_old , accept = metrop_select (new_tempered_post - old_tempered_post , q_new , q_old )
175
- if accept :
176
- accepted += 1
177
- posterior [draw ] = q_old
178
- old_tempered_post = new_tempered_post
179
-
180
- acc_rate = accepted / proposed
159
+ tempered_logp = priors + likelihoods * beta
160
+ deltas = np .squeeze (proposal (step .n_steps ) * step .scaling )
161
+
162
+ parameters = (
163
+ proposal ,
164
+ step .scaling ,
165
+ accepted ,
166
+ any_discrete ,
167
+ all_discrete ,
168
+ discrete ,
169
+ step .n_steps ,
170
+ prior_logp ,
171
+ likelihood_logp ,
172
+ beta ,
173
+ )
174
+
175
+ if step .parallel and cores > 1 :
176
+ pool = mp .Pool (processes = cores )
177
+ results = pool .starmap (
178
+ _metrop_kernel ,
179
+ [(posterior [draw ], tempered_logp [draw ], * parameters ) for draw in range (draws )],
180
+ )
181
+ else :
182
+ results = [
183
+ _metrop_kernel (posterior [draw ], tempered_logp [draw ], * parameters )
184
+ for draw in tqdm (range (draws ), disable = not progressbar )
185
+ ]
186
+
187
+ posterior , acc_list = zip (* results )
188
+ posterior = np .array (posterior )
189
+ acc_rate = sum (acc_list ) / proposed
181
190
stage += 1
182
191
183
192
trace = _posterior_to_trace (posterior , variables , model , var_info )
184
193
185
194
return trace
186
195
187
196
197
+ def _metrop_kernel (
198
+ q_old ,
199
+ old_tempered_logp ,
200
+ proposal ,
201
+ scaling ,
202
+ accepted ,
203
+ any_discrete ,
204
+ all_discrete ,
205
+ discrete ,
206
+ n_steps ,
207
+ prior_logp ,
208
+ likelihood_logp ,
209
+ beta ,
210
+ ):
211
+ """
212
+ Metropolis kernel
213
+ """
214
+ deltas = np .squeeze (proposal (n_steps ) * scaling )
215
+ for n_step in range (n_steps ):
216
+ delta = deltas [n_step ]
217
+
218
+ if any_discrete :
219
+ if all_discrete :
220
+ delta = np .round (delta , 0 ).astype ("int64" )
221
+ q_old = q_old .astype ("int64" )
222
+ q_new = (q_old + delta ).astype ("int64" )
223
+ else :
224
+ delta [discrete ] = np .round (delta [discrete ], 0 )
225
+ q_new = floatX (q_old + delta )
226
+ else :
227
+ q_new = floatX (q_old + delta )
228
+
229
+ new_tempered_logp = prior_logp (q_new ) + likelihood_logp (q_new )[0 ] * beta
230
+
231
+ q_old , accept = metrop_select (new_tempered_logp - old_tempered_logp , q_new , q_old )
232
+ if accept :
233
+ accepted += 1
234
+ old_tempered_logp = new_tempered_logp
235
+
236
+ return q_old , accepted
237
+
238
+
188
239
def _initial_population (draws , model , variables ):
189
240
"""
190
241
Create an initial population from the prior
0 commit comments