Source code for syne_tune.optimizer.schedulers.searchers.regularized_evolution

import copy
import logging

import numpy as np

from collections import deque
from typing import Optional, List, Dict, Any
from dataclasses import dataclass

from syne_tune.config_space import config_space_size, non_constant_hyperparameter_keys
from syne_tune.optimizer.schedulers.searchers.single_objective_searcher import (
    SingleObjectiveBaseSearcher,
)


logger = logging.getLogger(__name__)


[docs] @dataclass class PopulationElement: score: float = 0 config: Dict[str, Any] = None results: List[float] = None
[docs] def mutate_config( config: Dict[str, Any], config_space: Dict[str, Any], rng: np.random.RandomState, num_try: int = 1000, ) -> Dict[str, Any]: child_config = copy.deepcopy(config) hp_name = rng.choice(non_constant_hyperparameter_keys(config_space)) for i in range(num_try): child_config[hp_name] = config_space[hp_name].sample(random_state=rng) # make sure that we actually sample a new value if child_config[hp_name] != config[hp_name]: break return child_config
[docs] def sample_random_config( config_space: Dict[str, Any], rng: np.random.RandomState ) -> Dict[str, Any]: return { k: v.sample() if hasattr(v, "sample") else v for k, v in config_space.items() }
[docs] class RegularizedEvolution(SingleObjectiveBaseSearcher): """ Implements the regularized evolution algorithm. The original implementation only considers categorical hyperparameters. For integer and float parameters we sample a new value uniformly at random. Reference: | Real, E., Aggarwal, A., Huang, Y., and Le, Q. V. | Regularized Evolution for Image Classifier Architecture Search. | In Proceedings of the Conference on Artificial Intelligence (AAAI’19) The code is based one the original regularized evolution open-source implementation: https://colab.research.google.com/github/google-research/google-research/blob/master/evolution/regularized_evolution_algorithm/regularized_evolution.ipynb Additional arguments on top of parent class :class:`~syne_tune.optimizer.schedulers.searchers.StochasticSearcher`: :param population_size: Size of the population, defaults to 100 :param sample_size: Size of the candidate set to obtain a parent for the mutation, defaults to 10 """ def __init__( self, config_space, points_to_evaluate: Optional[List[dict]] = None, population_size: int = 100, sample_size: int = 10, random_seed: int = None, ): super(RegularizedEvolution, self).__init__( config_space=config_space, random_seed=random_seed, points_to_evaluate=points_to_evaluate, ) assert ( config_space_size(config_space) != 1 ), f"config_space = {config_space} has size 1, does not offer any diversity" self.population_size = population_size self.sample_size = sample_size self.population = deque() self.random_state = np.random.RandomState(self.random_seed)
[docs] def suggest(self, **kwargs) -> Optional[dict]: initial_config = self._next_points_to_evaluate() if initial_config is not None: return initial_config if len(self.population) < self.population_size: config = sample_random_config( config_space=self.config_space, rng=self.random_state ) else: candidates = self.random_state.choice( list(self.population), size=self.sample_size ) parent = min(candidates, key=lambda i: i.score) config = mutate_config( parent.config, config_space=self.config_space, rng=self.random_state ) return config
[docs] def on_trial_complete( self, trial_id: int, config: Dict[str, Any], metric: float, ): # Add element to the population element = PopulationElement(score=metric, config=config) self.population.append(element) # Remove the oldest element of the population. if len(self.population) > self.population_size: self.population.popleft()