Skip to content

Commit

Permalink
Implement KnnCmaEs optimizer and use it in experiment 002
Browse files Browse the repository at this point in the history
  • Loading branch information
mlojek committed Jan 13, 2025
1 parent b2cabda commit 05bb42a
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 159 deletions.
24 changes: 24 additions & 0 deletions docs/optilab.optimizers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,30 @@ optilab.optimizers package
Submodules
----------

optilab.optimizers.cma\_es module
---------------------------------

.. automodule:: optilab.optimizers.cma_es
:members:
:undoc-members:
:show-inheritance:

optilab.optimizers.knn\_cma\_es module
--------------------------------------

.. automodule:: optilab.optimizers.knn_cma_es
:members:
:undoc-members:
:show-inheritance:

optilab.optimizers.lmm\_cma\_es module
--------------------------------------

.. automodule:: optilab.optimizers.lmm_cma_es
:members:
:undoc-members:
:show-inheritance:

optilab.optimizers.optimizer module
-----------------------------------

Expand Down
110 changes: 0 additions & 110 deletions experiments/002_cmaes_knn_metamodel/cmaes_variations.py

This file was deleted.

61 changes: 12 additions & 49 deletions experiments/002_cmaes_knn_metamodel/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,87 +4,50 @@

# pylint: disable=import-error

from cmaes_variations import cma_es, knn_cma_es
from tqdm import tqdm
import numpy as np

from optilab.data_classes import Bounds
from optilab.functions.unimodal import SphereFunction
from optilab.functions.multimodal import RosenbrockFunction
from optilab.plotting import plot_ecdf_curves
from optilab.data_classes import OptimizationRun, OptimizerMetadata
from optilab.utils import dump_to_pickle
from optilab.optimizers import CmaEs, KnnCmaEs

if __name__ == "__main__":
# hyperparams:
DIM = 10
DIM = 2
POPSIZE = DIM * 4
NUM_NEIGHBORS = POPSIZE * 5
NUM_RUNS = 51
CALL_BUDGET = 1e4 * DIM
TOL = 1e-8
SIGMA0 = 1

# optimized problem
BOUNDS = Bounds(-100, 100)
FUNC = SphereFunction(DIM)

# perform optimization with vanilla cmaes
cmaes_logs = [
cma_es(FUNC, POPSIZE, CALL_BUDGET, BOUNDS, tolerance=TOL)
for _ in tqdm(range(NUM_RUNS), unit="run")
]
cmaes_optimizer = CmaEs(POPSIZE, SIGMA0)
cmaes_results = cmaes_optimizer.run_optimization(NUM_RUNS, FUNC, BOUNDS, CALL_BUDGET, TOL)

cmaes_run = OptimizationRun(
model_metadata=OptimizerMetadata(
name='CMA_ES',
population_size=POPSIZE,
hyperparameters={
'sigma0': 1
}
),
function_metadata=FUNC.get_metadata(),
bounds=BOUNDS,
tolerance=TOL,
logs=cmaes_logs
)

# perform optimization with knn cmaes
knn_cmaes_logs = [
knn_cma_es(FUNC, POPSIZE, CALL_BUDGET, BOUNDS, tolerance=TOL, num_neighbors=NUM_NEIGHBORS)
for _ in tqdm(range(NUM_RUNS), unit="run")
]

knn_cmaes_run = OptimizationRun(
model_metadata=OptimizerMetadata(
name='knn_CMA_ES',
population_size=POPSIZE,
hyperparameters={
'sigma0': 1,
'num_neighbor': NUM_NEIGHBORS
}
),
function_metadata=FUNC.get_metadata(),
bounds=BOUNDS,
tolerance=TOL,
logs=knn_cmaes_logs
)
knn_optimizer = KnnCmaEs(POPSIZE, SIGMA0, NUM_NEIGHBORS)
knn_results = knn_optimizer.run_optimization(NUM_RUNS, FUNC, BOUNDS, CALL_BUDGET, TOL)

# print stats
vanilla_times = [len(log) for log in cmaes_logs]
knn_times = [len(log) for log in knn_cmaes_logs]
vanilla_times = [len(log) for log in cmaes_results.logs]
knn_times = [len(log) for log in knn_results.logs]

print(f'vanilla {np.average(vanilla_times)} {np.std(vanilla_times)}')
print(f'knn {np.average(knn_times)} {np.std(knn_times)}')

# plot results
plot_ecdf_curves(
{
"cma-es": cmaes_logs,
"knn-cma-es": knn_cmaes_logs,
"cma-es": cmaes_results.logs,
"knn-cma-es": knn_results.logs,
},
n_dimensions=DIM,
savepath=f"ecdf_{FUNC.name}_{DIM}.png",
allowed_error=TOL
)

dump_to_pickle([cmaes_run, knn_cmaes_run], f'knn_reproduction_{FUNC.name}_{DIM}.pkl')
dump_to_pickle([cmaes_results, knn_results], f'knn_reproduction_{FUNC.name}_{DIM}.pkl')
1 change: 1 addition & 0 deletions src/optilab/optimizers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"""

from .cma_es import CmaEs
from .knn_cma_es import KnnCmaEs
from .lmm_cma_es import LmmCmaEs
from .optimizer import Optimizer
100 changes: 100 additions & 0 deletions src/optilab/optimizers/knn_cma_es.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""
KNN-CMA-ES optimizer. CMA-ES is enhanced with a KNN metamodel similar to the one from LMM-CMA-ES.
"""

# pylint: disable=too-many-arguments, too-many-positional-arguments, duplicate-code

import cma

from ..data_classes import Bounds, PointList
from ..functions import ObjectiveFunction
from ..functions.surrogate import KNNSurrogateObjectiveFunction
from ..metamodels import ApproximateRankingMetamodel
from .optimizer import Optimizer


class KnnCmaEs(Optimizer):
"""
KNN-CMA-ES optimizer. CMA-ES is enhanced with a KNN metamodel similar
to the one from LMM-CMA-ES.
"""

def __init__(self, population_size: int, sigma0: float, num_neighbors: int):
"""
Class constructor.
Args:
population_size (int): Size of the population.
sigma0 (float): Starting value of the sigma,
num_neighbors (int): Number of neighbors used by KNN metamodel.
"""
super().__init__(
"knn-cma-es",
population_size,
{"sigma0": sigma0, "num_neighbors": num_neighbors},
)

def optimize(
self,
function: ObjectiveFunction,
bounds: Bounds,
call_budget: int,
tolerance: float,
target: float = 0.0,
) -> PointList:
"""
Run a single optimization of provided objective function.
Args:
function (ObjectiveFunction): Objective function to optimize.
bounds (Bounds): Search space of the function.
call_budget (int): Max number of calls to the objective function.
tolerance (float): Tolerance of y value to count a solution as acceptable.
target (float): Objective function value target, default 0.
Returns:
PointList: Results log from the optimization.
"""
metamodel = ApproximateRankingMetamodel(
self.metadata.population_size,
self.metadata.population_size // 2,
function,
KNNSurrogateObjectiveFunction(
self.metadata.hyperparameters["num_neighbors"]
),
)

x0 = bounds.random_point(function.dim).x

es = cma.CMAEvolutionStrategy(
x0,
self.metadata.hyperparameters["sigma0"],
{
"popsize": self.metadata.population_size,
"bounds": bounds.to_list(),
"maxfevals": call_budget,
"ftarget": target,
"verbose": -9,
"tolfun": tolerance,
},
)

while (
metamodel.get_log().best_y() > tolerance
and len(metamodel.get_log()) <= call_budget
):
solutions = PointList.from_list(es.ask())

if (
len(metamodel.train_set)
< self.metadata.hyperparameters["num_neighbors"]
):
xy_pairs = metamodel.evaluate(solutions)
else:
metamodel.adapt(solutions)
xy_pairs = metamodel(solutions)

x, y = xy_pairs.pairs()
es.tell(x, y)

return metamodel.get_log()

0 comments on commit 05bb42a

Please sign in to comment.