Skip to content
Merged
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/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
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"):
Copy link
Contributor

@richardliaw richardliaw Sep 8, 2020

Choose a reason for hiding this comment

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

nice - need to explicitly say this on the release notes

"""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
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)
6 changes: 4 additions & 2 deletions python/ray/tune/suggest/bayesopt.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,10 @@ def register_analysis(self, analysis):
analysis (ExperimentAnalysis): Optionally, the previous analysis
to integrate.
"""
for (_, report), params in zip(analysis.dataframe().iterrows(),
analysis.get_all_configs().values()):
for (_, report), params in zip(
analysis.dataframe(metric=self._metric,
mode=self._mode).iterrows(),
analysis.get_all_configs().values()):
# We add the obtained results to the
# gaussian process optimizer
self._register_result(params, report)
Expand Down
2 changes: 1 addition & 1 deletion python/ray/tune/tests/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ def training_function(config):
metric="mean_loss", mode="min"))

# Get a dataframe for analyzing trial results.
df = analysis.dataframe()
df = analysis.results_df
# __quick_start_end__
9 changes: 6 additions & 3 deletions python/ray/tune/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,22 +520,25 @@ def train(config, reporter):
analysis = tune.run(train, num_samples=10, stop=stopper)
self.assertTrue(
all(t.status == Trial.TERMINATED for t in analysis.trials))
self.assertTrue(len(analysis.dataframe()) <= top)
self.assertTrue(
len(analysis.dataframe(metric="test", mode="max")) <= top)

patience = 5
stopper = EarlyStopping("test", top=top, mode="min", patience=patience)

analysis = tune.run(train, num_samples=20, stop=stopper)
self.assertTrue(
all(t.status == Trial.TERMINATED for t in analysis.trials))
self.assertTrue(len(analysis.dataframe()) <= patience)
self.assertTrue(
len(analysis.dataframe(metric="test", mode="max")) <= patience)

stopper = EarlyStopping("test", top=top, mode="min")

analysis = tune.run(train, num_samples=10, stop=stopper)
self.assertTrue(
all(t.status == Trial.TERMINATED for t in analysis.trials))
self.assertTrue(len(analysis.dataframe()) <= top)
self.assertTrue(
len(analysis.dataframe(metric="test", mode="max")) <= top)

def testBadStoppingFunction(self):
def train(config, reporter):
Expand Down
Loading