Unverified Commit 580c5979 authored by 98may's avatar 98may Committed by GitHub
Browse files

DNGO tuner (#3479)


Co-authored-by: default avatarYuge Zhang <Yuge.Zhang@microsoft.com>
parent 9b0bc378
......@@ -11,3 +11,6 @@ statsmodels==0.12.0
# PPOTuner
gym
# DNGO
pybnn
......@@ -45,7 +45,8 @@ Currently, we support the following algorithms:
- PPO Tuner is a Reinforcement Learning tuner based on PPO algorithm. `Reference Paper <https://arxiv.org/abs/1707.06347>`__
* - `PBT Tuner <#PBTTuner>`__
- PBT Tuner is a simple asynchronous optimization algorithm which effectively utilizes a fixed computational budget to jointly optimize a population of models and their hyperparameters to maximize performance. `Reference Paper <https://arxiv.org/abs/1711.09846v1>`__
* - `DNGO Tuner <#DNGOTuner>`__
- Use of neural networks as an alternative to GPs to model distributions over functions in bayesian optimization.
Usage of Built-in Tuners
------------------------
......@@ -574,6 +575,41 @@ Population Based Training (PBT) bridges and extends parallel search methods and
Note that, to use this tuner, your trial code should be modified accordingly, please refer to `the document of PBTTuner <./PBTTuner.rst>`__ for details.
DNGO Tuner
^^^^^^^^^^
..
Built-in Tuner Name: **DNGOTuner**
DNGO advisor requires `pybnn`, which can be installed with the following command.
.. code-block:: bash
pip install nni[DNGO]
**Suggested scenario**
Applicable to large scale hyperparameter optimization. Bayesian optimization that rapidly finds competitive models on benchmark object recognition tasks using convolutional networks, and image caption generation using neural language models.
**classArgs requirements:**
* **optimize_mode** (*'maximize' or 'minimize'*\ ) - If 'maximize', the tuner will target to maximize metrics. If 'minimize', the tuner will target to minimize metrics.
* **sample_size** (*int, default = 1000*) - Number of samples to select in each iteration. The best one will be picked from the samples as the next trial.
* **trials_per_update** (*int, default = 20*) - Number of trials to collect before updating the model.
* **num_epochs_per_training** (*int, default = 500*) - Number of epochs to train DNGO model.
**Usage example**
.. code-block:: yaml
# config.yml
tuner:
builtinTunerName: DNGOTuner
classArgs:
optimize_mode: maximize
**Reference and Feedback**
------------------------------
......
......@@ -243,13 +243,25 @@ Search Space Types Supported by Each Tuner
-
-
-
* - DNGO Tuner
- :raw-html:`&#10003;`
-
- :raw-html:`&#10003;`
- :raw-html:`&#10003;`
- :raw-html:`&#10003;`
- :raw-html:`&#10003;`
- :raw-html:`&#10003;`
-
-
-
-
Known Limitations:
*
GP Tuner and Metis Tuner support only **numerical values** in search space (\ ``choice`` type values can be no-numerical with other tuners, e.g. string values). Both GP Tuner and Metis Tuner use Gaussian Process Regressor(GPR). GPR make predictions based on a kernel function and the 'distance' between different points, it's hard to get the true distance between no-numerical values.
GP Tuner, Metis Tuner and DNGO tuner support only **numerical values** in search space (\ ``choice`` type values can be no-numerical with other tuners, e.g. string values). Both GP Tuner and Metis Tuner use Gaussian Process Regressor(GPR). GPR make predictions based on a kernel function and the 'distance' between different points, it's hard to get the true distance between no-numerical values.
*
Note that for nested search space:
......
import logging
import numpy as np
import torch
import nni.parameter_expressions as parameter_expressions
from nni import ClassArgsValidator
from nni.tuner import Tuner
from pybnn import DNGO
from torch.distributions import Normal
_logger = logging.getLogger(__name__)
def _random_config(search_space, random_state):
chosen_config = {}
for key, val in search_space.items():
if val['_type'] == 'choice':
choices = val['_value']
index = random_state.randint(len(choices))
if all([isinstance(c, (int, float)) for c in choices]):
chosen_config[key] = choices[index]
else:
raise ValueError('Choices with type other than int and float is not supported.')
elif val['_type'] == 'uniform':
chosen_config[key] = random_state.uniform(val['_value'][0], val['_value'][1])
elif val['_type'] == 'randint':
chosen_config[key] = random_state.randint(
val['_value'][0], val['_value'][1])
elif val['_type'] == 'quniform':
chosen_config[key] = parameter_expressions.quniform(
val['_value'][0], val['_value'][1], val['_value'][2], random_state)
elif val['_type'] == 'loguniform':
chosen_config[key] = parameter_expressions.loguniform(
val['_value'][0], val['_value'][1], random_state)
elif val['_type'] == 'qloguniform':
chosen_config[key] = parameter_expressions.qloguniform(
val['_value'][0], val['_value'][1], val['_value'][2], random_state)
else:
raise ValueError('Unknown key %s and value %s' % (key, val))
return chosen_config
class DngoTuner(Tuner):
def __init__(self, optimize_mode='maximize', sample_size=1000, trials_per_update=20, num_epochs_per_training=500):
self.searchspace_json = None
self.random_state = None
self.model = DNGO(do_mcmc=False, num_epochs=num_epochs_per_training)
self._model_initialized = False
self.sample_size = sample_size
self.trials_per_update = trials_per_update
self.optimize_mode = optimize_mode
self.x = []
self.y = []
def receive_trial_result(self, parameter_id, parameters, value, **kwargs):
self.x.append(parameters)
self.y.append(self._get_default_value(value))
if len(self.y) % self.trials_per_update == 0:
self._update_model()
def generate_parameters(self, parameter_id, **kwargs):
if not self._model_initialized:
return _random_config(self.searchspace_json, self.random_state)
else:
# random samples and pick best with model
candidate_x = [_random_config(self.searchspace_json, self.random_state) for _ in range(self.sample_size)]
x_test = np.array([np.array(list(xi.values())) for xi in candidate_x])
m, v = self.model.predict(x_test)
mean = torch.Tensor(m)
sigma = torch.Tensor(v)
u = (mean - torch.Tensor([0.95]).expand_as(mean)) / sigma
normal = Normal(torch.zeros_like(u), torch.ones_like(u))
ucdf = normal.cdf(u)
updf = torch.exp(normal.log_prob(u))
ei = sigma * (updf + u * ucdf)
if self.optimize_mode == 'maximize':
ind = torch.argmax(ei)
else:
ind = torch.argmin(ei)
new_x = candidate_x[ind]
return new_x
def update_search_space(self, search_space):
self.searchspace_json = search_space
self.random_state = np.random.RandomState()
def import_data(self, data):
for d in data:
self.x.append(d['parameter'])
self.y.append(self._get_default_value(d['value']))
self._update_model()
def _update_model(self):
_logger.info('Updating model on %d samples', len(self.x))
x_arr = []
for x in self.x:
x_arr.append([x[k] for k in sorted(x.keys())])
self.model.train(np.array(x_arr), np.array(self.y), do_optimize=True)
self._model_initialized = True
def _get_default_value(self, value):
if isinstance(value, dict) and 'default' in value:
return value['default']
elif isinstance(value, float):
return value
else:
raise ValueError(f'Unsupported value: {value}')
class DNGOClassArgsValidator(ClassArgsValidator):
# DNGO tuner do not have much input arg, so the validation is actually hardly used
def validate_class_args(self, **kwargs):
pass
......@@ -76,3 +76,7 @@ tuners:
classArgsValidator: nni.algorithms.hpo.regularized_evolution_tuner.EvolutionClassArgsValidator
className: nni.algorithms.hpo.regularized_evolution_tuner.RegularizedEvolutionTuner
source: nni
- builtinName: DNGOTuner
classArgsValidator: nni.algorithms.hpo.dngo_tuner.DNGOClassArgsValidator
className: nni.algorithms.hpo.dngo_tuner.DNGOTuner
source: nni
......@@ -161,7 +161,7 @@ stages:
- script: |
set -e
python -m pip install -r dependencies/recommended.txt
python -m pip install -e .[SMAC,BOHB,PPOTuner]
python -m pip install -e .[SMAC,BOHB,PPOTuner,DNGO]
displayName: Install extra dependencies
# Need del later
......@@ -246,7 +246,7 @@ stages:
- script: |
set -e
python -m pip install -r dependencies/recommended_legacy.txt
python -m pip install -e .[SMAC,BOHB,PPOTuner]
python -m pip install -e .[SMAC,BOHB,PPOTuner,DNGO]
displayName: Install extra dependencies
# Need del later
......@@ -335,7 +335,7 @@ stages:
- script: |
set -e
python -m pip install -r dependencies/recommended.txt
python -m pip install -e .[SMAC,BOHB,PPOTuner]
python -m pip install -e .[SMAC,BOHB,PPOTuner,DNGO]
displayName: Install extra dependencies
# Need del later
......@@ -398,6 +398,7 @@ stages:
- script: |
python -m pip install -r dependencies/recommended.txt
python -m pip install -e .[DNGO]
displayName: Install extra dependencies
# Need del later
......
......@@ -35,6 +35,7 @@ jobs:
python3 -m pip install keras==2.1.6
python3 -m pip install tensorflow==2.3.1 tensorflow-estimator==2.3.0
python3 -m pip install thop
python3 -m pip install pybnn
python3 -m pip install tianshou>=0.4.1 gym
sudo apt-get install swig -y
displayName: Install extra dependencies
......
......@@ -30,6 +30,7 @@ jobs:
python -m pip install torch==1.6.0 torchvision==0.7.0 -f https://download.pytorch.org/whl/torch_stable.html
python -m pip install 'pytorch-lightning>=1.1.1'
python -m pip install tensorflow==2.3.1 tensorflow-estimator==2.3.0
python -m pip install pybnn
python -m pip install tianshou>=0.4.1 gym
displayName: Install extra dependencies
......
......@@ -88,7 +88,8 @@ def _setup():
extras_require = {
'SMAC': _read_requirements_txt('dependencies/required_extra.txt', 'SMAC'),
'BOHB': _read_requirements_txt('dependencies/required_extra.txt', 'BOHB'),
'PPOTuner': _read_requirements_txt('dependencies/required_extra.txt', 'PPOTuner')
'PPOTuner': _read_requirements_txt('dependencies/required_extra.txt', 'PPOTuner'),
'DNGO': _read_requirements_txt('dependencies/required_extra.txt', 'DNGO'),
},
setup_requires = ['requests'],
......
......@@ -12,6 +12,7 @@ from collections import deque
from unittest import TestCase, main
from nni.algorithms.hpo.batch_tuner import BatchTuner
from nni.algorithms.hpo.dngo_tuner import DngoTuner
from nni.algorithms.hpo.evolution_tuner import EvolutionTuner
from nni.algorithms.hpo.gp_tuner import GPTuner
from nni.algorithms.hpo.gridsearch_tuner import GridSearchTuner
......@@ -388,6 +389,12 @@ class BuiltinTunersTestCase(TestCase):
))
self.import_data_test_for_pbt()
def test_dngo(self):
tuner_fn = lambda: DngoTuner(trials_per_update=100, num_epochs_per_training=1)
self.search_space_test_all(tuner_fn, fail_types=["choice_str", "choice_mixed",
"normal", "lognormal", "qnormal", "qlognormal"])
self.import_data_test(tuner_fn, stype='choice_num')
def tearDown(self):
file_list = glob.glob("smac3*") + ["param_config_space.pcs", "scenario.txt", "model_path"]
for file in file_list:
......
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