Source code for autoflow.utils.config_space

import logging
import random
from copy import deepcopy
from typing import List, Union, Dict

import numpy as np
from ConfigSpace import ConfigurationSpace, Configuration, CategoricalHyperparameter, OrdinalHyperparameter, Constant, \
    UniformFloatHyperparameter, UniformIntegerHyperparameter

from autoflow.hdl.smac import _encode

logger = logging.getLogger(__name__)


[docs]def get_random_initial_configs(shps: ConfigurationSpace, n_configs, random_state=42) -> List[Configuration]: shps = deepcopy(shps) shps.seed(random_state) results = shps.sample_configuration(n_configs) if not isinstance(results, list): results = [results] return results
[docs]def replace_phps(shps: ConfigurationSpace, key, value): for hp in shps.get_hyperparameters(): if hp.__class__.__name__ == "Constant" and hp.name.endswith(key): hp.value = _encode(value)
[docs]def get_grid_initial_configs(shps: ConfigurationSpace, n_configs=-1, random_state=42): grid_phps = ConfigSpaceGrid(shps) grid_configs = grid_phps.generate_grid() if n_configs > 0: random.seed(random_state) grid_configs = random.sample(grid_configs, n_configs) logger.info(f"Length of grid_initial_configs = {len(grid_configs)}.") return grid_configs
[docs]def estimate_config_space_numbers(cs: ConfigurationSpace): result = 1 for config in cs.get_hyperparameters(): result *= (config.get_num_neighbors() + 1) return result
[docs]class ConfigSpaceGrid: def __init__(self, configuration_space: ConfigurationSpace, ): self.configuration_space = configuration_space
[docs] def generate_grid(self, num_steps_dict: Union[None, Dict[str, int]] = None, ) -> List[Configuration]: """ Generates a grid of Configurations for a given ConfigurationSpace. Can be used, for example, for grid search. Parameters ---------- configuration_space: :class:`~ConfigSpace.configuration_space.ConfigurationSpace` The Configuration space over which to create a grid of HyperParameter Configuration values. It knows the types for all parameter values. num_steps_dict: dict A dict containing the number of points to divide the grid side formed by Hyperparameters which are either of type UniformFloatHyperparameter or type UniformIntegerHyperparameter. The keys in the dict should be the names of the corresponding Hyperparameters and the values should be the number of points to divide the grid side formed by the corresponding Hyperparameter in to. Returns ------- list List containing Configurations. It is a cartesian product of tuples of HyperParameter values. Each tuple lists the possible values taken by the corresponding HyperParameter. Within the cartesian product, in each element, the ordering of HyperParameters is the same for the OrderedDict within the ConfigurationSpace. """ value_sets = [] # list of tuples: each tuple within is the grid values to be taken on by a Hyperparameter hp_names = [] for hp_name in self.configuration_space._children['__HPOlib_configuration_space_root__']: value_sets.append(self.get_value_set(num_steps_dict, hp_name)) hp_names.append(hp_name) unchecked_grid_pts = self.get_cartesian_product(value_sets, hp_names) checked_grid_pts = [] condtional_grid_lens = [] while len(unchecked_grid_pts) > 0: try: grid_point = Configuration(self.configuration_space, unchecked_grid_pts[0]) checked_grid_pts.append(grid_point) except ValueError as e: value_sets = [] hp_names = [] new_active_hp_names = [] for hp_name in unchecked_grid_pts[0]: # For loop over currently active HP names value_sets.append(tuple([unchecked_grid_pts[0][hp_name], ])) hp_names.append(hp_name) for new_hp_name in self.configuration_space._children[ hp_name]: # Checks the HPs already active for their children also being active if new_hp_name not in new_active_hp_names and new_hp_name not in unchecked_grid_pts[0]: all_cond_ = True for cond in self.configuration_space._parent_conditions_of[new_hp_name]: if not cond.evaluate(unchecked_grid_pts[0]): all_cond_ = False if all_cond_: new_active_hp_names.append(new_hp_name) for hp_name in new_active_hp_names: value_sets.append(self.get_value_set(num_steps_dict, hp_name)) hp_names.append(hp_name) if len( new_active_hp_names) > 0: # this check might not be needed, as there is always going to be a new active HP when in this except block? new_conditonal_grid = self.get_cartesian_product(value_sets, hp_names) condtional_grid_lens.append(len(new_conditonal_grid)) unchecked_grid_pts += new_conditonal_grid del unchecked_grid_pts[0] return checked_grid_pts
[docs] def get_value_set(self, num_steps_dict, hp_name): param = self.configuration_space.get_hyperparameter(hp_name) if isinstance(param, (CategoricalHyperparameter)): return param.choices elif isinstance(param, (OrdinalHyperparameter)): return param.sequence elif isinstance(param, Constant): return tuple([param.value, ]) elif isinstance(param, UniformFloatHyperparameter): if param.log: lower, upper = np.log([param.lower, param.upper]) else: lower, upper = param.lower, param.upper if num_steps_dict is not None: num_steps = num_steps_dict[param.name] grid_points = np.linspace(lower, upper, num_steps) else: grid_points = np.arange(lower, upper, param.q) # check for log and for rounding issues if param.log: grid_points = np.exp(grid_points) # Avoiding rounding off issues if grid_points[0] < param.lower: grid_points[0] = param.lower if grid_points[-1] > param.upper: grid_points[-1] = param.upper return tuple(grid_points) elif isinstance(param, UniformIntegerHyperparameter): if param.log: lower, upper = np.log([param.lower, param.upper]) else: lower, upper = param.lower, param.upper if num_steps_dict is not None: num_steps = num_steps_dict[param.name] grid_points = np.linspace(lower, upper, num_steps) else: grid_points = np.arange(lower, upper, param.q) # check for log and for rounding issues if param.log: grid_points = np.exp(grid_points) grid_points = grid_points.astype(int) # Avoiding rounding off issues if grid_points[0] < param.lower: grid_points[0] = param.lower if grid_points[-1] > param.upper: grid_points[-1] = param.upper return tuple(grid_points) else: raise TypeError("Unknown hyperparameter type %s" % type(param))
[docs] def get_cartesian_product(self, value_sets, hp_names): grid = [] import itertools for i, element in enumerate(itertools.product(*value_sets)): config_dict = {} for j, hp_name in enumerate(hp_names): config_dict[hp_name] = element[j] grid.append(config_dict) return grid