CUQIpy 1.1.1.post0.dev36__py3-none-any.whl → 1.4.1.post0.dev124__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of CUQIpy might be problematic. Click here for more details.
- cuqi/__init__.py +2 -0
- cuqi/_version.py +3 -3
- cuqi/algebra/__init__.py +2 -0
- cuqi/algebra/_abstract_syntax_tree.py +358 -0
- cuqi/algebra/_ordered_set.py +82 -0
- cuqi/algebra/_random_variable.py +457 -0
- cuqi/array/_array.py +4 -13
- cuqi/config.py +7 -0
- cuqi/density/_density.py +9 -1
- cuqi/distribution/__init__.py +3 -2
- cuqi/distribution/_beta.py +7 -11
- cuqi/distribution/_cauchy.py +2 -2
- cuqi/distribution/_custom.py +0 -6
- cuqi/distribution/_distribution.py +31 -45
- cuqi/distribution/_gamma.py +7 -3
- cuqi/distribution/_gaussian.py +2 -12
- cuqi/distribution/_inverse_gamma.py +4 -10
- cuqi/distribution/_joint_distribution.py +112 -15
- cuqi/distribution/_lognormal.py +0 -7
- cuqi/distribution/{_modifiedhalfnormal.py → _modified_half_normal.py} +23 -23
- cuqi/distribution/_normal.py +34 -7
- cuqi/distribution/_posterior.py +9 -0
- cuqi/distribution/_truncated_normal.py +129 -0
- cuqi/distribution/_uniform.py +47 -1
- cuqi/experimental/__init__.py +2 -2
- cuqi/experimental/_recommender.py +216 -0
- cuqi/geometry/__init__.py +2 -0
- cuqi/geometry/_geometry.py +15 -1
- cuqi/geometry/_product_geometry.py +181 -0
- cuqi/implicitprior/__init__.py +5 -3
- cuqi/implicitprior/_regularized_gaussian.py +483 -0
- cuqi/implicitprior/{_regularizedGMRF.py → _regularized_gmrf.py} +4 -2
- cuqi/implicitprior/{_regularizedUnboundedUniform.py → _regularized_unbounded_uniform.py} +3 -2
- cuqi/implicitprior/_restorator.py +269 -0
- cuqi/legacy/__init__.py +2 -0
- cuqi/{experimental/mcmc → legacy/sampler}/__init__.py +7 -11
- cuqi/legacy/sampler/_conjugate.py +55 -0
- cuqi/legacy/sampler/_conjugate_approx.py +52 -0
- cuqi/legacy/sampler/_cwmh.py +196 -0
- cuqi/legacy/sampler/_gibbs.py +231 -0
- cuqi/legacy/sampler/_hmc.py +335 -0
- cuqi/{experimental/mcmc → legacy/sampler}/_langevin_algorithm.py +82 -111
- cuqi/legacy/sampler/_laplace_approximation.py +184 -0
- cuqi/legacy/sampler/_mh.py +190 -0
- cuqi/legacy/sampler/_pcn.py +244 -0
- cuqi/{experimental/mcmc → legacy/sampler}/_rto.py +132 -90
- cuqi/legacy/sampler/_sampler.py +182 -0
- cuqi/likelihood/_likelihood.py +9 -1
- cuqi/model/__init__.py +1 -1
- cuqi/model/_model.py +1361 -359
- cuqi/pde/__init__.py +4 -0
- cuqi/pde/_observation_map.py +36 -0
- cuqi/pde/_pde.py +134 -33
- cuqi/problem/_problem.py +93 -87
- cuqi/sampler/__init__.py +120 -8
- cuqi/sampler/_conjugate.py +376 -35
- cuqi/sampler/_conjugate_approx.py +40 -16
- cuqi/sampler/_cwmh.py +132 -138
- cuqi/{experimental/mcmc → sampler}/_direct.py +1 -1
- cuqi/sampler/_gibbs.py +288 -130
- cuqi/sampler/_hmc.py +328 -201
- cuqi/sampler/_langevin_algorithm.py +284 -100
- cuqi/sampler/_laplace_approximation.py +87 -117
- cuqi/sampler/_mh.py +47 -157
- cuqi/sampler/_pcn.py +65 -213
- cuqi/sampler/_rto.py +211 -142
- cuqi/sampler/_sampler.py +553 -136
- cuqi/samples/__init__.py +1 -1
- cuqi/samples/_samples.py +24 -18
- cuqi/solver/__init__.py +6 -4
- cuqi/solver/_solver.py +230 -26
- cuqi/testproblem/_testproblem.py +2 -3
- cuqi/utilities/__init__.py +6 -1
- cuqi/utilities/_get_python_variable_name.py +2 -2
- cuqi/utilities/_utilities.py +182 -2
- {CUQIpy-1.1.1.post0.dev36.dist-info → cuqipy-1.4.1.post0.dev124.dist-info}/METADATA +10 -6
- cuqipy-1.4.1.post0.dev124.dist-info/RECORD +101 -0
- {CUQIpy-1.1.1.post0.dev36.dist-info → cuqipy-1.4.1.post0.dev124.dist-info}/WHEEL +1 -1
- CUQIpy-1.1.1.post0.dev36.dist-info/RECORD +0 -92
- cuqi/experimental/mcmc/_conjugate.py +0 -197
- cuqi/experimental/mcmc/_conjugate_approx.py +0 -81
- cuqi/experimental/mcmc/_cwmh.py +0 -191
- cuqi/experimental/mcmc/_gibbs.py +0 -268
- cuqi/experimental/mcmc/_hmc.py +0 -470
- cuqi/experimental/mcmc/_laplace_approximation.py +0 -156
- cuqi/experimental/mcmc/_mh.py +0 -78
- cuqi/experimental/mcmc/_pcn.py +0 -89
- cuqi/experimental/mcmc/_sampler.py +0 -561
- cuqi/experimental/mcmc/_utilities.py +0 -17
- cuqi/implicitprior/_regularizedGaussian.py +0 -323
- {CUQIpy-1.1.1.post0.dev36.dist-info → cuqipy-1.4.1.post0.dev124.dist-info/licenses}/LICENSE +0 -0
- {CUQIpy-1.1.1.post0.dev36.dist-info → cuqipy-1.4.1.post0.dev124.dist-info}/top_level.txt +0 -0
cuqi/sampler/_hmc.py
CHANGED
|
@@ -1,42 +1,46 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
+
import numpy as np
|
|
2
3
|
from cuqi.sampler import Sampler
|
|
4
|
+
from cuqi.array import CUQIarray
|
|
5
|
+
from numbers import Number
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
# another implementation is in https://github.com/mfouesneau/NUTS
|
|
6
7
|
class NUTS(Sampler):
|
|
7
8
|
"""No-U-Turn Sampler (Hoffman and Gelman, 2014).
|
|
8
9
|
|
|
9
|
-
Samples a distribution given its logpdf and gradient using a Hamiltonian
|
|
10
|
+
Samples a distribution given its logpdf and gradient using a Hamiltonian
|
|
11
|
+
Monte Carlo (HMC) algorithm with automatic parameter tuning.
|
|
10
12
|
|
|
11
|
-
For more details see: See Hoffman, M. D., & Gelman, A. (2014). The no-U-turn
|
|
13
|
+
For more details see: See Hoffman, M. D., & Gelman, A. (2014). The no-U-turn
|
|
14
|
+
sampler: Adaptively setting path lengths in Hamiltonian Monte Carlo. Journal
|
|
15
|
+
of Machine Learning Research, 15, 1593-1623.
|
|
12
16
|
|
|
13
17
|
Parameters
|
|
14
18
|
----------
|
|
15
|
-
|
|
16
19
|
target : `cuqi.distribution.Distribution`
|
|
17
|
-
The target distribution to sample. Must have logpdf and gradient method.
|
|
20
|
+
The target distribution to sample. Must have logpdf and gradient method.
|
|
21
|
+
Custom logpdfs and gradients are supported by using a
|
|
22
|
+
:class:`cuqi.distribution.UserDefinedDistribution`.
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
Initial parameters. *Optional
|
|
24
|
+
initial_point : ndarray
|
|
25
|
+
Initial parameters. *Optional*. If not provided, the initial point is
|
|
26
|
+
an array of ones.
|
|
21
27
|
|
|
22
28
|
max_depth : int
|
|
23
|
-
Maximum depth of the tree.
|
|
29
|
+
Maximum depth of the tree >=0 and the default is 15.
|
|
24
30
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
If
|
|
28
|
-
If False, the step size is fixed to the initially estimated value.
|
|
29
|
-
If set to a scalar, the step size will be given by user and not adapted.
|
|
31
|
+
step_size : None or float
|
|
32
|
+
If step_size is provided (as positive float), it will be used as initial
|
|
33
|
+
step size. If None, the step size will be estimated by the sampler.
|
|
30
34
|
|
|
31
35
|
opt_acc_rate : float
|
|
32
36
|
The optimal acceptance rate to reach if using adaptive step size.
|
|
33
|
-
Suggested values are 0.6 (default) or 0.8 (as in stan).
|
|
37
|
+
Suggested values are 0.6 (default) or 0.8 (as in stan). In principle,
|
|
38
|
+
opt_acc_rate should be in (0, 1), however, choosing a value that is very
|
|
39
|
+
close to 1 or 0 might lead to poor performance of the sampler.
|
|
34
40
|
|
|
35
|
-
callback : callable,
|
|
36
|
-
|
|
37
|
-
The
|
|
38
|
-
where `sample` is the current sample and `sample_index` is the index of the sample.
|
|
39
|
-
An example is shown in demos/demo31_callback.py.
|
|
41
|
+
callback : callable, optional
|
|
42
|
+
A function that will be called after each sampling step. It can be useful for monitoring the sampler during sampling.
|
|
43
|
+
The function should take three arguments: the sampler object, the index of the current sampling step, the total number of requested samples. The last two arguments are integers. An example of the callback function signature is: `callback(sampler, sample_index, num_of_samples)`.
|
|
40
44
|
|
|
41
45
|
Example
|
|
42
46
|
-------
|
|
@@ -53,7 +57,11 @@ class NUTS(Sampler):
|
|
|
53
57
|
sampler = cuqi.sampler.NUTS(target)
|
|
54
58
|
|
|
55
59
|
# Sample
|
|
56
|
-
|
|
60
|
+
sampler.warmup(5000)
|
|
61
|
+
sampler.sample(10000)
|
|
62
|
+
|
|
63
|
+
# Get samples
|
|
64
|
+
samples = sampler.get_samples()
|
|
57
65
|
|
|
58
66
|
# Plot samples
|
|
59
67
|
samples.plot_pair()
|
|
@@ -70,170 +78,234 @@ class NUTS(Sampler):
|
|
|
70
78
|
sampler.epsilon_list
|
|
71
79
|
|
|
72
80
|
# Suggested step size during adaptation (the value of this step size is
|
|
73
|
-
# only used after adaptation).
|
|
74
|
-
# adaptation is not requested.
|
|
81
|
+
# only used after adaptation).
|
|
75
82
|
sampler.epsilon_bar_list
|
|
76
83
|
|
|
77
|
-
# Additionally, iterations' number can be accessed via
|
|
78
|
-
sampler.iteration_list
|
|
79
|
-
|
|
80
84
|
"""
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
|
|
86
|
+
_STATE_KEYS = Sampler._STATE_KEYS.union({'_epsilon', '_epsilon_bar',
|
|
87
|
+
'_H_bar',
|
|
88
|
+
'current_target_logd',
|
|
89
|
+
'current_target_grad',
|
|
90
|
+
'max_depth'})
|
|
91
|
+
|
|
92
|
+
_HISTORY_KEYS = Sampler._HISTORY_KEYS.union({'num_tree_node_list',
|
|
93
|
+
'epsilon_list',
|
|
94
|
+
'epsilon_bar_list'})
|
|
95
|
+
|
|
96
|
+
def __init__(self, target=None, initial_point=None, max_depth=None,
|
|
97
|
+
step_size=None, opt_acc_rate=0.6, **kwargs):
|
|
98
|
+
super().__init__(target, initial_point=initial_point, **kwargs)
|
|
99
|
+
|
|
100
|
+
# Assign parameters as attributes
|
|
83
101
|
self.max_depth = max_depth
|
|
84
|
-
self.
|
|
102
|
+
self.step_size = step_size
|
|
85
103
|
self.opt_acc_rate = opt_acc_rate
|
|
86
|
-
# if this flag is True, the samples and the burn-in will be returned
|
|
87
|
-
# otherwise, the burn-in will be truncated
|
|
88
|
-
self._return_burnin = False
|
|
89
104
|
|
|
90
|
-
|
|
105
|
+
|
|
106
|
+
def _initialize(self):
|
|
107
|
+
|
|
108
|
+
self._current_alpha_ratio = np.nan # Current alpha ratio will be set to some
|
|
109
|
+
# value (other than np.nan) before
|
|
110
|
+
# being used
|
|
111
|
+
|
|
112
|
+
self.current_target_logd, self.current_target_grad = self._nuts_target(self.current_point)
|
|
113
|
+
|
|
114
|
+
# Parameters dual averaging
|
|
115
|
+
# Initialize epsilon and epsilon_bar
|
|
116
|
+
# epsilon is the step size used in the current iteration
|
|
117
|
+
# after warm up and one sampling step, epsilon is updated
|
|
118
|
+
# to epsilon_bar for the remaining sampling steps.
|
|
119
|
+
if self.step_size is None:
|
|
120
|
+
self._epsilon = self._FindGoodEpsilon()
|
|
121
|
+
self.step_size = self._epsilon
|
|
122
|
+
else:
|
|
123
|
+
self._epsilon = self.step_size
|
|
124
|
+
|
|
125
|
+
self._epsilon_bar = "unset"
|
|
126
|
+
|
|
127
|
+
# Parameter mu, does not change during the run
|
|
128
|
+
self._mu = np.log(10*self._epsilon)
|
|
129
|
+
|
|
130
|
+
self._H_bar = 0
|
|
131
|
+
|
|
132
|
+
# NUTS run diagnostics
|
|
91
133
|
# number of tree nodes created each NUTS iteration
|
|
92
134
|
self._num_tree_node = 0
|
|
135
|
+
|
|
93
136
|
# Create lists to store NUTS run diagnostics
|
|
94
137
|
self._create_run_diagnostic_attributes()
|
|
95
138
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
139
|
+
#=========================================================================
|
|
140
|
+
#============================== Properties ===============================
|
|
141
|
+
#=========================================================================
|
|
142
|
+
@property
|
|
143
|
+
def max_depth(self):
|
|
144
|
+
return self._max_depth
|
|
145
|
+
|
|
146
|
+
@max_depth.setter
|
|
147
|
+
def max_depth(self, value):
|
|
148
|
+
if value is None:
|
|
149
|
+
value = 15 # default value
|
|
150
|
+
if not isinstance(value, int):
|
|
151
|
+
raise TypeError('max_depth must be an integer.')
|
|
152
|
+
if value < 0:
|
|
153
|
+
raise ValueError('max_depth must be >= 0.')
|
|
154
|
+
self._max_depth = value
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def step_size(self):
|
|
158
|
+
return self._step_size
|
|
159
|
+
|
|
160
|
+
@step_size.setter
|
|
161
|
+
def step_size(self, value):
|
|
162
|
+
if value is None:
|
|
163
|
+
pass # NUTS will adapt the step size
|
|
164
|
+
|
|
165
|
+
# step_size must be a positive float, raise error otherwise
|
|
166
|
+
elif isinstance(value, bool)\
|
|
167
|
+
or not isinstance(value, Number)\
|
|
168
|
+
or value <= 0:
|
|
169
|
+
raise TypeError('step_size must be a positive float or None.')
|
|
170
|
+
self._step_size = value
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def opt_acc_rate(self):
|
|
174
|
+
return self._opt_acc_rate
|
|
175
|
+
|
|
176
|
+
@opt_acc_rate.setter
|
|
177
|
+
def opt_acc_rate(self, value):
|
|
178
|
+
if not isinstance(value, Number) or value <= 0 or value >= 1:
|
|
179
|
+
raise ValueError('opt_acc_rate must be a float in (0, 1).')
|
|
180
|
+
self._opt_acc_rate = value
|
|
99
181
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
#
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
182
|
+
#=========================================================================
|
|
183
|
+
#================== Implement methods required by Sampler =============
|
|
184
|
+
#=========================================================================
|
|
185
|
+
def validate_target(self):
|
|
186
|
+
# Check if the target has logd and gradient methods
|
|
187
|
+
try:
|
|
188
|
+
current_target_logd, current_target_grad =\
|
|
189
|
+
self._nuts_target(np.ones(self.dim))
|
|
190
|
+
except:
|
|
191
|
+
raise ValueError('Target must have logd and gradient methods.')
|
|
192
|
+
|
|
193
|
+
def reinitialize(self):
|
|
194
|
+
# Call the parent reset method
|
|
195
|
+
super().reinitialize()
|
|
196
|
+
# Reset NUTS run diagnostic attributes
|
|
197
|
+
self._reset_run_diagnostic_attributes()
|
|
112
198
|
|
|
113
|
-
def
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
self.iteration_list.append(k)
|
|
117
|
-
# Store the number of tree nodes created in iteration k
|
|
118
|
-
self.num_tree_node_list.append(n_tree)
|
|
119
|
-
# Store the step size used in iteration k
|
|
120
|
-
self.epsilon_list.append(eps)
|
|
121
|
-
# Store the step size suggestion during adaptation in iteration k
|
|
122
|
-
self.epsilon_bar_list.append(eps_bar)
|
|
199
|
+
def step(self):
|
|
200
|
+
if isinstance(self._epsilon_bar, str) and self._epsilon_bar == "unset":
|
|
201
|
+
self._epsilon_bar = self._epsilon
|
|
123
202
|
|
|
124
|
-
|
|
125
|
-
|
|
203
|
+
# Convert current_point, logd, and grad to numpy arrays
|
|
204
|
+
# if they are CUQIarray objects
|
|
205
|
+
if isinstance(self.current_point, CUQIarray):
|
|
206
|
+
self.current_point = self.current_point.to_numpy()
|
|
207
|
+
if isinstance(self.current_target_logd, CUQIarray):
|
|
208
|
+
self.current_target_logd = self.current_target_logd.to_numpy()
|
|
209
|
+
if isinstance(self.current_target_grad, CUQIarray):
|
|
210
|
+
self.current_target_grad = self.current_target_grad.to_numpy()
|
|
126
211
|
|
|
127
|
-
|
|
128
|
-
|
|
212
|
+
# reset number of tree nodes for each iteration
|
|
213
|
+
self._num_tree_node = 0
|
|
129
214
|
|
|
130
|
-
|
|
131
|
-
#
|
|
132
|
-
self.
|
|
215
|
+
# copy current point, logd, and grad in local variables
|
|
216
|
+
point_k = self.current_point # initial position (parameters)
|
|
217
|
+
logd_k = self.current_target_logd
|
|
218
|
+
grad_k = self.current_target_grad # initial gradient
|
|
133
219
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
# Step size variables
|
|
148
|
-
epsilon, epsilon_bar = None, None
|
|
149
|
-
|
|
150
|
-
# parameters dual averaging
|
|
151
|
-
if (self.adapt_step_size == True):
|
|
152
|
-
epsilon = self._FindGoodEpsilon(theta[:, 0], joint_eval[0], grad)
|
|
153
|
-
mu = np.log(10*epsilon)
|
|
154
|
-
gamma, t_0, kappa = 0.05, 10, 0.75 # kappa in (0.5, 1]
|
|
155
|
-
epsilon_bar, H_bar = 1, 0
|
|
156
|
-
delta = self.opt_acc_rate # https://mc-stan.org/docs/2_18/reference-manual/hmc-algorithm-parameters.html
|
|
157
|
-
step_sizes[0] = epsilon
|
|
158
|
-
elif (self.adapt_step_size == False):
|
|
159
|
-
epsilon = self._FindGoodEpsilon(theta[:, 0], joint_eval[0], grad)
|
|
160
|
-
else:
|
|
161
|
-
epsilon = self.adapt_step_size # if scalar then user specifies the step size
|
|
220
|
+
# compute r_k and Hamiltonian
|
|
221
|
+
r_k = self._Kfun(1, 'sample') # resample momentum vector
|
|
222
|
+
Ham = logd_k - self._Kfun(r_k, 'eval') # Hamiltonian
|
|
223
|
+
|
|
224
|
+
# slice variable
|
|
225
|
+
log_u = Ham - np.random.exponential(1, size=1)
|
|
226
|
+
|
|
227
|
+
# initialization
|
|
228
|
+
j, s, n = 0, 1, 1
|
|
229
|
+
point_minus, point_plus = point_k.copy(), point_k.copy()
|
|
230
|
+
grad_minus, grad_plus = grad_k.copy(), grad_k.copy()
|
|
231
|
+
r_minus, r_plus = r_k.copy(), r_k.copy()
|
|
162
232
|
|
|
163
233
|
# run NUTS
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
234
|
+
acc = 0
|
|
235
|
+
while (s == 1) and (j <= self.max_depth):
|
|
236
|
+
# sample a direction
|
|
237
|
+
v = int(2*(np.random.rand() < 0.5)-1)
|
|
238
|
+
|
|
239
|
+
# build tree: doubling procedure
|
|
240
|
+
if (v == -1):
|
|
241
|
+
point_minus, r_minus, grad_minus, _, _, _, \
|
|
242
|
+
point_prime, logd_prime, grad_prime,\
|
|
243
|
+
n_prime, s_prime, alpha, n_alpha = \
|
|
244
|
+
self._BuildTree(point_minus, r_minus, grad_minus,
|
|
245
|
+
Ham, log_u, v, j, self._epsilon)
|
|
246
|
+
else:
|
|
247
|
+
_, _, _, point_plus, r_plus, grad_plus, \
|
|
248
|
+
point_prime, logd_prime, grad_prime,\
|
|
249
|
+
n_prime, s_prime, alpha, n_alpha = \
|
|
250
|
+
self._BuildTree(point_plus, r_plus, grad_plus,
|
|
251
|
+
Ham, log_u, v, j, self._epsilon)
|
|
252
|
+
|
|
253
|
+
# Metropolis step
|
|
254
|
+
alpha2 = min(1, (n_prime/n)) #min(0, np.log(n_p) - np.log(n))
|
|
255
|
+
if (s_prime == 1) and \
|
|
256
|
+
(np.random.rand() <= alpha2) and \
|
|
257
|
+
(not np.isnan(logd_prime)) and \
|
|
258
|
+
(not np.isinf(logd_prime)):
|
|
259
|
+
self.current_point = point_prime.copy()
|
|
260
|
+
# copy if array, else assign if scalar
|
|
261
|
+
self.current_target_logd = (
|
|
262
|
+
logd_prime.copy()
|
|
263
|
+
if isinstance(logd_prime, np.ndarray)
|
|
264
|
+
else logd_prime
|
|
265
|
+
)
|
|
266
|
+
self.current_target_grad = grad_prime.copy()
|
|
267
|
+
acc = 1
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
# update number of particles, tree level, and stopping criterion
|
|
271
|
+
n += n_prime
|
|
272
|
+
dpoints = point_plus - point_minus
|
|
273
|
+
s = s_prime *\
|
|
274
|
+
int((dpoints @ r_minus.T) >= 0) * int((dpoints @ r_plus.T) >= 0)
|
|
275
|
+
j += 1
|
|
276
|
+
self._current_alpha_ratio = alpha/n_alpha
|
|
277
|
+
|
|
278
|
+
# update run diagnostic attributes
|
|
279
|
+
self._update_run_diagnostic_attributes(
|
|
280
|
+
self._num_tree_node, self._epsilon, self._epsilon_bar)
|
|
281
|
+
|
|
282
|
+
self._epsilon = self._epsilon_bar
|
|
283
|
+
if np.isnan(self.current_target_logd):
|
|
284
|
+
raise NameError('NaN potential func')
|
|
196
285
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
self.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
epsilon_bar = np.exp(eta*np.log(epsilon) + (1-eta)*np.log(epsilon_bar))
|
|
221
|
-
elif (k == Nb+1) and (self.adapt_step_size == True):
|
|
222
|
-
epsilon = epsilon_bar # fix epsilon after burn-in
|
|
223
|
-
step_sizes[k] = epsilon
|
|
224
|
-
|
|
225
|
-
# msg
|
|
226
|
-
self._print_progress(k+1, Ns) #k+1 is the sample number, k is index assuming x0 is the first sample
|
|
227
|
-
self._call_callback(theta[:, k], k)
|
|
228
|
-
|
|
229
|
-
if np.isnan(joint_eval[k]):
|
|
230
|
-
raise NameError('NaN potential func')
|
|
231
|
-
|
|
232
|
-
# apply burn-in
|
|
233
|
-
if not self._return_burnin:
|
|
234
|
-
theta = theta[:, Nb:]
|
|
235
|
-
joint_eval = joint_eval[Nb:]
|
|
236
|
-
return theta, joint_eval, step_sizes
|
|
286
|
+
return acc
|
|
287
|
+
|
|
288
|
+
def tune(self, skip_len, update_count):
|
|
289
|
+
""" adapt epsilon during burn-in using dual averaging"""
|
|
290
|
+
if isinstance(self._epsilon_bar, str) and self._epsilon_bar == "unset":
|
|
291
|
+
self._epsilon_bar = 1
|
|
292
|
+
|
|
293
|
+
k = update_count+1
|
|
294
|
+
|
|
295
|
+
# Fixed parameters that do not change during the run
|
|
296
|
+
gamma, t_0, kappa = 0.05, 10, 0.75 # kappa in (0.5, 1]
|
|
297
|
+
|
|
298
|
+
eta1 = 1/(k + t_0)
|
|
299
|
+
self._H_bar = (1-eta1)*self._H_bar +\
|
|
300
|
+
eta1*(self.opt_acc_rate - (self._current_alpha_ratio))
|
|
301
|
+
self._epsilon = np.exp(self._mu - (np.sqrt(k)/gamma)*self._H_bar)
|
|
302
|
+
eta = k**(-kappa)
|
|
303
|
+
self._epsilon_bar =\
|
|
304
|
+
np.exp(eta*np.log(self._epsilon) +(1-eta)*np.log(self._epsilon_bar))
|
|
305
|
+
|
|
306
|
+
#=========================================================================
|
|
307
|
+
def _nuts_target(self, x): # returns logposterior tuple evaluation-gradient
|
|
308
|
+
return self.target.logd(x), self.target.gradient(x)
|
|
237
309
|
|
|
238
310
|
#=========================================================================
|
|
239
311
|
# auxiliary standard Gaussian PDF: kinetic energy function
|
|
@@ -245,48 +317,61 @@ class NUTS(Sampler):
|
|
|
245
317
|
return np.random.standard_normal(size=self.dim)
|
|
246
318
|
|
|
247
319
|
#=========================================================================
|
|
248
|
-
def _FindGoodEpsilon(self,
|
|
320
|
+
def _FindGoodEpsilon(self, epsilon=1):
|
|
321
|
+
point_k = self.current_point
|
|
322
|
+
self.current_target_logd, self.current_target_grad = self._nuts_target(
|
|
323
|
+
point_k)
|
|
324
|
+
logd = self.current_target_logd
|
|
325
|
+
grad = self.current_target_grad
|
|
326
|
+
|
|
249
327
|
r = self._Kfun(1, 'sample') # resample a momentum
|
|
250
|
-
Ham =
|
|
251
|
-
_, r_prime,
|
|
328
|
+
Ham = logd - self._Kfun(r, 'eval') # initial Hamiltonian
|
|
329
|
+
_, r_prime, logd_prime, grad_prime = self._Leapfrog(
|
|
330
|
+
point_k, r, grad, epsilon)
|
|
252
331
|
|
|
253
|
-
# trick to make sure the step is not huge, leading to infinite values of
|
|
332
|
+
# trick to make sure the step is not huge, leading to infinite values of
|
|
333
|
+
# the likelihood
|
|
254
334
|
k = 1
|
|
255
|
-
while np.isinf(
|
|
335
|
+
while np.isinf(logd_prime) or np.isinf(grad_prime).any():
|
|
256
336
|
k *= 0.5
|
|
257
|
-
_, r_prime,
|
|
337
|
+
_, r_prime, logd_prime, grad_prime = self._Leapfrog(
|
|
338
|
+
point_k, r, grad, epsilon*k)
|
|
258
339
|
epsilon = 0.5*k*epsilon
|
|
259
340
|
|
|
260
|
-
# doubles/halves the value of epsilon until the accprob of the Langevin
|
|
261
|
-
|
|
341
|
+
# doubles/halves the value of epsilon until the accprob of the Langevin
|
|
342
|
+
# proposal crosses 0.5
|
|
343
|
+
Ham_prime = logd_prime - self._Kfun(r_prime, 'eval')
|
|
262
344
|
log_ratio = Ham_prime - Ham
|
|
263
345
|
a = 1 if log_ratio > np.log(0.5) else -1
|
|
264
346
|
while (a*log_ratio > -a*np.log(2)):
|
|
265
347
|
epsilon = (2**a)*epsilon
|
|
266
|
-
_, r_prime,
|
|
267
|
-
|
|
348
|
+
_, r_prime, logd_prime, _ = self._Leapfrog(
|
|
349
|
+
point_k, r, grad, epsilon)
|
|
350
|
+
Ham_prime = logd_prime - self._Kfun(r_prime, 'eval')
|
|
268
351
|
log_ratio = Ham_prime - Ham
|
|
269
352
|
return epsilon
|
|
270
353
|
|
|
271
354
|
#=========================================================================
|
|
272
|
-
def _Leapfrog(self,
|
|
355
|
+
def _Leapfrog(self, point_old, r_old, grad_old, epsilon):
|
|
273
356
|
# symplectic integrator: trajectories preserve phase space volumen
|
|
274
357
|
r_new = r_old + 0.5*epsilon*grad_old # half-step
|
|
275
|
-
|
|
276
|
-
|
|
358
|
+
point_new = point_old + epsilon*r_new # full-step
|
|
359
|
+
logd_new, grad_new = self._nuts_target(point_new) # new gradient
|
|
277
360
|
r_new += 0.5*epsilon*grad_new # half-step
|
|
278
|
-
return
|
|
361
|
+
return point_new, r_new, logd_new, grad_new
|
|
279
362
|
|
|
280
363
|
#=========================================================================
|
|
281
|
-
|
|
282
|
-
|
|
364
|
+
def _BuildTree(
|
|
365
|
+
self, point_k, r, grad, Ham, log_u, v, j, epsilon, Delta_max=1000):
|
|
283
366
|
# Increment the number of tree nodes counter
|
|
284
367
|
self._num_tree_node += 1
|
|
285
368
|
|
|
286
369
|
if (j == 0): # base case
|
|
287
370
|
# single leapfrog step in the direction v
|
|
288
|
-
|
|
289
|
-
|
|
371
|
+
point_prime, r_prime, logd_prime, grad_prime = self._Leapfrog(
|
|
372
|
+
point_k, r, grad, v*epsilon)
|
|
373
|
+
Ham_prime = logd_prime - self._Kfun(r_prime, 'eval') # Hamiltonian
|
|
374
|
+
# eval
|
|
290
375
|
n_prime = int(log_u <= Ham_prime) # if particle is in the slice
|
|
291
376
|
s_prime = int(log_u < Delta_max + Ham_prime) # check U-turn
|
|
292
377
|
#
|
|
@@ -299,37 +384,79 @@ class NUTS(Sampler):
|
|
|
299
384
|
alpha_prime = 1 if diff_Ham > 0 else np.exp(diff_Ham)
|
|
300
385
|
n_alpha_prime = 1
|
|
301
386
|
#
|
|
302
|
-
|
|
387
|
+
point_minus, point_plus = point_prime, point_prime
|
|
303
388
|
r_minus, r_plus = r_prime, r_prime
|
|
304
389
|
grad_minus, grad_plus = grad_prime, grad_prime
|
|
305
390
|
else:
|
|
306
391
|
# recursion: build the left/right subtrees
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
392
|
+
point_minus, r_minus, grad_minus, point_plus, r_plus, grad_plus, \
|
|
393
|
+
point_prime, logd_prime, grad_prime,\
|
|
394
|
+
n_prime, s_prime, alpha_prime, n_alpha_prime = \
|
|
395
|
+
self._BuildTree(point_k, r, grad,
|
|
396
|
+
Ham, log_u, v, j-1, epsilon)
|
|
397
|
+
if (s_prime == 1): # do only if the stopping criteria does not
|
|
398
|
+
# verify at the first subtree
|
|
311
399
|
if (v == -1):
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
400
|
+
point_minus, r_minus, grad_minus, _, _, _, \
|
|
401
|
+
point_2prime, logd_2prime, grad_2prime,\
|
|
402
|
+
n_2prime, s_2prime, alpha_2prime, n_alpha_2prime = \
|
|
403
|
+
self._BuildTree(point_minus, r_minus, grad_minus,
|
|
404
|
+
Ham, log_u, v, j-1, epsilon)
|
|
315
405
|
else:
|
|
316
|
-
_, _, _,
|
|
317
|
-
|
|
318
|
-
|
|
406
|
+
_, _, _, point_plus, r_plus, grad_plus, \
|
|
407
|
+
point_2prime, logd_2prime, grad_2prime,\
|
|
408
|
+
n_2prime, s_2prime, alpha_2prime, n_alpha_2prime = \
|
|
409
|
+
self._BuildTree(point_plus, r_plus, grad_plus,
|
|
410
|
+
Ham, log_u, v, j-1, epsilon)
|
|
319
411
|
|
|
320
412
|
# Metropolis step
|
|
321
413
|
alpha2 = n_2prime / max(1, (n_prime + n_2prime))
|
|
322
414
|
if (np.random.rand() <= alpha2):
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
415
|
+
point_prime = point_2prime.copy()
|
|
416
|
+
# copy if array, else assign if scalar
|
|
417
|
+
logd_prime = (
|
|
418
|
+
logd_2prime.copy()
|
|
419
|
+
if isinstance(logd_2prime, np.ndarray)
|
|
420
|
+
else logd_2prime
|
|
421
|
+
)
|
|
422
|
+
grad_prime = grad_2prime.copy()
|
|
326
423
|
|
|
327
424
|
# update number of particles and stopping criterion
|
|
328
425
|
alpha_prime += alpha_2prime
|
|
329
426
|
n_alpha_prime += n_alpha_2prime
|
|
330
|
-
|
|
331
|
-
s_prime = s_2prime
|
|
427
|
+
dpoints = point_plus - point_minus
|
|
428
|
+
s_prime = s_2prime *\
|
|
429
|
+
int((dpoints@r_minus.T)>=0) * int((dpoints@r_plus.T)>=0)
|
|
332
430
|
n_prime += n_2prime
|
|
333
|
-
return theta_minus, r_minus, grad_minus, theta_plus, r_plus, grad_plus, \
|
|
334
|
-
theta_prime, joint_prime, grad_prime, n_prime, s_prime, alpha_prime, n_alpha_prime
|
|
335
431
|
|
|
432
|
+
return point_minus, r_minus, grad_minus, point_plus, r_plus, grad_plus,\
|
|
433
|
+
point_prime, logd_prime, grad_prime,\
|
|
434
|
+
n_prime, s_prime, alpha_prime, n_alpha_prime
|
|
435
|
+
|
|
436
|
+
#=========================================================================
|
|
437
|
+
#======================== Diagnostic methods =============================
|
|
438
|
+
#=========================================================================
|
|
439
|
+
|
|
440
|
+
def _create_run_diagnostic_attributes(self):
|
|
441
|
+
"""A method to create attributes to store NUTS run diagnostic."""
|
|
442
|
+
self._reset_run_diagnostic_attributes()
|
|
443
|
+
|
|
444
|
+
def _reset_run_diagnostic_attributes(self):
|
|
445
|
+
"""A method to reset attributes to store NUTS run diagnostic."""
|
|
446
|
+
# List to store number of tree nodes created each NUTS iteration
|
|
447
|
+
self.num_tree_node_list = []
|
|
448
|
+
# List of step size used in each NUTS iteration
|
|
449
|
+
self.epsilon_list = []
|
|
450
|
+
# List of burn-in step size suggestion during adaptation
|
|
451
|
+
# only used when adaptation is done
|
|
452
|
+
# remains fixed after adaptation (after burn-in)
|
|
453
|
+
self.epsilon_bar_list = []
|
|
454
|
+
|
|
455
|
+
def _update_run_diagnostic_attributes(self, n_tree, eps, eps_bar):
|
|
456
|
+
"""A method to update attributes to store NUTS run diagnostic."""
|
|
457
|
+
# Store the number of tree nodes created in iteration k
|
|
458
|
+
self.num_tree_node_list.append(n_tree)
|
|
459
|
+
# Store the step size used in iteration k
|
|
460
|
+
self.epsilon_list.append(eps)
|
|
461
|
+
# Store the step size suggestion during adaptation in iteration k
|
|
462
|
+
self.epsilon_bar_list.append(eps_bar)
|