예제 #1
0
    def test_sample_types_no_cat(self):
        import numpy as np
        from deephyper.evaluator import Evaluator
        from deephyper.problem import HpProblem
        from deephyper.search.hps import CBO

        problem = HpProblem()
        problem.add_hyperparameter((0, 10), "x_int")
        problem.add_hyperparameter((0.0, 10.0), "x_float")

        def run(config):

            assert np.issubdtype(type(config["x_int"]), np.integer)
            assert np.issubdtype(type(config["x_float"]), float)

            return 0

        create_evaluator = lambda: Evaluator.create(run, method="serial")

        CBO(problem,
            create_evaluator(),
            random_state=42,
            surrogate_model="DUMMY").search(10)

        CBO(problem, create_evaluator(), random_state=42,
            surrogate_model="RF").search(10)
예제 #2
0
    def test_add_starting_points_with_too_many_dim(self):
        from deephyper.problem import HpProblem

        pb = HpProblem()
        pb.add_hyperparameter((-10, 10), "dim0")
        with pytest.raises(ValueError):
            pb.add_starting_point(dim0=0, dim1=2)
예제 #3
0
    def test_dim_with_wrong_name(self):
        from deephyper.core.exceptions.problem import SpaceDimNameOfWrongType
        from deephyper.problem import HpProblem

        pb = HpProblem()
        with pytest.raises(SpaceDimNameOfWrongType):
            pb.add_hyperparameter((-10, 10), 0)
예제 #4
0
    def test_add_good_dim(self):
        from deephyper.problem import HpProblem

        pb = HpProblem()

        p0 = pb.add_hyperparameter((-10, 10), "p0")
        p0_csh = csh.UniformIntegerHyperparameter(
            name="p0", lower=-10, upper=10, log=False
        )
        assert p0 == p0_csh

        p1 = pb.add_hyperparameter((1, 100, "log-uniform"), "p1")
        p1_csh = csh.UniformIntegerHyperparameter(name="p1", lower=1, upper=100, log=True)
        assert p1 == p1_csh

        p2 = pb.add_hyperparameter((-10.0, 10.0), "p2")
        p2_csh = csh.UniformFloatHyperparameter(
            name="p2", lower=-10.0, upper=10.0, log=False
        )
        assert p2 == p2_csh

        p3 = pb.add_hyperparameter((1.0, 100.0, "log-uniform"), "p3")
        p3_csh = csh.UniformFloatHyperparameter(
            name="p3", lower=1.0, upper=100.0, log=True
        )
        assert p3 == p3_csh

        p4 = pb.add_hyperparameter([1, 2, 3, 4], "p4")
        p4_csh = csh.OrdinalHyperparameter(name="p4", sequence=[1, 2, 3, 4])
        assert p4 == p4_csh

        p5 = pb.add_hyperparameter([1.0, 2.0, 3.0, 4.0], "p5")
        p5_csh = csh.OrdinalHyperparameter(name="p5", sequence=[1.0, 2.0, 3.0, 4.0])
        assert p5 == p5_csh

        p6 = pb.add_hyperparameter(["cat0", "cat1"], "p6")
        p6_csh = csh.CategoricalHyperparameter(name="p6", choices=["cat0", "cat1"])
        assert p6 == p6_csh

        p7 = pb.add_hyperparameter({"mu": 0, "sigma": 1}, "p7")
        p7_csh = csh.NormalIntegerHyperparameter(name="p7", mu=0, sigma=1)
        assert p7 == p7_csh

        if cs.__version__ > "0.4.20":
            p8 = pb.add_hyperparameter(
                {"mu": 0, "sigma": 1, "lower": -5, "upper": 5}, "p8"
            )
            p8_csh = csh.NormalIntegerHyperparameter(
                name="p8", mu=0, sigma=1, lower=-5, upper=5
            )
            assert p8 == p8_csh

        p9 = pb.add_hyperparameter({"mu": 0.0, "sigma": 1.0}, "p9")
        p9_csh = csh.NormalFloatHyperparameter(name="p9", mu=0, sigma=1)
        assert p9 == p9_csh
예제 #5
0
    def test_random_seed(self):
        import numpy as np
        from deephyper.evaluator import Evaluator
        from deephyper.problem import HpProblem
        from deephyper.search.hps import CBO

        problem = HpProblem()
        problem.add_hyperparameter((0.0, 10.0), "x")

        def run(config):
            return config["x"]

        create_evaluator = lambda: Evaluator.create(run, method="serial")

        search = CBO(problem,
                     create_evaluator(),
                     random_state=42,
                     surrogate_model="DUMMY")

        res1 = search.search(max_evals=4)
        res1_array = res1[["x"]].to_numpy()

        search = CBO(problem,
                     create_evaluator(),
                     random_state=42,
                     surrogate_model="DUMMY")
        res2 = search.search(max_evals=4)
        res2_array = res2[["x"]].to_numpy()

        assert np.array_equal(res1_array, res2_array)

        # test multi-objective
        def run(config):
            return config["x"], config["x"]

        create_evaluator = lambda: Evaluator.create(run, method="serial")

        search = CBO(problem,
                     create_evaluator(),
                     random_state=42,
                     surrogate_model="DUMMY")

        res1 = search.search(max_evals=4)
        res1_array = res1[["x"]].to_numpy()

        search = CBO(problem,
                     create_evaluator(),
                     random_state=42,
                     surrogate_model="DUMMY")
        res2 = search.search(max_evals=4)
        res2_array = res2[["x"]].to_numpy()

        assert np.array_equal(res1_array, res2_array)
예제 #6
0
class Problem:
    """Representation of a problem."""
    def __init__(self, seed=None, **kwargs):
        self._space = OrderedDict()
        self._hp_space = HpProblem(seed)
        self.seed = seed

    def __str__(self):
        return repr(self)

    def __repr__(self):
        return f"Problem\n{pformat({k:v for k,v in self._space.items()}, indent=2)}"

    def add_dim(self, p_name, p_space):
        """Add a dimension to the search space.

        Args:
            p_name (str): name of the parameter/dimension.
            p_space (Object): space corresponding to the new dimension.
        """
        self._space[p_name] = p_space

    @property
    def space(self):
        dims = list(self._space.keys())
        dims.sort()
        space = OrderedDict(**{d: self._space[d] for d in dims})
        return space

    def add_hyperparameter(self,
                           value,
                           name: str = None,
                           default_value=None) -> csh.Hyperparameter:
        return self._hp_space.add_hyperparameter(value, name, default_value)
예제 #7
0
    def test_sample_types_conditional(self):
        import ConfigSpace as cs
        import numpy as np
        from deephyper.evaluator import Evaluator
        from deephyper.problem import HpProblem
        from deephyper.search.hps import CBO

        problem = HpProblem()

        # choices
        choice = problem.add_hyperparameter(
            name="choice",
            value=["choice1", "choice2"],
        )

        # integers
        x1_int = problem.add_hyperparameter(name="x1_int", value=(1, 10))

        x2_int = problem.add_hyperparameter(name="x2_int", value=(1, 10))

        # conditions
        cond_1 = cs.EqualsCondition(x1_int, choice, "choice1")

        cond_2 = cs.EqualsCondition(x2_int, choice, "choice2")

        problem.add_condition(cond_1)
        problem.add_condition(cond_2)

        def run(config):

            if config["choice"] == "choice1":
                assert np.issubdtype(type(config["x1_int"]), np.integer)
            else:
                assert np.issubdtype(type(config["x2_int"]), np.integer)

            return 0

        create_evaluator = lambda: Evaluator.create(run, method="serial")

        CBO(problem,
            create_evaluator(),
            random_state=42,
            surrogate_model="DUMMY").search(10)
예제 #8
0
    def test_quickstart(self):
        from deephyper.problem import HpProblem
        from deephyper.search.hps import CBO
        from deephyper.evaluator import Evaluator

        # define the variable you want to optimize
        problem = HpProblem()
        problem.add_hyperparameter((-10.0, 10.0), "x")

        # define the evaluator to distribute the computation
        evaluator = Evaluator.create(
            run,
            method="subprocess",
            method_kwargs={
                "num_workers": 2,
            },
        )

        # define you search and execute it
        search = CBO(problem, evaluator)

        results = search.search(max_evals=15)
예제 #9
0
    def test_gp(self):
        from deephyper.evaluator import Evaluator
        from deephyper.problem import HpProblem
        from deephyper.search.hps import CBO

        # test float hyperparameters
        problem = HpProblem()
        problem.add_hyperparameter((0.0, 10.0), "x")

        def run(config):
            return config["x"]

        CBO(
            problem,
            Evaluator.create(run, method="serial"),
            random_state=42,
            surrogate_model="GP",
        ).search(10)

        # test int hyperparameters
        problem = HpProblem()
        problem.add_hyperparameter((0, 10), "x")

        def run(config):
            return config["x"]

        CBO(
            problem,
            Evaluator.create(run, method="serial"),
            random_state=42,
            surrogate_model="GP",
        ).search(10)

        # test categorical hyperparameters
        problem = HpProblem()
        problem.add_hyperparameter([f"{i}" for i in range(10)], "x")

        def run(config):
            return int(config["x"])

        CBO(
            problem,
            Evaluator.create(run, method="serial"),
            random_state=42,
            surrogate_model="GP",
        ).search(10)
예제 #10
0
    def test_add_starting_points_not_in_space_def(self):
        from deephyper.problem import HpProblem

        pb = HpProblem()
        pb.add_hyperparameter((-10, 10), "dim0")
        pb.add_hyperparameter((-10.0, 10.0), "dim1")
        pb.add_hyperparameter(["a", "b"], "dim2")

        with pytest.raises(ValueError):
            pb.add_starting_point(dim0=-11, dim1=0.0, dim2="a")

        with pytest.raises(ValueError):
            pb.add_starting_point(dim0=11, dim1=0.0, dim2="a")

        with pytest.raises(ValueError):
            pb.add_starting_point(dim0=0, dim1=-11.0, dim2="a")

        with pytest.raises(ValueError):
            pb.add_starting_point(dim0=0, dim1=11.0, dim2="a")

        with pytest.raises(ValueError):
            pb.add_starting_point(dim0=0, dim1=0.0, dim2="c")

        pb.add_starting_point(dim0=0, dim1=0.0, dim2="a")
예제 #11
0
"""
deephyper hps ambs --problem nas_big_data.albert.tune_random_forest.Problem --run nas_big_data.albert.tune_random_forest.run --max-evals 30 --evaluator subprocess --n-jobs 3
"""

import json
import os

import pandas as pd
from deephyper.problem import HpProblem
from nas_big_data import RANDOM_STATE
from nas_big_data.albert.load_data import load_data
from sklearn.ensemble import RandomForestClassifier

Problem = HpProblem()

Problem.add_hyperparameter((10, 300), "n_estimators")
Problem.add_hyperparameter(["gini", "entropy"], "criterion")
Problem.add_hyperparameter((1, 50), "max_depth")
Problem.add_hyperparameter((2, 10), "min_samples_split")

# We define a starting point with the defaul hyperparameters from sklearn-learn
# that we consider good in average.
Problem.add_starting_point(n_estimators=100,
                           criterion="gini",
                           max_depth=50,
                           min_samples_split=2)


def test_best():
    """Test data with RandomForest
예제 #12
0
"""Inspired from https://scikit-learn.org/stable/auto_examples/classification/plot_classifier_comparison.html
"""
import ConfigSpace as cs
from deephyper.problem import HpProblem


Problem = HpProblem(seed=45)

classifier = Problem.add_hyperparameter(
    name="classifier",
    value=["RandomForest", "Logistic", "AdaBoost", "KNeighbors", "MLP", "SVC", "XGBoost"],
)

# n_estimators
n_estimators = Problem.add_hyperparameter(
    name="n_estimators", value=(1, 2000, "log-uniform")
)

cond_n_estimators = cs.OrConjunction(
    cs.EqualsCondition(n_estimators, classifier, "RandomForest"),
    cs.EqualsCondition(n_estimators, classifier, "AdaBoost"),
)

Problem.add_condition(cond_n_estimators)

# max_depth
max_depth = Problem.add_hyperparameter(name="max_depth", value=(2, 100, "log-uniform"))

cond_max_depth = cs.EqualsCondition(max_depth, classifier, "RandomForest")

Problem.add_condition(cond_max_depth)
예제 #13
0
"""


def run(config: dict) -> float:
    if config["y"] > 0.5:
        return "F_postfix"
    else:
        return config["x"]


# %%
# Then, we define the corresponding hyperparameter problem where ``x`` is the value to maximize and ``y`` is a value impact the appearance of failures.
from deephyper.problem import HpProblem

problem = HpProblem()
problem.add_hyperparameter([1, 2, 4, 8, 16, 32], "x")
problem.add_hyperparameter((0.0, 1.0), "y")

print(problem)


# %%
# Then, we define a centralized Bayesian optimization (CBO) search (i.e., master-worker architecture) which uses the Random-Forest regressor as default surrogate model. We will compare the ``ignore`` strategy which filters-out failed configurations, the ``mean`` strategy which replaces a failure by the running mean of collected objectives and the ``min`` strategy which replaces by the running min of collected objectives.
from deephyper.search.hps import CBO
from deephyper.evaluator import Evaluator
from deephyper.evaluator.callback import TqdmCallback

results = {}
max_evals = 30
for failure_strategy in ["ignore", "mean", "min"]:
    # for failure_strategy in ["min"]:
예제 #14
0
Hyperparameter optimization problem to try forbidden clauses.

Example command line::

    python -m deephyper.search.hps.ambs2 --evaluator threadPool --problem deephyper.benchmark.hps.toy.problem_forbidden_1.Problem --run deephyper.benchmark.hps.toy.problem_forbidden_1.run --max-evals 100 --kappa 0.001
"""
import numpy as np

from deephyper.problem import HpProblem
from deephyper.problem import config_space as cs

# Problem definition
Problem = HpProblem()

x_hp = Problem.add_hyperparameter(
    name="x", value=(0, 10))  # or Problem.add_dim("x", (0, 10))
y_hp = Problem.add_hyperparameter(
    name="y", value=(0, 10))  # or Problem.add_dim("y", (0, 10))

not_zero = cs.ForbiddenAndConjunction(cs.ForbiddenEqualsClause(x_hp, 0),
                                      cs.ForbiddenEqualsClause(y_hp, 0))

Problem.add_forbidden_clause(not_zero)

Problem.add_starting_point(x=1, y=1)

# Definition of the function which runs the model


def run(param_dict):
예제 #15
0
import ConfigSpace as cs
from deephyper.problem import HpProblem

Problem = HpProblem(seed=45)

#! Default value are very important when adding conditional and forbidden clauses
#! Otherwise the creation of the problem can fail if the default configuration is not
#! Acceptable
classifier = Problem.add_hyperparameter(
    name="classifier",
    value=["RandomForest", "GradientBoosting"],
    default_value="RandomForest",
)

# For both
Problem.add_hyperparameter(name="n_estimators", value=(1, 1000, "log-uniform"))
Problem.add_hyperparameter(name="max_depth", value=(1, 50))
Problem.add_hyperparameter(
    name="min_samples_split",
    value=(2, 10),
)
Problem.add_hyperparameter(name="min_samples_leaf", value=(1, 10))
criterion = Problem.add_hyperparameter(
    name="criterion",
    value=["friedman_mse", "mse", "mae", "gini", "entropy"],
    default_value="gini",
)

# GradientBoosting
loss = Problem.add_hyperparameter(name="loss",
                                  value=["deviance", "exponential"])
예제 #16
0
from deephyper.problem import HpProblem

Problem = HpProblem()
Problem.add_hyperparameter((5, 500), "epochs")
Problem.add_hyperparameter((1, 1000), "nunits_l1")
Problem.add_hyperparameter((1, 1000), "nunits_l2")
Problem.add_hyperparameter(["relu", "elu", "selu", "tanh"], "activation_l1")
Problem.add_hyperparameter(["relu", "elu", "selu", "tanh"], "activation_l2")
Problem.add_hyperparameter((8, 1024), "batch_size")
Problem.add_hyperparameter((0.0, 1.0), "dropout_l1")
Problem.add_hyperparameter((0.0, 1.0), "dropout_l2")

Problem.add_starting_point(
    epochs=5,
    nunits_l1=1,
    nunits_l2=2,
    activation_l1="relu",
    activation_l2="relu",
    batch_size=8,
    dropout_l1=0.0,
    dropout_l2=0.0,
)

if __name__ == "__main__":
    print(Problem)
예제 #17
0
    def test_kwargs(self):
        from deephyper.problem import HpProblem

        pb = HpProblem()
        pb.add_hyperparameter(value=(-10, 10), name="dim0")
예제 #18
0
   :language: python
   :emphasize-lines: 19-28 
   :linenos:

After defining the black-box we can continue with the definition of our main script:
"""
import black_box_util as black_box

# %%
# Then we define the variable(s) we want to optimize. For this problem we optimize Ackley in a 2-dimensional search space, the true minimul is located at ``(0, 0)``.
from deephyper.problem import HpProblem

nb_dim = 2
problem = HpProblem()
for i in range(nb_dim):
    problem.add_hyperparameter((-32.768, 32.768), f"x{i}")
problem

# %%
# Then we define a parallel search.
if __name__ == "__main__":
    from deephyper.evaluator import Evaluator
    from deephyper.evaluator.callback import TqdmCallback
    from deephyper.search.hps import CBO

    timeout = 20
    num_workers = 4
    results = {}

    evaluator = Evaluator.create(
        black_box.run_ackley,
예제 #19
0
    y = -sum([config[f"x{i}"] ** 2 for i in range(N)])
    return y


run_small = functools.partial(run, N=1)
run_large = functools.partial(run, N=2)

# %%
# Then, we can define the hyperparameter problem space based on :math:`n`
from deephyper.problem import HpProblem


N = 1
problem_small = HpProblem()
for i in range(N):
    problem_small.add_hyperparameter((-10.0, 10.0), f"x{i}")
problem_small

# %%
N = 2
problem_large = HpProblem()
for i in range(N):
    problem_large.add_hyperparameter((-10.0, 10.0), f"x{i}")
problem_large

# %%
# Then, we define setup the search and execute it:
from deephyper.evaluator import Evaluator
from deephyper.evaluator.callback import TqdmCallback
from deephyper.search.hps import CBO
예제 #20
0
class AMBSMixed(NeuralArchitectureSearch):
    """Asynchronous Model-Based Search baised on the `Scikit-Optimized Optimizer <https://scikit-optimize.github.io/stable/modules/generated/deephyper.skopt.Optimizer.html#deephyper.skopt.Optimizer>`_. It is extended to the case of joint hyperparameter and neural architecture search.

    Args:
        problem (NaProblem): Neural architecture search problem describing the search space to explore.
        evaluator (Evaluator): An ``Evaluator`` instance responsible of distributing the tasks.
        random_state (int, optional): Random seed. Defaults to None.
        log_dir (str, optional): Log directory where search's results are saved. Defaults to ".".
        verbose (int, optional): Indicate the verbosity level of the search. Defaults to 0.
        surrogate_model (str, optional): Surrogate model used by the Bayesian optimization. Can be a value in ``["RF", "ET", "GBRT", "DUMMY"]``. Defaults to ``"RF"``.
        acq_func (str, optional): Acquisition function used by the Bayesian optimization. Can be a value in ``["UCB", "EI", "PI", "gp_hedge"]``. Defaults to ``"UCB"``.
        kappa (float, optional): Manage the exploration/exploitation tradeoff for the "UCB" acquisition function. Defaults to ``1.96`` for a balance between exploitation and exploration.
        xi (float, optional): Manage the exploration/exploitation tradeoff of ``"EI"`` and ``"PI"`` acquisition function. Defaults to ``0.001`` for a balance between exploitation and exploration.
        n_points (int, optional): The number of configurations sampled from the search space to infer each batch of new evaluated configurations. Defaults to ``10000``.
        liar_strategy (str, optional): Definition of the constant value use for the Liar strategy. Can be a value in ``["cl_min", "cl_mean", "cl_max"]`` . Defaults to ``"cl_max"``.
        n_jobs (int, optional): Number of parallel processes used to fit the surrogate model of the Bayesian optimization. A value of ``-1`` will use all available cores. Defaults to ``1``.
    """
    def __init__(
        self,
        problem,
        evaluator,
        random_state=None,
        log_dir=".",
        verbose=0,
        surrogate_model: str = "RF",
        acq_func: str = "UCB",
        kappa: float = 1.96,
        xi: float = 0.001,
        n_points: int = 10000,
        liar_strategy: str = "cl_max",
        n_jobs: int = 1,
        **kwargs,
    ):
        super().__init__(problem, evaluator, random_state, log_dir, verbose)

        # Setup the search space
        na_search_space = self._problem.build_search_space()

        self.hp_space = self._problem._hp_space  #! hyperparameters
        self.hp_size = len(self.hp_space.space.get_hyperparameter_names())
        self.na_space = HpProblem()
        self.na_space._space.seed(self._random_state.get_state()[1][0])
        for i, vnode in enumerate(na_search_space.variable_nodes):
            self.na_space.add_hyperparameter((0, vnode.num_ops - 1),
                                             name=f"vnode_{i:05d}")

        self._space = CS.ConfigurationSpace(
            seed=self._random_state.get_state()[1][0])
        self._space.add_configuration_space(
            prefix="1", configuration_space=self.hp_space.space)
        self._space.add_configuration_space(
            prefix="2", configuration_space=self.na_space.space)

        # check input parameters
        surrogate_model_allowed = ["RF", "ET", "GBRT", "DUMMY"]
        if not (surrogate_model in surrogate_model_allowed):
            raise ValueError(
                f"Parameter 'surrogate_model={surrogate_model}' should have a value in {surrogate_model_allowed}!"
            )

        acq_func_allowed = ["UCB", "EI", "PI", "gp_hedge"]
        if not (acq_func in acq_func_allowed):
            raise ValueError(
                f"Parameter 'acq_func={acq_func}' should have a value in {acq_func_allowed}!"
            )

        if not (np.isscalar(kappa)):
            raise ValueError(f"Parameter 'kappa' should be a scalar value!")

        if not (np.isscalar(xi)):
            raise ValueError("Parameter 'xi' should be a scalar value!")

        if not (type(n_points) is int):
            raise ValueError("Parameter 'n_points' shoud be an integer value!")

        liar_strategy_allowed = ["cl_min", "cl_mean", "cl_max"]
        if not (liar_strategy in liar_strategy_allowed):
            raise ValueError(
                f"Parameter 'liar_strategy={liar_strategy}' should have a value in {liar_strategy_allowed}!"
            )

        if not (type(n_jobs) is int):
            raise ValueError(f"Parameter 'n_jobs' should be an integer value!")

        self._n_initial_points = self._evaluator.num_workers
        self._liar_strategy = MAP_liar_strategy.get(liar_strategy,
                                                    liar_strategy)

        base_estimator = self._get_surrogate_model(
            surrogate_model,
            n_jobs,
            random_state=self._random_state.get_state()[1][0])

        self._opt = None
        self._opt_kwargs = dict(
            dimensions=self._space,
            base_estimator=base_estimator,
            acq_func=MAP_acq_func.get(acq_func, acq_func),
            acq_optimizer="sampling",
            acq_func_kwargs={
                "xi": xi,
                "kappa": kappa,
                "n_points": n_points
            },
            n_initial_points=self._n_initial_points,
            random_state=self._random_state,
        )

    def _setup_optimizer(self):
        self._opt = deephyper.skopt.Optimizer(**self._opt_kwargs)

    def _saved_keys(self, job):

        res = {"arch_seq": str(job.config["arch_seq"])}
        hp_names = self._problem._hp_space._space.get_hyperparameter_names()

        for hp_name in hp_names:
            if hp_name == "loss":
                res["loss"] = job.config["loss"]
            else:
                res[hp_name] = job.config["hyperparameters"][hp_name]

        return res

    def _search(self, max_evals, timeout):

        if self._opt is None:
            self._setup_optimizer()

        num_evals_done = 0

        # Filling available nodes at start
        logging.info(
            f"Generating {self._evaluator.num_workers} initial points...")
        self._evaluator.submit(
            self._get_random_batch(size=self._n_initial_points))

        # Main loop
        while max_evals < 0 or num_evals_done < max_evals:

            # Collecting finished evaluations
            new_results = list(self._evaluator.gather("BATCH", size=1))
            num_received = len(new_results)

            if num_received > 0:

                self._evaluator.dump_evals(saved_keys=self._saved_keys,
                                           log_dir=self._log_dir)
                num_evals_done += num_received

                if num_evals_done >= max_evals:
                    break

                # Transform configurations to list to fit optimizer
                opt_X = []
                opt_y = []
                for cfg, obj in new_results:
                    arch_seq = cfg["arch_seq"]
                    hp_val = self._problem.extract_hp_values(cfg)
                    x = replace_nan(hp_val + arch_seq)
                    opt_X.append(x)
                    opt_y.append(-obj)  #! maximizing

                self._opt.tell(opt_X, opt_y)  #! fit: costly
                new_X = self._opt.ask(n_points=len(new_results),
                                      strategy=self._liar_strategy)

                new_batch = []
                for x in new_X:
                    new_cfg = self._problem.gen_config(x[self.hp_size:],
                                                       x[:self.hp_size])
                    new_batch.append(new_cfg)

                # submit_childs
                if len(new_results) > 0:
                    self._evaluator.submit(new_batch)

    def _get_surrogate_model(self,
                             name: str,
                             n_jobs: int = None,
                             random_state: int = None):
        """Get a surrogate model from Scikit-Optimize.

        Args:
            name (str): name of the surrogate model.
            n_jobs (int): number of parallel processes to distribute the computation of the surrogate model.

        Raises:
            ValueError: when the name of the surrogate model is unknown.
        """
        accepted_names = ["RF", "ET", "GBRT", "DUMMY"]
        if not (name in accepted_names):
            raise ValueError(
                f"Unknown surrogate model {name}, please choose among {accepted_names}."
            )

        if name == "RF":
            surrogate = deephyper.skopt.learning.RandomForestRegressor(
                n_jobs=n_jobs, random_state=random_state)
        elif name == "ET":
            surrogate = deephyper.skopt.learning.ExtraTreesRegressor(
                n_jobs=n_jobs, random_state=random_state)
        elif name == "GBRT":
            surrogate = deephyper.skopt.learning.GradientBoostingQuantileRegressor(
                n_jobs=n_jobs, random_state=random_state)
        else:  # for DUMMY and GP
            surrogate = name

        return surrogate

    def _get_random_batch(self, size: int) -> list:
        batch = []
        n_points = max(0, size - len(batch))
        if n_points > 0:
            points = self._opt.ask(n_points=n_points)
            for point in points:
                point_as_dict = self._problem.gen_config(
                    point[self.hp_size:], point[:self.hp_size])
                batch.append(point_as_dict)
        return batch
예제 #21
0
import pytest
import numpy as np
from deephyper.problem import HpProblem
from deephyper.evaluator import Evaluator
from deephyper.search.hps import AMBS


def run(config):
    return config["x"]


problem = HpProblem()
problem.add_hyperparameter((0.0, 10.0), "x")


def test_ambs():

    create_evaluator = lambda: Evaluator.create(
        run, method="process", method_kwargs={"num_workers": 1})

    search = AMBS(
        problem,
        create_evaluator(),
        random_state=42,
    )

    res1 = search.search(max_evals=4)
    res1_array = res1[["x"]].to_numpy()

    search.search(max_evals=100, timeout=1)
예제 #22
0
"""
python -m deephyper.search.hps.ambs2 --evaluator threadPool --problem deephyper.benchmark.hps.polynome2.Problem --run deephyper.benchmark.hps.polynome2.run --max-evals 100 --kappa 0.001
"""
import numpy as np

from deephyper.benchmark.benchmark_functions_wrappers import polynome_2
from deephyper.problem import HpProblem

# Problem definition
Problem = HpProblem()

num_dim = 10
for i in range(num_dim):
    Problem.add_hyperparameter((-10.0, 10.0), f"e{i}")

Problem.add_starting_point(**{f"e{i}": 10.0 for i in range(num_dim)})

# Definition of the function which runs the model


def run(param_dict):
    f, _, _ = polynome_2()

    num_dim = 10
    x = np.array([param_dict[f"e{i}"] for i in range(num_dim)])

    return f(x)  # the objective


if __name__ == "__main__":
    print(Problem)
예제 #23
0
class RegularizedEvolutionMixed(RegularizedEvolution):
    """Extention of the `Regularized evolution <https://arxiv.org/abs/1802.01548>`_ neural architecture search to the case of joint hyperparameter and neural architecture search.

    Args:
        problem (NaProblem): Neural architecture search problem describing the search space to explore.
        evaluator (Evaluator): An ``Evaluator`` instance responsible of distributing the tasks.
        random_state (int, optional): Random seed. Defaults to None.
        log_dir (str, optional): Log directory where search's results are saved. Defaults to ".".
        verbose (int, optional): Indicate the verbosity level of the search. Defaults to 0.
        population_size (int, optional): the number of individuals to keep in the population. Defaults to 100.
        sample_size (int, optional): the number of individuals that should participate in each tournament. Defaults to 10.
    """

    def __init__(
        self,
        problem,
        evaluator,
        random_state: int = None,
        log_dir: str = ".",
        verbose: int = 0,
        population_size: int = 100,
        sample_size: int = 10,
        **kwargs,
    ):
        super().__init__(
            problem,
            evaluator,
            random_state,
            log_dir,
            verbose,
            population_size,
            sample_size,
        )

        # Setup
        na_search_space = self._problem.build_search_space()

        self.hp_space = self._problem._hp_space  #! hyperparameters
        self.hp_size = len(self.hp_space.space.get_hyperparameter_names())
        self.na_space = HpProblem()
        self.na_space._space.seed(self._random_state.get_state()[1][0])
        for i, (low, high) in enumerate(na_search_space.choices()):
            self.na_space.add_hyperparameter((low, high), name=f"vnode_{i:05d}")

        self._space = CS.ConfigurationSpace(seed=self._random_state.get_state()[1][0])
        self._space.add_configuration_space(
            prefix="1", configuration_space=self.hp_space.space
        )
        self._space.add_configuration_space(
            prefix="2", configuration_space=self.na_space.space
        )
        self._space_size = len(self._space.get_hyperparameter_names())

    def _saved_keys(self, job):

        res = {"arch_seq": str(job.config["arch_seq"])}
        hp_names = self._problem._hp_space._space.get_hyperparameter_names()

        for hp_name in hp_names:
            if hp_name == "loss":
                res["loss"] = job.config["loss"]
            else:
                res[hp_name] = job.config["hyperparameters"][hp_name]

        return res

    def _search(self, max_evals, timeout):

        num_evals_done = 0

        # Filling available nodes at start
        self._evaluator.submit(self._gen_random_batch(size=self._evaluator.num_workers))

        # Main loop
        while max_evals < 0 or num_evals_done < max_evals:

            # Collecting finished evaluations
            new_results = list(self._evaluator.gather("BATCH", size=1))
            num_received = len(new_results)

            if num_received > 0:

                self._population.extend(new_results)
                self._evaluator.dump_evals(
                    saved_keys=self._saved_keys, log_dir=self._log_dir
                )
                num_evals_done += num_received

                if num_evals_done >= max_evals:
                    break

                # If the population is big enough evolve the population
                if len(self._population) == self._population_size:

                    children_batch = []

                    # For each new parent/result we create a child from it
                    for _ in range(num_received):

                        # select_sample
                        indexes = self._random_state.choice(
                            self._population_size, self._sample_size, replace=False
                        )
                        sample = [self._population[i] for i in indexes]

                        # select_parent
                        parent = self._select_parent(sample)

                        # copy_mutate_parent
                        child = self._copy_mutate_arch(parent)
                        # add child to batch
                        children_batch.append(child)

                    # submit_childs
                    self._evaluator.submit(children_batch)
                else:  # If the population is too small keep increasing it

                    new_batch = self._gen_random_batch(size=num_received)

                    self._evaluator.submit(new_batch)

    def _select_parent(self, sample: list) -> dict:
        cfg, _ = max(sample, key=lambda x: x[1])
        return cfg

    def _gen_random_batch(self, size: int) -> list:

        sample = lambda hp, size: [hp.sample(self._space.random) for _ in range(size)]
        batch = []
        iterator = zip(*(sample(hp, size) for hp in self._space.get_hyperparameters()))

        for x in iterator:
            cfg = self._problem.gen_config(
                list(x[self.hp_size :]), list(x[: self.hp_size])
            )
            batch.append(cfg)

        return batch

    def _copy_mutate_arch(self, parent_cfg: dict) -> dict:
        """
        # ! Time performance is critical because called sequentialy

        Args:
            parent_arch (list(int)): embedding of the parent's architecture.

        Returns:
            dict: embedding of the mutated architecture of the child.

        """

        hp_x = self._problem.extract_hp_values(parent_cfg)
        x = hp_x + parent_cfg["arch_seq"]
        i = self._random_state.choice(self._space_size)
        hp = self._space.get_hyperparameters()[i]
        x[i] = hp.sample(self._space.random)

        child_cfg = self._problem.gen_config(x[self.hp_size :], x[: self.hp_size])

        return child_cfg
예제 #24
0
class RegularizedEvolutionMixed(RegularizedEvolution):
    def __init__(
        self,
        problem,
        run,
        evaluator,
        population_size=100,
        sample_size=10,
        n_jobs=1,
        **kwargs,
    ):
        super().__init__(
            problem=problem,
            run=run,
            evaluator=evaluator,
            population_size=population_size,
            sample_size=sample_size,
            **kwargs,
        )

        self.n_jobs = int(n_jobs)

        # Setup
        na_search_space = self.problem.build_search_space()

        self.hp_space = self.problem._hp_space  #! hyperparameters
        self.hp_size = len(self.hp_space.space.get_hyperparameter_names())
        self.na_space = HpProblem(self.problem.seed)
        for i, vnode in enumerate(na_search_space.variable_nodes):
            self.na_space.add_hyperparameter((0, vnode.num_ops - 1),
                                             name=f"vnode_{i:05d}")

        self.space = CS.ConfigurationSpace(seed=self.problem.seed)
        self.space.add_configuration_space(
            prefix="1", configuration_space=self.hp_space.space)
        self.space.add_configuration_space(
            prefix="2", configuration_space=self.na_space.space)

    @staticmethod
    def _extend_parser(parser):
        RegularizedEvolution._extend_parser(parser)
        add_arguments_from_signature(parser, RegularizedEvolutionMixed)
        return parser

    def saved_keys(self, val: dict):
        res = {"arch_seq": str(val["arch_seq"])}
        hp_names = self.hp_space._space.get_hyperparameter_names()

        for hp_name in hp_names:
            if hp_name == "loss":
                res["loss"] = val["loss"]
            else:
                res[hp_name] = val["hyperparameters"][hp_name]

        return res

    def main(self):

        num_evals_done = 0
        population = collections.deque(maxlen=self.population_size)

        # Filling available nodes at start
        self.evaluator.add_eval_batch(
            self.gen_random_batch(size=self.free_workers))

        # Main loop
        while num_evals_done < self.max_evals:

            # Collecting finished evaluations
            new_results = list(self.evaluator.get_finished_evals())

            if len(new_results) > 0:
                population.extend(new_results)
                stats = {
                    "num_cache_used": self.evaluator.stats["num_cache_used"]
                }
                dhlogger.info(jm(type="env_stats", **stats))
                self.evaluator.dump_evals(saved_keys=self.saved_keys)

                num_received = len(new_results)
                num_evals_done += num_received

                # If the population is big enough evolve the population
                if len(population) == self.population_size:
                    children_batch = []

                    # For each new parent/result we create a child from it
                    for _ in range(len(new_results)):
                        # select_sample
                        indexes = np.random.choice(self.population_size,
                                                   self.sample_size,
                                                   replace=False)
                        sample = [population[i] for i in indexes]

                        # select_parent
                        parent = self.select_parent(sample)

                        # copy_mutate_parent
                        child = self.copy_mutate_arch(parent)
                        # add child to batch
                        children_batch.append(child)

                    # submit_childs
                    if len(new_results) > 0:
                        self.evaluator.add_eval_batch(children_batch)
                else:  # If the population is too small keep increasing it

                    new_batch = self.gen_random_batch(size=len(new_results))

                    self.evaluator.add_eval_batch(new_batch)

    def select_parent(self, sample: list) -> dict:
        cfg, _ = max(sample, key=lambda x: x[1])
        return cfg

    def gen_random_batch(self, size: int) -> list:

        sample = lambda hp, size: [
            hp.sample(self.space.random) for _ in range(size)
        ]
        batch = []
        iterator = zip(*(sample(hp, size)
                         for hp in self.space.get_hyperparameters()))

        for x in iterator:
            #! DeepCopy is critical for nested lists or dicts
            cfg = self.problem.gen_config(x[self.hp_size:], x[:self.hp_size])
            batch.append(cfg)

        return batch

    def copy_mutate_arch(self, parent_cfg: dict) -> dict:
        """
        # ! Time performance is critical because called sequentialy

        Args:
            parent_arch (list(int)): embedding of the parent's architecture.

        Returns:
            dict: embedding of the mutated architecture of the child.

        """

        hp_x = self.problem.extract_hp_values(parent_cfg)
        x = hp_x + parent_cfg["arch_seq"]
        i = np.random.choice(self.hp_size)
        hp = self.space.get_hyperparameters()[i]
        x[i] = hp.sample(self.space.random)

        child_cfg = self.problem.gen_config(x[self.hp_size:], x[:self.hp_size])

        return child_cfg
예제 #25
0
REGRESSORS = {
    "RandomForest": RandomForestRegressor,
    "Linear": LinearRegression,
    "AdaBoost": AdaBoostRegressor,
    "KNeighbors": KNeighborsRegressor,
    "MLP": MLPRegressor,
    "SVR": SVR,
    "XGBoost": XGBRegressor,
}

problem_autosklearn1 = HpProblem()

regressor = problem_autosklearn1.add_hyperparameter(
    name="regressor",
    value=[
        "RandomForest", "Linear", "AdaBoost", "KNeighbors", "MLP", "SVR",
        "XGBoost"
    ],
)

# n_estimators
n_estimators = problem_autosklearn1.add_hyperparameter(name="n_estimators",
                                                       value=(1, 2000,
                                                              "log-uniform"))

cond_n_estimators = cs.OrConjunction(
    cs.EqualsCondition(n_estimators, regressor, "RandomForest"),
    cs.EqualsCondition(n_estimators, regressor, "AdaBoost"),
)

problem_autosklearn1.add_condition(cond_n_estimators)
예제 #26
0
    def test_add_good_reference(self):
        from deephyper.problem import HpProblem

        pb = HpProblem()
        pb.add_hyperparameter((-10, 10), "dim0")
        pb.add_starting_point(dim0=0)
예제 #27
0
class AMBSMixed(NeuralArchitectureSearch):
    def __init__(
        self,
        problem,
        run,
        evaluator,
        surrogate_model="RF",
        acq_func="LCB",
        kappa=1.96,
        xi=0.001,
        liar_strategy="cl_min",
        n_jobs=1,
        **kwargs,
    ):
        super().__init__(
            problem=problem,
            run=run,
            evaluator=evaluator,
            **kwargs,
        )

        self.n_jobs = int(
            n_jobs)  # parallelism of BO surrogate model estimator
        self.kappa = float(kappa)
        self.xi = float(xi)
        self.n_initial_points = self.evaluator.num_workers
        self.liar_strategy = liar_strategy

        # Setup
        na_search_space = self.problem.build_search_space()

        self.hp_space = self.problem._hp_space  #! hyperparameters
        self.hp_size = len(self.hp_space.space.get_hyperparameter_names())
        self.na_space = HpProblem(self.problem.seed)
        for i, vnode in enumerate(na_search_space.variable_nodes):
            self.na_space.add_hyperparameter((0, vnode.num_ops - 1),
                                             name=f"vnode_{i:05d}")

        self.space = CS.ConfigurationSpace(seed=self.problem.seed)
        self.space.add_configuration_space(
            prefix="1", configuration_space=self.hp_space.space)
        self.space.add_configuration_space(
            prefix="2", configuration_space=self.na_space.space)

        # Initialize opitmizer of hyperparameter space
        self.opt = SkOptimizer(
            dimensions=self.space,
            base_estimator=self.get_surrogate_model(surrogate_model,
                                                    self.n_jobs),
            acq_func=acq_func,
            acq_optimizer="sampling",
            acq_func_kwargs={
                "xi": self.xi,
                "kappa": self.kappa
            },
            n_initial_points=self.n_initial_points,
        )

    @staticmethod
    def _extend_parser(parser):
        NeuralArchitectureSearch._extend_parser(parser)
        add_arguments_from_signature(parser, AMBSMixed)
        return parser

    def saved_keys(self, val: dict):
        res = {"arch_seq": str(val["arch_seq"])}
        hp_names = self.hp_space._space.get_hyperparameter_names()

        for hp_name in hp_names:
            if hp_name == "loss":
                res["loss"] = val["loss"]
            else:
                res[hp_name] = val["hyperparameters"][hp_name]

        return res

    def main(self):

        num_evals_done = 0

        # Filling available nodes at start
        dhlogger.info(
            f"Generating {self.evaluator.num_workers} initial points...")
        self.evaluator.add_eval_batch(
            self.get_random_batch(size=self.n_initial_points))

        # Main loop
        while num_evals_done < self.max_evals:

            # Collecting finished evaluations
            new_results = list(self.evaluator.get_finished_evals())

            if len(new_results) > 0:
                stats = {
                    "num_cache_used": self.evaluator.stats["num_cache_used"]
                }
                dhlogger.info(jm(type="env_stats", **stats))
                self.evaluator.dump_evals(saved_keys=self.saved_keys)

                num_received = len(new_results)
                num_evals_done += num_received

                # Transform configurations to list to fit optimizer
                opt_X = []
                opt_y = []
                for cfg, obj in new_results:
                    arch_seq = cfg["arch_seq"]
                    hp_val = self.problem.extract_hp_values(cfg)
                    x = replace_nan(hp_val + arch_seq)
                    opt_X.append(x)
                    opt_y.append(-obj)  #! maximizing

                self.opt.tell(opt_X, opt_y)  #! fit: costly
                new_X = self.opt.ask(n_points=len(new_results),
                                     strategy=self.liar_strategy)

                new_batch = []
                for x in new_X:
                    new_cfg = self.problem.gen_config(x[self.hp_size:],
                                                      x[:self.hp_size])
                    new_batch.append(new_cfg)

                # submit_childs
                if len(new_results) > 0:
                    self.evaluator.add_eval_batch(new_batch)

    def get_surrogate_model(self, name: str, n_jobs: int = None):
        """Get a surrogate model from Scikit-Optimize.

        Args:
            name (str): name of the surrogate model.
            n_jobs (int): number of parallel processes to distribute the computation of the surrogate model.

        Raises:
            ValueError: when the name of the surrogate model is unknown.
        """
        accepted_names = ["RF", "ET", "GBRT", "GP", "DUMMY"]
        if not (name in accepted_names):
            raise ValueError(
                f"Unknown surrogate model {name}, please choose among {accepted_names}."
            )

        if name == "RF":
            surrogate = skopt.learning.RandomForestRegressor(n_jobs=n_jobs)
        elif name == "ET":
            surrogate = skopt.learning.ExtraTreesRegressor(n_jobs=n_jobs)
        elif name == "GBRT":
            surrogate = skopt.learning.GradientBoostingQuantileRegressor(
                n_jobs=n_jobs)
        else:  # for DUMMY and GP
            surrogate = name

        return surrogate

    def get_random_batch(self, size: int) -> list:
        batch = []
        n_points = max(0, size - len(batch))
        if n_points > 0:
            points = self.opt.ask(n_points=n_points)
            for point in points:
                point_as_dict = self.problem.gen_config(
                    point[self.hp_size:], point[:self.hp_size])
                batch.append(point_as_dict)
        return batch

    def to_dict(self, x: list) -> dict:
        hp_x = x[:self.hp_size]
        arch_seq = x[self.hp_size:]
        cfg = self.problem.space.copy()
        cfg["arch_seq"] = arch_seq
        return cfg
예제 #28
0
"""
Hyperparameter optimization problem to try forbidden clauses by directly using ConfigSpace.

Example command line::

    python -m deephyper.search.hps.ambsv1 --evaluator threadPool --problem deephyper.benchmark.hps.toy.problem_cond_1.Problem --run deephyper.benchmark.hps.toy.problem_cond_1.run --max-evals 100 --kappa 0.001
"""
import ConfigSpace as cs
from deephyper.problem import HpProblem

Problem = HpProblem(seed=45)

func = Problem.add_hyperparameter(name="func", value=["f1", "f2"])

# f1 variables
f1_x = Problem.add_hyperparameter(name="f1_x", value=(0.0, 1.0, "uniform"))
f1_y = Problem.add_hyperparameter(name="f1_y", value=(0.0, 1.0, "uniform"))

# f2 variables
f2_x = Problem.add_hyperparameter(name="f2_x", value=(0.0, 1.0, "uniform"))
f2_y = Problem.add_hyperparameter(name="f2_y", value=(0.0, 1.0, "uniform"))

cond_f1_x = cs.EqualsCondition(f1_x, func, "f1")
cond_f1_y = cs.EqualsCondition(f1_y, func, "f1")
Problem.add_condition(cond_f1_x)
Problem.add_condition(cond_f1_y)

cond_f2_x = cs.EqualsCondition(f2_x, func, "f2")
cond_f2_y = cs.EqualsCondition(f2_y, func, "f2")
Problem.add_condition(cond_f2_x)
Problem.add_condition(cond_f2_y)
예제 #29
0
class NaProblem:
    """A Neural Architecture Problem specification for Neural Architecture Search.

    >>> from deephyper.problem import NaProblem
    >>> from deephyper.benchmark.nas.linearReg.load_data import load_data
    >>> from deephyper.nas.preprocessing import minmaxstdscaler
    >>> from deepspace.tabular import OneLayerSpace
    >>> Problem = NaProblem()
    >>> Problem.load_data(load_data)
    >>> Problem.preprocessing(minmaxstdscaler)
    >>> Problem.search_space(OneLayerSpace)
    >>> Problem.hyperparameters(
    ...     batch_size=100,
    ...     learning_rate=0.1,
    ...     optimizer='adam',
    ...     num_epochs=10,
    ...     callbacks=dict(
    ...        EarlyStopping=dict(
    ...             monitor='val_r2',
    ...             mode='max',
    ...             verbose=0,
    ...             patience=5
    ...         )
    ...     )
    ... )
    >>> Problem.loss('mse')
    >>> Problem.metrics(['r2'])
    >>> Problem.objective('val_r2__last')
    """
    def __init__(self):
        self._space = OrderedDict()
        self._hp_space = HpProblem()
        self._space["metrics"] = []
        self._space["hyperparameters"] = dict(verbose=0)

    def __repr__(self):

        preprocessing = (None if self._space.get("preprocessing") is None else
                         module_location(self._space["preprocessing"]["func"]))

        hps = "".join([
            f"\n        * {h}: {self._space['hyperparameters'][h]}"
            for h in self._space["hyperparameters"]
        ])

        if type(self._space["metrics"]) is list:
            metrics = "".join(
                [f"\n        * {m}" for m in self._space["metrics"]])
        else:
            metrics = "".join([
                f"\n        * {m[0]}: {m[1]}"
                for m in self._space["metrics"].items()
            ])

        objective = self._space["objective"]
        if not type(objective) is str:
            objective = module_location(objective)

        out = (
            f"Problem is:\n"
            f"    - search space   : {module_location(self._space['search_space']['class'])}\n"
            f"    - data loading   : {module_location(self._space['load_data']['func'])}\n"
            f"    - preprocessing  : {preprocessing}\n"
            f"    - hyperparameters: {hps}\n"
            f"    - loss           : {self._space['loss']}\n"
            f"    - metrics        : {metrics}\n"
            f"    - objective      : {objective}\n")

        return out

    def load_data(self, func: callable, **kwargs):
        """Define the function loading the data.
        .. code-block:: python

            Problem.load_data(load_data, load_data_kwargs)

        This ``load_data`` callable can follow two different interfaces: Numpy arrays or generators.

        1. **Numpy arrays**:

        In the case of Numpy arrays, the callable passed to ``Problem.load_data(...)`` has to return the following tuple: ``(X_train, y_train), (X_valid, y_valid)``. In the most simple case where the model takes a single input, each of these elements is a Numpy array. Generally, ``X_train`` and ``y_train`` have to be of the same length (i.e., same ``array.shape[0]``) which is also the case for ``X_valid`` and ``y_valid``. Similarly, the shape of the elements of ``X_train`` and ``X_valid`` which is also the case for ``y_train`` and ``y_valid``. An example ``load_data`` function can be

        .. code-block:: python

            import numpy as np

            def load_data(N=100):
                X = np.zeros((N, 1))
                y = np.zeros((N,1))
                return (X, y), (X, y)


        It is also possible for the model to take several inputs. In fact, experimentaly it can be notices that separating some inputs with different inputs can significantly help the learning of the model. Also, sometimes different inputs may be of the "types" for example two molecular fingerprints. In this case, it can be very interesting to share the weights of the model to process these two inputs. In the case of multi-inputs models the ``load_data`` function will also return ``(X_train, y_train), (X_valid, y_valid)`` bu where ``X_train`` and ``X_valid`` are two lists of Numpy arrays. For example, the following is correct:

        .. code-block:: python

            import numpy as np

            def load_data(N=100):
                X = np.zeros((N, 1))
                y = np.zeros((N,1))
                return ([X, X], y), ([X, X], y)


        2. **Generators**:

        Returning generators with a single input:

        .. code-block:: python

            def load_data(N=100):

                tX, ty = np.zeros((N,1)), np.zeros((N,1))
                vX, vy = np.zeros((N,1)), np.zeros((N,1))

                def train_gen():
                    for x, y in zip(tX, ty):
                        yield ({"input_0": x}, y)

                def valid_gen():
                    for x, y in zip(vX, vy):
                        yield ({"input_0": x}, y)

                res = {
                    "train_gen": train_gen,
                    "train_size": N,
                    "valid_gen": valid_gen,
                    "valid_size": N,
                    "types": ({"input_0": tf.float64}, tf.float64),
                    "shapes": ({"input_0": (1, )}, (1, ))
                    }
                return res

        Returning generators with multiple inputs:

        .. code-block:: python

            def load_data(N=100):

                tX0, tX1, ty = np.zeros((N,1)), np.zeros((N,1)), np.zeros((N,1)),
                vX0, vX1, vy = np.zeros((N,1)), np.zeros((N,1)), np.zeros((N,1)),

                def train_gen():
                    for x0, x1, y in zip(tX0, tX1, ty):
                        yield ({
                            "input_0": x0,
                            "input_1": x1
                            }, y)

                def valid_gen():
                    for x0, x1, y in zip(vX0, vX1, vy):
                        yield ({
                            "input_0": x0,
                            "input_1": x1
                        }, y)

                res = {
                    "train_gen": train_gen,
                    "train_size": N,
                    "valid_gen": valid_gen,
                    "valid_size": N,
                    "types": ({"input_0": tf.float64, "input_1": tf.float64}, tf.float64),
                    "shapes": ({"input_0": (5, ), "input_1": (5, )}, (1, ))
                    }

                return res

        Args:
            func (callable): the load data function.
        """

        if not callable(func):
            raise ProblemLoadDataIsNotCallable(func)

        self._space["load_data"] = {"func": func, "kwargs": kwargs}

    def augment(self, func: callable, **kwargs):
        """
        :meta private:
        """

        if not callable(func):
            raise ProblemLoadDataIsNotCallable(func)

        self._space["augment"] = {"func": func, "kwargs": kwargs}

    def search_space(self, space_class, **kwargs):
        """Set a search space for neural architecture search.

        Args:
            space_class (KSearchSpace): an object of type ``KSearchSpace`` which has to implement the ``build()`` method.

        Raises:
            SearchSpaceBuilderMissingParameter: raised when either of ``(input_shape, output_shape)`` are missing parameters of ``func``.
        """

        sign = signature(space_class)
        if not "input_shape" in sign.parameters:
            raise SearchSpaceBuilderMissingParameter("input_shape")

        if not "output_shape" in sign.parameters:
            raise SearchSpaceBuilderMissingParameter("output_shape")

        self._space["search_space"] = {"class": space_class, "kwargs": kwargs}

    def add_hyperparameter(self,
                           value,
                           name: str = None,
                           default_value=None) -> csh.Hyperparameter:
        """Add hyperparameters to search the neural architecture search problem.

        >>> Problem.hyperparameters(
        ...     batch_size=problem.add_hyperparameter((32, 256), "batch_size")
        ...     )

        Args:
            value: a hyperparameter description.
            name: a name of the defined hyperparameter, the same as the current key.
            default_value (Optional): a default value of the hyperparameter.

        Returns:
            Hyperparameter: the defined hyperparameter.
        """
        return self._hp_space.add_hyperparameter(value, name, default_value)

    def preprocessing(self, func: callable):
        """Define how to preprocess your data.

        Args:
            func (callable): a function which returns a preprocessing scikit-learn ``Pipeline``.
        """

        if not callable(func):
            raise ProblemPreprocessingIsNotCallable(func)

        self._space["preprocessing"] = {"func": func}

    def hyperparameters(self, **kwargs):
        """Define hyperparameters used to evaluate generated architectures.

        Hyperparameters can be defined such as:

        .. code-block:: python

            Problem.hyperparameters(
                batch_size=256,
                learning_rate=0.01,
                optimizer="adam",
                num_epochs=20,
                verbose=0,
                callbacks=dict(...),
            )
        """
        if self._space.get("hyperparameters") is None:
            self._space["hyperparameters"] = dict()
        self._space["hyperparameters"].update(kwargs)

    def loss(self, loss, loss_weights=None, class_weights=None):
        """Define the loss used to train generated architectures.

        It can be a ``str`` corresponding to a Keras loss function:

        .. code-block:: python

            problem.loss("categorical_crossentropy")

        A custom loss function can also be defined:

        .. code-block:: python

            def NLL(y, rv_y):
                return -rv_y.log_prob(y)

            problem.loss(NLL)

        The loss can be automatically searched:

        .. code-block:: python

            problem.loss(
                problem.add_hyperparameter(
                    ["mae", "mse", "huber_loss", "log_cosh", "mape", "msle"], "loss"
                )
            )

        It is possible to define a different loss for each output:

        .. code-block:: python

            problem.loss(
                loss={"output_0": "mse", "output_1": "mse"},
                loss_weights={"output_0": 0.0, "output_1": 1.0},
            )

        Args:
            loss (str or callable orlist): a string indicating a specific loss function.
            loss_weights (list): Optional.
            class_weights (dict): Optional.
        """
        if not (type(loss) is csh.CategoricalHyperparameter):
            if not type(loss) is str and not callable(
                    loss) and not type(loss) is dict:
                raise RuntimeError(
                    f"The loss should be either a str, dict or a callable when it's of type {type(loss)}"
                )

            if (type(loss) is dict and loss_weights is not None
                    and len(loss) != len(loss_weights)):
                raise RuntimeError(
                    f"The losses list (len={len(loss)}) and the weights list (len={len(loss_weights)}) should be of same length!"
                )

        self._space["loss"] = loss
        if loss_weights is not None:
            self._space["loss_weights"] = loss_weights

        if class_weights is not None:
            self._space["class_weights"] = class_weights

    def metrics(self, metrics=None):
        """Define a list of metrics for the training of generated architectures.

        A list of metrics can be defined to be monitored or used as an objective. It can be a keyword or a callable. For example, if it is a keyword:

        .. code-block:: python

            problem.metrics(["acc"])

        In case you need multiple metrics:

        .. code-block:: python

            problem.metrics["mae", "mse"]

        In case you want to use a custom metric:

        .. code-block:: python

            def sparse_perplexity(y_true, y_pred):
                cross_entropy = tf.keras.losses.sparse_categorical_crossentropy(y_true, y_pred)
                perplexity = tf.pow(2.0, cross_entropy)
                return perplexity

            problem.metrics([sparse_perplexity])

        Args:
            metrics (list(str or callable) or dict): If ``str`` the metric should be defined in Keras or in DeepHyper. If ``callable`` it should take 2 arguments ``(y_pred, y_true)`` which are a prediction and a true value respectively.
        """

        if metrics is None:
            metrics = []

        self._space["metrics"] = metrics

    def check_objective(self, objective):
        """
        :meta private:
        """

        if not type(objective) is str and not callable(objective):
            raise WrongProblemObjective(objective)

        # elif type(objective) is str:
        #     list_suffix = ["__min", "__max", "__last"]

        #     for suffix in list_suffix:
        #         if suffix in objective:
        #             objective = objective.replace(suffix, "")
        #             break  # only one suffix autorized

        #     objective = objective.replace("val_", "")
        #     possible_names = list()

        #     if type(self._space["metrics"]) is dict:
        #         metrics = list(self._space["metrics"].values())
        #         for k in self._space["metrics"].keys():
        #             objective = objective.replace(f"{k}_", "")

        #     else:  # assuming it s a list
        #         metrics = self._space["metrics"]

        #     for val in ["loss"] + metrics:
        #         if callable(val):
        #             possible_names.append(val.__name__)
        #         else:
        #             possible_names.append(val)

        #     if not (objective in possible_names):
        #         raise WrongProblemObjective(objective, possible_names)

    def objective(self, objective):
        """Define the objective you want to maximize for the search.

        If you want to use the validation accuracy at the last epoch:

        .. code-block:: python

            problem.objective("val_acc")

        .. note:: Be sure to define ``acc`` in the ``problem.metrics(["acc"])``.

        It can accept some prefix and suffix such as ``__min, __max, __last``:

        .. code-block:: python

            problem.objective("-val_acc__max")

        It can be a ``callable``:

        .. code-block:: python

            def myobjective(history: dict) -> float:
                return history["val_acc"][-1]

            problem.objective(myobjective)


        Args:
            objective (str or callable): The objective will be maximized. If ``objective`` is ``str`` then it should be either 'loss' or a defined metric. You can use the ``'val_'`` prefix when you want to select the objective on the validation set. You can use one of ``['__min', '__max', '__last']`` which respectively means you want to select the min, max or last value among all epochs. Using '__last' will save a consequent compute time because the evaluation will not compute metrics on validation set for all epochs but the last. If ``objective`` is callable it should return a scalar value (i.e. float) and it will take a ``dict`` parameter. The ``dict`` will contain keys corresponding to loss and metrics such as ``['loss', 'val_loss', 'r2', 'val_r2]``, it will also contains ``'n_parameters'`` which corresponds to the number of trainable parameters of the current model, ``'training_time'`` which corresponds to the time required to train the model, ``'predict_time'`` which corresponds to the time required to make a prediction over the whole validation set. If this callable has a ``'__last'`` suffix then the evaluation will only compute validation loss/metrics for the last epoch. If this callable has contains 'with_pred' in its name then the ``dict`` will have two other keys ``['y_pred', 'y_true']`` where ``'y_pred`` corresponds to prediction of the model on validation set and ``'y_true'`` corresponds to real prediction.
        Raise:
            WrongProblemObjective: raised when the objective is of a wrong definition.
        """
        if (not self._space.get("loss") is None
                and not self._space.get("metrics") is None):
            self.check_objective(objective)
        else:
            raise NaProblemError(
                ".loss and .metrics should be defined before .objective!")

        self._space["objective"] = objective

    @property
    def space(self):
        keys = list(self._space.keys())
        keys.sort()
        space = OrderedDict(**{d: self._space[d] for d in keys})
        return space

    def build_search_space(self, seed=None):
        """Build and return a search space object using the infered data shapes after loading data.

        Returns:
            KSearchSpace: A search space instance.
        """
        config = self.space
        input_shape, output_shape, _ = setup_data(config, add_to_config=False)

        search_space = get_search_space(config,
                                        input_shape,
                                        output_shape,
                                        seed=seed)
        return search_space

    def get_keras_model(self, arch_seq: list) -> tf.keras.Model:
        """Get a keras model object from a set of decisions in the current search space.
        Args:
            arch_seq (list): a list of int of floats describing a choice of operations for the search space defined in the current problem.
        """
        search_space = self.build_search_space()
        return search_space.sample(arch_seq)

    def gen_config(self, arch_seq: list, hp_values: list) -> dict:
        """Generate a ``dict`` configuration from the ``arch_seq`` and ``hp_values`` passed.

        Args:
            arch_seq (list): a valid embedding of a neural network described by the search space of the current ``NaProblem``.
            hp_values (list): a valid list of hyperparameters corresponding to the defined hyperparameters of the current ``NaProblem``.
        """
        config = deepcopy(self.space)

        # architecture DNA
        config["arch_seq"] = arch_seq

        # replace hp values in the config
        hp_names = self._hp_space._space.get_hyperparameter_names()

        for hp_name, hp_value in zip(hp_names, hp_values):

            if hp_name == "loss":
                config["loss"] = hp_value
            else:
                config["hyperparameters"][hp_name] = hp_value

        return config

    def extract_hp_values(self, config):
        """Extract the value of hyperparameters present in ``config`` based on the defined hyperparameters in the current ``NaProblem``"""
        hp_names = self.hyperparameter_names
        hp_values = []
        for hp_name in hp_names:
            if hp_name == "loss":
                hp_values.append(config["loss"])
            else:
                hp_values.append(config["hyperparameters"][hp_name])

        return hp_values

    @property
    def hyperparameter_names(self):
        """The list of hyperparameters names."""
        return self._hp_space.hyperparameter_names

    @property
    def default_hp_configuration(self):
        """The default configuration as a dictionnary."""
        return self._hp_space.default_configuration