diff --git a/python/ray/tune/examples/nevergrad_example.py b/python/ray/tune/examples/nevergrad_example.py index 082964f5f..3c63dea0a 100644 --- a/python/ray/tune/examples/nevergrad_example.py +++ b/python/ray/tune/examples/nevergrad_example.py @@ -42,9 +42,18 @@ if __name__ == "__main__": "timesteps_total": 100 } } - optimizer = optimizerlib.OnePlusOne(dimension=2) + instrumentation = 2 + parameter_names = ["height", "width"] + # With nevergrad v0.2.0+ the following is also possible: + # from nevergrad import instrumentation as inst + # instrumentation = inst.Instrumentation( + # height=inst.var.Array(1).bounded(0, 200).asfloat(), + # width=inst.var.OrderedDiscrete([0, 10, 20, 30, 40, 50])) + # parameter_names = None # names are provided by the instrumentation + optimizer = optimizerlib.OnePlusOne(instrumentation) algo = NevergradSearch( - optimizer, ["height", "width"], + optimizer, + parameter_names, max_concurrent=4, reward_attr="neg_mean_loss") scheduler = AsyncHyperBandScheduler(reward_attr="neg_mean_loss") diff --git a/python/ray/tune/suggest/nevergrad.py b/python/ray/tune/suggest/nevergrad.py index 941e570a9..284311a36 100644 --- a/python/ray/tune/suggest/nevergrad.py +++ b/python/ray/tune/suggest/nevergrad.py @@ -23,7 +23,9 @@ class NevergradSearch(SuggestionAlgorithm): optimizer (nevergrad.optimization.Optimizer): Optimizer provided from Nevergrad. parameter_names (list): List of parameter names. Should match - the dimension of the optimizer output. + the dimension of the optimizer output. Alternatively, set to None + if the optimizer is already instrumented with kwargs + (see nevergrad v0.2.0+). max_concurrent (int): Number of maximum concurrent trials. Defaults to 10. reward_attr (str): The training result objective value attribute. @@ -31,9 +33,24 @@ class NevergradSearch(SuggestionAlgorithm): Example: >>> from nevergrad.optimization import optimizerlib - >>> optimizer = optimizerlib.OnePlusOne(dimension=1, budget=100) - >>> algo = NevergradSearch( - >>> optimizer, max_concurrent=4, reward_attr="neg_mean_loss") + >>> instrumentation = 1 + >>> optimizer = optimizerlib.OnePlusOne(instrumentation, budget=100) + >>> algo = NevergradSearch(optimizer, ["lr"], max_concurrent=4, + >>> reward_attr="neg_mean_loss") + + Note: + In nevergrad v0.2.0+, optimizers can be instrumented. + For instance, the following will specifies searching + for "lr" from 1 to 2. + + >>> from nevergrad.optimization import optimizerlib + >>> from nevergrad import instrumentation as inst + >>> lr = inst.var.Array(1).bounded(1, 2).asfloat() + >>> instrumentation = inst.Instrumentation(lr=lr) + >>> optimizer = optimizerlib.OnePlusOne(instrumentation, budget=100) + >>> algo = NevergradSearch(optimizer, None, max_concurrent=4, + >>> reward_attr="neg_mean_loss") + """ def __init__(self, @@ -50,12 +67,40 @@ class NevergradSearch(SuggestionAlgorithm): self._nevergrad_opt = optimizer self._live_trial_mapping = {} super(NevergradSearch, self).__init__(**kwargs) + # validate parameters + if hasattr(optimizer, "instrumentation"): # added in v0.2.0 + if optimizer.instrumentation.kwargs: + if optimizer.instrumentation.args: + raise ValueError( + "Instrumented optimizers should use kwargs only") + if parameter_names is not None: + raise ValueError("Instrumented optimizers should provide " + "None as parameter_names") + else: + if parameter_names is None: + raise ValueError("Non-instrumented optimizers should have " + "a list of parameter_names") + if len(optimizer.instrumentation.args) != 1: + raise ValueError( + "Instrumented optimizers should use kwargs only") + if parameter_names is not None and optimizer.dimension != len( + parameter_names): + raise ValueError("len(parameters_names) must match optimizer " + "dimension for non-instrumented optimizers") def _suggest(self, trial_id): if self._num_live_trials() >= self._max_concurrent: return None suggested_config = self._nevergrad_opt.ask() self._live_trial_mapping[trial_id] = suggested_config + # in v0.2.0+, output of ask() is a Candidate, + # with fields args and kwargs + if hasattr(self._nevergrad_opt, "instrumentation"): + if not suggested_config.kwargs: + return dict(zip(self._parameters, suggested_config.args[0])) + else: + return suggested_config.kwargs + # legacy: output of ask() is a np.ndarray return dict(zip(self._parameters, suggested_config)) def on_trial_result(self, trial_id, result):