From ab9cb6af3094053e29fd56e1d6fe41775c8602af Mon Sep 17 00:00:00 2001 From: Hadar Shavit Date: Wed, 9 Oct 2024 13:11:09 +0000 Subject: [PATCH] Fix differential evolution --- .../maximizer/differential_evolution.py | 10 +++- .../initial_design/abstract_initial_design.py | 54 ----------------- smac/initial_design/latin_hypercube_design.py | 3 +- smac/initial_design/sobol_design.py | 4 +- smac/utils/configspace.py | 58 +++++++++++++++++++ 5 files changed, 70 insertions(+), 59 deletions(-) diff --git a/smac/acquisition/maximizer/differential_evolution.py b/smac/acquisition/maximizer/differential_evolution.py index 0f2ce15e6..855842783 100644 --- a/smac/acquisition/maximizer/differential_evolution.py +++ b/smac/acquisition/maximizer/differential_evolution.py @@ -5,6 +5,7 @@ from scipy.optimize._differentialevolution import DifferentialEvolutionSolver from smac.acquisition.maximizer import AbstractAcquisitionMaximizer +from smac.utils.configspace import transform_continuous_designs __copyright__ = "Copyright 2022, automl.org" __license__ = "3-clause BSD" @@ -36,7 +37,9 @@ def _maximize( def func(x: np.ndarray) -> np.ndarray: assert self._acquisition_function is not None - return -self._acquisition_function([Configuration(self._configspace, vector=x)]) + return -self._acquisition_function([transform_continuous_designs( + design=np.expand_dims(x, axis=0), origin="Diffrential Evolution", configspace=self._configspace + )[0]]) ds = DifferentialEvolutionSolver( func, @@ -58,8 +61,9 @@ def func(x: np.ndarray) -> np.ndarray: _ = ds.solve() for pop, val in zip(ds.population, ds.population_energies): - rc = Configuration(self._configspace, vector=pop) - rc.origin = "Acquisition Function Maximizer: Differential Evolution" + rc = transform_continuous_designs( + design=np.expand_dims(pop, axis=0), origin="Acquisition Function Maximizer: Differential Evolution", configspace=self._configspace + )[0] configs.append((-val, rc)) configs.sort(key=lambda t: t[0]) diff --git a/smac/initial_design/abstract_initial_design.py b/smac/initial_design/abstract_initial_design.py index 4b5224933..c9dc9376b 100644 --- a/smac/initial_design/abstract_initial_design.py +++ b/smac/initial_design/abstract_initial_design.py @@ -155,57 +155,3 @@ def select_configurations(self) -> list[Configuration]: def _select_configurations(self) -> list[Configuration]: """Selects the initial configurations, depending on the implementation of the initial design.""" raise NotImplementedError - - def _transform_continuous_designs( - self, design: np.ndarray, origin: str, configspace: ConfigurationSpace - ) -> list[Configuration]: - """Transforms the continuous designs into a discrete list of configurations. - - Parameters - ---------- - design : np.ndarray - Array of hyperparameters originating from the initial design strategy. - origin : str | None, defaults to None - Label for a configuration where it originated from. - configspace : ConfigurationSpace - - Returns - ------- - configs : list[Configuration] - Continuous transformed configs. - """ - params = list(configspace.values()) - for idx, param in enumerate(params): - if isinstance(param, IntegerHyperparameter): - design[:, idx] = param.to_vector(param.to_value(design[:, idx])) - elif isinstance(param, NumericalHyperparameter): - continue - elif isinstance(param, Constant): - design_ = np.zeros(np.array(design.shape) + np.array((0, 1))) - design_[:, :idx] = design[:, :idx] - design_[:, idx + 1 :] = design[:, idx:] - design = design_ - elif isinstance(param, CategoricalHyperparameter): - v_design = design[:, idx] - v_design[v_design == 1] = 1 - 10**-10 - design[:, idx] = np.array(v_design * len(param.choices), dtype=int) - elif isinstance(param, OrdinalHyperparameter): - v_design = design[:, idx] - v_design[v_design == 1] = 1 - 10**-10 - design[:, idx] = np.array(v_design * len(param.sequence), dtype=int) - else: - raise ValueError("Hyperparameter not supported when transforming a continuous design.") - - configs = [] - for vector in design: - try: - conf = deactivate_inactive_hyperparameters( - configuration=None, configuration_space=configspace, vector=vector - ) - except ForbiddenValueError: - continue - - conf.origin = origin - configs.append(conf) - - return configs diff --git a/smac/initial_design/latin_hypercube_design.py b/smac/initial_design/latin_hypercube_design.py index 8cd3cf2a9..6a073299a 100644 --- a/smac/initial_design/latin_hypercube_design.py +++ b/smac/initial_design/latin_hypercube_design.py @@ -5,6 +5,7 @@ from scipy.stats.qmc import LatinHypercube from smac.initial_design.abstract_initial_design import AbstractInitialDesign +from smac.utils.configspace import transform_continuous_designs __copyright__ = "Copyright 2022, automl.org" __license__ = "3-clause BSD" @@ -25,6 +26,6 @@ def _select_configurations(self) -> list[Configuration]: lhd = LatinHypercube(d=len(params) - constants, seed=self._rng.randint(0, 1000000)).random(n=self._n_configs) - return self._transform_continuous_designs( + return transform_continuous_designs( design=lhd, origin="Initial Design: Latin Hypercube", configspace=self._configspace ) diff --git a/smac/initial_design/sobol_design.py b/smac/initial_design/sobol_design.py index 1c3c646f5..a5c7998d9 100644 --- a/smac/initial_design/sobol_design.py +++ b/smac/initial_design/sobol_design.py @@ -9,6 +9,8 @@ from scipy.stats.qmc import Sobol from smac.initial_design.abstract_initial_design import AbstractInitialDesign +from smac.utils.configspace import transform_continuous_designs + __copyright__ = "Copyright 2022, automl.org" __license__ = "3-clause BSD" @@ -43,6 +45,6 @@ def _select_configurations(self) -> list[Configuration]: warnings.simplefilter("ignore") sobol = sobol_gen.random(self._n_configs) - return self._transform_continuous_designs( + return transform_continuous_designs( design=sobol, origin="Initial Design: Sobol", configspace=self._configspace ) diff --git a/smac/utils/configspace.py b/smac/utils/configspace.py index 78201f923..1a3612798 100644 --- a/smac/utils/configspace.py +++ b/smac/utils/configspace.py @@ -16,7 +16,10 @@ OrdinalHyperparameter, UniformFloatHyperparameter, UniformIntegerHyperparameter, + IntegerHyperparameter, + NumericalHyperparameter, ) +from ConfigSpace.util import ForbiddenValueError, deactivate_inactive_hyperparameters from ConfigSpace.util import get_one_exchange_neighbourhood __copyright__ = "Copyright 2022, automl.org" @@ -182,6 +185,61 @@ def print_config_changes( logger.debug(msg) +def transform_continuous_designs( + design: np.ndarray, origin: str, configspace: ConfigurationSpace + ) -> list[Configuration]: + """Transforms the continuous designs into a discrete list of configurations. + + Parameters + ---------- + design : np.ndarray + Array of hyperparameters originating from the initial design strategy. + origin : str | None, defaults to None + Label for a configuration where it originated from. + configspace : ConfigurationSpace + + Returns + ------- + configs : list[Configuration] + Continuous transformed configs. + """ + params = configspace.get_hyperparameters() + for idx, param in enumerate(params): + if isinstance(param, IntegerHyperparameter): + design[:, idx] = param._inverse_transform(param._transform(design[:, idx])) + elif isinstance(param, NumericalHyperparameter): + continue + elif isinstance(param, Constant): + design_ = np.zeros(np.array(design.shape) + np.array((0, 1))) + design_[:, :idx] = design[:, :idx] + design_[:, idx + 1 :] = design[:, idx:] + design = design_ + elif isinstance(param, CategoricalHyperparameter): + v_design = design[:, idx] + v_design[v_design == 1] = 1 - 10**-10 + design[:, idx] = np.array(v_design * len(param.choices), dtype=int) + elif isinstance(param, OrdinalHyperparameter): + v_design = design[:, idx] + v_design[v_design == 1] = 1 - 10**-10 + design[:, idx] = np.array(v_design * len(param.sequence), dtype=int) + else: + raise ValueError("Hyperparameter not supported when transforming a continuous design.") + + configs = [] + for vector in design: + try: + conf = deactivate_inactive_hyperparameters( + configuration=None, configuration_space=configspace, vector=vector + ) + except ForbiddenValueError: + continue + + conf.origin = origin + configs.append(conf) + + return configs + + # def check_subspace_points( # X: np.ndarray, # cont_dims: np.ndarray | list = [],