Source code for deephyper.search.nas._regevomixed

import ConfigSpace as CS
from deephyper.problem import HpProblem
from deephyper.search.nas._regevo import RegularizedEvolution


[docs]class RegularizedEvolutionMixed(RegularizedEvolution): """Extention of the `Regularized evolution <https://arxiv.org/abs/1802.01548>`_ neural architecture search to the case of joint hyperparameter and neural architecture search. Args: problem (NaProblem): Neural architecture search problem describing the search space to explore. evaluator (Evaluator): An ``Evaluator`` instance responsible of distributing the tasks. random_state (int, optional): Random seed. Defaults to None. log_dir (str, optional): Log directory where search's results are saved. Defaults to ".". verbose (int, optional): Indicate the verbosity level of the search. Defaults to 0. population_size (int, optional): the number of individuals to keep in the population. Defaults to 100. sample_size (int, optional): the number of individuals that should participate in each tournament. Defaults to 10. """ def __init__( self, problem, evaluator, random_state: int = None, log_dir: str = ".", verbose: int = 0, population_size: int = 100, sample_size: int = 10, **kwargs, ): super().__init__( problem, evaluator, random_state, log_dir, verbose, population_size, sample_size, ) # Setup na_search_space = self._problem.build_search_space() self.hp_space = self._problem._hp_space # !hyperparameters self.hp_size = len(self.hp_space.space.get_hyperparameter_names()) self.na_space = HpProblem() self.na_space._space.seed(self._random_state.get_state()[1][0]) for i, (low, high) in enumerate(na_search_space.choices()): self.na_space.add_hyperparameter((low, high), name=f"vnode_{i:05d}") self._space = CS.ConfigurationSpace(seed=self._random_state.get_state()[1][0]) self._space.add_configuration_space( prefix="1", configuration_space=self.hp_space.space ) self._space.add_configuration_space( prefix="2", configuration_space=self.na_space.space ) self._space_size = len(self._space.get_hyperparameter_names()) def _saved_keys(self, job): res = {"arch_seq": str(job.config["arch_seq"])} hp_names = self._problem._hp_space._space.get_hyperparameter_names() for hp_name in hp_names: if hp_name == "loss": res["loss"] = job.config["loss"] else: res[hp_name] = job.config["hyperparameters"][hp_name] return res def _search(self, max_evals, timeout): num_evals_done = 0 # Filling available nodes at start self._evaluator.submit(self._gen_random_batch(size=self._evaluator.num_workers)) # Main loop while max_evals < 0 or num_evals_done < max_evals: # Collecting finished evaluations new_results = list(self._evaluator.gather("BATCH", size=1)) num_received = len(new_results) if num_received > 0: self._population.extend(new_results) self._evaluator.dump_evals( saved_keys=self._saved_keys, log_dir=self._log_dir ) num_evals_done += num_received if num_evals_done >= max_evals: break # If the population is big enough evolve the population if len(self._population) == self._population_size: children_batch = [] # For each new parent/result we create a child from it for _ in range(num_received): # select_sample indexes = self._random_state.choice( self._population_size, self._sample_size, replace=False ) sample = [self._population[i] for i in indexes] # select_parent parent = self._select_parent(sample) # copy_mutate_parent child = self._copy_mutate_arch(parent) # add child to batch children_batch.append(child) # submit_childs self._evaluator.submit(children_batch) else: # If the population is too small keep increasing it new_batch = self._gen_random_batch(size=num_received) self._evaluator.submit(new_batch) def _select_parent(self, sample: list) -> dict: cfg, _ = max(sample, key=lambda x: x[1]) return cfg def _gen_random_batch(self, size: int) -> list: def sample(hp, size): return [hp.sample(self._space.random) for _ in range(size)] batch = [] iterator = zip(*(sample(hp, size) for hp in self._space.get_hyperparameters())) for x in iterator: cfg = self._problem.gen_config( list(x[self.hp_size :]), list(x[: self.hp_size]) ) batch.append(cfg) return batch def _copy_mutate_arch(self, parent_cfg: dict) -> dict: """ # ! Time performance is critical because called sequentialy Args: parent_arch (list(int)): embedding of the parent's architecture. Returns: dict: embedding of the mutated architecture of the child. """ hp_x = self._problem.extract_hp_values(parent_cfg) x = hp_x + parent_cfg["arch_seq"] i = self._random_state.choice(self._space_size) hp = self._space.get_hyperparameters()[i] x[i] = hp.sample(self._space.random) child_cfg = self._problem.gen_config(x[self.hp_size :], x[: self.hp_size]) return child_cfg