forked from huawei-noah/HEBO
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
v0.3.2, add VCBO and evolutionary algorithm
- Loading branch information
1 parent
9fcb7d7
commit 8ba60a6
Showing
7 changed files
with
534 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"# Evolutionary Optimization with HEBO API\n", | ||
"\n", | ||
"Though we mainly focus on Bayesian optimsation algorithms, we also include evolutionary optimisation algorithms in HEBO, the evolutionary algorithm is a wrapper of algorithms from `pymoo`, below is an example that applies differential evolution algorithm to optimise the Ackley function." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 10, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"from hebo.design_space import DesignSpace\n", | ||
"from hebo.optimizers.evolution import Evolution\n", | ||
"from hebo.benchmarks.synthetic_benchmarks import Ackley" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 11, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"prob = Ackley(dim = 2)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 12, | ||
"metadata": {}, | ||
"outputs": [ | ||
{ | ||
"name": "stdout", | ||
"output_type": "stream", | ||
"text": [ | ||
" 1 | 100 | 9.801246852 | 2.02280E+01\n", | ||
" 2 | 200 | 2.909631464 | 1.69228E+01\n", | ||
" 3 | 300 | 2.815217476 | 1.32613E+01\n", | ||
" 4 | 400 | 2.646412912 | 9.695341586\n", | ||
" 5 | 500 | 1.242801428 | 6.651983101\n", | ||
" 6 | 600 | 0.209210787 | 4.415596345\n", | ||
" 7 | 700 | 0.072168031 | 2.681911917\n", | ||
" 8 | 800 | 0.029901222 | 1.706953753\n", | ||
" 9 | 900 | 0.009651858 | 0.915311097\n", | ||
" 10 | 1000 | 0.009651858 | 0.442287468\n", | ||
" 11 | 1100 | 0.009651858 | 0.167843522\n", | ||
" 12 | 1200 | 0.008691742 | 0.064215683\n", | ||
" 13 | 1300 | 0.003673483 | 0.031874574\n", | ||
" 14 | 1400 | 0.000484503 | 0.015767501\n", | ||
" 15 | 1500 | 0.000196722 | 0.008707443\n", | ||
" 16 | 1600 | 0.000120490 | 0.004780801\n", | ||
" 17 | 1700 | 0.000120268 | 0.002420470\n", | ||
" 18 | 1800 | 0.000119337 | 0.001064583\n", | ||
" 19 | 1900 | 0.000119337 | 0.000527714\n", | ||
" 20 | 2000 | 0.000079943 | 0.000273763\n", | ||
" 21 | 2100 | 2.30069E-06 | 0.000152152\n", | ||
" 22 | 2200 | 1.03498E-06 | 0.000090650\n", | ||
" 23 | 2300 | 1.03498E-06 | 0.000056089\n", | ||
" 24 | 2400 | 1.02798E-06 | 0.000029375\n", | ||
" 25 | 2500 | 1.02798E-06 | 0.000014847\n", | ||
" 26 | 2600 | 1.94189E-07 | 7.05685E-06\n", | ||
" 27 | 2700 | 1.93329E-07 | 3.17235E-06\n", | ||
" 28 | 2800 | 1.84926E-08 | 1.49617E-06\n", | ||
" 29 | 2900 | 1.32598E-08 | 7.01790E-07\n", | ||
" 30 | 3000 | 1.04498E-08 | 3.34182E-07\n", | ||
"After iter 30, evaluated 3000, best_y is 1.0449771270515384e-08\n" | ||
] | ||
} | ||
], | ||
"source": [ | ||
"opt = Evolution(prob.space, num_obj = 1, num_constr = 0, algo = 'de', verbose = True)\n", | ||
"n_eval = 0\n", | ||
"for i in range(30):\n", | ||
" rec = opt.suggest()\n", | ||
" obs = prob(rec)\n", | ||
" n_eval += rec.shape[0]\n", | ||
" opt.observe(rec, obs)\n", | ||
"print(f'After iter {i+1}, evaluated {n_eval}, best_y is {opt.best_y.squeeze()}')" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 13, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"# Copyright (C) 2020. Huawei Technologies Co., Ltd. All rights reserved.\n", | ||
"\n", | ||
"# This program is free software; you can redistribute it and/or modify it under\n", | ||
"# the terms of the MIT license.\n", | ||
"\n", | ||
"# This program is distributed in the hope that it will be useful, but WITHOUT ANY\n", | ||
"# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\n", | ||
"# PARTICULAR PURPOSE. See the MIT License for more details" | ||
] | ||
} | ||
], | ||
"metadata": { | ||
"kernelspec": { | ||
"display_name": "Python 3", | ||
"language": "python", | ||
"name": "python3" | ||
}, | ||
"language_info": { | ||
"codemirror_mode": { | ||
"name": "ipython", | ||
"version": 3 | ||
}, | ||
"file_extension": ".py", | ||
"mimetype": "text/x-python", | ||
"name": "python", | ||
"nbconvert_exporter": "python", | ||
"pygments_lexer": "ipython3", | ||
"version": "3.7.6" | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 4 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,4 +14,4 @@ | |
from . import optimizers | ||
from . import sklearn_tuner | ||
|
||
__version__ = "0.3.1" | ||
__version__ = "0.3.2" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
# Copyright (C) 2020. Huawei Technologies Co., Ltd. All rights reserved. | ||
|
||
# This program is free software; you can redistribute it and/or modify it under | ||
# the terms of the MIT license. | ||
|
||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY | ||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A | ||
# PARTICULAR PURPOSE. See the MIT License for more details. | ||
|
||
""" | ||
Evolutionary optimzation, pymoo wrapper with HEBO API | ||
""" | ||
|
||
import numpy as np | ||
import pandas as pd | ||
import torch | ||
|
||
from pymoo.factory import get_mutation, get_crossover, get_algorithm | ||
from pymoo.operators.mixed_variable_operator import MixedVariableMutation, MixedVariableCrossover | ||
from pymoo.core.problem import Problem | ||
from pymoo.config import Config | ||
Config.show_compile_hint = False | ||
|
||
from hebo.design_space.design_space import DesignSpace | ||
from .abstract_optimizer import AbstractOptimizer | ||
|
||
class DummyProb(Problem): | ||
def __init__(self, | ||
lb : np.ndarray, | ||
ub : np.ndarray, | ||
num_obj : int, | ||
num_constr : int | ||
): | ||
super().__init__(len(lb), xl = lb, xu = ub, n_obj = num_obj, n_constr = num_constr) | ||
|
||
def _evaluate(self, x, out : dict, *args, **kwargs): | ||
for k, v in kwargs.items(): | ||
out[k] = v | ||
|
||
class Evolution(AbstractOptimizer): | ||
support_parallel_opt = True | ||
support_constraint = True | ||
support_multi_objective = True | ||
support_combinatorial = True | ||
support_contextual = False | ||
|
||
def __init__(self, | ||
space : DesignSpace, | ||
num_obj : int, | ||
num_constr : int, | ||
algo : str = None, | ||
verbose : bool = False, | ||
**algo_conf | ||
): | ||
super().__init__(space) | ||
if algo is None: | ||
algo = 'ga' if num_obj == 1 else 'nsga2' | ||
|
||
self.num_obj = num_obj | ||
self.num_constr = num_constr | ||
if algo in ['ga', 'nsga2']: | ||
self.algo = get_algorithm(algo, mutation = self.get_mutation(), crossover = self.get_crossover(), **algo_conf) | ||
else: | ||
self.algo = get_algorithm(algo, **algo_conf) | ||
lb = self.space.opt_lb.numpy() | ||
ub = self.space.opt_ub.numpy() | ||
self.prob = DummyProb(lb, ub, self.num_obj, self.num_constr) | ||
self.algo.setup(self.prob, ('n_gen', np.inf), verbose = verbose) | ||
|
||
def suggest(self, n_suggestion = None, fix_input : dict = None): | ||
self.pop = self.algo.ask() | ||
pop_x = torch.from_numpy(self.pop.get('X').astype(float)).float() | ||
x = pop_x[:, :self.space.num_numeric] | ||
xe = pop_x[:, self.space.num_numeric:].round().long() | ||
rec = self.space.inverse_transform(x, xe) | ||
if fix_input is not None: | ||
for k, v in fix_input.items(): | ||
rec[k] = v | ||
x, xe = self.space.transform(rec) | ||
x_cat = torch.cat([x, xe.float()], dim = 1).numpy() | ||
self.pop.set('X', x_cat) | ||
return rec | ||
|
||
def observe(self, rec : pd.DataFrame, obs : np.ndarray): | ||
x, xe = self.space.transform(rec) | ||
x_cat = torch.cat([x, xe.float()], dim = 1).numpy() | ||
obj = obs[:, :self.num_obj] | ||
vio = obs[:, self.num_obj:] | ||
|
||
self.pop.set('X', x_cat) | ||
if self.num_constr > 0: | ||
self.algo.evaluator.eval(self.prob, self.pop, F = obj, G = vio) | ||
else: | ||
self.algo.evaluator.eval(self.prob, self.pop, F = obj) | ||
self.algo.tell(infills = self.pop) | ||
|
||
@property | ||
def best_x(self) -> pd.DataFrame: | ||
opt = torch.from_numpy(self.algo.opt.get('X')).float() | ||
x = opt[:, :self.space.num_numeric] | ||
xe = opt[:, self.space.num_numeric:].round().long() | ||
return self.space.inverse_transform(x, xe) | ||
|
||
@property | ||
def best_y(self) -> np.ndarray: | ||
opt = self.algo.opt | ||
best_y = opt.get('F') | ||
if self.num_constr > 0: | ||
vio = opt.get('G') | ||
best_y = np.hstack([best_y, vio]) | ||
return best_y | ||
|
||
def get_mutation(self): | ||
mask = [] | ||
for name in (self.space.numeric_names + self.space.enum_names): | ||
if self.space.paras[name].is_discrete_after_transform: | ||
mask.append('int') | ||
else: | ||
mask.append('real') | ||
|
||
mutation = MixedVariableMutation(mask, { | ||
'real' : get_mutation('real_pm', eta = 20), | ||
'int' : get_mutation('int_pm', eta = 20) | ||
}) | ||
return mutation | ||
|
||
def get_crossover(self): | ||
mask = [] | ||
for name in (self.space.numeric_names + self.space.enum_names): | ||
if self.space.paras[name].is_discrete_after_transform: | ||
mask.append('int') | ||
else: | ||
mask.append('real') | ||
|
||
crossover = MixedVariableCrossover(mask, { | ||
'real' : get_crossover('real_sbx', eta = 15, prob = 0.9), | ||
'int' : get_crossover('int_sbx', eta = 15, prob = 0.9) | ||
}) | ||
return crossover |
Oops, something went wrong.