Skip to content

Commit 2db20b7

Browse files
authored
Merge pull request #55 from GPflow/doc_acquisition
Pass over the documentation.
2 parents 1273304 + 4b43022 commit 2db20b7

25 files changed

Lines changed: 467 additions & 170 deletions

GPflowOpt/acquisition/acquisition.py

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,19 @@ class Acquisition(Parameterized):
3434
In Bayesian Optimization this function is typically optimized over the optimization domain
3535
to determine the next point for evaluation.
3636
37-
An object of this class holds a list of GPflow models. For single objective optimization this is typically a
38-
single model. Subclasses implement a build_acquisition function which computes the acquisition function (usually
39-
from the predictive distribution) using TensorFlow. Each model is automatically optimized when an acquisition object
40-
is constructed or when set_data is called.
37+
An object of this class holds a list of GPflow models. Subclasses implement a build_acquisition function
38+
which computes the acquisition function (usually from the predictive distribution) using TensorFlow.
39+
Each model is automatically optimized when an acquisition object is constructed or when set_data is called.
4140
42-
Acquisition functions can be combined through addition or multiplication to construct joint criteria
43-
(for instance for constrained optimization)
41+
Acquisition functions can be combined through addition or multiplication to construct joint criteria.
42+
For instance, for constrained optimization.
4443
"""
4544

4645
def __init__(self, models=[], optimize_restarts=5):
46+
"""
47+
:param models: list of GPflow models representing our beliefs about the problem
48+
:param optimize_restarts: number of optimization restarts to use when training the models
49+
"""
4750
super(Acquisition, self).__init__()
4851
self._models = ParamList([DataScaler(m) for m in np.atleast_1d(models).tolist()])
4952
self._default_params = list(map(lambda m: m.get_free_state(), self._models))
@@ -56,10 +59,11 @@ def _optimize_models(self):
5659
"""
5760
Optimizes the hyperparameters of all models that the acquisition function is based on.
5861
59-
It is called after initialization and set_data(), and before optimizing the acquisition function itself.
62+
It is called automatically during initialization and each time set_data() is called.
63+
When using the high-level :class:`..BayesianOptimizer` class calling set_data() is taken care of.
6064
6165
For each model the hyperparameters of the model at the time it was passed to __init__() are used as initial
62-
point and optimized. If optimize_restarts was configured to values larger than one additional randomization
66+
point and optimized. If optimize_restarts is set to >1, additional randomization
6367
steps are performed.
6468
6569
As a special case, if optimize_restarts is set to zero, the hyperparameters of the models are not optimized.
@@ -82,14 +86,15 @@ def _optimize_models(self):
8286
best_idx = np.argmin([r.fun for r in runs])
8387
model.set_state(runs[best_idx].x)
8488

85-
def build_acquisition(self):
89+
def build_acquisition(self, Xcand):
8690
raise NotImplementedError
8791

8892
def enable_scaling(self, domain):
8993
"""
9094
Enables and configures the :class:`.DataScaler` objects wrapping the GP models.
95+
9196
:param domain: :class:`.Domain` object, the input transform of the data scalers is configured as a transform
92-
from domain to the unit cube with the same dimensionality.
97+
from domain to the unit cube with the same dimensionality.
9398
"""
9499
n_inputs = self.data[0].shape[1]
95100
assert (domain.size == n_inputs)
@@ -103,11 +108,11 @@ def set_data(self, X, Y):
103108
Update the training data of the contained models. Automatically triggers a hyperparameter optimization
104109
step by calling _optimize_all() and an update of pre-computed quantities by calling setup().
105110
106-
Consider Q to be the the sum of the output dimensions of the contained models, Y should have a minimum of
111+
Let Q be the the sum of the output dimensions of all contained models, Y should have a minimum of
107112
Q columns. Only the first Q columns of Y are used while returning the scalar Q
108113
109114
:param X: input data N x D
110-
:param Y: Responses N x M (M >= Q)
115+
:param Y: output data N x R (R >= Q)
111116
:return: Q (sum of output dimensions of contained models)
112117
"""
113118
num_outputs_sum = 0
@@ -120,23 +125,30 @@ def set_data(self, X, Y):
120125
model.Y = Ypart
121126

122127
self._optimize_models()
128+
129+
# Only call setup for the high-level acquisition function
123130
if self.highest_parent == self:
124131
self.setup()
125132
return num_outputs_sum
126133

127134
@property
128135
def models(self):
136+
"""
137+
The GPflow models representing our beliefs of the optimization problem.
138+
139+
:return: list of GPflow models
140+
"""
129141
return self._models
130142

131143
@property
132144
def data(self):
133145
"""
134-
Property for accessing the training data of the models.
146+
The training data of the models.
135147
136148
Corresponds to the input data X which is the same for every model,
137149
and column-wise concatenation of the Y data over all models
138150
139-
:return: X, Y tensors (if in tf_mode) or X, Y numpy arrays.
151+
:return: tuple X, Y of tensors (if in tf_mode) or numpy arrays.
140152
"""
141153
if self._tf_mode:
142154
return self.models[0].X, tf.concat(list(map(lambda model: model.Y, self.models)), 1)
@@ -153,32 +165,39 @@ def constraint_indices(self):
153165
def objective_indices(self):
154166
"""
155167
Method returning the indices of the model outputs which are objective functions.
156-
By default all outputs are objectives
168+
169+
By default all outputs are objectives.
170+
171+
:return: indices to the objectives, size R
157172
"""
158173
return np.setdiff1d(np.arange(self.data[1].shape[1]), self.constraint_indices())
159174

160175
def feasible_data_index(self):
161176
"""
162177
Returns a boolean array indicating which data points are considered feasible (according to the acquisition
163178
function(s) ) and which not.
164-
By default all data is considered feasible
165-
:return: boolean ndarray, N
179+
180+
By default all data is considered feasible.
181+
182+
:return: logical indices to the feasible data points, size N
166183
"""
167184
return np.ones(self.data[0].shape[0], dtype=bool)
168185

169186
def setup(self):
170187
"""
171-
Method triggered after calling set_data().
172-
173-
Override for pre-calculation of quantities used later in
174-
the evaluation of the acquisition function for candidate points
188+
Pre-calculation of quantities used later in the evaluation of the acquisition function for candidate points.
189+
190+
Automatically triggered by :meth:`~.Acquisition.set_data`.
175191
"""
176192
pass
177193

178194
@AutoFlow((float_type, [None, None]))
179195
def evaluate_with_gradients(self, Xcand):
180196
"""
181197
AutoFlow method to compute the acquisition scores for candidates, also returns the gradients.
198+
199+
:return: acquisition scores, size N x 1
200+
the gradients of the acquisition scores, size N x D
182201
"""
183202
acq = self.build_acquisition(Xcand)
184203
return acq, tf.gradients(acq, [Xcand], name="acquisition_gradient")[0]
@@ -187,6 +206,8 @@ def evaluate_with_gradients(self, Xcand):
187206
def evaluate(self, Xcand):
188207
"""
189208
AutoFlow method to compute the acquisition scores for candidates, without returning the gradients.
209+
210+
:return: acquisition scores, size N x 1
190211
"""
191212
return self.build_acquisition(Xcand)
192213

@@ -198,7 +219,6 @@ def __add__(self, other):
198219
>>> a2 = GPflowOpt.acquisition.ProbabilityOfFeasibility(m2)
199220
>>> type(a1 + a2)
200221
<type 'GPflowOpt.acquisition.AcquisitionSum'>
201-
202222
"""
203223
if isinstance(other, AcquisitionSum):
204224
return AcquisitionSum([self] + other.operands.sorted_params)
@@ -212,7 +232,6 @@ def __mul__(self, other):
212232
>>> a2 = GPflowOpt.acquisition.ProbabilityOfFeasibility(m2)
213233
>>> type(a1 * a2)
214234
<type 'GPflowOpt.acquisition.AcquisitionProduct'>
215-
216235
"""
217236
if isinstance(other, AcquisitionProduct):
218237
return AcquisitionProduct([self] + other.operands.sorted_params)
@@ -221,12 +240,11 @@ def __mul__(self, other):
221240

222241
class AcquisitionAggregation(Acquisition):
223242
"""
224-
Special acquisition implementation for aggregating multiple others, using a TensorFlow reduce operation.
243+
Aggregates multiple acquisition functions, using a TensorFlow reduce operation.
225244
"""
226245

227246
def __init__(self, operands, oper):
228247
"""
229-
Constructor
230248
:param operands: list of acquisition objects
231249
:param oper: a tf.reduce operation (e.g., tf.reduce_sum) for aggregating the returned scores of each operand.
232250
"""
@@ -310,11 +328,12 @@ def __mul__(self, other):
310328

311329
class MCMCAcquistion(AcquisitionSum):
312330
"""
313-
Acquisition object to apply MCMC over the hyperparameters of the models. The models of the acquisition object passed
314-
into an object of this class is optimized with MLE, and then sampled with HMC. These hyperparameter samples are then
315-
set in copies of the acquisition.
331+
Apply MCMC over the hyperparameters of an acquisition function (= over the hyperparameters of the contained models).
332+
333+
The models passed into an object of this class are optimized with MLE, and then further sampled with HMC.
334+
These hyperparameter samples are then set in copies of the acquisition.
316335
317-
To compute the acquisition, the predictions of the acquisition copies are averaged.
336+
For evaluating the underlying acquisition function, the predictions of the acquisition copies are averaged.
318337
"""
319338
def __init__(self, acquisition, n_slices, **kwargs):
320339
assert isinstance(acquisition, Acquisition)

GPflowOpt/acquisition/ei.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,17 @@ class ExpectedImprovement(Acquisition):
4545
}
4646
4747
This acquisition function is the expectation of the improvement over the current best observation
48-
w.r.t. the predictive distribution. The definition is closely related to the Probability of Improvement,
48+
w.r.t. the predictive distribution. The definition is closely related to the :class:`.ProbabilityOfImprovement`,
4949
but adds a multiplication with the improvement w.r.t the current best observation to the integral.
5050
5151
.. math::
5252
\\alpha(\\mathbf x_{\\star}) = \\int \\max(f_{\\min} - f_{\\star}, 0) \\, p( f_{\\star}\\,|\\, \\mathbf x, \\mathbf y, \\mathbf x_{\\star} ) \\, d f_{\\star}
5353
"""
5454

5555
def __init__(self, model):
56+
"""
57+
:param model: GPflow model (single output) representing our belief of the objective
58+
"""
5659
super(ExpectedImprovement, self).__init__(model)
5760
assert (isinstance(model, Model))
5861
self.fmin = DataHolder(np.zeros(1))
@@ -74,4 +77,4 @@ def build_acquisition(self, Xcand):
7477
normal = tf.contrib.distributions.Normal(candidate_mean, tf.sqrt(candidate_var))
7578
t1 = (self.fmin - candidate_mean) * normal.cdf(self.fmin)
7679
t2 = candidate_var * normal.prob(self.fmin)
77-
return tf.add(t1, t2, name=self.__class__.__name__)
80+
return tf.add(t1, t2, name=self.__class__.__name__)

GPflowOpt/acquisition/lcb.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ class LowerConfidenceBound(Acquisition):
2727
"""
2828

2929
def __init__(self, model, sigma=2.0):
30+
"""
31+
:param model: GPflow model (single output) representing our belief of the objective
32+
:param sigma: See formula, the higher the more exploration
33+
"""
3034
super(LowerConfidenceBound, self).__init__(model)
3135
self.sigma = sigma
3236

GPflowOpt/acquisition/pof.py

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,33 +29,29 @@ class ProbabilityOfFeasibility(Acquisition):
2929
Bayesian Optimization with black-box expensive constraints.
3030
3131
Key reference:
32-
32+
3333
::
34-
35-
@article{parr2012infill,
36-
title={Infill sampling criteria for surrogate-based optimization with constraint handling},
37-
author={Parr, JM and Keane, AJ and Forrester, Alexander IJ and Holden, CME},
38-
journal={Engineering Optimization},
39-
volume={44},
40-
number={10},
41-
pages={1147--1166},
42-
year={2012},
43-
publisher={Taylor & Francis}
44-
}
45-
46-
The acquisition function measures the probability of the latent function being smaller than 0 for a candidate point.
34+
35+
@article{Schonlau:1997,
36+
title={Computer experiments and global optimization},
37+
author={Schonlau, Matthias},
38+
year={1997},
39+
publisher={University of Waterloo}
40+
}
41+
42+
The acquisition function measures the probability of the latent function
43+
being smaller than a threshold for a candidate point.
4744
4845
.. math::
4946
\\alpha(\\mathbf x_{\\star}) = \\int_{-\\infty}^{0} \\, p(f_{\\star}\\,|\\, \\mathbf x, \\mathbf y, \\mathbf x_{\\star} ) \\, d f_{\\star}
5047
"""
5148

5249
def __init__(self, model, threshold=0.0, minimum_pof=0.5):
5350
"""
54-
55-
:param model: GPflow model (single output) for computing the PoF
56-
:param threshold: threshold value. Observed values lower than this value are considered valid
57-
:param minimum_pof: minimum pof score required for a point to be valid. For more information, see docstring
58-
of feasible_data_index
51+
:param model: GPflow model (single output) representing our belief of the constraint
52+
:param threshold: Observed values lower than the threshold are considered valid
53+
:param minimum_pof: minimum pof score required for a point to be valid.
54+
For more information, see docstring of feasible_data_index
5955
"""
6056
super(ProbabilityOfFeasibility, self).__init__(model)
6157
self.threshold = threshold
@@ -66,18 +62,19 @@ def constraint_indices(self):
6662

6763
def feasible_data_index(self):
6864
"""
69-
Returns a boolean array indicating which points are feasible (True) and which are not (False)
65+
Returns a boolean array indicating which points are feasible (True) and which are not (False).
66+
7067
Answering the question *which points are feasible?* is slightly troublesome in case noise is present.
7168
Directly relying on the noisy data and comparing it to self.threshold does not make much sense.
7269
73-
Instead, we rely on the model belief. More specifically, we evaluate the PoF (score between 0 and 1).
70+
Instead, we rely on the model belief using the PoF (a probability between 0 and 1).
7471
As the implementation of the PoF corresponds to the cdf of the (normal) predictive distribution in
7572
a point evaluated at the threshold, requiring a minimum pof of 0.5 implies the mean of the predictive
7673
distribution is below the threshold, hence it is marked as feasible. A minimum pof of 0 marks all points valid.
7774
Setting it to 1 results in all invalid.
78-
:return: boolean ndarray, size N
75+
76+
:return: boolean ndarray (size N)
7977
"""
80-
# In
8178
pred = self.evaluate(self.data[0])
8279
return pred.ravel() > self.minimum_pof
8380

GPflowOpt/acquisition/poi.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ class ProbabilityOfImprovement(Acquisition):
3232
"""
3333

3434
def __init__(self, model):
35+
"""
36+
:param model: GPflow model (single output) representing our belief of the objective
37+
"""
3538
super(ProbabilityOfImprovement, self).__init__(model)
3639
self.fmin = DataHolder(np.zeros(1))
3740
self.setup()

0 commit comments

Comments
 (0)