[tune] Experiment Management API (#1328)

* init for exposing external interface

* revisions

* http server

* small

* simplify

* ui

* fixes

* test

* nit

* nit

* merge

* untested

* nits

* nit

* init tests

* tests

* more tests

* nit

* fix hyperband

* cleanup

* nits

* good stuff

* cleanup

* comments and need to test

* nit

* notebook

* testing

* test and expose server

* server_tests

* docs

* periods

* fix tests

* committing test

* fi
This commit is contained in:
Richard Liaw
2018-01-24 13:45:10 -08:00
committed by Eric Liang
parent 1d2a28ab07
commit a7d544424c
15 changed files with 599 additions and 88 deletions
+40 -39
View File
@@ -87,7 +87,9 @@ class HyperBandScheduler(FIFOScheduler):
self._time_attr = time_attr
def on_trial_add(self, trial_runner, trial):
"""On a new trial add, if current bracket is not filled,
"""Adds new trial.
On a new trial add, if current bracket is not filled,
add to current bracket. Else, if current band is not filled,
create new bracket, add to current bracket.
Else, create new iteration, create new bracket, add to bracket."""
@@ -136,9 +138,8 @@ class HyperBandScheduler(FIFOScheduler):
This scheduler will not start trials but will stop trials.
The current running trial will not be handled,
as the trialrunner will be given control to handle it.
as the trialrunner will be given control to handle it."""
# TODO(rliaw) should be only called if trial has not errored"""
bracket, _ = self._trial_info[trial]
bracket.update_trial_stats(trial, result)
@@ -160,16 +161,17 @@ class HyperBandScheduler(FIFOScheduler):
action = TrialScheduler.PAUSE
if bracket.cur_iter_done():
if bracket.finished():
self._cleanup_bracket(trial_runner, bracket)
bracket.cleanup_full(trial_runner)
return TrialScheduler.CONTINUE
good, bad = bracket.successive_halving(self._reward_attr)
# kill bad trials
self._num_stopped += len(bad)
for t in bad:
if t.status == Trial.PAUSED:
self._cleanup_trial(trial_runner, t, bracket, hard=True)
trial_runner.stop_trial(t)
elif t.status == Trial.RUNNING:
self._cleanup_trial(trial_runner, t, bracket, hard=False)
bracket.cleanup_trial(t)
action = TrialScheduler.STOP
else:
raise Exception("Trial with unexpected status encountered")
@@ -185,47 +187,30 @@ class HyperBandScheduler(FIFOScheduler):
action = TrialScheduler.CONTINUE
return action
def _cleanup_trial(self, trial_runner, t, bracket, hard=False):
"""Bookkeeping for trials finished. If `hard=True`, then
this scheduler will force the trial_runner to release resources.
def on_trial_remove(self, trial_runner, trial):
"""Notification when trial terminates.
Otherwise, only clean up trial information locally."""
self._num_stopped += 1
if hard:
trial_runner._stop_trial(t)
bracket.cleanup_trial(t)
def _cleanup_bracket(self, trial_runner, bracket):
"""Cleans up bracket after bracket is completely finished.
Lets the last trial continue to run until termination condition
kicks in."""
for trial in bracket.current_trials():
if (trial.status == Trial.PAUSED):
self._cleanup_trial(
trial_runner, trial, bracket,
hard=True)
Trial info is removed from bracket. Triggers halving if bracket is
not finished."""
bracket, _ = self._trial_info[trial]
bracket.cleanup_trial(trial)
if not bracket.finished():
self._process_bracket(trial_runner, bracket, trial)
def on_trial_complete(self, trial_runner, trial, result):
"""Cleans up trial info from bracket if trial completed early."""
bracket, _ = self._trial_info[trial]
self._cleanup_trial(trial_runner, trial, bracket, hard=False)
self._process_bracket(trial_runner, bracket, trial)
self.on_trial_remove(trial_runner, trial)
def on_trial_error(self, trial_runner, trial):
"""Cleans up trial info from bracket if trial errored early."""
bracket, _ = self._trial_info[trial]
self._cleanup_trial(trial_runner, trial, bracket, hard=False)
self._process_bracket(trial_runner, bracket, trial)
self.on_trial_remove(trial_runner, trial)
def choose_trial_to_run(self, trial_runner, *args):
"""Fair scheduling within iteration by completion percentage.
List of trials not used since all trials are tracked as state
of scheduler.
If iteration is occupied (ie, no trials to run), then look into
next iteration."""
List of trials not used since all trials are tracked as state
of scheduler. If iteration is occupied (ie, no trials to run),
then look into next iteration."""
for hyperband in self._hyperbands:
for bracket in sorted(hyperband,
@@ -237,6 +222,7 @@ class HyperBandScheduler(FIFOScheduler):
return None
def debug_string(self):
# TODO(rliaw): This debug string needs work
brackets = [
"({0}/{1})".format(
len(bracket._live_trials), len(bracket._all_trials))
@@ -301,8 +287,11 @@ class Bracket():
return False
def filled(self):
"""We will only let new trials be added at current level,
minimizing the need to backtrack and bookkeep previous medians"""
"""Checks if bracket is filled.
Only let new trials be added at current level minimizing the need
to backtrack and bookkeep previous medians."""
return len(self._live_trials) == self._n
def successive_halving(self, reward_attr):
@@ -346,6 +335,15 @@ class Bracket():
assert trial in self._live_trials
del self._live_trials[trial]
def cleanup_full(self, trial_runner):
"""Cleans up bracket after bracket is completely finished.
Lets the last trial continue to run until termination condition
kicks in."""
for trial in self.current_trials():
if (trial.status == Trial.PAUSED):
trial_runner.stop_trial(trial)
def completion_percentage(self):
"""Returns a progress metric.
@@ -374,5 +372,8 @@ class Bracket():
"r={}".format(self._r),
"progress={}".format(self.completion_percentage())
])
return "Bracket({})".format(status)
def debug_string(self):
trials = ", ".join([t.status for t in self._live_trials])
return "Bracket({})[{}]".format(status, trials)
return "{}[{}]".format(self, trials)