diff --git a/skopt/optimizer/optimizer.py b/skopt/optimizer/optimizer.py index c0430edc..52c950d7 100644 --- a/skopt/optimizer/optimizer.py +++ b/skopt/optimizer/optimizer.py @@ -4,6 +4,13 @@ from numbers import Number import ConfigSpace as CS +ccs_active = False +try: + import cconfigspace as CCS + ccs_active = True +except (ImportError, OSError) as a: + warnings.warn("CCS could not be loaded and is deactivated: " + str(a), category=ImportWarning) + import numpy as np import pandas as pd @@ -316,6 +323,12 @@ def __init__( if isinstance(self.base_estimator_, GaussianProcessRegressor): raise RuntimeError("GP estimator is not available with ConfigSpace!") + elif ccs_active and isinstance(dimensions, CCS.ConfigurationSpace): + self.ccs = dimensions + self.ccs.rng.seed = self.rng.get_state()[1][0] + + if isinstance(self.base_estimator_, GaussianProcessRegressor): + raise RuntimeError("GP estimator is not available with CCS!") else: # normalize space if GP regressor @@ -386,10 +399,16 @@ def copy(self, random_state=None): Set the random state of the copy. """ + dimens = None + if hasattr(self, "config_space"): + dimens = self.config_space + elif hasattr(self, "ccs"): + dimens = self.ccs + else: + dimens = self.space.dimensions + optimizer = Optimizer( - dimensions=self.config_space - if hasattr(self, "config_space") - else self.space.dimensions, + dimensions=dimens, base_estimator=self.base_estimator_, n_initial_points=self.n_initial_points_, initial_point_generator=self._initial_point_generator, @@ -601,6 +620,8 @@ def _filter_duplicated(self, samples): if hasattr(self, "config_space"): hps_names = self.config_space.get_hyperparameter_names() + elif hasattr(self, "ccs"): + hps_names = [x.name for x in self.ccs.hyperparameters] else: hps_names = self.space.dimension_names @@ -710,7 +731,7 @@ def _ask(self): next_x = self._next_x if next_x is not None: - if not self.space.is_config_space: + if not self.space.is_config_space and not self.space.is_ccs: min_delta_x = min( [self.space.distance(next_x, xi) for xi in self.Xi] ) @@ -751,6 +772,8 @@ def tell(self, x, y, fit=True): """ if self.space.is_config_space: pass + elif self.space.is_ccs: + pass else: check_x_in_space(x, self.space) @@ -924,7 +947,7 @@ def _tell(self, x, y, fit=True): # lbfgs should handle this but just in case there are # precision errors. if not self.space.is_categorical: - if not self.space.is_config_space: + if not self.space.is_config_space and not self.space.is_ccs: next_x = np.clip( next_x, transformed_bounds[:, 0], transformed_bounds[:, 1] ) diff --git a/skopt/space/space.py b/skopt/space/space.py index 406fe73a..694b308e 100644 --- a/skopt/space/space.py +++ b/skopt/space/space.py @@ -30,6 +30,13 @@ import ConfigSpace as CS from ConfigSpace.util import deactivate_inactive_hyperparameters +ccs_active = False +try: + import cconfigspace as CCS + ccs_active = True +except (ImportError, OSError) as a: + import warnings + warnings.warn("CCS could not be loaded and is deactivated: " + str(a), category=ImportWarning) from sklearn.impute import SimpleImputer @@ -896,8 +903,11 @@ def __init__(self, dimensions, model_sdv=None): # attributes used when a ConfigurationSpace from ConfigSpace is given self.is_config_space = False + self.is_ccs = False self.config_space_samples = None + self.ccs_samples = None self.config_space_explored = False + self.ccs_explored = False self.imp_const = SimpleImputer( missing_values=np.nan, strategy="constant", fill_value=-1000 @@ -970,6 +980,56 @@ def __init__(self, dimensions, model_sdv=None): else: raise ValueError("Unknown Hyperparameter type.") dimensions = space + elif ccs_active and isinstance(dimensions, CCS.ConfigurationSpace): + self.is_ccs = True + self.ccs = dimensions + self.hps_type = {} + + hps = self.ccs.hyperparameters + cond_hps = [x.name for x in self.ccs.conditional_hyperparameters] + + space = [] + for x in hps: + self.hps_names.append(x.name) + distrib = self.ccs.get_hyperparameter_distribution(x)[0] + if (isinstance(x, CCS.CategoricalHyperparameter) or + isinstance(x, CCS.OrdinalHyperparameter) or + isinstance(x, CCS.DiscreteHyperparameter)): + vals = list(x.values) + if x.name in cond_hps: + vals.append("NA") + if isinstance(distrib, CCS.RouletteDistribution): + param = Categorical(vals, prior=distrib.areas, name=x.name) + elif isinstance(distrib, CCS.UniformDistribution): + param = Categorical(vals, name=x.name) + else: + raise ValueError("Unsupported distribution") + space.append(param) + self.hps_type[x.name] = "Categorical" + elif isinstance(x, CCS.NumericalHyperparameter): + prior = "uniform" + lower = x.lower + upper = x.upper + t = x.data_type + if isinstance(distrib, CCS.UniformDistribution): + if distrib.scale_type == CCS.ccs_scale_type.LOGARITHMIC: + prior = "log-uniform" + elif isinstance(distrib, CCS.NormalDistribution): + prior = "normal" + if distrib.scale_type == CCS.ccs_scale_type.LOGARITHMIC: + raise ValueError("Unsupported 'log' transformation for CCS.NumericalHyperparameter with normal prior.") + else: + raise ValueError("Unsupported distribution") + if CCS.ccs_numeric_type.NUM_INTEGER: + param = Integer(lower, upper, prior=prior, name=x.name) + self.hps_type[x.name] = "Integer" + else: + param = Real(lower, upper, prior=prior, name=x.name) + self.hps_type[x.name] = "Real" + space.append(param) + else: + raise ValueError("Unknown Hyperparameter type") + dimensions = space self.dimensions = [check_dimension(dim) for dim in dimensions] def __eq__(self, other): @@ -1149,6 +1209,24 @@ def rvs(self, n_samples=1, random_state=None): req_points.append(point) return req_points + elif self.is_ccs: + confs = self.ccs.samples(n_samples) + hps = self.ccs.hyperparameters + points = [] + for conf in confs: + point = [] + values = conf.values + for i, hp in enumerate(hps): + val = values[i] + if CCS.ccs_inactive == val: + if self.hps_type[hp.name] == "Categorical": + val = "NA" + else: + val = np.nan + point.append(val) + points.append(point) + + return points else: if self.model_sdv is None: # Draw @@ -1240,7 +1318,7 @@ def transform(self, X): # Repack as an array Xt = np.hstack([np.asarray(c).reshape((len(X), -1)) for c in columns]) - if False and self.is_config_space: + if False and (self.is_config_space or self.is_ccs): self.imp_const.fit(Xt) Xtt = self.imp_const.transform(Xt) Xt = Xtt