Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 70 additions & 16 deletions bayes_optim/BayesOpt.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def pre_eval_check(self, X: Solution) -> Solution:
class ParallelBO(BO):
def __init__(self, **kwargs):
super().__init__(**kwargs)
assert self.n_point > 1
# assert self.n_point > 1

if self._acquisition_fun == 'MGFI':
self._par_name = 't'
Expand Down Expand Up @@ -170,7 +170,25 @@ def _batch_arg_max_acquisition(
self._acquisition_par['t'] = np.mean([_t_list[i] for i in idx])
return tuple(zip(*__))


class NoisyBO(ParallelBO):
def pre_eval_check(self, X):
if not isinstance(X, Solution):
X = Solution(X, var_name=self.var_names)
return X

def _create_acquisition(self, fun=None, par={}, return_dx=False):
if hasattr(getattr(AcquisitionFunction, self._acquisition_fun), 'plugin'):
# use the model prediction to determine the plugin under noisy scenarios
# TODO: add more options for determining the plugin value
y_ = self.model.predict(self.data)
plugin = np.min(y_) if self.minimize else np.max(y_)
par.update({'plugin' : plugin})

return super()._create_acquisition(par=par, return_dx=return_dx)


class IntensificationBO(ParallelBO):
def __init__(
self,
max_r: int = 200,
Expand Down Expand Up @@ -219,33 +237,69 @@ def evaluate(self, X: Solution) -> List:
List
The objective value for each solution in `X`
"""
incumbent = self.xopt
for i in enumerate(X):
x = X[i]

#Convert to internal representation to allow storing n_eval
X = self._to_geno(X)
#Need to explicitly check remaining budget to not exceed in intensification
remaining_budget = self.max_FEs - self.eval_count
if self.xopt is None:
#First iteration, no incumbant yet
incumbent = X[0]
else:
incumbent = self.xopt


for x in X:
# add one more sampling point to the incumbent
if incumbent.n_eval < self._max_r:
incumbent.fitness = (
incumbent.fitness * incumbent.n_eval + self.obj_fun(incumbent)
) / (incumbent.n_eval + 1)
incumbent.n_eval += 1
if remaining_budget > 0:
inc_pheno = self._to_pheno(incumbent)
if self._eval_type == 'dict':
inc_pheno = inc_pheno[0]
if incumbent.n_eval == 0:
#check for 0 since otherwise fitness keeps being nan
incumbent.fitness = self.obj_fun(inc_pheno)
incumbent.n_eval = 1
remaining_budget -= 1
self.eval_count += 1
else:
incumbent.fitness = (
incumbent.fitness * incumbent.n_eval + self.obj_fun(inc_pheno)
) / (incumbent.n_eval + 1)
incumbent.n_eval += 1
remaining_budget -= 1
self.eval_count += 1
else:
break

N = 1
while True:
_N = min(N, incumbent.n_eval - x.n_eval)
if _N != 0:
_val = np.sum([self.obj_fun(x) for _ in range(_N)])
x.fitness = (x.fitness * x.n_eval + _val) / (x.n_eval + _N)
x.n_eval += _N

_N = min(N, incumbent.n_eval[0] - x.n_eval[0])
_N = min(_N, remaining_budget)
if _N > 0:
if self._eval_type == 'dict':
vals = [self.obj_fun(self._to_pheno(x)[0]) for _ in range(_N)]
else:
vals = [self.obj_fun(self._to_pheno(x)) for _ in range(_N)]
remaining_budget -= _N
self.eval_count += _N
_val = np.sum(vals)
if x.n_eval == 0:
#check for 0 since otherwise fitness keeps being nan
x.fitness = _val
x.n_eval = _N
else:
x.fitness = (x.fitness * x.n_eval + _val) / (x.n_eval + _N)
x.n_eval += _N

if self._compare(x.fitness, incumbent.fitness):
break
elif _N == 0:
incumbent = x
break
else:
N *= 2

x.n_eval -= 1 #TODO: Fix this. Currently here because tell always adds 1
self.eval_count -= 1 #TODO: Fix this. Currently here because tell always adds 1
return X.fitness.tolist()

def _create_acquisition(
Expand Down
2 changes: 1 addition & 1 deletion bayes_optim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Callable, Any, Tuple, List, Union, Optional

from . import AcquisitionFunction, Surrogate
from .BayesOpt import BO, ParallelBO, NoisyBO, AnnealingBO
from .BayesOpt import BO, ParallelBO, IntensificationBO, AnnealingBO
from .Solution import Solution
from .Surrogate import RandomForest, GaussianProcess, trend
from .SearchSpace import SearchSpace, OrdinalSpace, ContinuousSpace, NominalSpace
Expand Down
19 changes: 12 additions & 7 deletions bayes_optim/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,9 @@ def __init__(
self._set_aux_vars()
self._set_internal_optimization(**acquisition_optimization)
self.warm_data = warm_data

self.xopt = None
self.fopt = None

@property
def acquisition_fun(self):
return self._acquisition_fun
Expand Down Expand Up @@ -463,7 +465,10 @@ def _compare(self, f1, f2):
def run(self):
while not self.check_stop():
self.step()
return self.xopt, self.fopt, self.stop_dict
_xopt_write = self._to_pheno(self.xopt)
if self._eval_type == 'dict':
_xopt_write = _xopt_write[0]
return _xopt_write, self.fopt, self.stop_dict

def step(self):
X = self.ask()
Expand Down Expand Up @@ -552,13 +557,13 @@ def tell(self, X, func_vals, warm_start=False):
X.to_csv(self.data_file, header=False, append=True)

self.fopt = self._get_best(self.data.fitness)
_xopt = self.data[np.where(self.data.fitness == self.fopt)[0][0]]
self.xopt = self._to_pheno(_xopt)
self.xopt = self.data[np.where(self.data.fitness == self.fopt)[0][0]]
_xopt_write = self._to_pheno(self.xopt)
if self._eval_type == 'dict':
self.xopt = self.xopt[0]
_xopt_write = _xopt_write[0]

self._logger.info('fopt: {}'.format(self.fopt))
self._logger.info('xopt: {}'.format(self.xopt))
self._logger.info('xopt: {}'.format(_xopt_write))

if not self.model.is_fitted:
self._fBest_DoE = copy(self.fopt) # the best f-value from DoE
Expand Down Expand Up @@ -586,7 +591,7 @@ def pre_eval_check(self, X):
def post_eval_check(self, X):
_ = np.isnan(X.fitness) | np.isinf(X.fitness)
if np.any(_):
self._logger.warn(
self._logger.warning(
'{} candidate solutions are removed '
'due to falied fitness evaluation: \n{}'.format(sum(_), str(X[_, :]))
)
Expand Down
53 changes: 52 additions & 1 deletion unittest/test_BO.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pytest
sys.path.insert(0, '../')

from bayes_optim import ParallelBO, BO, ContinuousSpace, OrdinalSpace, NominalSpace
from bayes_optim import ParallelBO, BO, ContinuousSpace, OrdinalSpace, NominalSpace, IntensificationBO
from bayes_optim.Surrogate import trend, GaussianProcess, RandomForest

np.random.seed(123)
Expand Down Expand Up @@ -162,6 +162,57 @@ def obj_fun(x):
)
xopt, fopt, stop_dict = opt.run()

print('xopt: {}'.format(xopt))
print('fopt: {}'.format(fopt))
print('stop criteria: {}'.format(stop_dict))


# Test for intensification BO is the same as the mixed_param test, can probably merge them in future
@pytest.mark.parametrize("eval_type", ['list', 'dict', 'dataframe']) # type: ignore
def test_intensification(eval_type):
dim_r = 2 # dimension of the real values
if eval_type == 'dict' or eval_type == 'dataframe':
def obj_fun(x):
#Do explicit type-casting since dataframe rows might be strings otherwise
x_r = np.array([float(x['continuous_%d'%i]) for i in range(dim_r)])
x_i = int(x['ordinal'])
x_d = x['nominal']
if type(x_d) != str: #TODO: Check why this is needed here
x_d = x_d[0]
_ = 0 if x_d == 'OK' else 1
temp = np.random.normal(1,0.1)*np.sum(x_r ** 2) + abs(x_i - 10) / 123. + _ * 2
return temp
elif eval_type == 'list':
def obj_fun(x):
x_r = np.array([x[i] for i in range(dim_r)])
x_i = x[-2]
x_d = x[-1]
_ = 0 if x_d == 'OK' else 1
temp = np.random.normal(1,0.1)*np.sum(x_r ** 2) + abs(x_i - 10) / 123. + _ * 2
return temp
else:
raise NotImplemented
search_space = ContinuousSpace([-5, 5], var_name='continuous') * dim_r + \
OrdinalSpace([5, 15], var_name='ordinal') + \
NominalSpace(['OK', 'A', 'B', 'C', 'D', 'E', 'F', 'G'], var_name='nominal')

model = RandomForest(levels=search_space.levels)

opt = IntensificationBO(
search_space=search_space,
obj_fun=obj_fun,
model=model,
max_FEs=60,
DoE_size=5, # the initial DoE size
eval_type=eval_type,
acquisition_fun='MGFI',
acquisition_par={'t' : 2},
n_job=3, # number of processes
n_point=3, # number of the candidate solution proposed in each iteration
verbose=False # turn this off, if you prefer no output
)
xopt, fopt, stop_dict = opt.run()

print('xopt: {}'.format(xopt))
print('fopt: {}'.format(fopt))
print('stop criteria: {}'.format(stop_dict))