Unverified Commit 926c42de authored by QuanluZhang's avatar QuanluZhang Committed by GitHub
Browse files

improve doc docstring of gridsearch/smac/ppo (#1693)

parent 81fcff86
...@@ -24,10 +24,10 @@ Tuner ...@@ -24,10 +24,10 @@ Tuner
.. autoclass:: nni.evolution_tuner.evolution_tuner.EvolutionTuner .. autoclass:: nni.evolution_tuner.evolution_tuner.EvolutionTuner
:members: :members:
.. autoclass:: nni.smac_tuner.smac_tuner.SMACTuner .. autoclass:: nni.smac_tuner.SMACTuner
:members: :members:
.. autoclass:: nni.gridsearch_tuner.gridsearch_tuner.GridSearchTuner .. autoclass:: nni.gridsearch_tuner.GridSearchTuner
:members: :members:
.. autoclass:: nni.networkmorphism_tuner.networkmorphism_tuner.NetworkMorphismTuner .. autoclass:: nni.networkmorphism_tuner.networkmorphism_tuner.NetworkMorphismTuner
...@@ -36,6 +36,9 @@ Tuner ...@@ -36,6 +36,9 @@ Tuner
.. autoclass:: nni.metis_tuner.metis_tuner.MetisTuner .. autoclass:: nni.metis_tuner.metis_tuner.MetisTuner
:members: :members:
.. autoclass:: nni.ppo_tuner.PPOTuner
:members:
.. autoclass:: nni.batch_tuner.batch_tuner.BatchTuner .. autoclass:: nni.batch_tuner.batch_tuner.BatchTuner
:members: :members:
......
from .gridsearch_tuner import GridSearchTuner
\ No newline at end of file
...@@ -17,10 +17,10 @@ ...@@ -17,10 +17,10 @@
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
''' """
gridsearch_tuner.py including: gridsearch_tuner.py including:
class GridSearchTuner class GridSearchTuner
''' """
import copy import copy
import logging import logging
...@@ -37,29 +37,40 @@ VALUE = '_value' ...@@ -37,29 +37,40 @@ VALUE = '_value'
logger = logging.getLogger('grid_search_AutoML') logger = logging.getLogger('grid_search_AutoML')
class GridSearchTuner(Tuner): class GridSearchTuner(Tuner):
''' """
GridSearchTuner will search all the possible configures that the user define in the searchSpace. GridSearchTuner will search all the possible configures that the user define in the searchSpace.
The only acceptable types of search space are 'choice', 'quniform', 'randint' The only acceptable types of search space are ``choice``, ``quniform``, ``randint``
Type 'choice' will select one of the options. Note that it can also be nested. Type ``choice`` will select one of the options. Note that it can also be nested.
Type 'quniform' will receive three values [low, high, q], where [low, high] specifies a range and 'q' specifies the interval Type ``quniform`` will receive three values [``low``, ``high``, ``q``],
It will be sampled in a way that the first sampled value is 'low', where [``low``, ``high``] specifies a range and ``q`` specifies the interval.
It will be sampled in a way that the first sampled value is ``low``,
and each of the following values is 'interval' larger than the value in front of it. and each of the following values is 'interval' larger than the value in front of it.
Type 'randint' gives all possible intergers in range[low, high). Note that 'high' is not included. Type ``randint`` gives all possible intergers in range[``low``, ``high``). Note that ``high`` is not included.
''' """
def __init__(self): def __init__(self):
self.count = -1 self.count = -1
self.expanded_search_space = [] self.expanded_search_space = []
self.supplement_data = dict() self.supplement_data = dict()
def json2parameter(self, ss_spec): def _json2parameter(self, ss_spec):
''' """
generate all possible configs for hyperparameters from hyperparameter space. Generate all possible configs for hyperparameters from hyperparameter space.
ss_spec: hyperparameter space
''' Parameters
----------
ss_spec : dict or list
Hyperparameter space or the ``_value`` of a hyperparameter
Returns
-------
list or dict
All the candidate choices of hyperparameters. for a hyperparameter, chosen_params
is a list. for multiple hyperparameters (e.g., search space), chosen_params is a dict.
"""
if isinstance(ss_spec, dict): if isinstance(ss_spec, dict):
if '_type' in ss_spec.keys(): if '_type' in ss_spec.keys():
_type = ss_spec['_type'] _type = ss_spec['_type']
...@@ -67,7 +78,7 @@ class GridSearchTuner(Tuner): ...@@ -67,7 +78,7 @@ class GridSearchTuner(Tuner):
chosen_params = list() chosen_params = list()
if _type == 'choice': if _type == 'choice':
for value in _value: for value in _value:
choice = self.json2parameter(value) choice = self._json2parameter(value)
if isinstance(choice, list): if isinstance(choice, list):
chosen_params.extend(choice) chosen_params.extend(choice)
else: else:
...@@ -81,12 +92,12 @@ class GridSearchTuner(Tuner): ...@@ -81,12 +92,12 @@ class GridSearchTuner(Tuner):
else: else:
chosen_params = dict() chosen_params = dict()
for key in ss_spec.keys(): for key in ss_spec.keys():
chosen_params[key] = self.json2parameter(ss_spec[key]) chosen_params[key] = self._json2parameter(ss_spec[key])
return self.expand_parameters(chosen_params) return self._expand_parameters(chosen_params)
elif isinstance(ss_spec, list): elif isinstance(ss_spec, list):
chosen_params = list() chosen_params = list()
for subspec in ss_spec[1:]: for subspec in ss_spec[1:]:
choice = self.json2parameter(subspec) choice = self._json2parameter(subspec)
if isinstance(choice, list): if isinstance(choice, list):
chosen_params.extend(choice) chosen_params.extend(choice)
else: else:
...@@ -97,27 +108,39 @@ class GridSearchTuner(Tuner): ...@@ -97,27 +108,39 @@ class GridSearchTuner(Tuner):
return chosen_params return chosen_params
def _parse_quniform(self, param_value): def _parse_quniform(self, param_value):
'''parse type of quniform parameter and return a list''' """
Parse type of quniform parameter and return a list
"""
low, high, q = param_value[0], param_value[1], param_value[2] low, high, q = param_value[0], param_value[1], param_value[2]
return np.clip(np.arange(np.round(low/q), np.round(high/q)+1) * q, low, high) return np.clip(np.arange(np.round(low/q), np.round(high/q)+1) * q, low, high)
def _parse_randint(self, param_value): def _parse_randint(self, param_value):
'''parse type of randint parameter and return a list''' """
Parse type of randint parameter and return a list
"""
return np.arange(param_value[0], param_value[1]).tolist() return np.arange(param_value[0], param_value[1]).tolist()
def expand_parameters(self, para): def _expand_parameters(self, para):
''' """
Enumerate all possible combinations of all parameters Enumerate all possible combinations of all parameters
para: {key1: [v11, v12, ...], key2: [v21, v22, ...], ...}
return: {{key1: v11, key2: v21, ...}, {key1: v11, key2: v22, ...}, ...} Parameters
''' ----------
para : dict
{key1: [v11, v12, ...], key2: [v21, v22, ...], ...}
Returns
-------
dict
{{key1: v11, key2: v21, ...}, {key1: v11, key2: v22, ...}, ...}
"""
if len(para) == 1: if len(para) == 1:
for key, values in para.items(): for key, values in para.items():
return list(map(lambda v: {key: v}, values)) return list(map(lambda v: {key: v}, values))
key = list(para)[0] key = list(para)[0]
values = para.pop(key) values = para.pop(key)
rest_para = self.expand_parameters(para) rest_para = self._expand_parameters(para)
ret_para = list() ret_para = list()
for val in values: for val in values:
for config in rest_para: for config in rest_para:
...@@ -126,12 +149,37 @@ class GridSearchTuner(Tuner): ...@@ -126,12 +149,37 @@ class GridSearchTuner(Tuner):
return ret_para return ret_para
def update_search_space(self, search_space): def update_search_space(self, search_space):
''' """
Check if the search space is valid and expand it: support only 'choice', 'quniform', randint' Check if the search space is valid and expand it: support only ``choice``, ``quniform``, ``randint``.
'''
self.expanded_search_space = self.json2parameter(search_space) Parameters
----------
search_space : dict
The format could be referred to search space spec (https://nni.readthedocs.io/en/latest/Tutorial/SearchSpaceSpec.html).
"""
self.expanded_search_space = self._json2parameter(search_space)
def generate_parameters(self, parameter_id, **kwargs): def generate_parameters(self, parameter_id, **kwargs):
"""
Generate parameters for one trial.
Parameters
----------
parameter_id : int
The id for the generated hyperparameter
**kwargs
Not used
Returns
-------
dict
One configuration from the expanded search space.
Raises
------
NoMoreTrialError
If all the configurations has been sent, raise :class:`~nni.NoMoreTrialError`.
"""
self.count += 1 self.count += 1
while self.count <= len(self.expanded_search_space) - 1: while self.count <= len(self.expanded_search_space) - 1:
_params_tuple = convert_dict2tuple(self.expanded_search_space[self.count]) _params_tuple = convert_dict2tuple(self.expanded_search_space[self.count])
...@@ -142,15 +190,20 @@ class GridSearchTuner(Tuner): ...@@ -142,15 +190,20 @@ class GridSearchTuner(Tuner):
raise nni.NoMoreTrialError('no more parameters now.') raise nni.NoMoreTrialError('no more parameters now.')
def receive_trial_result(self, parameter_id, parameters, value, **kwargs): def receive_trial_result(self, parameter_id, parameters, value, **kwargs):
"""
Receive a trial's final performance result reported through :func:`~nni.report_final_result` by the trial.
GridSearchTuner does not need trial's results.
"""
pass pass
def import_data(self, data): def import_data(self, data):
"""Import additional data for tuning """
Import additional data for tuning
Parameters Parameters
---------- ----------
data: list
a list of dictionarys, each of which has at least two keys, 'parameter' and 'value' 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:
......
...@@ -77,7 +77,7 @@ class PdType: ...@@ -77,7 +77,7 @@ class PdType:
class CategoricalPd(Pd): class CategoricalPd(Pd):
""" """
categorical prossibility distribution Categorical prossibility distribution
""" """
def __init__(self, logits, mask_npinf, nsteps, size, is_act_model): def __init__(self, logits, mask_npinf, nsteps, size, is_act_model):
self.logits = logits self.logits = logits
...@@ -154,7 +154,7 @@ class CategoricalPd(Pd): ...@@ -154,7 +154,7 @@ class CategoricalPd(Pd):
class CategoricalPdType(PdType): class CategoricalPdType(PdType):
""" """
to create CategoricalPd To create CategoricalPd
""" """
def __init__(self, ncat, nsteps, np_mask, is_act_model): def __init__(self, ncat, nsteps, np_mask, is_act_model):
self.ncat = ncat self.ncat = ncat
...@@ -180,7 +180,7 @@ class CategoricalPdType(PdType): ...@@ -180,7 +180,7 @@ class CategoricalPdType(PdType):
def _matching_fc(tensor, name, size, nsteps, init_scale, init_bias, np_mask, is_act_model): def _matching_fc(tensor, name, size, nsteps, init_scale, init_bias, np_mask, is_act_model):
""" """
add fc op, and add mask op when not in action mode Add fc op, and add mask op when not in action mode
""" """
if tensor.shape[-1] == size: if tensor.shape[-1] == size:
assert False assert False
......
...@@ -28,21 +28,18 @@ from .util import initialize, get_session ...@@ -28,21 +28,18 @@ from .util import initialize, get_session
class Model: class Model:
""" """
We use this object to : We use this object to :
__init__: __init__:
- Creates the step_model - Creates the step_model
- Creates the train_model - Creates the train_model
train(): train():
- Make the training part (feedforward and retropropagation of gradients) - Make the training part (feedforward and retropropagation of gradients)
save/load(): save/load():
- Save load the model - Save load the model
""" """
def __init__(self, *, policy, nbatch_act, nbatch_train, def __init__(self, *, policy, nbatch_act, nbatch_train,
nsteps, ent_coef, vf_coef, max_grad_norm, microbatch_size=None, np_mask=None): nsteps, ent_coef, vf_coef, max_grad_norm, microbatch_size=None, np_mask=None):
"""
init
"""
self.sess = sess = get_session() self.sess = sess = get_session()
with tf.variable_scope('ppo2_model', reuse=tf.AUTO_REUSE): with tf.variable_scope('ppo2_model', reuse=tf.AUTO_REUSE):
...@@ -137,9 +134,13 @@ class Model: ...@@ -137,9 +134,13 @@ class Model:
def train(self, lr, cliprange, obs, returns, masks, actions, values, neglogpacs, states=None): def train(self, lr, cliprange, obs, returns, masks, actions, values, neglogpacs, states=None):
""" """
train the model. Train the model.
Here we calculate advantage A(s,a) = R + yV(s') - V(s) Here we calculate advantage A(s,a) = R + yV(s') - V(s)
Returns = R + yV(s')
Returns
-------
obj
= R + yV(s')
""" """
advs = returns - values advs = returns - values
......
...@@ -34,14 +34,20 @@ class PolicyWithValue: ...@@ -34,14 +34,20 @@ class PolicyWithValue:
def __init__(self, env, observations, latent, estimate_q=False, vf_latent=None, sess=None, np_mask=None, is_act_model=False, **tensors): def __init__(self, env, observations, latent, estimate_q=False, vf_latent=None, sess=None, np_mask=None, is_act_model=False, **tensors):
""" """
Parameters: Parameters
---------- ----------
env: RL environment env : obj
observations: tensorflow placeholder in which the observations will be fed RL environment
latent: latent state from which policy distribution parameters should be inferred observations : tensorflow placeholder
vf_latent: latent state from which value function should be inferred (if None, then latent is used) Tensorflow placeholder in which the observations will be fed
sess: tensorflow session to run calculations in (if None, default session is used) latent : tensor
**tensors: tensorflow tensors for additional attributes such as state or mask Latent state from which policy distribution parameters should be inferred
vf_latent : tensor
Latent state from which value function should be inferred (if None, then latent is used)
sess : tensorflow session
Tensorflow session to run calculations in (if None, default session is used)
**tensors
Tensorflow tensors for additional attributes such as state or mask
""" """
self.X = observations self.X = observations
...@@ -138,12 +144,14 @@ class PolicyWithValue: ...@@ -138,12 +144,14 @@ class PolicyWithValue:
""" """
Compute next action(s) given the observation(s) Compute next action(s) given the observation(s)
Parameters: Parameters
---------- ----------
observation: observation data (either single or a batch) observation : np array
**extra_feed: additional data such as state or mask (names of the arguments should match the ones in constructor, see __init__) Observation data (either single or a batch)
**extra_feed
Additional data such as state or mask (names of the arguments should match the ones in constructor, see __init__)
Returns: Returns
------- -------
(action, value estimate, next state, negative log likelihood of the action under current policy parameters) tuple (action, value estimate, next state, negative log likelihood of the action under current policy parameters) tuple
""" """
...@@ -157,22 +165,40 @@ class PolicyWithValue: ...@@ -157,22 +165,40 @@ class PolicyWithValue:
""" """
Compute value estimate(s) given the observation(s) Compute value estimate(s) given the observation(s)
Parameters: Parameters
---------- ----------
observation: observation data (either single or a batch) observation : np array
**extra_feed: additional data such as state or mask (names of the arguments should match the ones in constructor, see __init__) Observation data (either single or a batch)
**extra_feed
Additional data such as state or mask (names of the arguments should match the ones in constructor, see __init__)
Returns: Returns
------- -------
value estimate Value estimate
""" """
return self._evaluate(self.vf, ob, *args, **kwargs) return self._evaluate(self.vf, ob, *args, **kwargs)
def build_lstm_policy(model_config, value_network=None, estimate_q=False, **policy_kwargs): def build_lstm_policy(model_config, value_network=None, estimate_q=False, **policy_kwargs):
""" """
build lstm policy and value network, they share the same lstm network. Build lstm policy and value network, they share the same lstm network.
the parameters all use their default values. the parameters all use their default values.
Parameter
---------
model_config : obj
Configurations of the model
value_network : obj
The network for value function
estimate_q : bool
Whether to estimate ``q``
**policy_kwargs
The kwargs for policy network, i.e., lstm model
Returns
-------
func
The policy network
""" """
policy_network = lstm_model(**policy_kwargs) policy_network = lstm_model(**policy_kwargs)
......
...@@ -38,8 +38,10 @@ from .policy import build_lstm_policy ...@@ -38,8 +38,10 @@ from .policy import build_lstm_policy
logger = logging.getLogger('ppo_tuner_AutoML') logger = logging.getLogger('ppo_tuner_AutoML')
def constfn(val): def _constfn(val):
"""wrap as function""" """
Wrap as function
"""
def f(_): def f(_):
return val return val
return f return f
...@@ -90,7 +92,7 @@ class TrialsInfo: ...@@ -90,7 +92,7 @@ class TrialsInfo:
def get_next(self): def get_next(self):
""" """
get actions of the next trial Get actions of the next trial
""" """
if self.iter >= self.inf_batch_size: if self.iter >= self.inf_batch_size:
return None, None return None, None
...@@ -102,14 +104,14 @@ class TrialsInfo: ...@@ -102,14 +104,14 @@ class TrialsInfo:
def update_rewards(self, rewards, returns): def update_rewards(self, rewards, returns):
""" """
after the trial is finished, reward and return of this trial is updated After the trial is finished, reward and return of this trial is updated
""" """
self.rewards = rewards self.rewards = rewards
self.returns = returns self.returns = returns
def convert_shape(self): def convert_shape(self):
""" """
convert shape Convert shape
""" """
def sf01(arr): def sf01(arr):
""" """
...@@ -138,9 +140,9 @@ class PPOModel: ...@@ -138,9 +140,9 @@ class PPOModel:
set_global_seeds(None) set_global_seeds(None)
assert isinstance(self.model_config.lr, float) assert isinstance(self.model_config.lr, float)
self.lr = constfn(self.model_config.lr) self.lr = _constfn(self.model_config.lr)
assert isinstance(self.model_config.cliprange, float) assert isinstance(self.model_config.cliprange, float)
self.cliprange = constfn(self.model_config.cliprange) self.cliprange = _constfn(self.model_config.cliprange)
# build lstm policy network, value share the same network # build lstm policy network, value share the same network
policy = build_lstm_policy(model_config) policy = build_lstm_policy(model_config)
...@@ -165,12 +167,28 @@ class PPOModel: ...@@ -165,12 +167,28 @@ class PPOModel:
def inference(self, num): def inference(self, num):
""" """
generate actions along with related info from policy network. Generate actions along with related info from policy network.
observation is the action of the last step. observation is the action of the last step.
Parameters: Parameters
---------- ----------
num: the number of trials to generate num: int
The number of trials to generate
Returns
-------
mb_obs : list
Observation of the ``num`` configurations
mb_actions : list
Actions of the ``num`` configurations
mb_values : list
Values from the value function of the ``num`` configurations
mb_neglogpacs : list
``neglogp`` of the ``num`` configurations
mb_dones : list
To show whether the play is done, always ``True``
last_values : tensorflow tensor
The last values of the ``num`` configurations, got with session run
""" """
# Here, we init the lists that will contain the mb of experiences # Here, we init the lists that will contain the mb of experiences
mb_obs, mb_actions, mb_values, mb_dones, mb_neglogpacs = [], [], [], [], [] mb_obs, mb_actions, mb_values, mb_dones, mb_neglogpacs = [], [], [], [], []
...@@ -212,13 +230,15 @@ class PPOModel: ...@@ -212,13 +230,15 @@ class PPOModel:
def compute_rewards(self, trials_info, trials_result): def compute_rewards(self, trials_info, trials_result):
""" """
compute the rewards of the trials in trials_info based on trials_result, Compute the rewards of the trials in trials_info based on trials_result,
and update the rewards in trials_info and update the rewards in trials_info
Parameters: Parameters
---------- ----------
trials_info: info of the generated trials trials_info : TrialsInfo
trials_result: final results (e.g., acc) of the generated trials Info of the generated trials
trials_result : list
Final results (e.g., acc) of the generated trials
""" """
mb_rewards = np.asarray([trials_result for _ in trials_info.actions], dtype=np.float32) mb_rewards = np.asarray([trials_result for _ in trials_info.actions], dtype=np.float32)
# discount/bootstrap off value fn # discount/bootstrap off value fn
...@@ -243,12 +263,14 @@ class PPOModel: ...@@ -243,12 +263,14 @@ class PPOModel:
def train(self, trials_info, nenvs): def train(self, trials_info, nenvs):
""" """
train the policy/value network using trials_info Train the policy/value network using trials_info
Parameters: Parameters
---------- ----------
trials_info: complete info of the generated trials from the previous inference trials_info : TrialsInfo
nenvs: the batch size of the (previous) inference Complete info of the generated trials from the previous inference
nenvs : int
The batch size of the (previous) inference
""" """
# keep frac decay for future optimization # keep frac decay for future optimization
if self.cur_update <= self.nupdates: if self.cur_update <= self.nupdates:
...@@ -282,27 +304,40 @@ class PPOModel: ...@@ -282,27 +304,40 @@ class PPOModel:
class PPOTuner(Tuner): class PPOTuner(Tuner):
""" """
PPOTuner PPOTuner, the implementation inherits the main logic of the implementation
[ppo2 from openai](https://github.com/openai/baselines/tree/master/baselines/ppo2), and is adapted for NAS scenario.
It uses ``lstm`` for its policy network and value network, policy and value share the same network.
""" """
def __init__(self, optimize_mode, trials_per_update=20, epochs_per_update=4, minibatch_size=4, def __init__(self, optimize_mode, trials_per_update=20, epochs_per_update=4, minibatch_size=4,
ent_coef=0.0, lr=3e-4, vf_coef=0.5, max_grad_norm=0.5, gamma=0.99, lam=0.95, cliprange=0.2): ent_coef=0.0, lr=3e-4, vf_coef=0.5, max_grad_norm=0.5, gamma=0.99, lam=0.95, cliprange=0.2):
""" """
initialization, PPO model is not initialized here as search space is not received yet. Initialization, PPO model is not initialized here as search space is not received yet.
Parameters: Parameters
---------- ----------
optimize_mode: maximize or minimize optimize_mode : str
trials_per_update: number of trials to have for each model update maximize or minimize
epochs_per_update: number of epochs to run for each model update trials_per_update : int
minibatch_size: minibatch size (number of trials) for the update Number of trials to have for each model update
ent_coef: policy entropy coefficient in the optimization objective epochs_per_update : int
lr: learning rate of the model (lstm network), constant Number of epochs to run for each model update
vf_coef: value function loss coefficient in the optimization objective minibatch_size : int
max_grad_norm: gradient norm clipping coefficient Minibatch size (number of trials) for the update
gamma: discounting factor ent_coef : float
lam: advantage estimation discounting factor (lambda in the paper) Policy entropy coefficient in the optimization objective
cliprange: cliprange in the PPO algorithm, constant lr : float
Learning rate of the model (lstm network), constant
vf_coef : float
Value function loss coefficient in the optimization objective
max_grad_norm : float
Gradient norm clipping coefficient
gamma : float
Discounting factor
lam : float
Advantage estimation discounting factor (lambda in the paper)
cliprange : float
Cliprange in the PPO algorithm, constant
""" """
self.optimize_mode = OptimizeMode(optimize_mode) self.optimize_mode = OptimizeMode(optimize_mode)
self.model_config = ModelConfig() self.model_config = ModelConfig()
...@@ -330,21 +365,25 @@ class PPOTuner(Tuner): ...@@ -330,21 +365,25 @@ class PPOTuner(Tuner):
self.model_config.nminibatches = minibatch_size self.model_config.nminibatches = minibatch_size
self.send_trial_callback = None self.send_trial_callback = None
logger.info('=== finished PPOTuner initialization') logger.info('Finished PPOTuner initialization')
def _process_one_nas_space(self, block_name, block_space): def _process_one_nas_space(self, block_name, block_space):
""" """
process nas space to determine observation space and action space Process nas space to determine observation space and action space
Parameters: Parameters
---------- ----------
block_name: the name of the mutable block block_name : str
block_space: search space of this mutable block The name of the mutable block
block_space : dict
Search space of this mutable block
Returns: Returns
---------- -------
actions_spaces: list of the space of each action actions_spaces : list
actions_to_config: the mapping from action to generated configuration List of the space of each action
actions_to_config : list
The mapping from action to generated configuration
""" """
actions_spaces = [] actions_spaces = []
actions_to_config = [] actions_to_config = []
...@@ -385,7 +424,7 @@ class PPOTuner(Tuner): ...@@ -385,7 +424,7 @@ class PPOTuner(Tuner):
def _process_nas_space(self, search_space): def _process_nas_space(self, search_space):
""" """
process nas search space to get action/observation space Process nas search space to get action/observation space
""" """
actions_spaces = [] actions_spaces = []
actions_to_config = [] actions_to_config = []
...@@ -412,7 +451,7 @@ class PPOTuner(Tuner): ...@@ -412,7 +451,7 @@ class PPOTuner(Tuner):
def _generate_action_mask(self): def _generate_action_mask(self):
""" """
different step could have different action space. to deal with this case, we merge all the Different step could have different action space. to deal with this case, we merge all the
possible actions into one action space, and use mask to indicate available actions for each step possible actions into one action space, and use mask to indicate available actions for each step
""" """
two_masks = [] two_masks = []
...@@ -439,15 +478,13 @@ class PPOTuner(Tuner): ...@@ -439,15 +478,13 @@ class PPOTuner(Tuner):
def update_search_space(self, search_space): def update_search_space(self, search_space):
""" """
get search space, currently the space only includes that for NAS Get search space, currently the space only includes that for NAS
Parameters: Parameters
---------- ----------
search_space: search space for NAS search_space : dict
Search space for NAS
Returns: the format could be referred to search space spec (https://nni.readthedocs.io/en/latest/Tutorial/SearchSpaceSpec.html).
-------
no return
""" """
logger.info('=== update search space %s', search_space) logger.info('=== update search space %s', search_space)
assert self.search_space is None assert self.search_space is None
...@@ -470,7 +507,7 @@ class PPOTuner(Tuner): ...@@ -470,7 +507,7 @@ class PPOTuner(Tuner):
def _actions_to_config(self, actions): def _actions_to_config(self, actions):
""" """
given actions, to generate the corresponding trial configuration Given actions, to generate the corresponding trial configuration
""" """
chosen_arch = copy.deepcopy(self.chosen_arch_template) chosen_arch = copy.deepcopy(self.chosen_arch_template)
for cnt, act in enumerate(actions): for cnt, act in enumerate(actions):
...@@ -490,6 +527,19 @@ class PPOTuner(Tuner): ...@@ -490,6 +527,19 @@ class PPOTuner(Tuner):
def generate_multiple_parameters(self, parameter_id_list, **kwargs): def generate_multiple_parameters(self, parameter_id_list, **kwargs):
""" """
Returns multiple sets of trial (hyper-)parameters, as iterable of serializable objects. Returns multiple sets of trial (hyper-)parameters, as iterable of serializable objects.
Parameters
----------
parameter_id_list : list of int
Unique identifiers for each set of requested hyper-parameters.
These will later be used in :meth:`receive_trial_result`.
**kwargs
Not used
Returns
-------
list
A list of newly generated configurations
""" """
result = [] result = []
self.send_trial_callback = kwargs['st_callback'] self.send_trial_callback = kwargs['st_callback']
...@@ -506,7 +556,17 @@ class PPOTuner(Tuner): ...@@ -506,7 +556,17 @@ class PPOTuner(Tuner):
def generate_parameters(self, parameter_id, **kwargs): def generate_parameters(self, parameter_id, **kwargs):
""" """
generate parameters, if no trial configration for now, self.credit plus 1 to send the config later Generate parameters, if no trial configration for now, self.credit plus 1 to send the config later
parameter_id : int
Unique identifier for requested hyper-parameters. This will later be used in :meth:`receive_trial_result`.
**kwargs
Not used
Returns
-------
dict
One newly generated configuration
""" """
if self.first_inf: if self.first_inf:
self.trials_result = [None for _ in range(self.inf_batch_size)] self.trials_result = [None for _ in range(self.inf_batch_size)]
...@@ -527,6 +587,7 @@ class PPOTuner(Tuner): ...@@ -527,6 +587,7 @@ class PPOTuner(Tuner):
def _next_round_inference(self): def _next_round_inference(self):
""" """
Run a inference to generate next batch of configurations
""" """
self.finished_trials = 0 self.finished_trials = 0
self.model.compute_rewards(self.trials_info, self.trials_result) self.model.compute_rewards(self.trials_info, self.trials_result)
...@@ -554,8 +615,17 @@ class PPOTuner(Tuner): ...@@ -554,8 +615,17 @@ class PPOTuner(Tuner):
def receive_trial_result(self, parameter_id, parameters, value, **kwargs): def receive_trial_result(self, parameter_id, parameters, value, **kwargs):
""" """
receive trial's result. if the number of finished trials equals self.inf_batch_size, start the next update to Receive trial's result. if the number of finished trials equals self.inf_batch_size, start the next update to
train the model train the model.
Parameters
----------
parameter_id : int
Unique identifier of used hyper-parameters, same with :meth:`generate_parameters`.
parameters : dict
Hyper-parameters generated by :meth:`generate_parameters`.
value : dict
Result from trial (the return value of :func:`nni.report_final_result`).
""" """
trial_info_idx = self.running_trials.pop(parameter_id, None) trial_info_idx = self.running_trials.pop(parameter_id, None)
assert trial_info_idx is not None assert trial_info_idx is not None
...@@ -572,7 +642,17 @@ class PPOTuner(Tuner): ...@@ -572,7 +642,17 @@ class PPOTuner(Tuner):
def trial_end(self, parameter_id, success, **kwargs): def trial_end(self, parameter_id, success, **kwargs):
""" """
to deal with trial failure To deal with trial failure. If a trial fails, it is popped out from ``self.running_trials``,
and the final result of this trial is assigned with the average of the finished trials.
Parameters
----------
parameter_id : int
Unique identifier for hyper-parameters used by this trial.
success : bool
True if the trial successfully completed; False if failed or terminated.
**kwargs
Not used
""" """
if not success: if not success:
if parameter_id not in self.running_trials: if parameter_id not in self.running_trials:
...@@ -582,7 +662,7 @@ class PPOTuner(Tuner): ...@@ -582,7 +662,7 @@ class PPOTuner(Tuner):
assert trial_info_idx is not None assert trial_info_idx is not None
# use mean of finished trials as the result of this failed trial # use mean of finished trials as the result of this failed trial
values = [val for val in self.trials_result if val is not None] values = [val for val in self.trials_result if val is not None]
logger.warning('zql values: %s', values) logger.warning('In trial_end, values: %s', values)
self.trials_result[trial_info_idx] = (sum(values) / len(values)) if values else 0 self.trials_result[trial_info_idx] = (sum(values) / len(values)) if values else 0
self.finished_trials += 1 self.finished_trials += 1
if self.finished_trials == self.inf_batch_size: if self.finished_trials == self.inf_batch_size:
...@@ -590,10 +670,11 @@ class PPOTuner(Tuner): ...@@ -590,10 +670,11 @@ class PPOTuner(Tuner):
def import_data(self, data): def import_data(self, data):
""" """
Import additional data for tuning Import additional data for tuning, not supported yet.
Parameters Parameters
---------- ----------
data: a list of dictionarys, each of which has at least two keys, 'parameter' and 'value' data : list
A list of dictionarys, each of which has at least two keys, ``parameter`` and ``value``
""" """
logger.warning('PPOTuner cannot leverage imported data.') logger.warning('PPOTuner cannot leverage imported data.')
...@@ -94,12 +94,14 @@ def lstm_model(nlstm=128, layer_norm=False): ...@@ -94,12 +94,14 @@ def lstm_model(nlstm=128, layer_norm=False):
An example of usage of lstm-based policy can be found here: common/tests/test_doc_examples.py/test_lstm_example An example of usage of lstm-based policy can be found here: common/tests/test_doc_examples.py/test_lstm_example
Parameters: Parameters
---------- ----------
nlstm: int LSTM hidden state size nlstm : int
layer_norm: bool if True, layer-normalized version of LSTM is used LSTM hidden state size
layer_norm : bool
if True, layer-normalized version of LSTM is used
Returns: Returns
------- -------
function that builds LSTM with a given input tensor / placeholder function that builds LSTM with a given input tensor / placeholder
""" """
...@@ -171,11 +173,15 @@ def adjust_shape(placeholder, data): ...@@ -171,11 +173,15 @@ def adjust_shape(placeholder, data):
adjust shape of the data to the shape of the placeholder if possible. adjust shape of the data to the shape of the placeholder if possible.
If shape is incompatible, AssertionError is thrown If shape is incompatible, AssertionError is thrown
Parameters: Parameters
placeholder: tensorflow input placeholder ----------
data: input data to be (potentially) reshaped to be fed into placeholder placeholder
tensorflow input placeholder
data
input data to be (potentially) reshaped to be fed into placeholder
Returns: Returns
-------
reshaped data reshaped data
""" """
if not isinstance(data, np.ndarray) and not isinstance(data, list): if not isinstance(data, np.ndarray) and not isinstance(data, list):
...@@ -230,13 +236,16 @@ def observation_placeholder(ob_space, batch_size=None, name='Ob'): ...@@ -230,13 +236,16 @@ def observation_placeholder(ob_space, batch_size=None, name='Ob'):
""" """
Create placeholder to feed observations into of the size appropriate to the observation space Create placeholder to feed observations into of the size appropriate to the observation space
Parameters: Parameters
---------- ----------
ob_space: gym.Space observation space ob_space : gym.Space
batch_size: int size of the batch to be fed into input. Can be left None in most cases. observation space
name: str name of the placeholder batch_size : int
size of the batch to be fed into input. Can be left None in most cases.
Returns: name : str
name of the placeholder
Returns
------- -------
tensorflow placeholder tensor tensorflow placeholder tensor
""" """
......
...@@ -24,11 +24,14 @@ import numpy as np ...@@ -24,11 +24,14 @@ import numpy as np
def get_json_content(file_path): def get_json_content(file_path):
"""Load json file content """
Load json file content
Parameters Parameters
---------- ----------
file_path: file_path:
path to the file path to the file
Raises Raises
------ ------
TypeError TypeError
...@@ -43,7 +46,8 @@ def get_json_content(file_path): ...@@ -43,7 +46,8 @@ def get_json_content(file_path):
def generate_pcs(nni_search_space_content): def generate_pcs(nni_search_space_content):
"""Generate the Parameter Configuration Space (PCS) which defines the """
Generate the Parameter Configuration Space (PCS) which defines the
legal ranges of the parameters to be optimized and their default values. legal ranges of the parameters to be optimized and their default values.
Generally, the format is: Generally, the format is:
# parameter_name categorical {value_1, ..., value_N} [default value] # parameter_name categorical {value_1, ..., value_N} [default value]
...@@ -53,14 +57,17 @@ def generate_pcs(nni_search_space_content): ...@@ -53,14 +57,17 @@ def generate_pcs(nni_search_space_content):
# parameter_name real [min_value, max_value] [default value] # parameter_name real [min_value, max_value] [default value]
# parameter_name real [min_value, max_value] [default value] log # parameter_name real [min_value, max_value] [default value] log
Reference: https://automl.github.io/SMAC3/stable/options.html Reference: https://automl.github.io/SMAC3/stable/options.html
Parameters Parameters
---------- ----------
nni_search_space_content: search_space nni_search_space_content: search_space
The search space in this experiment in nni The search space in this experiment in nni
Returns Returns
------- -------
Parameter Configuration Space (PCS) Parameter Configuration Space (PCS)
the legal ranges of the parameters to be optimized and their default values the legal ranges of the parameters to be optimized and their default values
Raises Raises
------ ------
RuntimeError RuntimeError
...@@ -122,7 +129,8 @@ def generate_pcs(nni_search_space_content): ...@@ -122,7 +129,8 @@ def generate_pcs(nni_search_space_content):
def generate_scenario(ss_content): def generate_scenario(ss_content):
"""Generate the scenario. The scenario-object (smac.scenario.scenario.Scenario) is used to configure SMAC and """
Generate the scenario. The scenario-object (smac.scenario.scenario.Scenario) is used to configure SMAC and
can be constructed either by providing an actual scenario-object, or by specifing the options in a scenario file. can be constructed either by providing an actual scenario-object, or by specifing the options in a scenario file.
Reference: https://automl.github.io/SMAC3/stable/options.html Reference: https://automl.github.io/SMAC3/stable/options.html
The format of the scenario file is one option per line: The format of the scenario file is one option per line:
...@@ -191,6 +199,7 @@ def generate_scenario(ss_content): ...@@ -191,6 +199,7 @@ def generate_scenario(ss_content):
wallclock_limit: int wallclock_limit: int
Maximum amount of wallclock-time used for optimization. Default: inf. Maximum amount of wallclock-time used for optimization. Default: inf.
Use default because this is controlled by nni Use default because this is controlled by nni
Returns Returns
------- -------
Scenario: Scenario:
......
...@@ -41,13 +41,17 @@ from .convert_ss_to_scenario import generate_scenario ...@@ -41,13 +41,17 @@ from .convert_ss_to_scenario import generate_scenario
class SMACTuner(Tuner): class SMACTuner(Tuner):
""" """
Parameters This is a wrapper of [SMAC](https://github.com/automl/SMAC3) following NNI tuner interface.
---------- It only supports ``SMAC`` mode, and does not support the multiple instances of SMAC3 (i.e.,
optimize_mode: str the same configuration is run multiple times).
optimize mode, 'maximize' or 'minimize', by default 'maximize'
""" """
def __init__(self, optimize_mode="maximize"): def __init__(self, optimize_mode="maximize"):
"""Constructor""" """
Parameters
----------
optimize_mode : str
Optimize mode, 'maximize' or 'minimize', by default 'maximize'
"""
self.logger = logging.getLogger( self.logger = logging.getLogger(
self.__module__ + "." + self.__class__.__name__) self.__module__ + "." + self.__class__.__name__)
self.optimize_mode = OptimizeMode(optimize_mode) self.optimize_mode = OptimizeMode(optimize_mode)
...@@ -61,11 +65,14 @@ class SMACTuner(Tuner): ...@@ -61,11 +65,14 @@ class SMACTuner(Tuner):
self.cs = None self.cs = None
def _main_cli(self): def _main_cli(self):
"""Main function of SMAC for CLI interface """
Main function of SMAC for CLI interface. Some initializations of the wrapped SMAC are done
in this function.
Returns Returns
------- -------
instance obj
optimizer The object of the SMAC optimizer
""" """
self.logger.info("SMAC call: %s", " ".join(sys.argv)) self.logger.info("SMAC call: %s", " ".join(sys.argv))
...@@ -126,20 +133,23 @@ class SMACTuner(Tuner): ...@@ -126,20 +133,23 @@ class SMACTuner(Tuner):
def update_search_space(self, search_space): def update_search_space(self, search_space):
""" """
NOTE: updating search space is not supported. Convert search_space to the format that ``SMAC3`` could recognize, thus, not all the search space types
are supported. In this function, we also do the initialization of `SMAC3`, i.e., calling ``self._main_cli``.
NOTE: updating search space during experiment running is not supported.
Parameters Parameters
---------- ----------
search_space: dict search_space : dict
search space The format could be referred to search space spec (https://nni.readthedocs.io/en/latest/Tutorial/SearchSpaceSpec.html).
""" """
# TODO: this is ugly, we put all the initialization work in this method, because initialization relies
# on search space, also because update_search_space is called at the beginning.
if not self.update_ss_done: if not self.update_ss_done:
self.categorical_dict = generate_scenario(search_space) self.categorical_dict = generate_scenario(search_space)
if self.categorical_dict is None: if self.categorical_dict is None:
raise RuntimeError('categorical dict is not correctly returned after parsing search space.') raise RuntimeError('categorical dict is not correctly returned after parsing search space.')
# TODO: this is ugly, we put all the initialization work in this method, because initialization relies
# on search space, also because update_search_space is called at the beginning.
self.optimizer = self._main_cli() self.optimizer = self._main_cli()
self.smbo_solver = self.optimizer.solver self.smbo_solver = self.optimizer.solver
self.loguniform_key = {key for key in search_space.keys() if search_space[key]['_type'] == 'loguniform'} self.loguniform_key = {key for key in search_space.keys() if search_space[key]['_type'] == 'loguniform'}
...@@ -148,19 +158,23 @@ class SMACTuner(Tuner): ...@@ -148,19 +158,23 @@ class SMACTuner(Tuner):
self.logger.warning('update search space is not supported.') self.logger.warning('update search space is not supported.')
def receive_trial_result(self, parameter_id, parameters, value, **kwargs): def receive_trial_result(self, parameter_id, parameters, value, **kwargs):
"""receive_trial_result """
Receive a trial's final performance result reported through :func:``nni.report_final_result`` by the trial.
GridSearchTuner does not need trial's results.
Parameters Parameters
---------- ----------
parameter_id: int parameter_id : int
parameter id Unique identifier of used hyper-parameters, same with :meth:`generate_parameters`.
parameters: parameters : dict
parameters Hyper-parameters generated by :meth:`generate_parameters`.
value: value : dict
value Result from trial (the return value of :func:`nni.report_final_result`).
Raises Raises
------ ------
RuntimeError RuntimeError
Received parameter id not in total_data Received parameter id not in ``self.total_data``
""" """
reward = extract_scalar_reward(value) reward = extract_scalar_reward(value)
if self.optimize_mode is OptimizeMode.Maximize: if self.optimize_mode is OptimizeMode.Maximize:
...@@ -176,14 +190,16 @@ class SMACTuner(Tuner): ...@@ -176,14 +190,16 @@ class SMACTuner(Tuner):
def param_postprocess(self, challenger_dict): def param_postprocess(self, challenger_dict):
""" """
Postprocessing for a set of parameter includes: Postprocessing for a set of hyperparameters includes:
1. Convert the values of type `loguniform` back to their initial range. 1. Convert the values of type ``loguniform`` back to their initial range.
2. Convert categorical: categorical values in search space are changed to list of numbers before, 2. Convert ``categorical``: categorical values in search space are changed to list of numbers before,
those original values will be changed back in this function. those original values will be changed back in this function.
Parameters Parameters
---------- ----------
challenger_dict: dict challenger_dict : dict
challenger dict challenger dict
Returns Returns
------- -------
dict dict
...@@ -203,15 +219,21 @@ class SMACTuner(Tuner): ...@@ -203,15 +219,21 @@ class SMACTuner(Tuner):
return converted_dict return converted_dict
def generate_parameters(self, parameter_id, **kwargs): def generate_parameters(self, parameter_id, **kwargs):
"""generate one instance of hyperparameters """
Generate one instance of hyperparameters (i.e., one configuration).
Get one from SMAC3's ``challengers``.
Parameters Parameters
---------- ----------
parameter_id: int parameter_id : int
parameter id Unique identifier for requested hyper-parameters. This will later be used in :meth:`receive_trial_result`.
**kwargs
Not used
Returns Returns
------- -------
list dict
new generated parameters One newly generated configuration
""" """
if self.first_one: if self.first_one:
init_challenger = self.smbo_solver.nni_smac_start() init_challenger = self.smbo_solver.nni_smac_start()
...@@ -224,15 +246,23 @@ class SMACTuner(Tuner): ...@@ -224,15 +246,23 @@ class SMACTuner(Tuner):
return self.param_postprocess(challenger.get_dictionary()) return self.param_postprocess(challenger.get_dictionary())
def generate_multiple_parameters(self, parameter_id_list, **kwargs): def generate_multiple_parameters(self, parameter_id_list, **kwargs):
"""generate mutiple instances of hyperparameters """
Generate mutiple instances of hyperparameters. If it is a first request,
retrieve the instances from initial challengers. While if it is not, request
new challengers and retrieve instances from the requested challengers.
Parameters Parameters
---------- ----------
parameter_id_list: list parameter_id_list: list of int
list of parameter id Unique identifiers for each set of requested hyper-parameters.
These will later be used in :meth:`receive_trial_result`.
**kwargs
Not used
Returns Returns
------- -------
list list
list of new generated parameters a list of newly generated configurations
""" """
if self.first_one: if self.first_one:
params = [] params = []
...@@ -254,11 +284,12 @@ class SMACTuner(Tuner): ...@@ -254,11 +284,12 @@ class SMACTuner(Tuner):
def import_data(self, data): def import_data(self, data):
""" """
Import additional data for tuning Import additional data for tuning.
Parameters Parameters
---------- ----------
data: list of dict data : list of dict
Each of which has at least two keys, `parameter` and `value`. 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:
......
...@@ -76,10 +76,11 @@ class Tuner(Recoverable): ...@@ -76,10 +76,11 @@ class Tuner(Recoverable):
Builtin tuners: Builtin tuners:
:class:`~nni.hyperopt_tuner.hyperopt_tuner.HyperoptTuner` :class:`~nni.hyperopt_tuner.hyperopt_tuner.HyperoptTuner`
:class:`~nni.evolution_tuner.evolution_tuner.EvolutionTuner` :class:`~nni.evolution_tuner.evolution_tuner.EvolutionTuner`
:class:`~nni.smac_tuner.smac_tuner.SMACTuner` :class:`~nni.smac_tuner.SMACTuner`
:class:`~nni.gridsearch_tuner.gridsearch_tuner.GridSearchTuner` :class:`~nni.gridsearch_tuner.GridSearchTuner`
:class:`~nni.networkmorphism_tuner.networkmorphism_tuner.NetworkMorphismTuner` :class:`~nni.networkmorphism_tuner.networkmorphism_tuner.NetworkMorphismTuner`
:class:`~nni.metis_tuner.metis_tuner.MetisTuner` :class:`~nni.metis_tuner.mets_tuner.MetisTuner`
:class:`~nni.ppo_tuner.PPOTuner`
:class:`~nni.gp_tuner.gp_tuner.GPTuner` :class:`~nni.gp_tuner.gp_tuner.GPTuner`
""" """
......
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