Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ This example runs a parallel grid search to optimize an example objective functi
print("Best config: ", analysis.get_best_config(metric="mean_loss"))

# Get a dataframe for analyzing trial results.
df = analysis.dataframe()
df = analysis.results_df

If TensorBoard is installed, automatically visualize all trial results:

Expand Down
2 changes: 1 addition & 1 deletion doc/requirements-doc.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ sphinx_rtd_theme
tabulate
uvicorn
werkzeug
tune-sklearn==0.0.5
git+git://github.com/ray-project/tune-sklearn@master#tune-sklearn
scikit-optimize
2 changes: 1 addition & 1 deletion doc/source/tune/api_docs/analysis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Here are some example operations for obtaining a summary of your experiment:
.. code-block:: python

# Get a dataframe for the last reported results of all of the trials
df = analysis.dataframe()
df = analysis.results_df

# Get a dataframe for the max accuracy seen for each trial
df = analysis.dataframe(metric="mean_accuracy", mode="max")
Expand Down
14 changes: 11 additions & 3 deletions doc/source/tune/key-concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -219,16 +219,24 @@ Analysis

analysis = tune.run(trainable, search_alg=algo, stop={"training_iteration": 20})

# Get the best hyperparameters
best_hyperparameters = analysis.get_best_config()
best_trial = analysis.best_trial # Get best trial
best_config = analysis.best_config # Get best trial's hyperparameters
best_logdir = analysis.best_logdir # Get best trial's logdir
best_checkpoint = analysis.best_checkpoint # Get best trial's best checkpoint
best_result = analysis.best_result # Get best trial's last results
best_result_df = analysis.best_result_df # Get best result as pandas dataframe

This object can also retrieve all training runs as dataframes, allowing you to do ad-hoc data analysis over your results.

.. code-block:: python

# Get a dataframe for the max score seen for each trial
# Get a dataframe with the last results for each trial
df_results = analysis.results_df

# Get a dataframe of results for a specific score or mode
df = analysis.dataframe(metric="score", mode="max")


What's Next?
-------------

Expand Down
2 changes: 1 addition & 1 deletion python/ray/dashboard/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -806,7 +806,7 @@ def collect(self):

# search through all the sub_directories in log directory
analysis = Analysis(str(self._logdir))
df = analysis.dataframe()
df = analysis.dataframe(metric="episode_reward_mean", mode="max")

if len(df) == 0 or "trial_id" not in df.columns:
return
Expand Down
2 changes: 1 addition & 1 deletion python/ray/tune/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ py_test(

py_test(
name = "test_sample",
size = "medium",
size = "small",
srcs = ["tests/test_sample.py"],
deps = [":tune_lib"],
tags = ["exclusive"],
Expand Down
161 changes: 158 additions & 3 deletions python/ray/tune/analysis/experiment_analysis.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import json
import logging
import os
from typing import Dict

from ray.tune.checkpoint_manager import Checkpoint
from ray.tune.utils import flatten_dict

try:
import pandas as pd
from pandas import DataFrame
except ImportError:
pd = None
DataFrame = None

from ray.tune.error import TuneError
from ray.tune.result import EXPR_PROGRESS_FILE, EXPR_PARAM_FILE,\
Expand Down Expand Up @@ -80,6 +86,9 @@ def dataframe(self, metric=None, mode=None):
Returns:
pd.DataFrame: Constructed from a result dict of each trial.
"""
metric = self._validate_metric(metric)
mode = self._validate_mode(mode)

rows = self._retrieve_rows(metric=metric, mode=mode)
all_configs = self.get_all_configs(prefix=True)
for path, config in all_configs.items():
Expand Down Expand Up @@ -227,6 +236,9 @@ def get_best_checkpoint(self, trial, metric=None, mode=None):
mode = self._validate_mode(mode)

checkpoint_paths = self.get_trial_checkpoints_paths(trial, metric)
if not checkpoint_paths:
logger.error(f"No checkpoints have been found for trial {trial}.")
return None
if mode == "max":
return max(checkpoint_paths, key=lambda x: x[1])[0]
else:
Expand Down Expand Up @@ -316,7 +328,150 @@ def __init__(self,
os.path.dirname(experiment_checkpoint_path), default_metric,
default_mode)

def get_best_trial(self, metric=None, mode=None, scope="all"):
@property
def best_trial(self) -> Trial:
"""Get the best trial of the experiment

The best trial is determined by comparing the last trial results
using the `metric` and `mode` parameters passed to `tune.run()`.

If you didn't pass these parameters, use
`get_best_trial(metric, mode, scope)` instead.
"""
if not self.default_metric or not self.default_mode:
raise ValueError(
"To fetch the `best_trial`, pass a `metric` and `mode` "
"parameter to `tune.run()`. Alternatively, use the "
"`get_best_trial(metric, mode)` method to set the metric "
"and mode explicitly.")
return self.get_best_trial(self.default_metric, self.default_mode)

@property
def best_config(self) -> Dict:
"""Get the config of the best trial of the experiment

The best trial is determined by comparing the last trial results
using the `metric` and `mode` parameters passed to `tune.run()`.

If you didn't pass these parameters, use
`get_best_config(metric, mode, scope)` instead.
"""
if not self.default_metric or not self.default_mode:
raise ValueError(
"To fetch the `best_config`, pass a `metric` and `mode` "
"parameter to `tune.run()`. Alternatively, use the "
"`get_best_config(metric, mode)` method to set the metric "
"and mode explicitly.")
return self.get_best_config(self.default_metric, self.default_mode)

@property
def best_checkpoint(self) -> Checkpoint:
"""Get the checkpoint of the best trial of the experiment

The best trial is determined by comparing the last trial results
using the `metric` and `mode` parameters passed to `tune.run()`.

If you didn't pass these parameters, use
`get_best_checkpoint(trial, metric, mode)` instead.
"""
if not self.default_metric or not self.default_mode:
raise ValueError(
"To fetch the `best_checkpoint`, pass a `metric` and `mode` "
"parameter to `tune.run()`. Alternatively, use the "
"`get_best_checkpoint(trial, metric, mode)` method to set the "
"metric and mode explicitly.")
best_trial = self.best_trial
return self.get_best_checkpoint(best_trial, self.default_metric,
self.default_mode)

@property
def best_logdir(self) -> str:
"""Get the logdir of the best trial of the experiment

The best trial is determined by comparing the last trial results
using the `metric` and `mode` parameters passed to `tune.run()`.

If you didn't pass these parameters, use
`get_best_logdir(metric, mode)` instead.
"""
if not self.default_metric or not self.default_mode:
raise ValueError(
"To fetch the `best_logdir`, pass a `metric` and `mode` "
"parameter to `tune.run()`. Alternatively, use the "
"`get_best_logdir(metric, mode, scope)` method to set the "
"metric and mode explicitly.")
return self.get_best_logdir(self.default_metric, self.default_mode)

@property
def best_dataframe(self) -> DataFrame:
"""Get the full result dataframe of the best trial of the experiment

The best trial is determined by comparing the last trial results
using the `metric` and `mode` parameters passed to `tune.run()`.

If you didn't pass these parameters, use
`get_best_logdir(metric, mode)` and use it to look for the dataframe
in the `self.trial_dataframes` dict.
"""
if not self.default_metric or not self.default_mode:
raise ValueError(
"To fetch the `best_result`, pass a `metric` and `mode` "
"parameter to `tune.run()`.")
best_logdir = self.best_logdir
return self.trial_dataframes[best_logdir]

@property
def best_result(self) -> Dict:
"""Get the last result of the best trial of the experiment

The best trial is determined by comparing the last trial results
using the `metric` and `mode` parameters passed to `tune.run()`.

If you didn't pass these parameters, use
`get_best_trial(metric, mode, scope).last_result` instead.
"""
if not self.default_metric or not self.default_mode:
raise ValueError(
"To fetch the `best_result`, pass a `metric` and `mode` "
"parameter to `tune.run()`. Alternatively, use "
"`get_best_trial(metric, mode).last_result` to set "
"the metric and mode explicitly and fetch the last result.")
return self.best_trial.last_result

@property
def best_result_df(self) -> DataFrame:
"""Get the best result of the experiment as a pandas dataframe.

The best trial is determined by comparing the last trial results
using the `metric` and `mode` parameters passed to `tune.run()`.

If you didn't pass these parameters, use
`get_best_trial(metric, mode, scope).last_result` instead.
"""
if not pd:
raise ValueError("`best_result_df` requires pandas. Install with "
"`pip install pandas`.")
best_result = flatten_dict(self.best_result, delimiter=".")
return pd.DataFrame.from_records([best_result], index="trial_id")

@property
def results(self) -> Dict[str, Dict]:
"""Get the last result of the all trials of the experiment"""
return {trial.trial_id: trial.last_result for trial in self.trials}

@property
def results_df(self) -> DataFrame:
if not pd:
raise ValueError("`best_result_df` requires pandas. Install with "
"`pip install pandas`.")
return pd.DataFrame.from_records(
[
flatten_dict(trial.last_result, delimiter=".")
for trial in self.trials
],
index="trial_id")

def get_best_trial(self, metric=None, mode=None, scope="last"):
"""Retrieve the best trial object.

Compares all trials' scores on ``metric``.
Expand Down Expand Up @@ -380,7 +535,7 @@ def get_best_trial(self, metric=None, mode=None, scope="all"):
"parameter?")
return best_trial

def get_best_config(self, metric=None, mode=None, scope="all"):
def get_best_config(self, metric=None, mode=None, scope="last"):
"""Retrieve the best config corresponding to the trial.

Compares all trials' scores on `metric`.
Expand All @@ -407,7 +562,7 @@ def get_best_config(self, metric=None, mode=None, scope="all"):
best_trial = self.get_best_trial(metric, mode, scope)
return best_trial.config if best_trial else None

def get_best_logdir(self, metric=None, mode=None, scope="all"):
def get_best_logdir(self, metric=None, mode=None, scope="last"):
"""Retrieve the logdir corresponding to the best trial.

Compares all trials' scores on `metric`.
Expand Down
3 changes: 2 additions & 1 deletion python/ray/tune/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ def list_trials(experiment_path,
_check_tabulate()

try:
checkpoints_df = Analysis(experiment_path).dataframe()
checkpoints_df = Analysis(experiment_path).dataframe(
metric="episode_reward_mean", mode="max")
except TuneError:
raise click.ClickException("No trial data found!")

Expand Down
2 changes: 1 addition & 1 deletion python/ray/tune/examples/mnist_pytorch.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def train_mnist(config):
else:
ray.init(num_cpus=2 if args.smoke_test else None)
sched = AsyncHyperBandScheduler(
time_attr="training_iteration", metric="mean_accuracy")
time_attr="training_iteration", metric="mean_accuracy", mode="max")
analysis = tune.run(
train_mnist,
name="exp",
Expand Down
4 changes: 3 additions & 1 deletion python/ray/tune/examples/mnist_pytorch_trainable.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,11 @@ def load_checkpoint(self, checkpoint_path):
if __name__ == "__main__":
args = parser.parse_args()
ray.init(address=args.ray_address, num_cpus=6 if args.smoke_test else None)
sched = ASHAScheduler(metric="mean_accuracy")
sched = ASHAScheduler()
analysis = tune.run(
TrainMNIST,
metric="mean_accuracy",
mode="max",
scheduler=sched,
stop={
"mean_accuracy": 0.95,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,6 @@ def _export_model(self, export_formats, export_dir):

# demo of the trained Generators
if not args.smoke_test:
logdirs = analysis.dataframe()["logdir"].tolist()
logdirs = analysis.results_df["logdir"].tolist()
model_paths = [os.path.join(d, "exported_models") for d in logdirs]
demo_gan(analysis, model_paths)
4 changes: 2 additions & 2 deletions python/ray/tune/schedulers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

def create_scheduler(
scheduler,
metric="episode_reward_mean",
mode="max",
metric=None,
mode=None,
**kwargs,
):
"""Instantiate a scheduler based on the given string.
Expand Down
39 changes: 34 additions & 5 deletions python/ray/tune/schedulers/async_hyperband.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ class AsyncHyperBandScheduler(FIFOScheduler):
def __init__(self,
time_attr="training_iteration",
reward_attr=None,
metric="episode_reward_mean",
mode="max",
metric=None,
mode=None,
max_t=100,
grace_period=1,
reduction_factor=4,
Expand All @@ -49,7 +49,8 @@ def __init__(self,
assert grace_period > 0, "grace_period must be positive!"
assert reduction_factor > 1, "Reduction Factor not valid!"
assert brackets > 0, "brackets must be positive!"
assert mode in ["min", "max"], "`mode` must be 'min' or 'max'!"
if mode:
assert mode in ["min", "max"], "`mode` must be 'min' or 'max'!"

if reward_attr is not None:
mode = "max"
Expand All @@ -73,13 +74,41 @@ def __init__(self,
self._counter = 0 # for
self._num_stopped = 0
self._metric = metric
if mode == "max":
self._mode = mode
self._metric_op = None
if self._mode == "max":
self._metric_op = 1.
elif mode == "min":
elif self._mode == "min":
self._metric_op = -1.
self._time_attr = time_attr

def set_search_properties(self, metric, mode):
if self._metric and metric:
return False
if self._mode and mode:
return False

if metric:
self._metric = metric
if mode:
self._mode = mode

if self._mode == "max":
self._metric_op = 1.
elif self._mode == "min":
self._metric_op = -1.

return True

def on_trial_add(self, trial_runner, trial):
if not self._metric or not self._metric_op:
raise ValueError(
"{} has been instantiated without a valid `metric` ({}) or "
"`mode` ({}) parameter. Either pass these parameters when "
"instantiating the scheduler, or pass them as parameters "
"to `tune.run()`".format(self.__class__.__name__, self._metric,
self._mode))

sizes = np.array([len(b._rungs) for b in self._brackets])
probs = np.e**(sizes - sizes.max())
normalized = probs / probs.sum()
Expand Down
Loading