Skip to content

Commit a870fbf

Browse files
authored
Merge pull request #372 from bwheelz36/fix_duplicate_points
Fix duplicate points
2 parents f692d04 + b1d932c commit a870fbf

9 files changed

+266
-166
lines changed

bayes_opt/bayesian_optimization.py

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ class BayesianOptimization(Observable):
9191
bounds_transformer: DomainTransformer, optional(default=None)
9292
If provided, the transformation is applied to the bounds.
9393
94+
allow_duplicate_points: bool, optional (default=False)
95+
If True, the optimizer will allow duplicate points to be registered.
96+
This behavior may be desired in high noise situations where repeatedly probing
97+
the same point will give different answers. In other situations, the acquisition
98+
may occasionaly generate a duplicate point.
99+
94100
Methods
95101
-------
96102
probe()
@@ -111,9 +117,10 @@ def __init__(self,
111117
constraint=None,
112118
random_state=None,
113119
verbose=2,
114-
bounds_transformer=None):
120+
bounds_transformer=None,
121+
allow_duplicate_points=False):
115122
self._random_state = ensure_rng(random_state)
116-
123+
self._allow_duplicate_points = allow_duplicate_points
117124
self._queue = Queue()
118125

119126
# Internal GP regressor
@@ -129,7 +136,8 @@ def __init__(self,
129136
# Data structure containing the function to be optimized, the
130137
# bounds of its domain, and a record of the evaluations we have
131138
# done so far
132-
self._space = TargetSpace(f, pbounds, random_state=random_state)
139+
self._space = TargetSpace(f, pbounds, random_state=random_state,
140+
allow_duplicate_points=self._allow_duplicate_points)
133141
self.is_constrained = False
134142
else:
135143
constraint_ = ConstraintModel(
@@ -242,12 +250,14 @@ def _prime_subscriptions(self):
242250
def maximize(self,
243251
init_points=5,
244252
n_iter=25,
245-
acq='ucb',
246-
kappa=2.576,
247-
kappa_decay=1,
248-
kappa_decay_delay=0,
249-
xi=0.0,
253+
acquisition_function=None,
254+
acq=None,
255+
kappa=None,
256+
kappa_decay=None,
257+
kappa_decay_delay=None,
258+
xi=None,
250259
**gp_params):
260+
251261
"""
252262
Probes the target space to find the parameters that yield the maximum
253263
value for the given function.
@@ -262,38 +272,34 @@ def maximize(self,
262272
Number of iterations where the method attempts to find the maximum
263273
value.
264274
265-
acq: {'ucb', 'ei', 'poi'}
266-
The acquisition method used.
267-
* 'ucb' stands for the Upper Confidence Bounds method
268-
* 'ei' is the Expected Improvement method
269-
* 'poi' is the Probability Of Improvement criterion.
270-
271-
kappa: float, optional(default=2.576)
272-
Parameter to indicate how closed are the next parameters sampled.
273-
Higher value = favors spaces that are least explored.
274-
Lower value = favors spaces where the regression function is
275-
the highest.
275+
acquisition_function: object, optional
276+
An instance of bayes_opt.util.UtilityFunction.
277+
If nothing is passed, a default using ucb is used
276278
277-
kappa_decay: float, optional(default=1)
278-
`kappa` is multiplied by this factor every iteration.
279-
280-
kappa_decay_delay: int, optional(default=0)
281-
Number of iterations that must have passed before applying the
282-
decay to `kappa`.
283-
284-
xi: float, optional(default=0.0)
285-
[unused]
279+
All other parameters are unused, and are only available to ensure backwards compatability - these
280+
will be removed in a future release
286281
"""
287282
self._prime_subscriptions()
288283
self.dispatch(Events.OPTIMIZATION_START)
289284
self._prime_queue(init_points)
290-
self.set_gp_params(**gp_params)
291285

292-
util = UtilityFunction(kind=acq,
293-
kappa=kappa,
294-
xi=xi,
295-
kappa_decay=kappa_decay,
296-
kappa_decay_delay=kappa_decay_delay)
286+
old_params_used = any([param is not None for param in [acq, kappa, kappa_decay, kappa_decay_delay, xi]])
287+
if old_params_used or gp_params:
288+
warnings.warn('\nPassing acquisition function parameters or gaussian process parameters to maximize'
289+
'\nis no longer supported, and will cause an error in future releases. Instead,'
290+
'\nplease use the "set_gp_params" method to set the gp params, and pass an instance'
291+
'\n of bayes_opt.util.UtilityFunction using the acquisition_function argument\n',
292+
DeprecationWarning, stacklevel=2)
293+
294+
if acquisition_function is None:
295+
util = UtilityFunction(kind='ucb',
296+
kappa=2.576,
297+
xi=0.0,
298+
kappa_decay=1,
299+
kappa_decay_delay=0)
300+
else:
301+
util = acquisition_function
302+
297303
iteration = 0
298304
while not self._queue.empty or iteration < n_iter:
299305
try:

bayes_opt/target_space.py

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import warnings
2+
13
import numpy as np
24
from .util import ensure_rng, NotUniqueError
5+
from .util import Colours
36

47

58
def _hashable(x):
@@ -22,7 +25,9 @@ class TargetSpace(object):
2225
>>> y = space.register_point(x)
2326
>>> assert self.max_point()['max_val'] == y
2427
"""
25-
def __init__(self, target_func, pbounds, constraint=None, random_state=None):
28+
29+
def __init__(self, target_func, pbounds, constraint=None, random_state=None,
30+
allow_duplicate_points=False):
2631
"""
2732
Parameters
2833
----------
@@ -35,8 +40,16 @@ def __init__(self, target_func, pbounds, constraint=None, random_state=None):
3540
3641
random_state : int, RandomState, or None
3742
optionally specify a seed for a random number generator
43+
44+
allow_duplicate_points: bool, optional (default=False)
45+
If True, the optimizer will allow duplicate points to be registered.
46+
This behavior may be desired in high noise situations where repeatedly probing
47+
the same point will give different answers. In other situations, the acquisition
48+
may occasionaly generate a duplicate point.
3849
"""
3950
self.random_state = ensure_rng(random_state)
51+
self._allow_duplicate_points = allow_duplicate_points
52+
self.n_duplicate_points = 0
4053

4154
# The function to be optimized
4255
self.target_func = target_func
@@ -56,7 +69,6 @@ def __init__(self, target_func, pbounds, constraint=None, random_state=None):
5669
# keep track of unique points we have seen so far
5770
self._cache = {}
5871

59-
6072
self._constraint = constraint
6173

6274
if constraint is not None:
@@ -96,7 +108,7 @@ def keys(self):
96108
@property
97109
def bounds(self):
98110
return self._bounds
99-
111+
100112
@property
101113
def constraint(self):
102114
return self._constraint
@@ -176,8 +188,13 @@ def register(self, params, target, constraint_value=None):
176188
"""
177189
x = self._as_array(params)
178190
if x in self:
179-
raise NotUniqueError('Data point {} is not unique'.format(x))
180-
191+
if self._allow_duplicate_points:
192+
self.n_duplicate_points = self.n_duplicate_points + 1
193+
print(f'{Colours.RED}Data point {x} is not unique. {self.n_duplicate_points} duplicates registered.'
194+
f' Continuing ...{Colours.END}')
195+
else:
196+
raise NotUniqueError(f'Data point {x} is not unique. You can set "allow_duplicate_points=True" to '
197+
f'avoid this error')
181198

182199
self._params = np.concatenate([self._params, x.reshape(1, -1)])
183200
self._target = np.concatenate([self._target, [target]])
@@ -188,12 +205,12 @@ def register(self, params, target, constraint_value=None):
188205
else:
189206
if constraint_value is None:
190207
msg = ("When registering a point to a constrained TargetSpace" +
191-
" a constraint value needs to be present.")
208+
" a constraint value needs to be present.")
192209
raise ValueError(msg)
193210
# Insert data into unique dictionary
194211
self._cache[_hashable(x.ravel())] = (target, constraint_value)
195212
self._constraint_values = np.concatenate([self._constraint_values,
196-
[constraint_value]])
213+
[constraint_value]])
197214

198215
def probe(self, params):
199216
"""
@@ -215,21 +232,16 @@ def probe(self, params):
215232
target function value.
216233
"""
217234
x = self._as_array(params)
235+
params = dict(zip(self._keys, x))
236+
target = self.target_func(**params)
218237

219-
try:
220-
return self._cache[_hashable(x)]
221-
except KeyError:
222-
params = dict(zip(self._keys, x))
223-
target = self.target_func(**params)
224-
225-
if self._constraint is None:
226-
self.register(x, target)
227-
return target
228-
else:
229-
constraint_value = self._constraint.eval(**params)
230-
self.register(x, target, constraint_value)
231-
return target, constraint_value
232-
238+
if self._constraint is None:
239+
self.register(x, target)
240+
return target
241+
else:
242+
constraint_value = self._constraint.eval(**params)
243+
self.register(x, target, constraint_value)
244+
return target, constraint_value
233245

234246
def random_sample(self):
235247
"""
@@ -317,7 +329,7 @@ def res(self):
317329
self._constraint_values,
318330
params,
319331
self._constraint.allowed(self._constraint_values)
320-
)
332+
)
321333
]
322334

323335
def set_bounds(self, new_bounds):

bayes_opt/util.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,29 @@ def to_minimize(x):
9696
class UtilityFunction(object):
9797
"""
9898
An object to compute the acquisition functions.
99+
100+
kind: {'ucb', 'ei', 'poi'}
101+
* 'ucb' stands for the Upper Confidence Bounds method
102+
* 'ei' is the Expected Improvement method
103+
* 'poi' is the Probability Of Improvement criterion.
104+
105+
kappa: float, optional(default=2.576)
106+
Parameter to indicate how closed are the next parameters sampled.
107+
Higher value = favors spaces that are least explored.
108+
Lower value = favors spaces where the regression function is
109+
the highest.
110+
111+
kappa_decay: float, optional(default=1)
112+
`kappa` is multiplied by this factor every iteration.
113+
114+
kappa_decay_delay: int, optional(default=0)
115+
Number of iterations that must have passed before applying the
116+
decay to `kappa`.
117+
118+
xi: float, optional(default=0.0)
99119
"""
100120

101-
def __init__(self, kind, kappa, xi, kappa_decay=1, kappa_decay_delay=0):
121+
def __init__(self, kind='ucb', kappa=2.576, xi=0, kappa_decay=1, kappa_decay_delay=0):
102122

103123
self.kappa = kappa
104124
self._kappa_decay = kappa_decay

0 commit comments

Comments
 (0)