Skip to content

[Feature] Allow perturbing a configuration #289

Description

@eddiebergman
space = ConfigurationSpace({...})
optimum = space.sample_configuration()  # Or more likely evaluation

close_neighbor = config.perturb(std=0.05)  # std on a scale of 0-1 

std doesn't really make sense as a name since it's more of a percentage sphere around a config.

One thing is that is unclear behaviour for categoricals.

Here's some reference work for mfpbench::Config::perturb():

def perturb(
    value: ValueT,
    hp: (
        Constant
        | UniformIntegerHyperparameter
        | UniformFloatHyperparameter
        | NormalIntegerHyperparameter
        | NormalFloatHyperparameter
        | CategoricalHyperparameter
        | OrdinalHyperparameter
    ),
    std: float,
    seed: int | np.random.RandomState | None = None,
) -> ValueT:
    # TODO:
    # * https://github.com/automl/ConfigSpace/issues/289
    assert 0 <= std <= 1, "Noise must be between 0 and 1"
    rng: np.random.RandomState
    if seed is None:
        rng = np.random.RandomState()
    elif isinstance(seed, int):
        rng = np.random.RandomState(seed)
    else:
        rng = seed

    if isinstance(hp, Constant):
        return value

    if isinstance(
        hp,
        (
            NormalIntegerHyperparameter,
            NormalFloatHyperparameter,
            UniformFloatHyperparameter,
            UniformIntegerHyperparameter,
        ),
    ):
        # TODO:
        # * https://github.com/automl/ConfigSpace/issues/287
        # * https://github.com/automl/ConfigSpace/issues/290
        # * https://github.com/automl/ConfigSpace/issues/291
        assert hp.upper is not None and hp.lower is not None
        assert hp.q is None
        assert isinstance(value, (int, float))

        if isinstance(hp, UniformIntegerHyperparameter):
            if hp.log:
                _lower = np.log(hp.lower)
                _upper = np.log(hp.upper)
            else:
                _lower = hp.lower
                _upper = hp.upper
        elif isinstance(hp, NormalIntegerHyperparameter):
            _lower = hp.nfhp._lower
            _upper = hp.nfhp._upper
        elif isinstance(hp, (UniformFloatHyperparameter, NormalFloatHyperparameter)):
            _lower = hp._lower
            _upper = hp._upper
        else:
            raise RuntimeError("Wut")

        space_length = std * (_upper - _lower)
        rescaled_std = std * space_length



        if not hp.log:
            sample = np.clip(rng.normal(value, rescaled_std), _lower, _upper)
        else:
            logged_value = np.log(value)
            sample = rng.normal(logged_value, rescaled_std)
            sample = np.clip(np.exp(sample), hp.lower, hp.upper)

        if isinstance(hp, (UniformIntegerHyperparameter, NormalIntegerHyperparameter)):
            return int(np.rint(sample))
        elif isinstance(hp, (UniformFloatHyperparameter, NormalFloatHyperparameter)):
            return float(sample)  # type: ignore
        else:
            raise RuntimeError("Please report to github, shouldn't get here")

        # if isinstance(hp, (BetaIntegerHyperparameter, BetaFloatHyperparameter)):
        # TODO
        # raise NotImplementedError(
        # "BetaIntegerHyperparameter, BetaFloatHyperparameter not implemented"
        # )

    if isinstance(hp, CategoricalHyperparameter):
        # We basically with (1 - std) choose the same value, otherwise uniformly select
        # at random
        if rng.uniform() < 1 - std:
            return value

        choices = set(hp.choices) - {value}
        return rng.choice(list(choices))

    if isinstance(hp, OrdinalHyperparameter):
        # TODO:
        # * https://github.com/automl/ConfigSpace/issues/288
        # We build a normal centered at the index of value
        # which acts on index spacings
        index_value = hp.sequence.index(value)
        index_std = std * len(hp.sequence)
        normal_value = rng.normal(index_value, index_std)
        index = int(np.rint(np.clip(normal_value, 0, len(hp.sequence))))
        return hp.sequence[index]

    raise ValueError(f"Can't perturb {hp}")

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions