import typing
import logging
import numpy as np
from ConfigSpace import ConfigurationSpace
from ConfigSpace.hyperparameters import (
BetaFloatHyperparameter,
BetaIntegerHyperparameter,
CategoricalHyperparameter,
Constant,
NormalFloatHyperparameter,
NormalIntegerHyperparameter,
OrdinalHyperparameter,
UniformFloatHyperparameter,
UniformIntegerHyperparameter,
)
from smac.utils.constants import MAXINT
__copyright__ = "Copyright 2021, AutoML.org Freiburg-Hannover"
__license__ = "3-clause BSD"
[docs]def get_types(
config_space: ConfigurationSpace,
instance_features: typing.Optional[np.ndarray] = None,
) -> typing.Tuple[typing.List[int], typing.List[typing.Tuple[float, float]]]:
"""Return the types of the hyperparameters and the bounds of the
hyperparameters and instance features.
"""
# Extract types vector for rf from config space and the bounds
types = [0] * len(config_space.get_hyperparameters())
bounds = [(np.nan, np.nan)] * len(types)
for i, param in enumerate(config_space.get_hyperparameters()):
parents = config_space.get_parents_of(param.name)
if len(parents) == 0:
can_be_inactive = False
else:
can_be_inactive = True
if isinstance(param, (CategoricalHyperparameter)):
n_cats = len(param.choices)
if can_be_inactive:
n_cats = len(param.choices) + 1
types[i] = n_cats
bounds[i] = (int(n_cats), np.nan)
elif isinstance(param, (OrdinalHyperparameter)):
n_cats = len(param.sequence)
types[i] = 0
if can_be_inactive:
bounds[i] = (0, int(n_cats))
else:
bounds[i] = (0, int(n_cats) - 1)
elif isinstance(param, Constant):
# for constants we simply set types to 0 which makes it a numerical
# parameter
if can_be_inactive:
bounds[i] = (2, np.nan)
types[i] = 2
else:
bounds[i] = (0, np.nan)
types[i] = 0
# and we leave the bounds to be 0 for now
elif isinstance(param, UniformFloatHyperparameter):
# Are sampled on the unit hypercube thus the bounds
# are always 0.0, 1.0
if can_be_inactive:
bounds[i] = (-1.0, 1.0)
else:
bounds[i] = (0, 1.0)
elif isinstance(param, UniformIntegerHyperparameter):
if can_be_inactive:
bounds[i] = (-1.0, 1.0)
else:
bounds[i] = (0, 1.0)
elif isinstance(param, NormalFloatHyperparameter):
if can_be_inactive:
raise ValueError("Inactive parameters not supported for Beta and Normal Hyperparameters")
bounds[i] = (param._lower, param._upper)
elif isinstance(param, NormalIntegerHyperparameter):
if can_be_inactive:
raise ValueError("Inactive parameters not supported for Beta and Normal Hyperparameters")
bounds[i] = (param.nfhp._lower, param.nfhp._upper)
elif isinstance(param, BetaFloatHyperparameter):
if can_be_inactive:
raise ValueError("Inactive parameters not supported for Beta and Normal Hyperparameters")
bounds[i] = (param._lower, param._upper)
elif isinstance(param, BetaIntegerHyperparameter):
if can_be_inactive:
raise ValueError("Inactive parameters not supported for Beta and Normal Hyperparameters")
bounds[i] = (param.bfhp._lower, param.bfhp._upper)
elif not isinstance(
param,
(
UniformFloatHyperparameter,
UniformIntegerHyperparameter,
OrdinalHyperparameter,
CategoricalHyperparameter,
NormalFloatHyperparameter,
NormalIntegerHyperparameter,
BetaFloatHyperparameter,
BetaIntegerHyperparameter,
),
):
raise TypeError("Unknown hyperparameter type %s" % type(param))
if instance_features is not None:
types = types + [0] * instance_features.shape[1]
return types, bounds
[docs]def get_rng(
rng: typing.Optional[typing.Union[int, np.random.RandomState]] = None,
run_id: typing.Optional[int] = None,
logger: typing.Optional[logging.Logger] = None,
) -> typing.Tuple[int, np.random.RandomState]:
"""Initialize random number generator and set run_id.
* If rng and run_id are None, initialize a new generator and sample a run_id
* If rng is None and a run_id is given, use the run_id to initialize the rng
* If rng is an int, a RandomState object is created from that.
* If rng is RandomState, return it
* If only run_id is None, a run_id is sampled from the random state.
Parameters
----------
rng : np.random.RandomState|int|None
run_id : int, optional
logger: logging.Logger, optional
Returns
-------
int
np.random.RandomState
"""
if logger is None:
logger = logging.getLogger("GetRNG")
# initialize random number generator
if rng is not None and not isinstance(rng, (int, np.random.RandomState)):
raise TypeError(
"Argument rng accepts only arguments of type None, int or np.random.RandomState, "
"you provided %s." % str(type(rng))
)
if run_id is not None and not isinstance(run_id, int):
raise TypeError(
"Argument run_id accepts only arguments of type None, int, " "you provided %s." % str(type(run_id))
)
if rng is None and run_id is None:
# Case that both are None
logger.debug("No rng and no run_id given: using a random value to initialize run_id.")
rng_return = np.random.RandomState()
run_id_return = rng_return.randint(MAXINT)
elif rng is None and isinstance(run_id, int):
logger.debug("No rng and no run_id given: using run_id %d as seed.", run_id)
rng_return = np.random.RandomState(seed=run_id)
run_id_return = run_id
elif isinstance(rng, int) and run_id is None:
run_id_return = rng
rng_return = np.random.RandomState(seed=rng)
elif isinstance(rng, int) and isinstance(run_id, int):
run_id_return = run_id
rng_return = np.random.RandomState(seed=rng)
elif isinstance(rng, np.random.RandomState) and run_id is None:
rng_return = rng
run_id_return = rng.randint(MAXINT)
elif isinstance(rng, np.random.RandomState) and isinstance(run_id, int):
rng_return = rng
run_id_return = run_id
else:
raise ValueError(
"This should not happen! Please contact the developers! Arguments: rng=%s of type %s and "
"run_id=%s of type %s" % (rng, type(rng), str(run_id), type(run_id))
)
return run_id_return, rng_return
[docs]def check_subspace_points(
X: np.ndarray,
cont_dims: typing.Union[np.ndarray, typing.List] = [],
cat_dims: typing.Union[np.ndarray, typing.List] = [],
bounds_cont: typing.Optional[np.ndarray] = None,
bounds_cat: typing.Optional[typing.List[typing.Tuple]] = None,
expand_bound: bool = False,
) -> np.ndarray:
"""
Check which points are placed inside a given subspace
Parameters
----------
X: typing.Optional[np.ndarray(N,D)],
points to be checked, where D = D_cont + D_cat
cont_dims: typing.Union[np.ndarray(D_cont), typing.List]
which dimensions represent continuous hyperparameters
cat_dims: typing.Union[np.ndarray(D_cat), typing.List]
which dimensions represent categorical hyperparameters
bounds_cont: typing.optional[typing.List[typing.Tuple]]
subspaces bounds of categorical hyperparameters, its length is the number of continuous hyperparameters
bounds_cat: typing.Optional[typing.List[typing.Tuple]]
subspaces bounds of continuous hyperparameters, its length is the number of categorical hyperparameters
expand_bound: bool
if the bound needs to be expanded to contain more points rather than the points inside the subregion
Return
----------
indices_in_ss:np.ndarray(N)
indices of data that included in subspaces
"""
if len(X.shape) == 1:
X = X[np.newaxis, :]
if len(cont_dims) == 0 and len(cat_dims) == 0:
return np.ones(X.shape[0], dtype=bool)
if len(cont_dims) > 0:
if bounds_cont is None:
raise ValueError("bounds_cont must be given if cont_dims provided")
if len(bounds_cont.shape) != 2 or bounds_cont.shape[1] != 2 or bounds_cont.shape[0] != len(cont_dims):
raise ValueError(
f"bounds_cont (with shape {bounds_cont.shape}) should be an array with shape of"
f"({len(cont_dims)}, 2)"
)
data_in_ss = np.all(X[:, cont_dims] <= bounds_cont[:, 1], axis=1) & np.all(
X[:, cont_dims] >= bounds_cont[:, 0], axis=1
)
if expand_bound:
bound_left = bounds_cont[:, 0] - np.min(X[data_in_ss][:, cont_dims] - bounds_cont[:, 0], axis=0)
bound_right = bounds_cont[:, 1] + np.min(bounds_cont[:, 1] - X[data_in_ss][:, cont_dims], axis=0)
data_in_ss = np.all(X[:, cont_dims] <= bound_right, axis=1) & np.all(X[:, cont_dims] >= bound_left, axis=1)
else:
data_in_ss = np.ones(X.shape[0], dtype=bool)
if len(cat_dims) == 0:
return data_in_ss
if bounds_cat is None:
raise ValueError("bounds_cat must be given if cat_dims provided")
if len(bounds_cat) != len(cat_dims):
raise ValueError(
f"bounds_cat ({len(bounds_cat)}) and cat_dims ({len(cat_dims)}) must have " f"the same number of elements"
)
for bound_cat, cat_dim in zip(bounds_cat, cat_dims):
data_in_ss &= np.in1d(X[:, cat_dim], bound_cat)
return data_in_ss