Unverified Commit 8c203f30 authored by SparkSnail's avatar SparkSnail Committed by GitHub
Browse files

Merge pull request #211 from microsoft/master

merge master
parents 7c1ab114 483232c8
......@@ -24,7 +24,6 @@ import sys
import nni.metis_tuner.lib_acquisition_function as lib_acquisition_function
import nni.metis_tuner.lib_constraint_summation as lib_constraint_summation
import nni.metis_tuner.lib_data as lib_data
sys.path.insert(1, os.path.join(sys.path[0], '..'))
......
......@@ -19,12 +19,12 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import argparse, json, os, sys
import os
import sys
from multiprocessing.dummy import Pool as ThreadPool
import nni.metis_tuner.Regression_GP.CreateModel as gp_create_model
import nni.metis_tuner.Regression_GP.Prediction as gp_prediction
import nni.metis_tuner.lib_data as lib_data
sys.path.insert(1, os.path.join(sys.path[0], '..'))
......@@ -71,14 +71,15 @@ def outlierDetection_threaded(samples_x, samples_y_aggregation):
else:
print("error here.")
outliers = None if len(outliers) == 0 else outliers
outliers = outliers if outliers else None
return outliers
def outlierDetection(samples_x, samples_y_aggregation):
'''
TODO
'''
outliers = []
for samples_idx in range(0, len(samples_x)):
for samples_idx, _ in enumerate(samples_x):
#sys.stderr.write("[%s] DEBUG: Evaluating %d of %d samples\n"
# \ % (os.path.basename(__file__), samples_idx + 1, len(samples_x)))
diagnostic_regressor_gp = gp_create_model.create_model(\
......@@ -93,5 +94,5 @@ def outlierDetection(samples_x, samples_y_aggregation):
"expected_sigma": sigma,
"difference": abs(samples_y_aggregation[samples_idx] - mu) - (2.33 * sigma)})
outliers = None if len(outliers) == 0 else outliers
outliers = outliers if outliers else None
return outliers
......@@ -31,6 +31,7 @@ def match_val_type(vals, vals_bounds, vals_types):
for i, _ in enumerate(vals_types):
if vals_types[i] == "discrete_int":
# Find the closest integer in the array, vals_bounds
# pylint: disable=cell-var-from-loop
vals_new.append(min(vals_bounds[i], key=lambda x: abs(x - vals[i])))
elif vals_types[i] == "range_int":
# Round down to the nearest integer
......@@ -64,4 +65,3 @@ def rand(x_bounds, x_types):
return None
return outputs
\ No newline at end of file
......@@ -20,14 +20,11 @@
import copy
import logging
import numpy as np
import os
import random
import statistics
import sys
import warnings
from enum import Enum, unique
from multiprocessing.dummy import Pool as ThreadPool
import numpy as np
import nni.metis_tuner.lib_constraint_summation as lib_constraint_summation
import nni.metis_tuner.lib_data as lib_data
......@@ -99,6 +96,8 @@ class MetisTuner(Tuner):
self.minimize_constraints_fun = None
self.minimize_starting_points = None
self.supplement_data_num = 0
self.x_bounds = []
self.x_types = []
def update_search_space(self, search_space):
......@@ -144,7 +143,7 @@ class MetisTuner(Tuner):
self.x_types[idx] = 'discrete_int'
else:
logger.info("Metis Tuner doesn't support this kind of variable: " + str(key_type))
logger.info("Metis Tuner doesn't support this kind of variable: %s", key_type)
raise RuntimeError("Metis Tuner doesn't support this kind of variable: " + str(key_type))
else:
logger.info("The format of search space is not a dict.")
......@@ -198,7 +197,7 @@ class MetisTuner(Tuner):
minimize_starting_points=self.minimize_starting_points,
minimize_constraints_fun=self.minimize_constraints_fun)
logger.info("Generate paramageters:\n" + str(results))
logger.info("Generate paramageters:\n%s", results)
return results
......@@ -217,8 +216,8 @@ class MetisTuner(Tuner):
value = -value
logger.info("Received trial result.")
logger.info("value is :" + str(value))
logger.info("parameter is : " + str(parameters))
logger.info("value is :%s", value)
logger.info("parameter is : %s", parameters)
# parse parameter to sample_x
sample_x = [0 for i in range(len(self.key_order))]
......@@ -271,10 +270,12 @@ class MetisTuner(Tuner):
minimize_constraints_fun=minimize_constraints_fun)
if not lm_current:
return None
logger.info({'hyperparameter': lm_current['hyperparameter'],
logger.info({
'hyperparameter': lm_current['hyperparameter'],
'expected_mu': lm_current['expected_mu'],
'expected_sigma': lm_current['expected_sigma'],
'reason': "exploitation_gp"})
'reason': "exploitation_gp"
})
if no_candidates is False:
# ===== STEP 2: Get recommended configurations for exploration =====
......@@ -289,10 +290,12 @@ class MetisTuner(Tuner):
if results_exploration is not None:
if _num_past_samples(results_exploration['hyperparameter'], samples_x, samples_y) == 0:
temp_candidate = {'hyperparameter': results_exploration['hyperparameter'],
temp_candidate = {
'hyperparameter': results_exploration['hyperparameter'],
'expected_mu': results_exploration['expected_mu'],
'expected_sigma': results_exploration['expected_sigma'],
'reason': "exploration"}
'reason': "exploration"
}
candidates.append(temp_candidate)
logger.info("DEBUG: 1 exploration candidate selected\n")
......@@ -322,11 +325,14 @@ class MetisTuner(Tuner):
if results_exploitation is not None:
if _num_past_samples(results_exploitation['hyperparameter'], samples_x, samples_y) == 0:
temp_expected_mu, temp_expected_sigma = gp_prediction.predict(results_exploitation['hyperparameter'], gp_model['model'])
temp_candidate = {'hyperparameter': results_exploitation['hyperparameter'],
temp_expected_mu, temp_expected_sigma = \
gp_prediction.predict(results_exploitation['hyperparameter'], gp_model['model'])
temp_candidate = {
'hyperparameter': results_exploitation['hyperparameter'],
'expected_mu': temp_expected_mu,
'expected_sigma': temp_expected_sigma,
'reason': "exploitation_gmm"}
'reason': "exploitation_gmm"
}
candidates.append(temp_candidate)
logger.info("DEBUG: 1 exploitation_gmm candidate selected\n")
......@@ -349,7 +355,7 @@ class MetisTuner(Tuner):
results_outliers = gp_outlier_detection.outlierDetection_threaded(samples_x, samples_y_aggregation)
if results_outliers is not None:
for results_outlier in results_outliers:
for results_outlier in results_outliers: # pylint: disable=not-an-iterable
if _num_past_samples(samples_x[results_outlier['samples_idx']], samples_x, samples_y) < max_resampling_per_x:
temp_candidate = {'hyperparameter': samples_x[results_outlier['samples_idx']],\
'expected_mu': results_outlier['expected_mu'],\
......@@ -398,10 +404,12 @@ class MetisTuner(Tuner):
next_candidate = {'hyperparameter': next_candidate, 'reason': "random",
'expected_mu': expected_mu, 'expected_sigma': expected_sigma}
# ===== STEP 7: If current optimal hyperparameter occurs in the history or exploration probability is less than the threshold, take next config as exploration step =====
# ===== STEP 7 =====
# If current optimal hyperparameter occurs in the history or exploration probability is less than the threshold,
# take next config as exploration step
outputs = self._pack_output(lm_current['hyperparameter'])
ap = random.uniform(0, 1)
if outputs in self.total_data or ap<=self.exploration_probability:
if outputs in self.total_data or ap <= self.exploration_probability:
if next_candidate is not None:
outputs = self._pack_output(next_candidate['hyperparameter'])
else:
......@@ -419,14 +427,14 @@ class MetisTuner(Tuner):
"""
_completed_num = 0
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))
_completed_num += 1
assert "parameter" in trial_info
_params = trial_info["parameter"]
assert "value" in trial_info
_value = trial_info['value']
if not _value:
logger.info("Useless trial data, value is %s, skip this trial data." %_value)
logger.info("Useless trial data, value is %s, skip this trial data.", _value)
continue
self.supplement_data_num += 1
_parameter_id = '_'.join(["ImportData", str(self.supplement_data_num)])
......
......@@ -61,7 +61,7 @@ _customized_parameter_ids = set()
def _create_parameter_id():
global _next_parameter_id # pylint: disable=global-statement
global _next_parameter_id
_next_parameter_id += 1
return _next_parameter_id - 1
......@@ -106,15 +106,15 @@ class MsgDispatcher(MsgDispatcherBase):
self.tuner.update_search_space(data)
send(CommandType.Initialized, '')
def send_trial_callback(self, id, params):
def send_trial_callback(self, id_, params):
"""For tuner to issue trial config when the config is generated
"""
send(CommandType.NewTrialJob, _pack_parameter(id, params))
send(CommandType.NewTrialJob, _pack_parameter(id_, params))
def handle_request_trial_jobs(self, data):
# data: number or trial jobs
ids = [_create_parameter_id() for _ in range(data)]
_logger.debug("requesting for generating params of {}".format(ids))
_logger.debug("requesting for generating params of %s", ids)
params_list = self.tuner.generate_multiple_parameters(ids, st_callback=self.send_trial_callback)
for i, _ in enumerate(params_list):
......@@ -218,7 +218,8 @@ class MsgDispatcher(MsgDispatcherBase):
try:
result = self.assessor.assess_trial(trial_job_id, ordered_history)
except Exception as e:
_logger.exception('Assessor error')
_logger.error('Assessor error')
_logger.exception(e)
if isinstance(result, bool):
result = AssessResult.Good if result else AssessResult.Bad
......
......@@ -18,7 +18,6 @@
# OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# ==================================================================================================
import os
import threading
import logging
from multiprocessing.dummy import Pool as ThreadPool
......@@ -146,7 +145,7 @@ class MsgDispatcherBase(Recoverable):
pass
def process_command(self, command, data):
_logger.debug('process_command: command: [{}], data: [{}]'.format(command, data))
_logger.debug('process_command: command: [%s], data: [%s]', command, data)
command_handlers = {
# Tuner commands:
......
......@@ -25,6 +25,11 @@ from . import trial
_logger = logging.getLogger(__name__)
_MUTABLE_LAYER_SPACE_PREFIX = "_mutable_layer"
_namespace = {}
_tf_variables = {}
_arch_logits_list = []
_optimizer = None
_train_op = None
def classic_mode(
......@@ -64,47 +69,40 @@ def enas_mode(
it can be known which inputs should be masked and which op should be executed.'''
name_prefix = "{}_{}".format(mutable_id, mutable_layer_id)
# store namespace
if 'name_space' not in globals():
global name_space
name_space = dict()
name_space[mutable_id] = True
name_space[name_prefix] = dict()
name_space[name_prefix]['funcs'] = list(funcs)
name_space[name_prefix]['optional_inputs'] = list(optional_inputs)
_namespace[mutable_id] = True
_namespace[name_prefix] = dict()
_namespace[name_prefix]['funcs'] = list(funcs)
_namespace[name_prefix]['optional_inputs'] = list(optional_inputs)
# create tensorflow variables as 1/0 signals used to form subgraph
if 'tf_variables' not in globals():
global tf_variables
tf_variables = dict()
name_for_optional_inputs = name_prefix + '_optional_inputs'
name_for_funcs = name_prefix + '_funcs'
tf_variables[name_prefix] = dict()
tf_variables[name_prefix]['optional_inputs'] = tf.get_variable(name_for_optional_inputs,
[len(
optional_inputs)],
_tf_variables[name_prefix] = dict()
_tf_variables[name_prefix]['optional_inputs'] = tf.get_variable(
name_for_optional_inputs,
[len(optional_inputs)],
dtype=tf.bool,
trainable=False)
tf_variables[name_prefix]['funcs'] = tf.get_variable(
trainable=False
)
_tf_variables[name_prefix]['funcs'] = tf.get_variable(
name_for_funcs, [], dtype=tf.int64, trainable=False)
# get real values using their variable names
real_optional_inputs_value = [optional_inputs[name]
for name in name_space[name_prefix]['optional_inputs']]
for name in _namespace[name_prefix]['optional_inputs']]
real_func_value = [funcs[name]
for name in name_space[name_prefix]['funcs']]
for name in _namespace[name_prefix]['funcs']]
real_funcs_args = [funcs_args[name]
for name in name_space[name_prefix]['funcs']]
for name in _namespace[name_prefix]['funcs']]
# build tensorflow graph of geting chosen inputs by masking
real_chosen_inputs = tf.boolean_mask(
real_optional_inputs_value, tf_variables[name_prefix]['optional_inputs'])
real_optional_inputs_value, _tf_variables[name_prefix]['optional_inputs'])
# build tensorflow graph of different branches by using tf.case
branches = dict()
func_output = None
for func_id in range(len(funcs)):
func_output = real_func_value[func_id](
[fixed_inputs, real_chosen_inputs], **real_funcs_args[func_id])
branches[tf.equal(tf_variables[name_prefix]['funcs'],
func_id)] = lambda: func_output
layer_out = tf.case(branches, exclusive=True,
default=lambda: func_output)
func_output = real_func_value[func_id]([fixed_inputs, real_chosen_inputs], **real_funcs_args[func_id])
branches[tf.equal(_tf_variables[name_prefix]['funcs'], func_id)] = lambda: func_output
layer_out = tf.case(branches, exclusive=True, default=lambda: func_output)
return layer_out
......@@ -157,12 +155,9 @@ def darts_mode(
layer_outs = [func([fixed_inputs, optional_inputs], **funcs_args[func_name])
for func_name, func in funcs.items()]
# Create architecture weights for every func(op)
var_name = "{}_{}_".format(mutable_id, mutable_layer_id, "arch_weights")
if 'arch_logits_list' not in globals():
global arch_logits_list
arch_logits_list = list()
arch_logits = tf.get_variable(var_name, shape=[len[funcs]], trainable=False)
arch_logits_list.append(arch_logits)
var_name = "{}_{}_arch_weights".format(mutable_id, mutable_layer_id)
arch_logits = tf.get_variable(var_name, shape=[len(funcs)], trainable=False)
_arch_logits_list.append(arch_logits)
arch_weights = tf.nn.softmax(arch_logits)
layer_out = tf.add_n([arch_weights[idx] * out for idx, out in enumerate(layer_outs)])
......@@ -186,19 +181,19 @@ def reload_tensorflow_variables(tf, session):
mutable_layers.add((mutable_id, mutable_layer_id))
mutable_layers = sorted(list(mutable_layers))
for mutable_id, mutable_layer_id in mutable_layers:
if mutable_id not in name_space:
_logger.warning("{} not found in name space".format(mutable_id))
if mutable_id not in _namespace:
_logger.warning("%s not found in name space", mutable_id)
continue
name_prefix = "{}_{}".format(mutable_id, mutable_layer_id)
# get optional inputs names
optional_inputs = name_space[name_prefix]['optional_inputs']
optional_inputs = _namespace[name_prefix]['optional_inputs']
# extract layer information from the subgraph sampled by tuner
chosen_layer, chosen_inputs = _get_layer_and_inputs_from_tuner(mutable_id, mutable_layer_id, optional_inputs)
chosen_layer = name_space[name_prefix]['funcs'].index(chosen_layer)
chosen_layer = _namespace[name_prefix]['funcs'].index(chosen_layer)
chosen_inputs = [1 if inp in chosen_inputs else 0 for inp in optional_inputs]
# load these information into pre-defined tensorflow variables
tf_variables[name_prefix]['funcs'].load(chosen_layer, session)
tf_variables[name_prefix]['optional_inputs'].load(
_tf_variables[name_prefix]['funcs'].load(chosen_layer, session)
_tf_variables[name_prefix]['optional_inputs'].load(
chosen_inputs, session)
......@@ -218,15 +213,13 @@ def _decompose_general_key(key):
def darts_training(tf, session, loss, feed_dict):
if 'optimizer' not in globals():
global arch_logits_list
global optimizer
global train_op
optimizer = tf.MomentumOptimizer(learning_rate=0.025)
global _optimizer, _train_op
if _optimizer is None:
_optimizer = tf.MomentumOptimizer(learning_rate=0.025)
# TODO: Calculate loss
grads_and_vars = optimizer.compute_gradients(loss, arch_logits_list)
train_op = optimizer.apply_gradients(grads_and_vars)
session.run(train_op)
grads_and_vars = _optimizer.compute_gradients(loss, _arch_logits_list)
_train_op = _optimizer.apply_gradients(grads_and_vars)
session.run(_train_op)
def training_update(nas_mode, tf=None, session=None, loss=None, feed_dict=None):
......@@ -258,12 +251,11 @@ def _get_layer_and_inputs_from_tuner(mutable_id, mutable_layer_id, optional_inpu
chosen_inputs = []
# make sure dict -> list produce stable result by sorting
optional_inputs_keys = sorted(optional_inputs)
for i in range(optional_input_size):
for _ in range(optional_input_size):
chosen_inputs.append(optional_inputs_keys[optional_input_state % len(optional_inputs)])
optional_input_state //= len(optional_inputs)
_logger.info("%s_%s: layer: %s, optional inputs: %s" % (mutable_id, mutable_layer_id,
chosen_layer, chosen_inputs))
_logger.info("%s_%s: layer: %s, optional inputs: %s", mutable_id, mutable_layer_id, chosen_layer, chosen_inputs)
return chosen_layer, chosen_inputs
......@@ -278,12 +270,12 @@ def convert_nas_search_space(search_space):
if "_type" not in v:
# this should not happen
_logger.warning("There is no _type in one of your search space values with key '%s'"
". Please check your search space" % k)
". Please check your search space", k)
ret[k] = v
elif v["_type"] != "mutable_layer":
ret[k] = v
else:
_logger.info("Converting mutable_layer search space with key '%s'" % k)
_logger.info("Converting mutable_layer search space with key '%s'", k)
# v["_value"] looks like {'mutable_layer_1': {'layer_choice': ...} ...}
values = v["_value"]
for layer_name, layer_data in values.items():
......@@ -305,13 +297,13 @@ def convert_nas_search_space(search_space):
_logger.error("Might not be able to handle optional_input_size < 0, please double check")
input_size[1] += 1
else:
_logger.info("Optional input choices are set to empty by default in %s" % layer_key)
_logger.info("Optional input choices are set to empty by default in %s", layer_key)
input_size = [0, 1]
if layer_data.get("optional_inputs"):
total_state_size = len(layer_data["optional_inputs"]) ** (input_size[1] - 1)
else:
_logger.info("Optional inputs not found in %s" % layer_key)
_logger.info("Optional inputs not found in %s", layer_key)
total_state_size = 1
converted = {
......
......@@ -19,7 +19,7 @@
# ==================================================================================================
import json
from collections import Iterable
from collections.abc import Iterable
from copy import deepcopy, copy
from queue import Queue
......@@ -653,7 +653,7 @@ class Graph:
return JSONModel(self).data
@classmethod
def parsing_json_model(self, json_model):
def parsing_json_model(cls, json_model):
'''build a graph from json
'''
return json_to_graph(json_model)
......@@ -910,7 +910,6 @@ def graph_to_onnx(graph, onnx_model_path):
def onnx_to_graph(onnx_model, input_shape):
import onnx
# to do in the future using onnx ir
graph = Graph(input_shape, False)
graph.parsing_onnx_model(onnx_model)
......
......@@ -124,7 +124,7 @@ def wider_pre_conv(layer, n_add_filters, weighted=True):
student_w = teacher_w.copy()
student_b = teacher_b.copy()
# target layer update (i)
for i in range(len(rand)):
for i, _ in enumerate(rand):
teacher_index = rand[i]
new_weight = teacher_w[teacher_index, ...]
new_weight = new_weight[np.newaxis, ...]
......
......@@ -19,7 +19,7 @@
# ==================================================================================================
from abc import abstractmethod
from collections import Iterable
from collections.abc import Iterable
import torch
from torch import nn
......@@ -76,7 +76,6 @@ class StubLayer:
def build(self, shape):
'''build shape.
'''
pass
def set_weights(self, weights):
'''set weights.
......@@ -86,22 +85,18 @@ class StubLayer:
def import_weights(self, torch_layer):
'''import weights.
'''
pass
def import_weights_keras(self, keras_layer):
'''import weights from keras layer.
'''
pass
def export_weights(self, torch_layer):
'''export weights.
'''
pass
def export_weights_keras(self, keras_layer):
'''export weights to keras layer.
'''
pass
def get_weights(self):
'''get weights.
......@@ -122,7 +117,6 @@ class StubLayer:
def to_real_layer(self):
'''to real layer.
'''
pass
def __str__(self):
'''str() function to print.
......@@ -576,6 +570,7 @@ def to_real_keras_layer(layer):
return layers.Flatten()
if is_layer(layer, "GlobalAveragePooling"):
return layers.GlobalAveragePooling2D()
return None # note: this is not written by original author, feel free to modify if you think it's incorrect
def is_layer(layer, layer_type):
......@@ -608,6 +603,7 @@ def is_layer(layer, layer_type):
return isinstance(layer, (StubFlatten,))
elif layer_type == "GlobalAveragePooling":
return isinstance(layer, StubGlobalPooling)
return None # note: this is not written by original author, feel free to modify if you think it's incorrect
def layer_description_extractor(layer, node_to_id):
......@@ -664,7 +660,6 @@ def layer_description_extractor(layer, node_to_id):
def layer_description_builder(layer_information, id_to_node):
'''build layer from description.
'''
# pylint: disable=W0123
layer_type = layer_information[0]
layer_input_ids = layer_information[1]
......@@ -678,26 +673,26 @@ def layer_description_builder(layer_information, id_to_node):
filters = layer_information[4]
kernel_size = layer_information[5]
stride = layer_information[6]
return eval(layer_type)(
return globals()[layer_type](
input_channel, filters, kernel_size, stride, layer_input, layer_output
)
elif layer_type.startswith("StubDense"):
input_units = layer_information[3]
units = layer_information[4]
return eval(layer_type)(input_units, units, layer_input, layer_output)
return globals()[layer_type](input_units, units, layer_input, layer_output)
elif layer_type.startswith("StubBatchNormalization"):
num_features = layer_information[3]
return eval(layer_type)(num_features, layer_input, layer_output)
return globals()[layer_type](num_features, layer_input, layer_output)
elif layer_type.startswith("StubDropout"):
rate = layer_information[3]
return eval(layer_type)(rate, layer_input, layer_output)
return globals()[layer_type](rate, layer_input, layer_output)
elif layer_type.startswith("StubPooling"):
kernel_size = layer_information[3]
stride = layer_information[4]
padding = layer_information[5]
return eval(layer_type)(kernel_size, stride, padding, layer_input, layer_output)
return globals()[layer_type](kernel_size, stride, padding, layer_input, layer_output)
else:
return eval(layer_type)(layer_input, layer_output)
return globals()[layer_type](layer_input, layer_output)
def layer_width(layer):
......
......@@ -310,4 +310,3 @@ class NetworkMorphismTuner(Tuner):
def import_data(self, data):
pass
......@@ -19,8 +19,6 @@
# ==================================================================================================
# pylint: disable=wildcard-import
from ..env_vars import trial_env_vars
if trial_env_vars.NNI_PLATFORM is None:
......
......@@ -58,7 +58,7 @@ def request_next_parameter():
def get_next_parameter():
global _param_index
params_file_name = ''
if _multiphase and (_multiphase == 'true' or _multiphase == 'True'):
if _multiphase in ('true', 'True'):
params_file_name = ('parameter_{}.cfg'.format(_param_index), 'parameter.cfg')[_param_index == 0]
else:
if _param_index > 0:
......@@ -92,7 +92,7 @@ def send_metric(string):
file = open(_metric_file.name)
file.close()
else:
subprocess.run(['touch', _metric_file.name], check = True)
subprocess.run(['touch', _metric_file.name], check=True)
def get_experiment_id():
return trial_env_vars.NNI_EXP_ID
......
......@@ -21,7 +21,6 @@
import logging
import threading
from enum import Enum
from .common import multi_thread_enabled
class CommandType(Enum):
......@@ -49,7 +48,6 @@ try:
_out_file = open(4, 'wb')
except OSError:
_msg = 'IPC pipeline not exists, maybe you are importing tuner/assessor from trial code?'
import logging
logging.getLogger(__name__).warning(_msg)
......@@ -64,7 +62,7 @@ def send(command, data):
data = data.encode('utf8')
assert len(data) < 1000000, 'Command too long'
msg = b'%b%06d%b' % (command.value, len(data), data)
logging.getLogger(__name__).debug('Sending command, data: [%s]' % msg)
logging.getLogger(__name__).debug('Sending command, data: [%s]', msg)
_out_file.write(msg)
_out_file.flush()
finally:
......@@ -76,7 +74,7 @@ def receive():
Returns a tuple of command (CommandType) and payload (str)
"""
header = _in_file.read(8)
logging.getLogger(__name__).debug('Received command, header: [%s]' % header)
logging.getLogger(__name__).debug('Received command, header: [%s]', header)
if header is None or len(header) < 8:
# Pipe EOF encountered
logging.getLogger(__name__).debug('Pipe EOF encountered')
......@@ -85,5 +83,5 @@ def receive():
data = _in_file.read(length)
command = CommandType(header[:2])
data = data.decode('utf8')
logging.getLogger(__name__).debug('Received command, data: [%s]' % data)
logging.getLogger(__name__).debug('Received command, data: [%s]', data)
return command, data
......@@ -21,6 +21,7 @@
import os
class Recoverable:
def load_checkpoint(self):
pass
......
from .smac_tuner import SMACTuner
\ No newline at end of file
......@@ -18,18 +18,17 @@
# 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.
import os
import json
import numpy as np
def get_json_content(file_path):
"""Load json file content
Parameters
----------
file_path:
path to the file
Raises
------
TypeError
......@@ -42,10 +41,10 @@ def get_json_content(file_path):
print('Error: ', err)
return None
def generate_pcs(nni_search_space_content):
"""Generate the Parameter Configuration Space (PCS) which defines the
legal ranges of the parameters to be optimized and their default values.
Generally, the format is:
# parameter_name categorical {value_1, ..., value_N} [default value]
# parameter_name ordinal {value_1, ..., value_N} [default value]
......@@ -53,19 +52,15 @@ def generate_pcs(nni_search_space_content):
# parameter_name integer [min_value, max_value] [default value] log
# parameter_name real [min_value, max_value] [default value]
# parameter_name real [min_value, max_value] [default value] log
Reference: https://automl.github.io/SMAC3/stable/options.html
Parameters
----------
nni_search_space_content: search_space
The search space in this experiment in nni
Returns
-------
Parameter Configuration Space (PCS)
the legal ranges of the parameters to be optimized and their default values
Raises
------
RuntimeError
......@@ -73,41 +68,45 @@ def generate_pcs(nni_search_space_content):
"""
categorical_dict = {}
search_space = nni_search_space_content
def dump_categorical(fd, key, categories):
choice_len = len(categories)
if key in categorical_dict:
raise RuntimeError(
'%s has already existed, please make sure search space has no duplicate key.' % key)
categorical_dict[key] = search_space[key]['_value']
fd.write('%s categorical {%s} [0]\n' % (key, ','.join(map(str, range(choice_len)))))
with open('param_config_space.pcs', 'w') as pcs_fd:
if isinstance(search_space, dict):
for key in search_space.keys():
if isinstance(search_space[key], dict):
try:
if search_space[key]['_type'] == 'choice':
choice_len = len(search_space[key]['_value'])
pcs_fd.write('%s categorical {%s} [%s]\n' % (
key,
json.dumps(list(range(choice_len)))[1:-1],
json.dumps(0)))
if key in categorical_dict:
raise RuntimeError('%s has already existed, please make sure search space has no duplicate key.' % key)
categorical_dict[key] = search_space[key]['_value']
dump_categorical(pcs_fd, key, search_space[key]['_value'])
elif search_space[key]['_type'] == 'randint':
pcs_fd.write('%s integer [%d, %d] [%d]\n' % (
key,
search_space[key]['_value'][0],
search_space[key]['_value'][1] - 1,
search_space[key]['_value'][0]))
lower, upper = search_space[key]['_value']
if lower + 1 == upper:
dump_categorical(pcs_fd, key, [lower])
else:
pcs_fd.write('%s integer [%d, %d] [%d]\n' % (key, lower, upper - 1, lower))
elif search_space[key]['_type'] == 'uniform':
pcs_fd.write('%s real %s [%s]\n' % (
key,
json.dumps(search_space[key]['_value']),
json.dumps(search_space[key]['_value'][0])))
low, high = search_space[key]['_value']
if low == high:
dump_categorical(pcs_fd, key, [low])
else:
pcs_fd.write('%s real [%s, %s] [%s]\n' % (key, low, high, low))
elif search_space[key]['_type'] == 'loguniform':
# use np.round here to ensure that the rounded defaut value is in the range, which will be rounded in configure_space package
search_space[key]['_value'] = list(np.round(np.log(search_space[key]['_value']), 10))
pcs_fd.write('%s real %s [%s]\n' % (
key,
json.dumps(search_space[key]['_value']),
json.dumps(search_space[key]['_value'][0])))
# use np.round here to ensure that the rounded default value is in the range,
# which will be rounded in configure_space package
low, high = list(np.round(np.log(search_space[key]['_value']), 10))
if low == high:
dump_categorical(pcs_fd, key, [search_space[key]['_value'][0]])
else:
pcs_fd.write('%s real [%s, %s] [%s]\n' % (key, low, high, low))
elif search_space[key]['_type'] == 'quniform':
low, high, q = search_space[key]['_value'][0:3]
vals = np.clip(np.arange(np.round(low/q), np.round(high/q)+1) * q, low, high).tolist()
vals = np.clip(np.arange(np.round(low / q), np.round(high / q) + 1) * q, low, high).tolist()
pcs_fd.write('%s ordinal {%s} [%s]\n' % (
key,
json.dumps(vals)[1:-1],
......@@ -121,17 +120,15 @@ def generate_pcs(nni_search_space_content):
return categorical_dict
return None
def generate_scenario(ss_content):
"""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.
Reference: https://automl.github.io/SMAC3/stable/options.html
The format of the scenario file is one option per line:
OPTION1 = VALUE1
OPTION2 = VALUE2
...
Parameters
----------
abort_on_first_run_crash: bool
......@@ -194,7 +191,6 @@ def generate_scenario(ss_content):
wallclock_limit: int
Maximum amount of wallclock-time used for optimization. Default: inf.
Use default because this is controlled by nni
Returns
-------
Scenario:
......@@ -203,11 +199,12 @@ def generate_scenario(ss_content):
"""
with open('scenario.txt', 'w') as sce_fd:
sce_fd.write('deterministic = 0\n')
#sce_fd.write('output_dir = \n')
# sce_fd.write('output_dir = \n')
sce_fd.write('paramfile = param_config_space.pcs\n')
sce_fd.write('run_obj = quality\n')
return generate_pcs(ss_content)
if __name__ == '__main__':
generate_scenario('search_space.json')
......@@ -21,34 +21,33 @@
smac_tuner.py
"""
import sys
import logging
import numpy as np
import sys
from nni.tuner import Tuner
from nni.utils import OptimizeMode, extract_scalar_reward
import numpy as np
from smac.utils.io.cmd_reader import CMDReader
from smac.scenario.scenario import Scenario
from smac.facade.smac_facade import SMAC
from smac.facade.roar_facade import ROAR
from smac.facade.epils_facade import EPILS
from ConfigSpaceNNI import Configuration
from smac.facade.roar_facade import ROAR
from smac.facade.smac_facade import SMAC
from smac.scenario.scenario import Scenario
from smac.utils.io.cmd_reader import CMDReader
from .convert_ss_to_scenario import generate_scenario
from ConfigSpaceNNI import Configuration
from nni.tuner import Tuner
from nni.utils import OptimizeMode, extract_scalar_reward
from .convert_ss_to_scenario import generate_scenario
class SMACTuner(Tuner):
"""
Parameters
----------
optimize_mode: str
optimize mode, 'maximize' or 'minimize'
optimize mode, 'maximize' or 'minimize', by default 'maximize'
"""
def __init__(self, optimize_mode):
def __init__(self, optimize_mode="maximize"):
"""Constructor"""
self.logger = logging.getLogger(
self.__module__ + "." + self.__class__.__name__)
......@@ -64,7 +63,6 @@ class SMACTuner(Tuner):
def _main_cli(self):
"""Main function of SMAC for CLI interface
Returns
-------
instance
......@@ -77,11 +75,9 @@ class SMACTuner(Tuner):
root_logger = logging.getLogger()
root_logger.setLevel(args.verbose_level)
logger_handler = logging.StreamHandler(
stream=sys.stdout)
logger_handler = logging.StreamHandler(stream=sys.stdout)
if root_logger.level >= logging.INFO:
formatter = logging.Formatter(
"%(levelname)s:\t%(message)s")
formatter = logging.Formatter("%(levelname)s:\t%(message)s")
else:
formatter = logging.Formatter(
"%(asctime)s:%(levelname)s:%(name)s:%(message)s",
......@@ -130,15 +126,17 @@ class SMACTuner(Tuner):
return optimizer
def update_search_space(self, search_space):
"""TODO: this is urgly, 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.
"""
NOTE: updating search space is not supported.
Parameters
----------
search_space:
search_space: dict
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.
if not self.update_ss_done:
self.categorical_dict = generate_scenario(search_space)
if self.categorical_dict is None:
......@@ -152,7 +150,6 @@ class SMACTuner(Tuner):
def receive_trial_result(self, parameter_id, parameters, value, **kwargs):
"""receive_trial_result
Parameters
----------
parameter_id: int
......@@ -161,7 +158,6 @@ class SMACTuner(Tuner):
parameters
value:
value
Raises
------
RuntimeError
......@@ -179,17 +175,16 @@ class SMACTuner(Tuner):
else:
self.smbo_solver.nni_smac_receive_runs(self.total_data[parameter_id], reward)
def convert_loguniform_categorical(self, challenger_dict):
"""Convert the values of type `loguniform` back to their initial range
Also, we convert categorical:
categorical values in search space are changed to list of numbers before,
those original values will be changed back in this function
def param_postprocess(self, challenger_dict):
"""
Postprocessing for a set of parameter includes:
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,
those original values will be changed back in this function.
Parameters
----------
challenger_dict: dict
challenger dict
Returns
-------
dict
......@@ -210,12 +205,10 @@ class SMACTuner(Tuner):
def generate_parameters(self, parameter_id, **kwargs):
"""generate one instance of hyperparameters
Parameters
----------
parameter_id: int
parameter id
Returns
-------
list
......@@ -224,21 +217,19 @@ class SMACTuner(Tuner):
if self.first_one:
init_challenger = self.smbo_solver.nni_smac_start()
self.total_data[parameter_id] = init_challenger
return self.convert_loguniform_categorical(init_challenger.get_dictionary())
return self.param_postprocess(init_challenger.get_dictionary())
else:
challengers = self.smbo_solver.nni_smac_request_challengers()
for challenger in challengers:
self.total_data[parameter_id] = challenger
return self.convert_loguniform_categorical(challenger.get_dictionary())
return self.param_postprocess(challenger.get_dictionary())
def generate_multiple_parameters(self, parameter_id_list, **kwargs):
"""generate mutiple instances of hyperparameters
Parameters
----------
parameter_id_list: list
list of parameter id
Returns
-------
list
......@@ -249,7 +240,7 @@ class SMACTuner(Tuner):
for one_id in parameter_id_list:
init_challenger = self.smbo_solver.nni_smac_start()
self.total_data[one_id] = init_challenger
params.append(self.convert_loguniform_categorical(init_challenger.get_dictionary()))
params.append(self.param_postprocess(init_challenger.get_dictionary()))
else:
challengers = self.smbo_solver.nni_smac_request_challengers()
cnt = 0
......@@ -258,16 +249,17 @@ class SMACTuner(Tuner):
if cnt >= len(parameter_id_list):
break
self.total_data[parameter_id_list[cnt]] = challenger
params.append(self.convert_loguniform_categorical(challenger.get_dictionary()))
params.append(self.param_postprocess(challenger.get_dictionary()))
cnt += 1
return params
def import_data(self, data):
"""Import additional data for tuning
"""
Import additional data for tuning
Parameters
----------
data:
a list of dictionarys, each of which has at least two keys, 'parameter' and 'value'
data: list of dict
Each of which has at least two keys, `parameter` and `value`.
"""
_completed_num = 0
for trial_info in data:
......
......@@ -43,8 +43,6 @@ __all__ = [
]
# pylint: disable=unused-argument
if trial_env_vars.NNI_PLATFORM is None:
def choice(*options, name=None):
return param_exp.choice(options, np.random.RandomState())
......@@ -150,42 +148,16 @@ else:
optional_input_size: number of candidate inputs to be chosen
tf: tensorflow module
'''
args = (mutable_id, mutable_layer_id, funcs, funcs_args, fixed_inputs, optional_inputs, optional_input_size)
if mode == 'classic_mode':
return classic_mode(mutable_id,
mutable_layer_id,
funcs,
funcs_args,
fixed_inputs,
optional_inputs,
optional_input_size)
return classic_mode(*args)
assert tf is not None, 'Internal Error: Tensorflow should not be None in modes other than classic_mode'
if mode == 'enas_mode':
return enas_mode(mutable_id,
mutable_layer_id,
funcs,
funcs_args,
fixed_inputs,
optional_inputs,
optional_input_size,
tf)
return enas_mode(*args, tf)
if mode == 'oneshot_mode':
return oneshot_mode(mutable_id,
mutable_layer_id,
funcs,
funcs_args,
fixed_inputs,
optional_inputs,
optional_input_size,
tf)
return oneshot_mode(*args, tf)
if mode == 'darts_mode':
return darts_mode(mutable_id,
mutable_layer_id,
funcs,
funcs_args,
fixed_inputs,
optional_inputs,
optional_input_size,
tf)
return darts_mode(*args, tf)
raise RuntimeError('Unrecognized mode: %s' % mode)
def _get_param(key):
......
......@@ -96,7 +96,7 @@ def report_final_result(metric):
'parameter_id': _params['parameter_id'],
'trial_job_id': trial_env_vars.NNI_TRIAL_JOB_ID,
'type': 'FINAL',
'sequence': 0, # TODO: may be unnecessary
'sequence': 0,
'value': metric
})
platform.send_metric(metric)
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