Skip to content

Commit

Permalink
v0.3.2, add VCBO and evolutionary algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
Alaya-in-Matrix committed Dec 22, 2021
1 parent 9fcb7d7 commit 8ba60a6
Show file tree
Hide file tree
Showing 7 changed files with 534 additions and 2 deletions.
2 changes: 2 additions & 0 deletions HEBO/doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,10 @@ Features
design_space.ipynb
optimisation
sklearn_tuner

mo_constrained.ipynb
alebo_demo.ipynb
pymoo_evolution
custom

.. Indices and tables
Expand Down
124 changes: 124 additions & 0 deletions HEBO/doc/source/pymoo_evolution.ipynb
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
}
2 changes: 1 addition & 1 deletion HEBO/hebo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
from . import optimizers
from . import sklearn_tuner

__version__ = "0.3.1"
__version__ = "0.3.2"
139 changes: 139 additions & 0 deletions HEBO/hebo/optimizers/evolution.py
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
Loading

0 comments on commit 8ba60a6

Please sign in to comment.