Skip to content

Commit aeff20e

Browse files
committed
Adding CMA-ES (both inside MVRSM and separate) and Hospital simulator problem
1 parent 6b02fdf commit aeff20e

File tree

8 files changed

+275
-20
lines changed

8 files changed

+275
-20
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from .base import BaseProblem
2+
3+
import subprocess
4+
import platform
5+
import numpy as np
6+
import os
7+
8+
class DockerHospitalBenchmarkProblem(BaseProblem):
9+
10+
def __init__(self, name, d, lbs, ubs, vartype, direction, errval):
11+
self.name = name
12+
self.d = d
13+
self.lb = np.asarray(lbs)
14+
self.ub = np.asarray(ubs)
15+
self.vt = np.asarray(vartype)
16+
self.direction = 1 if direction == "min" else -1
17+
self.errval = errval
18+
19+
if os.path.exists("./evaluate.sh"):
20+
self.evalCommand = ["./evaluate.sh"]
21+
elif os.path.exists("/evaluate.sh"):
22+
self.evalCommand = ["/evaluate.sh"]
23+
else:
24+
# Note: experiment should be ran with sudo under linux to allow this command to work,
25+
# or under an user in the docker group.
26+
self.evalCommand = ["docker", "run", "--rm", "mrebolle/r-geccoc:Track1", "-c"]
27+
28+
def evaluate(self, xs):
29+
concatenatedCandidate = ",".join(["%.8f" % x if xvt == 'cont' else "%i" % x for (x, xvt) in zip(xs, self.vt)])
30+
parsedCandidate = f"Rscript objfun.R \"{concatenatedCandidate}\""
31+
cmd = f"{self.evalCommand + [parsedCandidate]}"
32+
print(f"Running '{cmd}'")
33+
# res = subprocess.check_output(cmd, shell=True)
34+
res = subprocess.check_output(self.evalCommand + [parsedCandidate])
35+
reslast = res.strip().split(b"\n")[-1]
36+
print(f"Result: {res}. Objective: {reslast}")
37+
try:
38+
return self.direction * float(reslast)
39+
except:
40+
return self.errval
41+
42+
def lbs(self):
43+
return self.lb
44+
45+
def ubs(self):
46+
return self.ub
47+
48+
def vartype(self):
49+
return self.vt
50+
51+
def dims(self):
52+
return self.d
53+
54+
def __str__(self):
55+
return f"Hospital()"
56+
57+
Hospital = DockerHospitalBenchmarkProblem("hospital", 29,
58+
np.array([ 6, 7, 3, 3, 3, 5, 3, 3, 25, 17, 2, 1, 0.25, 0.05, 0.07, 0.005, 0.07, 1e-04, 0.08, 0.25, 0.08, 0.5, 1e-6, 2, 1e-6, 1e-6, 1, 2, 0.5 ]),
59+
np.array([14, 13, 7, 9, 7, 9, 5, 7, 35, 25, 5, 7, 2 , 0.15, 0.11, 0.02 , 0.13, 0.002, 0.12, 0.35, 0.12, 0.9, 0.01, 4, 1.1, 0.0625, 2, 5, 0.75]),
60+
['cont'] * 29, "min", 10000.0)
61+
62+
dockerhospitalsimbenches = [Hospital]

expensiveoptimbenchmark/run_experiment.py

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,16 @@ def generate_synthetic(args):
292292
for dockerproblem in dockersimbenches
293293
})
294294

295+
from problems.DockerHospitalBenchmark import dockerhospitalsimbenches
296+
problems.update({
297+
dockerproblem.name.lower(): {
298+
'args': set(),
299+
'defaults': {},
300+
'constructor': generate_construct_synthetic(dockerproblem)
301+
}
302+
for dockerproblem in dockerhospitalsimbenches
303+
})
304+
295305
def nop(*x, **y):
296306
pass
297307

@@ -325,6 +335,7 @@ def execute_IDONE(params, problem, max_eval, log):
325335
assert rand_evals >= 1, "IDONE requires at least one initial random evaluation."
326336
return optimize_IDONE(problem, max_eval, rand_evals=rand_evals, model=type_model, binarize_categorical=binarize_categorical, binarize_int=binarize_int, sampling=sampling, enable_scaling=enable_scaling, log=log, exploration_prob=expl_prob, idone_log=idone_log)
327337

338+
## MVRSM
328339
def execute_MVRSM(params, problem, max_eval, log):
329340
from solvers.MVRSM.wMVRSM import optimize_MVRSM
330341
if params['--model'] not in ['basic', 'advanced']:
@@ -333,21 +344,27 @@ def execute_MVRSM(params, problem, max_eval, log):
333344
raise ValueError("--binarize-categorical should be a boolean.")
334345
if params['--scaling'] not in ['true', 't', 'yes', 'y', 'false', 'f', 'no', 'n']:
335346
raise ValueError("--scaling should be a boolean.")
347+
if params['--optimizer'] not in ['adam', 'L-BFGS-B', 'CMA-ES']:
348+
raise ValueError("Valid optimizers are `adam` and 'L-BFGS-B'")
336349

337350
type_model = params['--model']
338351
binarize_categorical = params['--binarize-categorical'] in ['true','t', 'yes', 'y']
339352
enable_scaling = params['--scaling'] in ['true','t', 'yes', 'y']
340353
rand_evals = int(params['--rand-evals']) - 1
354+
optimizer = params.get('--optimizer')
355+
bound_h = params['--boundary-handler']
341356
assert rand_evals >= 0, "MVRSM requires at least one initial random evaluation."
342357

343-
return optimize_MVRSM(problem, max_eval, rand_evals=rand_evals, model=type_model, binarize_categorical=binarize_categorical, enable_scaling=enable_scaling, log=log)
358+
return optimize_MVRSM(problem, max_eval, rand_evals=rand_evals, model=type_model, binarize_categorical=binarize_categorical, enable_scaling=enable_scaling, optimizer=optimizer, bound_h=bound_h, log=log)
344359

345-
# SA
360+
## SA
346361
def execute_SA(params, problem, max_eval, log):
347362
from solvers.SA.wSA import optimize_SA
348363

349364
return optimize_SA(problem, max_eval, log=log)
350365

366+
367+
## DONE
351368
def check_DONEjl():
352369
from solvers.DONEjl.wDONEjl import minimize_DONEjl
353370
import numpy as np
@@ -376,7 +393,7 @@ def execute_DONEjl(params, problem, max_eval, log):
376393

377394
return optimize_DONEjl(problem, rand_evals, max_eval, hyperparams, log=log)
378395

379-
# Hyperopt TPE
396+
## Hyperopt TPE
380397
def execute_hyperopt(params, problem, max_eval, log):
381398
from solvers.hyperopt.whyperopt import optimize_hyperopt_tpe
382399
rand_evals = int(params['--rand-evals'])
@@ -396,7 +413,7 @@ def execute_hyperopt_rnd(params, problem, max_eval, log):
396413

397414
return optimize_hyperopt_rnd(problem, max_eval, cparams=conversion_params, log=log)
398415

399-
# pyGPGO
416+
## pyGPGO
400417
def execute_pygpgo(params, problem, max_eval, log):
401418
from solvers.pyGPGO.wpyGPGO import optimize_pyGPGO
402419
from pyGPGO.covfunc import matern32
@@ -410,14 +427,14 @@ def execute_pygpgo(params, problem, max_eval, log):
410427
acq = Acquisition(mode='ExpectedImprovement')
411428
return optimize_pyGPGO(problem, max_eval, gp, acq, random_init_evals=rand_evals, log=log)
412429

413-
# bayesian-optimization
430+
## bayesian-optimization
414431
def execute_bayesianoptimization(params, problem, max_eval, log):
415432
from solvers.bayesianoptimization.wbayesianoptimization import optimize_bayesian_optimization
416433
rand_evals = int(params['--rand-evals'])
417434
# TODO: Allow picking different configurations?
418435
return optimize_bayesian_optimization(problem, max_eval, random_init_evals=rand_evals, log=log)
419436

420-
# smac
437+
## smac
421438
def execute_smac(params, problem, max_eval, log):
422439
from solvers.smac.wsmac import optimize_smac
423440
rand_evals = int(params['--rand-evals'])
@@ -428,12 +445,20 @@ def check_smac():
428445
from solvers.smac.wsmac import optimize_smac
429446
pass
430447

431-
# CoCaBO
448+
## CoCaBO
432449
def execute_cocabo(params, problem, max_eval, log):
433450
from solvers.CoCaBO.wCoCaBo import optimize_CoCaBO
434451
rand_evals = int(params['--rand-evals'])
435452
return optimize_CoCaBO(problem, max_eval, init_points=rand_evals, log=log)
436453

454+
## CMA
455+
def execute_cma(params, problem, max_eval, log):
456+
from solvers.CMA.wCMA import optimize_CMA
457+
bound_h = params['--boundary-handler']
458+
459+
return optimize_CMA(problem, max_eval, bound_h=bound_h, binarize_categorical=False, log=log)
460+
461+
## Basinhopping
437462
def execute_scipy_basinhopping(params, problem, max_eval, log):
438463
from solvers.scipy.wscipy import optimize_basinhopping
439464
T = float(params['--T'])
@@ -442,6 +467,7 @@ def execute_scipy_basinhopping(params, problem, max_eval, log):
442467

443468
return optimize_basinhopping(problem, max_eval, T=T, stepsize=stepsize, localmethod=method, log=log)
444469

470+
## Local search
445471
def execute_scipy_local(params, problem, max_eval, log):
446472
from solvers.scipy.wscipy import optimize_scipy_local
447473
method = params['--method']
@@ -466,12 +492,14 @@ def execute_scipy_local(params, problem, max_eval, log):
466492
'check': nop
467493
},
468494
'mvrsm': {
469-
'args': {'--model', '--binarize-categorical', '--rand-evals', '--scaling'},
495+
'args': {'--model', '--binarize-categorical', '--rand-evals', '--scaling', '--optimizer', '--boundary-handler'},
470496
'defaults': {
471497
'--model': 'advanced',
472498
'--binarize-categorical': 'false',
473499
'--rand-evals': '5',
474-
'--scaling': 'true'
500+
'--scaling': 'true',
501+
'--optimizer': 'L-BFGS-B',
502+
'--boundary-handler': 'transform'
475503
},
476504
'executor': execute_MVRSM,
477505
'check': nop
@@ -544,6 +572,14 @@ def execute_scipy_local(params, problem, max_eval, log):
544572
'executor': execute_cocabo,
545573
'check': nop
546574
},
575+
'cma': {
576+
'args': {'--boundary-handler'},
577+
'defaults': {
578+
'--boundary-handler': 'penalty'
579+
},
580+
'executor': execute_cma,
581+
'check': nop
582+
},
547583
'scipy-basin': {
548584
'args': {'--T', '--stepsize', '--method'},
549585
'defaults': {
@@ -645,6 +681,7 @@ def execute_scipy_local(params, problem, max_eval, log):
645681
print(f" --binarize-categorical=<t|true|f|false> \t Whether to binarize categorical variables. (default: false)")
646682
print(f" --scaling=<t|true|f|false> \t Whether scaling is applied. (default: true)")
647683
print(f" --rand-evals=<int> \t Number of random evaluations. (default: 1)")
684+
print(f" --optimizer=<adam|LBFGSB> Optimizer for the surrogate model (default: LBFGSB)")
648685
print()
649686
# HyperOpt
650687
print(f" hyperopt")
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import numpy as np
2+
3+
from cma import BoundPenalty, BoundTransform, fmin, CMAOptions
4+
from ..utils import Monitor, Binarizer
5+
6+
def optimize_CMA(problem, max_evals, bound_h='transform', binarize_categorical=False, log=None):
7+
d = problem.dims()
8+
9+
vartypes = problem.vartype()
10+
11+
if binarize_categorical:
12+
raise Exception("While sort-of implemented, this implementation has issues. Please do not enable binarization.")
13+
14+
# Bounds
15+
lb = problem.lbs()
16+
ub = problem.ubs()
17+
18+
# Generate initial point, round the integers.
19+
20+
if binarize_categorical:
21+
b = Binarizer(problem.vartype()[perm] == 'cat', lb, ub)
22+
x0 = b.binarize(x0)
23+
# Get new bounds.
24+
lb = b.lbs()
25+
ub = b.ubs()
26+
# All new variables introduced by the binarized are 'integer'.
27+
# print(f"Binarization introduced {b.dout - b.din} new variables.")
28+
vartypes = vartypes[self.origin_mapping]
29+
30+
cmascale = (ub - lb)
31+
x0 = (0.5*(ub-lb) + lb) / cmascale
32+
not_continuous_variables = vartypes != "cont"
33+
x0[not_continuous_variables] = np.round(x0[not_continuous_variables])
34+
sigma0 = 1/4
35+
36+
mon = Monitor(f"CMAES{'/cbinarized' if binarize_categorical else ''}/{bound_h}", problem, log=log)
37+
def f(x):
38+
x = x * cmascale
39+
if binarize_categorical:
40+
xred = b.unbinarize(x)
41+
else:
42+
xred = x
43+
x = x[vartypes != "cont"]
44+
# print(xred)
45+
mon.commit_start_eval()
46+
r = problem.evaluate(xred)
47+
mon.commit_end_eval(xred, r)
48+
return r
49+
50+
# lb, ub, max_evals
51+
opts = CMAOptions()
52+
opts['bounds'] = [list(lb / cmascale), list(ub / cmascale)]
53+
opts['BoundaryHandler'] = BoundPenalty if bound_h == "penalty" else BoundTransform
54+
opts['maxfevals'] = max_evals
55+
opts['verbose'] = 0 # Supress printing!
56+
opts['integer_variables'] = [i for i,t in enumerate(vartypes) if t != 'cont']
57+
mon.start()
58+
res = fmin(f, x0 / cmascale, sigma0, options=opts)
59+
mon.end()
60+
61+
# """
62+
# - `res[0]` (`xopt`) -- best evaluated solution
63+
# - `res[1]` (`fopt`) -- respective function value
64+
# - `res[2]` (`evalsopt`) -- respective number of function evaluations
65+
# - `res[3]` (`evals`) -- number of overall conducted objective function evaluations
66+
# - `res[4]` (`iterations`) -- number of overall conducted iterations
67+
# - `res[5]` (`xmean`) -- mean of the final sample distribution
68+
# - `res[6]` (`stds`) -- effective stds of the final sample distribution
69+
# - `res[-3]` (`stop`) -- termination condition(s) in a dictionary
70+
# - `res[-2]` (`cmaes`) -- class `CMAEvolutionStrategy` instance
71+
# - `res[-1]` (`logger`) -- class `CMADataLogger` instance
72+
# """
73+
74+
return mon.best_x, mon.best_fitness, mon

0 commit comments

Comments
 (0)