Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for CCS as an alternative for ConfigSpace #6

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
33 changes: 28 additions & 5 deletions skopt/optimizer/optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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]
)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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]
)
Expand Down
80 changes: 79 additions & 1 deletion skopt/space/space.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down