"examples/vscode:/vscode.git/clone" did not exist on "4d54854be46c6ace0f53055897517fd9fd2359ef"
Unverified Commit 22ee2ac4 authored by liuzhe-lz's avatar liuzhe-lz Committed by GitHub
Browse files

HPO doc update (#4634)

parent 899a7959
...@@ -63,6 +63,29 @@ _logger = logging.getLogger('nni.tuner.gridsearch') ...@@ -63,6 +63,29 @@ _logger = logging.getLogger('nni.tuner.gridsearch')
## ##
class GridSearchTuner(Tuner): class GridSearchTuner(Tuner):
"""
The original grid search approach performs an exhaustive search through a space consists of ``choice`` and ``randint``.
This implementation extends grid search to support all NNI search spaces.
When the search space contains continuous parameters like ``normal`` and ``loguniform``,
grid search tuner works in following steps:
1. Divide the search space into a grid.
2. Perform an exhaustive searth throught the grid.
3. Subdivide the grid into a finer-grained one.
4. Goto step 2, until experiment end.
As a deterministic algorithm, grid search has no argument.
Examples
--------
.. code-block::
config.tuner.name = 'GridSearch'
"""
def __init__(self): def __init__(self):
self.space = None self.space = None
......
...@@ -191,23 +191,31 @@ class HyperoptClassArgsValidator(ClassArgsValidator): ...@@ -191,23 +191,31 @@ class HyperoptClassArgsValidator(ClassArgsValidator):
class HyperoptTuner(Tuner): class HyperoptTuner(Tuner):
""" """
HyperoptTuner is a tuner which using hyperopt algorithm. NNI wraps `hyperopt <https://github.com/hyperopt/hyperopt>`__ to provide anneal tuner.
"""
This simple annealing algorithm begins by sampling from the prior
but tends over time to sample from points closer and closer to the best ones observed.
This algorithm is a simple variation of random search that leverages smoothness in the response surface.
The annealing rate is not adaptive.
Examples
--------
.. code-block::
config.tuner.name = 'Anneal'
config.tuner.class_args = {
'optimize_mode': 'minimize'
}
def __init__(self, algorithm_name, optimize_mode='minimize',
parallel_optimize=False, constant_liar_type='min'):
"""
Parameters Parameters
---------- ----------
algorithm_name : str optimze_mode: 'minimize' or 'maximize'
algorithm_name includes "tpe", "random_search" and anneal". Whether optimize to minimize or maximize trial result.
optimize_mode : str
parallel_optimize : bool
More detail could reference: docs/en_US/Tuner/HyperoptTuner.md
constant_liar_type : str
constant_liar_type including "min", "max" and "mean"
More detail could reference: docs/en_US/Tuner/HyperoptTuner.md
""" """
def __init__(self, algorithm_name, optimize_mode='minimize',
parallel_optimize=False, constant_liar_type='min'):
self.algorithm_name = algorithm_name self.algorithm_name = algorithm_name
self.optimize_mode = OptimizeMode(optimize_mode) self.optimize_mode = OptimizeMode(optimize_mode)
self.json = None self.json = None
...@@ -238,15 +246,6 @@ class HyperoptTuner(Tuner): ...@@ -238,15 +246,6 @@ class HyperoptTuner(Tuner):
raise RuntimeError('Not support tuner algorithm in hyperopt.') raise RuntimeError('Not support tuner algorithm in hyperopt.')
def update_search_space(self, search_space): def update_search_space(self, search_space):
"""
Update search space definition in tuner by search_space in parameters.
Will called when first setup experiemnt or update search space in WebUI.
Parameters
----------
search_space : dict
"""
validate_search_space(search_space) validate_search_space(search_space)
self.json = search_space self.json = search_space
...@@ -266,22 +265,11 @@ class HyperoptTuner(Tuner): ...@@ -266,22 +265,11 @@ class HyperoptTuner(Tuner):
self.rval.catch_eval_exceptions = False self.rval.catch_eval_exceptions = False
def generate_parameters(self, parameter_id, **kwargs): def generate_parameters(self, parameter_id, **kwargs):
""" total_params = self._get_suggestion(random_search=False)
Returns a set of trial (hyper-)parameters, as a serializable object.
Parameters
----------
parameter_id : int
Returns
-------
params : dict
"""
total_params = self.get_suggestion(random_search=False)
# avoid generating same parameter with concurrent trials because hyperopt doesn't support parallel mode # avoid generating same parameter with concurrent trials because hyperopt doesn't support parallel mode
if total_params in self.total_data.values(): if total_params in self.total_data.values():
# but it can cause duplicate parameter rarely # but it can cause duplicate parameter rarely
total_params = self.get_suggestion(random_search=True) total_params = self._get_suggestion(random_search=True)
self.total_data[parameter_id] = total_params self.total_data[parameter_id] = total_params
if self.parallel: if self.parallel:
...@@ -291,17 +279,6 @@ class HyperoptTuner(Tuner): ...@@ -291,17 +279,6 @@ class HyperoptTuner(Tuner):
return params return params
def receive_trial_result(self, parameter_id, parameters, value, **kwargs): def receive_trial_result(self, parameter_id, parameters, value, **kwargs):
"""
Record an observation of the objective function
Parameters
----------
parameter_id : int
parameters : dict
value : dict/float
if value is dict, it should have "default" key.
value is final metrics of the trial.
"""
reward = extract_scalar_reward(value) reward = extract_scalar_reward(value)
# restore the paramsters contains '_index' # restore the paramsters contains '_index'
if parameter_id not in self.total_data: if parameter_id not in self.total_data:
...@@ -369,7 +346,7 @@ class HyperoptTuner(Tuner): ...@@ -369,7 +346,7 @@ class HyperoptTuner(Tuner):
idxs[key] = [new_id] idxs[key] = [new_id]
vals[key] = [vals[key]] vals[key] = [vals[key]]
self.miscs_update_idxs_vals(rval_miscs, self._miscs_update_idxs_vals(rval_miscs,
idxs, idxs,
vals, vals,
idxs_map={new_id: new_id}, idxs_map={new_id: new_id},
...@@ -382,7 +359,7 @@ class HyperoptTuner(Tuner): ...@@ -382,7 +359,7 @@ class HyperoptTuner(Tuner):
trials.insert_trial_docs([trial]) trials.insert_trial_docs([trial])
trials.refresh() trials.refresh()
def miscs_update_idxs_vals(self, def _miscs_update_idxs_vals(self,
miscs, miscs,
idxs, idxs,
vals, vals,
...@@ -416,7 +393,7 @@ class HyperoptTuner(Tuner): ...@@ -416,7 +393,7 @@ class HyperoptTuner(Tuner):
misc_by_id[tid]['idxs'][key] = [tid] misc_by_id[tid]['idxs'][key] = [tid]
misc_by_id[tid]['vals'][key] = [val] misc_by_id[tid]['vals'][key] = [val]
def get_suggestion(self, random_search=False): def _get_suggestion(self, random_search=False):
""" """
get suggestion from hyperopt get suggestion from hyperopt
...@@ -469,14 +446,6 @@ class HyperoptTuner(Tuner): ...@@ -469,14 +446,6 @@ class HyperoptTuner(Tuner):
return total_params return total_params
def import_data(self, data): def import_data(self, data):
"""
Import additional data for tuning
Parameters
----------
data:
a list of dictionarys, each of which has at least two keys, 'parameter' and 'value'
"""
_completed_num = 0 _completed_num = 0
for trial_info in data: for trial_info in data:
logger.info("Importing data, current processing progress %s / %s", _completed_num, len(data)) logger.info("Importing data, current processing progress %s / %s", _completed_num, len(data))
......
# Copyright (c) Microsoft Corporation. # Copyright (c) Microsoft Corporation.
# Licensed under the MIT license. # Licensed under the MIT license.
from __future__ import annotations
import logging import logging
from schema import Schema, Optional from schema import Schema, Optional
from nni import ClassArgsValidator from nni import ClassArgsValidator
from nni.assessor import Assessor, AssessResult from nni.assessor import Assessor, AssessResult
from nni.typehint import Literal
from nni.utils import extract_scalar_history from nni.utils import extract_scalar_history
logger = logging.getLogger('medianstop_Assessor') logger = logging.getLogger('medianstop_Assessor')
...@@ -18,18 +21,33 @@ class MedianstopClassArgsValidator(ClassArgsValidator): ...@@ -18,18 +21,33 @@ class MedianstopClassArgsValidator(ClassArgsValidator):
}).validate(kwargs) }).validate(kwargs)
class MedianstopAssessor(Assessor): class MedianstopAssessor(Assessor):
"""MedianstopAssessor is The median stopping rule stops a pending trial X at step S """
The median stopping rule stops a pending trial X at step S
if the trial’s best objective value by step S is strictly worse than the median value if the trial’s best objective value by step S is strictly worse than the median value
of the running averages of all completed trials’ objectives reported up to step S of the running averages of all completed trials’ objectives reported up to step S
The algorithm is mentioned in *Google Vizer: A Service for Black-Box Optimization*.
(`paper <https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/46180.pdf>`__)
Examples
--------
.. code-block::
config.assessor.name = 'Medianstop'
config.tuner.class_args = {
'optimize_mode': 'maximize'
}
Parameters Parameters
---------- ----------
optimize_mode : str optimize_mode
optimize mode, 'maximize' or 'minimize' Whether optimize to minimize or maximize trial result.
start_step : int start_step
only after receiving start_step number of reported intermediate results Only after receiving start_step number of reported intermediate results.
""" """
def __init__(self, optimize_mode='maximize', start_step=0):
def __init__(self, optimize_mode: Literal['minimize', 'maximize'] = 'maximize', start_step: int = 0):
self._start_step = start_step self._start_step = start_step
self._running_history = dict() self._running_history = dict()
self._completed_avg_history = dict() self._completed_avg_history = dict()
...@@ -56,15 +74,6 @@ class MedianstopAssessor(Assessor): ...@@ -56,15 +74,6 @@ class MedianstopAssessor(Assessor):
self._running_history[trial_job_id].extend(trial_history[len(self._running_history[trial_job_id]):]) self._running_history[trial_job_id].extend(trial_history[len(self._running_history[trial_job_id]):])
def trial_end(self, trial_job_id, success): def trial_end(self, trial_job_id, success):
"""trial_end
Parameters
----------
trial_job_id : int
trial job id
success : bool
True if succssfully finish the experiment, False otherwise
"""
if trial_job_id in self._running_history: if trial_job_id in self._running_history:
if success: if success:
cnt = 0 cnt = 0
...@@ -79,25 +88,6 @@ class MedianstopAssessor(Assessor): ...@@ -79,25 +88,6 @@ class MedianstopAssessor(Assessor):
logger.warning('trial_end: trial_job_id does not exist in running_history') logger.warning('trial_end: trial_job_id does not exist in running_history')
def assess_trial(self, trial_job_id, trial_history): def assess_trial(self, trial_job_id, trial_history):
"""assess_trial
Parameters
----------
trial_job_id : int
trial job id
trial_history : list
The history performance matrix of each trial
Returns
-------
bool
AssessResult.Good or AssessResult.Bad
Raises
------
Exception
unrecognize exception in medianstop_assessor
"""
curr_step = len(trial_history) curr_step = len(trial_history)
if curr_step < self._start_step: if curr_step < self._start_step:
return AssessResult.Good return AssessResult.Good
......
...@@ -2,9 +2,10 @@ ...@@ -2,9 +2,10 @@
# Licensed under the MIT license. # Licensed under the MIT license.
""" """
Tree-structured Parzen Estimator (TPE) tuner for hyper-parameter optimization. Tree-structured Parzen Estimator (TPE) tuner.
Paper: https://proceedings.neurips.cc/paper/2011/file/86e8f7ab32cfd12577bc2619bc635690-Paper.pdf Paper: https://proceedings.neurips.cc/paper/2011/file/86e8f7ab32cfd12577bc2619bc635690-Paper.pdf
Official code: https://github.com/hyperopt/hyperopt/blob/master/hyperopt/tpe.py Official code: https://github.com/hyperopt/hyperopt/blob/master/hyperopt/tpe.py
This is a slightly modified re-implementation of the algorithm. This is a slightly modified re-implementation of the algorithm.
...@@ -34,8 +35,9 @@ _logger = logging.getLogger('nni.tuner.tpe') ...@@ -34,8 +35,9 @@ _logger = logging.getLogger('nni.tuner.tpe')
class TpeArguments(NamedTuple): class TpeArguments(NamedTuple):
""" """
These are the hyper-parameters of TPE algorithm itself. Hyperparameters of TPE algorithm itself.
To avoid confusing with trials' hyper-parameters, they are called "arguments" in TPE source code.
To avoid confusing with trials' hyperparameters to be tuned, these are called "arguments" here.
Parameters Parameters
---------- ----------
...@@ -83,15 +85,15 @@ class TpeArguments(NamedTuple): ...@@ -83,15 +85,15 @@ class TpeArguments(NamedTuple):
class TpeTuner(Tuner): class TpeTuner(Tuner):
""" """
Tree-structured Parzen Estimator (TPE) is an SMBO tuner. Tree-structured Parzen Estimator (TPE) tuner.
TPE models P(x|y) and P(y) where x represents hyperparameters and y the associated evaluation metric. TPE is an SMBO algorithm.
It models P(x|y) and P(y) where x represents hyperparameters and y the evaluation result.
P(x|y) is modeled by transforming the generative process of hyperparameters, P(x|y) is modeled by transforming the generative process of hyperparameters,
replacing the distributions of the configuration prior with non-parametric densities. replacing the distributions of the configuration prior with non-parametric densities.
TPE is described in detail in *Algorithms for Hyper-Parameter Optimization*. (`paper`_) Paper: :footcite:`bergstra2011algorithms`.
(`PDF <https://proceedings.neurips.cc/paper/2011/file/86e8f7ab32cfd12577bc2619bc635690-Paper.pdf>`__)
.. _paper: https://proceedings.neurips.cc/paper/2011/file/86e8f7ab32cfd12577bc2619bc635690-Paper.pdf
Examples Examples
-------- --------
...@@ -102,7 +104,7 @@ class TpeTuner(Tuner): ...@@ -102,7 +104,7 @@ class TpeTuner(Tuner):
config.tuner.name = 'TPE' config.tuner.name = 'TPE'
config.tuner.class_args = { config.tuner.class_args = {
'optimize_mode': 'minimize' 'optimize_mode': 'maximize'
} }
.. code-block:: .. code-block::
......
...@@ -225,12 +225,9 @@ def copy_nni_node(version): ...@@ -225,12 +225,9 @@ def copy_nni_node(version):
""" """
_print('Copying files') _print('Copying files')
# copytree(..., dirs_exist_ok=True) is not supported by Python 3.6 shutil.copytree('ts/nni_manager/dist', 'nni_node', dirs_exist_ok=True)
for path in Path('ts/nni_manager/dist').iterdir(): shutil.copyfile('ts/nni_manager/yarn.lock', 'nni_node/yarn.lock')
if path.is_dir(): Path('nni_node/nni_manager.tsbuildinfo').unlink()
shutil.copytree(path, Path('nni_node', path.name))
elif path.name != 'nni_manager.tsbuildinfo':
shutil.copyfile(path, Path('nni_node', path.name))
package_json = json.load(open('ts/nni_manager/package.json')) package_json = json.load(open('ts/nni_manager/package.json'))
if version: if version:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment