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

[tune] OptunaSearch: check compatibility of search space with evaluated_rewards #18625

Merged
merged 8 commits into from
Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 83 additions & 30 deletions python/ray/tune/suggest/optuna.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ class _OptunaTrialSuggestCaptor:
`suggest_` callables with a function capturing the returned value,
which will be saved in the ``captured_values`` dict.
"""

def __init__(self, ot_trial: OptunaTrial) -> None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we leave an empty line here?

self.ot_trial = ot_trial
self.captured_values: Dict[str, Any] = {}
Expand Down Expand Up @@ -126,6 +125,12 @@ class OptunaSearch(Searcher):
needing to re-compute the trial. Must be the same length as
points_to_evaluate.

..warning::
When using evaluated_rewards, the search space `space` must
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
When using evaluated_rewards, the search space `space` must
When using ``evaluated_rewards``, the search space ``space`` must

be provided as a :class:`dict` with parameter names as keys and
``optuna.distributions``. The define-by-run definition is not
yet supported.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
``optuna.distributions``. The define-by-run definition is not
yet supported.
``optuna.distributions`` instances as values. The define-by-run
search space definition is not yet supported with this functionality.


Tune automatically converts search spaces to Optuna's format:

.. code-block:: python
Expand All @@ -151,7 +156,7 @@ class OptunaSearch(Searcher):
from ray.tune.suggest.optuna import OptunaSearch
import optuna

config = {
space = {
"a": optuna.distributions.UniformDistribution(6, 8),
"b": optuna.distributions.LogUniformDistribution(1e-4, 1e-2),
}
Expand All @@ -178,14 +183,58 @@ def define_search_space(trial: optuna.Trial):

tune.run(trainable, search_alg=optuna_search)

You can pass configs that will be evaluated first using
Yard1 marked this conversation as resolved.
Show resolved Hide resolved
`points_to_evaluate`:

.. code-block:: python

from ray.tune.suggest.optuna import OptunaSearch
import optuna

space = {
"a": optuna.distributions.UniformDistribution(6, 8),
"b": optuna.distributions.LogUniformDistribution(1e-4, 1e-2),
}

optuna_search = OptunaSearch(
space,
points_to_evaluate=[{"a": 6.5, "b": 5e-4}, {"a": 7.5, "b": 1e-3}]
metric="loss",
mode="min")

tune.run(trainable, search_alg=optuna_search)

Avoid re-running evaluated trials by passing the rewards together with
`points_to_evaluate`:

.. code-block:: python

from ray.tune.suggest.optuna import OptunaSearch
import optuna

space = {
"a": optuna.distributions.UniformDistribution(6, 8),
"b": optuna.distributions.LogUniformDistribution(1e-4, 1e-2),
}

optuna_search = OptunaSearch(
space,
points_to_evaluate=[{"a": 6.5, "b": 5e-4}, {"a": 7.5, "b": 1e-3}]
evaluated_rewards=[0.89, 0.42]
metric="loss",
mode="min")

tune.run(trainable, search_alg=optuna_search)

.. versionadded:: 0.8.8

"""

def __init__(self,
space: Optional[Union[Dict[str, "OptunaDistribution"], List[
Tuple], Callable[["OptunaTrial"], Optional[Dict[
str, Any]]]]] = None,
space: Optional[Union[Dict[str,
"OptunaDistribution"], List[Tuple],
Callable[["OptunaTrial"],
Optional[Dict[str,
Any]]]]] = None,
metric: Optional[str] = None,
mode: Optional[str] = None,
points_to_evaluate: Optional[List[Dict]] = None,
Expand All @@ -194,18 +243,17 @@ def __init__(self,
evaluated_rewards: Optional[List] = None):
assert ot is not None, (
"Optuna must be installed! Run `pip install optuna`.")
super(OptunaSearch, self).__init__(
metric=metric,
mode=mode,
max_concurrent=None,
use_early_stopped_trials=None)
super(OptunaSearch, self).__init__(metric=metric,
mode=mode,
max_concurrent=None,
use_early_stopped_trials=None)

if isinstance(space, dict) and space:
resolved_vars, domain_vars, grid_vars = parse_spec_vars(space)
if domain_vars or grid_vars:
logger.warning(
UNRESOLVED_SEARCH_SPACE.format(
par="space", cls=type(self).__name__))
UNRESOLVED_SEARCH_SPACE.format(par="space",
cls=type(self).__name__))
space = self.convert_search_space(space)
else:
# Flatten to support nested dicts
Expand Down Expand Up @@ -287,9 +335,11 @@ def set_search_properties(self, metric: Optional[str], mode: Optional[str],
self._setup_study(mode)
return True

def _suggest_from_define_by_run_func(
self, func: Callable[["OptunaTrial"], Optional[Dict[str, Any]]],
ot_trial: "OptunaTrial") -> Dict:
def _suggest_from_define_by_run_func(self,
func: Callable[["OptunaTrial"],
Optional[Dict[str,
Any]]],
ot_trial: "OptunaTrial") -> Dict:
captor = _OptunaTrialSuggestCaptor(ot_trial)
time_start = time.time()
ret = func(captor)
Expand Down Expand Up @@ -321,14 +371,13 @@ def _suggest_from_define_by_run_func(
def suggest(self, trial_id: str) -> Optional[Dict]:
if not self._space:
raise RuntimeError(
UNDEFINED_SEARCH_SPACE.format(
cls=self.__class__.__name__, space="space"))
UNDEFINED_SEARCH_SPACE.format(cls=self.__class__.__name__,
space="space"))
if not self._metric or not self._mode:
raise RuntimeError(
UNDEFINED_METRIC_MODE.format(
cls=self.__class__.__name__,
metric=self._metric,
mode=self._mode))
UNDEFINED_METRIC_MODE.format(cls=self.__class__.__name__,
metric=self._metric,
mode=self._mode))

if isinstance(self._space, list):
# Keep for backwards compatibility
Expand All @@ -340,8 +389,8 @@ def suggest(self, trial_id: str) -> Optional[Dict]:

# getattr will fetch the trial.suggest_ function on Optuna trials
params = {
args[0] if len(args) > 0 else kwargs["name"]: getattr(
ot_trial, fn)(*args, **kwargs)
args[0] if len(args) > 0 else kwargs["name"]:
getattr(ot_trial, fn)(*args, **kwargs)
for (fn, args, kwargs) in self._space
}
elif callable(self._space):
Expand Down Expand Up @@ -394,14 +443,18 @@ def add_evaluated_point(self,
intermediate_values: Optional[List[float]] = None):
if not self._space:
raise RuntimeError(
UNDEFINED_SEARCH_SPACE.format(
cls=self.__class__.__name__, space="space"))
UNDEFINED_SEARCH_SPACE.format(cls=self.__class__.__name__,
space="space"))
if not self._metric or not self._mode:
raise RuntimeError(
UNDEFINED_METRIC_MODE.format(
cls=self.__class__.__name__,
metric=self._metric,
mode=self._mode))
UNDEFINED_METRIC_MODE.format(cls=self.__class__.__name__,
metric=self._metric,
mode=self._mode))
if callable(self._space):
raise TypeError(
"Define-by-run function passed in `space` argument is not"
"yet supported when using `evaluated_rewards`. Please provide"
"a 'OptunaDistribution' dict.")
ccssmnn marked this conversation as resolved.
Show resolved Hide resolved

ot_trial_state = OptunaTrialState.COMPLETE
if error:
Expand Down
Loading