Skip to content

Commit 857f0fe

Browse files
osullivryanfmfn
authored andcommitted
Added sequential domain reduction
changed defaults and added copy Adding SDR to README and rev the project.
1 parent 235de0d commit 857f0fe

File tree

8 files changed

+501
-4
lines changed

8 files changed

+501
-4
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,14 @@ new_optimizer = BayesianOptimization(
261261
load_logs(new_optimizer, logs=["./logs.json"]);
262262
```
263263

264+
### 4.3 Sequential Domain Reduction
265+
266+
Using the `SequentialDomainReductionTransformer` the bounds of the problem can be panned and zoomed in an attempt to improve convergence. Sometimes the initial boundaries specified for a problem are too wide, and adding points to improve the response surface in regions of the solution domain is extraneous. Other times the cost function is very expensive to compute, and minimizing the number of calls is extremely beneficial.
267+
268+
![sequential domain reduction](https://github.com/fmfn/BayesianOptimization/blob/master/examples/sdr.png)
269+
270+
An example of using the `SequentialDomainReductionTransformer` is shown in the [domain reduction notebook](https://github.com/fmfn/BayesianOptimization/blob/master/examples/domain_reduction.ipynb)
271+
264272
## Next Steps
265273

266274
This introduction covered the most basic functionality of the package. Checkout the [basic-tour](https://github.com/fmfn/BayesianOptimization/blob/master/examples/basic-tour.ipynb) and [advanced-tour](https://github.com/fmfn/BayesianOptimization/blob/master/examples/advanced-tour.ipynb) notebooks in the example folder, where you will find detailed explanations and other more advanced functionality. Also, browse the examples folder for implementation tips and ideas.

bayes_opt/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .bayesian_optimization import BayesianOptimization, Events
2+
from .domain_reduction import SequentialDomainReductionTransformer
23
from .util import UtilityFunction
34
from .logger import ScreenLogger, JSONLogger
45

@@ -8,4 +9,5 @@
89
"Events",
910
"ScreenLogger",
1011
"JSONLogger",
12+
"SequentialDomainReductionTransformer",
1113
]

bayes_opt/bayesian_optimization.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import warnings
2-
import numpy as np
32

43
from .target_space import TargetSpace
54
from .event import Events, DEFAULT_EVENTS
@@ -51,7 +50,7 @@ def get_subscribers(self, event):
5150
return self._events[event]
5251

5352
def subscribe(self, event, subscriber, callback=None):
54-
if callback == None:
53+
if callback is None:
5554
callback = getattr(subscriber, 'update')
5655
self.get_subscribers(event)[subscriber] = callback
5756

@@ -64,7 +63,8 @@ def dispatch(self, event):
6463

6564

6665
class BayesianOptimization(Observable):
67-
def __init__(self, f, pbounds, random_state=None, verbose=2):
66+
def __init__(self, f, pbounds, random_state=None, verbose=2,
67+
bounds_transformer=None):
6868
""""""
6969
self._random_state = ensure_rng(random_state)
7070

@@ -85,6 +85,10 @@ def __init__(self, f, pbounds, random_state=None, verbose=2):
8585
)
8686

8787
self._verbose = verbose
88+
self._bounds_transformer = bounds_transformer
89+
if self._bounds_transformer:
90+
self._bounds_transformer.initialize(self._space)
91+
8892
super(BayesianOptimization, self).__init__(events=DEFAULT_EVENTS)
8993

9094
@property
@@ -180,6 +184,10 @@ def maximize(self,
180184

181185
self.probe(x_probe, lazy=False)
182186

187+
if self._bounds_transformer:
188+
self.set_bounds(
189+
self._bounds_transformer.transform(self._space))
190+
183191
self.dispatch(Events.OPTIMIZATION_END)
184192

185193
def set_bounds(self, new_bounds):

bayes_opt/domain_reduction.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import numpy as np
2+
from .target_space import TargetSpace
3+
4+
5+
class DomainTransformer():
6+
'''The base transformer class'''
7+
8+
def __init__(self, **kwargs):
9+
pass
10+
11+
def initialize(self, target_space: TargetSpace):
12+
raise NotImplementedError
13+
14+
def transform(self, target_space: TargetSpace):
15+
raise NotImplementedError
16+
17+
18+
class SequentialDomainReductionTransformer(DomainTransformer):
19+
"""
20+
A sequential domain reduction transformer bassed on the work by Stander, N. and Craig, K:
21+
"On the robustness of a simple domain reduction scheme for simulation‐based optimization"
22+
"""
23+
24+
def __init__(
25+
self,
26+
gamma_osc: float = 0.7,
27+
gamma_pan: float = 1.0,
28+
eta: float = 0.9
29+
) -> None:
30+
self.gamma_osc = gamma_osc
31+
self.gamma_pan = gamma_pan
32+
self.eta = eta
33+
pass
34+
35+
def initialize(self, target_space: TargetSpace) -> None:
36+
"""Initialize all of the parameters"""
37+
self.original_bounds = np.copy(target_space.bounds)
38+
self.bounds = [self.original_bounds]
39+
40+
self.previous_optimal = np.mean(target_space.bounds, axis=1)
41+
self.current_optimal = np.mean(target_space.bounds, axis=1)
42+
self.r = target_space.bounds[:, 1] - target_space.bounds[:, 0]
43+
44+
self.previous_d = 2.0 * \
45+
(self.current_optimal - self.previous_optimal) / self.r
46+
47+
self.current_d = 2.0 * (self.current_optimal -
48+
self.previous_optimal) / self.r
49+
50+
self.c = self.current_d * self.previous_d
51+
self.c_hat = np.sqrt(np.abs(self.c)) * np.sign(self.c)
52+
53+
self.gamma = 0.5 * (self.gamma_pan * (1.0 + self.c_hat) +
54+
self.gamma_osc * (1.0 - self.c_hat))
55+
56+
self.contraction_rate = self.eta + \
57+
np.abs(self.current_d) * (self.gamma - self.eta)
58+
59+
self.r = self.contraction_rate * self.r
60+
61+
def _update(self, target_space: TargetSpace) -> None:
62+
63+
# setting the previous
64+
self.previous_optimal = self.current_optimal
65+
self.previous_d = self.current_d
66+
67+
self.current_optimal = target_space.params[
68+
np.argmax(target_space.target)
69+
]
70+
71+
self.current_d = 2.0 * (self.current_optimal -
72+
self.previous_optimal) / self.r
73+
74+
self.c = self.current_d * self.previous_d
75+
76+
self.c_hat = np.sqrt(np.abs(self.c)) * np.sign(self.c)
77+
78+
self.gamma = 0.5 * (self.gamma_pan * (1.0 + self.c_hat) +
79+
self.gamma_osc * (1.0 - self.c_hat))
80+
81+
self.contraction_rate = self.eta + \
82+
np.abs(self.current_d) * (self.gamma - self.eta)
83+
84+
self.r = self.contraction_rate * self.r
85+
86+
def _trim(self, new_bounds: np.array, global_bounds: np.array) -> np.array:
87+
for i, variable in enumerate(new_bounds):
88+
if variable[0] < global_bounds[i, 0]:
89+
variable[0] = global_bounds[i, 0]
90+
if variable[1] > global_bounds[i, 1]:
91+
variable[1] = global_bounds[i, 1]
92+
93+
return new_bounds
94+
95+
def _create_bounds(self, parameters: dict, bounds: np.array) -> dict:
96+
return {param: bounds[i, :] for i, param in enumerate(parameters)}
97+
98+
def transform(self, target_space: TargetSpace) -> dict:
99+
100+
self._update(target_space)
101+
102+
new_bounds = np.array(
103+
[
104+
self.current_optimal - 0.5 * self.r,
105+
self.current_optimal + 0.5 * self.r
106+
]
107+
).T
108+
109+
self._trim(new_bounds, self.original_bounds)
110+
self.bounds.append(new_bounds)
111+
return self._create_bounds(target_space.keys, new_bounds)

examples/domain_reduction.ipynb

Lines changed: 302 additions & 0 deletions
Large diffs are not rendered by default.

examples/sdr.png

28.3 KB
Loading

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
setup(
44
name='bayesian-optimization',
5-
version='1.1.0',
5+
version='1.2.0',
66
url='https://github.com/fmfn/BayesianOptimization',
77
packages=find_packages(),
88
author='Fernando Nogueira',

tests/test_seq_domain_red.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from bayes_opt import SequentialDomainReductionTransformer
2+
from bayes_opt import BayesianOptimization
3+
4+
5+
def black_box_function(x, y):
6+
"""Function with unknown internals we wish to maximize.
7+
8+
This is just serving as an example, for all intents and
9+
purposes think of the internals of this function, i.e.: the process
10+
which generates its output values, as unknown.
11+
"""
12+
return -x ** 2 - (y - 1) ** 2 + 1
13+
14+
15+
def test_bound_x_maximize():
16+
17+
class Tracker:
18+
def __init__(self):
19+
self.start_count = 0
20+
self.step_count = 0
21+
self.end_count = 0
22+
23+
def update_start(self, event, instance):
24+
self.start_count += 1
25+
26+
def update_step(self, event, instance):
27+
self.step_count += 1
28+
29+
def update_end(self, event, instance):
30+
self.end_count += 1
31+
32+
def reset(self):
33+
self.__init__()
34+
35+
bounds_transformer = SequentialDomainReductionTransformer()
36+
pbounds = {'x': (-10, 10), 'y': (-10, 10)}
37+
n_iter = 10
38+
39+
standard_optimizer = BayesianOptimization(
40+
f=black_box_function,
41+
pbounds=pbounds,
42+
verbose=2,
43+
random_state=1,
44+
)
45+
46+
standard_optimizer.maximize(
47+
init_points=2,
48+
n_iter=n_iter,
49+
)
50+
51+
mutated_optimizer = BayesianOptimization(
52+
f=black_box_function,
53+
pbounds=pbounds,
54+
verbose=2,
55+
random_state=1,
56+
bounds_transformer=bounds_transformer
57+
)
58+
59+
mutated_optimizer.maximize(
60+
init_points=2,
61+
n_iter=n_iter,
62+
)
63+
64+
assert len(standard_optimizer.space) == len(mutated_optimizer.space)
65+
assert not (standard_optimizer._space.bounds ==
66+
mutated_optimizer._space.bounds).any()

0 commit comments

Comments
 (0)