Source code for optilab.optimizers.top_half_knn_ipop_cma_es

"""
Top-half KNN-IPOP-CMA-ES optimizer.

IPOP-CMA-ES enhanced with a KNN-based top-half metamodel: each generation,
the surrogate estimates all candidates and only the best half is evaluated
with the real objective function.
"""

from ..data_classes import Bounds, PointList
from ..functions import ObjectiveFunction
from ..functions.surrogate import KNNSurrogateObjectiveFunction
from ..metamodels import TopHalfMetamodel
from .cma_es import CmaEs
from .optimizer import Optimizer


[docs] class TopHalfKnnIpopCmaEs(CmaEs): """ Top-half KNN-IPOP-CMA-ES optimizer. Uses the :class:`TopHalfMetamodel` with a KNN surrogate: every generation the surrogate pre-screens all *lambda* candidates and only the best *mu* = *lambda* / 2 are evaluated with the real objective. Penalised values for the remaining candidates guarantee that CMA-ES updates its state exclusively from real evaluations. """ # pylint: disable=super-init-not-called, non-parent-init-called
[docs] def __init__( self, population_size: int, num_neighbors: int, buffer_size: int, ): """ Class constructor. Args: population_size: Starting size of the population. num_neighbors: Number of neighbours used by the KNN surrogate. buffer_size: Number of last evaluated points provided to the KNN surrogate for training. """ buffer_size = max(buffer_size, num_neighbors) Optimizer.__init__( self, f"th-knn{num_neighbors}b{buffer_size}-ipop-cma-es", population_size, {"num_neighbors": num_neighbors, "buffer_size": buffer_size}, )
# pylint: disable=duplicate-code
[docs] def optimize( self, function: ObjectiveFunction, bounds: Bounds, call_budget: int, tolerance: float, target: float = 0.0, ) -> PointList: """ Run a single optimisation of the provided objective function. Args: function: Objective function to optimise. bounds: Search space of the function. call_budget: Max number of calls to the objective function. tolerance: Tolerance of y value to accept a solution. target: Objective function value target, default 0. Returns: Results log from the optimisation. """ current_population_size = self.metadata.population_size metamodel = TopHalfMetamodel( self.metadata.population_size, self.metadata.population_size // 2, function, KNNSurrogateObjectiveFunction( self.metadata.hyperparameters["num_neighbors"] ), buffer_size=self.metadata.hyperparameters["buffer_size"], ) while not self._stop_external( metamodel.get_log(), current_population_size, call_budget, target, tolerance, ): es = self._spawn_cmaes( bounds, function.metadata.dim, current_population_size, len(bounds) / 2, ) while not self._stop( es, metamodel.get_log(), current_population_size, call_budget, target, tolerance, ): 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) current_population_size *= 2 metamodel.population_size *= 2 metamodel.mu *= 2 return metamodel.get_log()