Commit b2cdc30d authored by ShufanHuang's avatar ShufanHuang Committed by QuanluZhang
Browse files

Unified docstring (#753)

change Relative path to absolute path, unified docstring (include Hyperband), sdk_reference.rst
parent eb5463e6
...@@ -22,10 +22,16 @@ Tuner ...@@ -22,10 +22,16 @@ Tuner
.. autoclass:: nni.evolution_tuner.evolution_tuner.EvolutionTuner .. autoclass:: nni.evolution_tuner.evolution_tuner.EvolutionTuner
:members: :members:
.. autoclass:: nni.smac_tuner.smac_tuner.SMACTuner
:members:
.. autoclass:: nni.gridsearch_tuner.gridsearch_tuner.GridSearchTuner .. autoclass:: nni.gridsearch_tuner.gridsearch_tuner.GridSearchTuner
:members: :members:
.. autoclass:: nni.smac_tuner.smac_tuner.SMACTuner .. autoclass:: nni.networkmorphism_tuner.networkmorphism_tuner.NetworkMorphismTuner
:members:
.. autoclass:: nni.metis_tuner.metis_tuner.MetisTuner
:members: :members:
Assessor Assessor
...@@ -42,4 +48,5 @@ Assessor ...@@ -42,4 +48,5 @@ Assessor
Advisor Advisor
------------------------ ------------------------
.. autoclass:: nni.hyperband_advisor.hyperband_advisor.Hyperband .. autoclass:: nni.hyperband_advisor.hyperband_advisor.Hyperband
\ No newline at end of file :members:
\ No newline at end of file
...@@ -44,20 +44,18 @@ assessor: ...@@ -44,20 +44,18 @@ assessor:
builtinAssessorName: Curvefitting builtinAssessorName: Curvefitting
classArgs: classArgs:
# (required)The total number of epoch. # (required)The total number of epoch.
# We need to know the number of epoch to determine which point we need to predict. # We need to know the number of epoch to determine which point we need to predict.
epoch_num: 20 epoch_num: 20
# (optional) choice: maximize, minimize # (optional) choice: maximize, minimize
* The default value of optimize_mode is maximize * The default value of optimize_mode is maximize
optimize_mode: maximize optimize_mode: maximize
# Kindly reminds that if you choose minimize mode, please adjust the value of threshold >= 1.0 (e.g threshold=1.1) # (optional) In order to save our computing resource, we start to predict when we have more than only after receiving start_step number of reported intermediate results.
# (optional) A trial is determined to be stopped or not
# In order to save our computing resource, we start to predict when we have more than start_step(default=6) accuracy points.
# only after receiving start_step number of reported intermediate results.
* The default value of start_step is 6. * The default value of start_step is 6.
start_step: 6 start_step: 6
# (optional) The threshold that we decide to early stop the worse performance curve. # (optional) The threshold that we decide to early stop the worse performance curve.
# For example: if threshold = 0.95, optimize_mode = maximize, best performance in the history is 0.9, then we will stop the trial which predict value is lower than 0.95 * 0.9 = 0.855. # For example: if threshold = 0.95, optimize_mode = maximize, best performance in the history is 0.9, then we will stop the trial which predict value is lower than 0.95 * 0.9 = 0.855.
* The default value of threshold is 0.95. * The default value of threshold is 0.95.
# Kindly reminds that if you choose minimize mode, please adjust the value of threshold >= 1.0 (e.g threshold=1.1)
threshold: 0.95 threshold: 0.95
``` ```
......
...@@ -22,11 +22,21 @@ from .model_factory import CurveModel ...@@ -22,11 +22,21 @@ from .model_factory import CurveModel
logger = logging.getLogger('curvefitting_Assessor') logger = logging.getLogger('curvefitting_Assessor')
class CurvefittingAssessor(Assessor): class CurvefittingAssessor(Assessor):
''' """CurvefittingAssessor uses learning curve fitting algorithm to predict the learning curve performance in the future.
CurvefittingAssessor uses learning curve fitting algorithm to predict the learning curve performance in the future.
It stops a pending trial X at step S if the trial's forecast result at target step is convergence and lower than the It stops a pending trial X at step S if the trial's forecast result at target step is convergence and lower than the
best performance in the history. best performance in the history.
'''
Parameters
----------
epoch_num: int
The total number of epoch
optimize_mode: str
optimize mode, 'maximize' or 'minimize'
start_step: int
only after receiving start_step number of reported intermediate results
threshold: float
The threshold that we decide to early stop the worse performance curve.
"""
def __init__(self, epoch_num=20, optimize_mode='maximize', start_step=6, threshold=0.95): def __init__(self, epoch_num=20, optimize_mode='maximize', start_step=6, threshold=0.95):
if start_step <= 0: if start_step <= 0:
logger.warning('It\'s recommended to set start_step to a positive number') logger.warning('It\'s recommended to set start_step to a positive number')
...@@ -51,9 +61,15 @@ class CurvefittingAssessor(Assessor): ...@@ -51,9 +61,15 @@ class CurvefittingAssessor(Assessor):
logger.info('Successfully initials the curvefitting assessor') logger.info('Successfully initials the curvefitting assessor')
def trial_end(self, trial_job_id, success): def trial_end(self, trial_job_id, success):
''' """update the best performance of completed trial job
trial end: update the best performance of completed trial job
''' Parameters
----------
trial_job_id: int
trial job id
success: bool
True if succssfully finish the experiment, False otherwise
"""
if success: if success:
if self.set_best_performance: if self.set_best_performance:
self.completed_best_performance = max(self.completed_best_performance, self.trial_history[-1]) self.completed_best_performance = max(self.completed_best_performance, self.trial_history[-1])
...@@ -65,10 +81,25 @@ class CurvefittingAssessor(Assessor): ...@@ -65,10 +81,25 @@ class CurvefittingAssessor(Assessor):
logger.info('No need to update, trial job id: ', trial_job_id) logger.info('No need to update, trial job id: ', trial_job_id)
def assess_trial(self, trial_job_id, trial_history): def assess_trial(self, trial_job_id, trial_history):
''' """assess whether a trial should be early stop by curve fitting algorithm
assess whether a trial should be early stop by curve fitting algorithm
return AssessResult.Good or AssessResult.Bad Parameters
''' ----------
trial_job_id: int
trial job id
trial_history: list
The history performance matrix of each trial
Returns
-------
bool
AssessResult.Good or AssessResult.Bad
Raises
------
Exception
unrecognize exception in curvefitting_assessor
"""
self.trial_history = trial_history self.trial_history = trial_history
curr_step = len(trial_history) curr_step = len(trial_history)
if curr_step < self.start_step: if curr_step < self.start_step:
...@@ -94,4 +125,4 @@ class CurvefittingAssessor(Assessor): ...@@ -94,4 +125,4 @@ class CurvefittingAssessor(Assessor):
return AssessResult.Bad return AssessResult.Bad
except Exception as exception: except Exception as exception:
logger.exception('unrecognize exception in curvefitting_asserssor', exception) logger.exception('unrecognize exception in curvefitting_assessor', exception)
...@@ -25,7 +25,20 @@ curve_combination_models = ['vap', 'pow3', 'linear', 'logx_linear', 'dr_hill_zer ...@@ -25,7 +25,20 @@ curve_combination_models = ['vap', 'pow3', 'linear', 'logx_linear', 'dr_hill_zer
'exp4', 'ilog2', 'weibull', 'janoschek'] 'exp4', 'ilog2', 'weibull', 'janoschek']
def vap(x, a, b, c): def vap(x, a, b, c):
''' Vapor pressure model ''' """Vapor pressure model
Parameters
----------
x: int
a: float
b: float
c: float
Returns
-------
float
np.exp(a+b/x+c*np.log(x))
"""
return np.exp(a+b/x+c*np.log(x)) return np.exp(a+b/x+c*np.log(x))
all_models['vap'] = vap all_models['vap'] = vap
...@@ -33,6 +46,20 @@ model_para['vap'] = [-0.622028, -0.470050, 0.042322] ...@@ -33,6 +46,20 @@ model_para['vap'] = [-0.622028, -0.470050, 0.042322]
model_para_num['vap'] = 3 model_para_num['vap'] = 3
def pow3(x, c, a, alpha): def pow3(x, c, a, alpha):
"""pow3
Parameters
----------
x: int
c: float
a: float
alpha: float
Returns
-------
float
c - a * x**(-alpha)
"""
return c - a * x**(-alpha) return c - a * x**(-alpha)
all_models['pow3'] = pow3 all_models['pow3'] = pow3
...@@ -40,6 +67,19 @@ model_para['pow3'] = [0.84, 0.52, 0.01] ...@@ -40,6 +67,19 @@ model_para['pow3'] = [0.84, 0.52, 0.01]
model_para_num['pow3'] = 3 model_para_num['pow3'] = 3
def linear(x, a, b): def linear(x, a, b):
"""linear
Parameters
----------
x: int
a: float
b: float
Returns
-------
float
a*x + b
"""
return a*x + b return a*x + b
all_models['linear'] = linear all_models['linear'] = linear
...@@ -47,6 +87,19 @@ model_para['linear'] = [1., 0] ...@@ -47,6 +87,19 @@ model_para['linear'] = [1., 0]
model_para_num['linear'] = 2 model_para_num['linear'] = 2
def logx_linear(x, a, b): def logx_linear(x, a, b):
"""logx linear
Parameters
----------
x: int
a: float
b: float
Returns
-------
float
a * np.log(x) + b
"""
x = np.log(x) x = np.log(x)
return a*x + b return a*x + b
...@@ -55,6 +108,20 @@ model_para['logx_linear'] = [0.378106, 0.046506] ...@@ -55,6 +108,20 @@ model_para['logx_linear'] = [0.378106, 0.046506]
model_para_num['logx_linear'] = 2 model_para_num['logx_linear'] = 2
def dr_hill_zero_background(x, theta, eta, kappa): def dr_hill_zero_background(x, theta, eta, kappa):
"""dr hill zero background
Parameters
----------
x: int
theta: float
eta: float
kappa: float
Returns
-------
float
(theta* x**eta) / (kappa**eta + x**eta)
"""
return (theta* x**eta) / (kappa**eta + x**eta) return (theta* x**eta) / (kappa**eta + x**eta)
all_models['dr_hill_zero_background'] = dr_hill_zero_background all_models['dr_hill_zero_background'] = dr_hill_zero_background
...@@ -62,7 +129,20 @@ model_para['dr_hill_zero_background'] = [0.772320, 0.586449, 2.460843] ...@@ -62,7 +129,20 @@ model_para['dr_hill_zero_background'] = [0.772320, 0.586449, 2.460843]
model_para_num['dr_hill_zero_background'] = 3 model_para_num['dr_hill_zero_background'] = 3
def log_power(x, a, b, c): def log_power(x, a, b, c):
#logistic power """"logistic power
Parameters
----------
x: int
a: float
b: float
c: float
Returns
-------
float
a/(1.+(x/np.exp(b))**c)
"""
return a/(1.+(x/np.exp(b))**c) return a/(1.+(x/np.exp(b))**c)
all_models['log_power'] = log_power all_models['log_power'] = log_power
...@@ -70,6 +150,21 @@ model_para['log_power'] = [0.77, 2.98, -0.51] ...@@ -70,6 +150,21 @@ model_para['log_power'] = [0.77, 2.98, -0.51]
model_para_num['log_power'] = 3 model_para_num['log_power'] = 3
def pow4(x, alpha, a, b, c): def pow4(x, alpha, a, b, c):
"""pow4
Parameters
----------
x: int
alpha: float
a: float
b: float
c: float
Returns
-------
float
c - (a*x+b)**-alpha
"""
return c - (a*x+b)**-alpha return c - (a*x+b)**-alpha
all_models['pow4'] = pow4 all_models['pow4'] = pow4
...@@ -77,10 +172,22 @@ model_para['pow4'] = [0.1, 200, 0., 0.8] ...@@ -77,10 +172,22 @@ model_para['pow4'] = [0.1, 200, 0., 0.8]
model_para_num['pow4'] = 4 model_para_num['pow4'] = 4
def mmf(x, alpha, beta, kappa, delta): def mmf(x, alpha, beta, kappa, delta):
''' """Morgan-Mercer-Flodin
Morgan-Mercer-Flodin
http://www.pisces-conservation.com/growthhelp/index.html?morgan_mercer_floden.htm http://www.pisces-conservation.com/growthhelp/index.html?morgan_mercer_floden.htm
'''
Parameters
----------
x: int
alpha: float
beta: float
kappa: float
delta: float
Returns
-------
float
alpha - (alpha - beta) / (1. + (kappa * x)**delta)
"""
return alpha - (alpha - beta) / (1. + (kappa * x)**delta) return alpha - (alpha - beta) / (1. + (kappa * x)**delta)
all_models['mmf'] = mmf all_models['mmf'] = mmf
...@@ -88,6 +195,21 @@ model_para['mmf'] = [0.7, 0.1, 0.01, 5] ...@@ -88,6 +195,21 @@ model_para['mmf'] = [0.7, 0.1, 0.01, 5]
model_para_num['mmf'] = 4 model_para_num['mmf'] = 4
def exp4(x, c, a, b, alpha): def exp4(x, c, a, b, alpha):
"""exp4
Parameters
----------
x: int
c: float
a: float
b: float
alpha: float
Returns
-------
float
c - np.exp(-a*(x**alpha)+b)
"""
return c - np.exp(-a*(x**alpha)+b) return c - np.exp(-a*(x**alpha)+b)
all_models['exp4'] = exp4 all_models['exp4'] = exp4
...@@ -95,6 +217,19 @@ model_para['exp4'] = [0.7, 0.8, -0.8, 0.3] ...@@ -95,6 +217,19 @@ model_para['exp4'] = [0.7, 0.8, -0.8, 0.3]
model_para_num['exp4'] = 4 model_para_num['exp4'] = 4
def ilog2(x, c, a): def ilog2(x, c, a):
"""ilog2
Parameters
----------
x: int
c: float
a: float
Returns
-------
float
c - a / np.log(x)
"""
return c - a / np.log(x) return c - a / np.log(x)
all_models['ilog2'] = ilog2 all_models['ilog2'] = ilog2
...@@ -102,10 +237,22 @@ model_para['ilog2'] = [0.78, 0.43] ...@@ -102,10 +237,22 @@ model_para['ilog2'] = [0.78, 0.43]
model_para_num['ilog2'] = 2 model_para_num['ilog2'] = 2
def weibull(x, alpha, beta, kappa, delta): def weibull(x, alpha, beta, kappa, delta):
''' """Weibull model
Weibull model
http://www.pisces-conservation.com/growthhelp/index.html?morgan_mercer_floden.htm http://www.pisces-conservation.com/growthhelp/index.html?morgan_mercer_floden.htm
'''
Parameters
----------
x: int
alpha: float
beta: float
kappa: float
delta: float
Returns
-------
float
alpha - (alpha - beta) * np.exp(-(kappa * x)**delta)
"""
return alpha - (alpha - beta) * np.exp(-(kappa * x)**delta) return alpha - (alpha - beta) * np.exp(-(kappa * x)**delta)
all_models['weibull'] = weibull all_models['weibull'] = weibull
...@@ -113,7 +260,21 @@ model_para['weibull'] = [0.7, 0.1, 0.01, 1] ...@@ -113,7 +260,21 @@ model_para['weibull'] = [0.7, 0.1, 0.01, 1]
model_para_num['weibull'] = 4 model_para_num['weibull'] = 4
def janoschek(x, a, beta, k, delta): def janoschek(x, a, beta, k, delta):
'''http://www.pisces-conservation.com/growthhelp/janoschek.htm''' """http://www.pisces-conservation.com/growthhelp/janoschek.htm
Parameters
----------
x: int
a: float
beta: float
k: float
delta: float
Returns
-------
float
a - (a - beta) * np.exp(-k*x**delta)
"""
return a - (a - beta) * np.exp(-k*x**delta) return a - (a - beta) * np.exp(-k*x**delta)
all_models['janoschek'] = janoschek all_models['janoschek'] = janoschek
......
...@@ -36,6 +36,15 @@ LEAST_FITTED_FUNCTION = 4 ...@@ -36,6 +36,15 @@ LEAST_FITTED_FUNCTION = 4
logger = logging.getLogger('curvefitting_Assessor') logger = logging.getLogger('curvefitting_Assessor')
class CurveModel(object): class CurveModel(object):
"""Build a Curve Model to predict the performance
Algorithm: https://github.com/Microsoft/nni/blob/master/src/sdk/pynni/nni/curvefitting_assessor/README.md
Parameters
----------
target_pos: int
The point we need to predict
"""
def __init__(self, target_pos): def __init__(self, target_pos):
self.target_pos = target_pos self.target_pos = target_pos
self.trial_history = [] self.trial_history = []
...@@ -45,7 +54,12 @@ class CurveModel(object): ...@@ -45,7 +54,12 @@ class CurveModel(object):
self.weight_samples = [] self.weight_samples = []
def fit_theta(self): def fit_theta(self):
'''use least squares to fit all default curves parameter seperately''' """use least squares to fit all default curves parameter seperately
Returns
-------
None
"""
x = range(1, self.point_num + 1) x = range(1, self.point_num + 1)
y = self.trial_history y = self.trial_history
for i in range(NUM_OF_FUNCTIONS): for i in range(NUM_OF_FUNCTIONS):
...@@ -73,7 +87,12 @@ class CurveModel(object): ...@@ -73,7 +87,12 @@ class CurveModel(object):
logger.critical("Exceptions in fit_theta:", exception) logger.critical("Exceptions in fit_theta:", exception)
def filter_curve(self): def filter_curve(self):
'''filter the poor performing curve''' """filter the poor performing curve
Returns
-------
None
"""
avg = np.sum(self.trial_history) / self.point_num avg = np.sum(self.trial_history) / self.point_num
standard = avg * avg * self.point_num standard = avg * avg * self.point_num
predict_data = [] predict_data = []
...@@ -97,7 +116,20 @@ class CurveModel(object): ...@@ -97,7 +116,20 @@ class CurveModel(object):
logger.info('List of effective model: ', self.effective_model) logger.info('List of effective model: ', self.effective_model)
def predict_y(self, model, pos): def predict_y(self, model, pos):
'''return the predict y of 'model' when epoch = pos''' """return the predict y of 'model' when epoch = pos
Parameters
----------
model: string
name of the curve function model
pos: int
the epoch number of the position you want to predict
Returns
-------
int:
The expected matrix at pos
"""
if model_para_num[model] == 2: if model_para_num[model] == 2:
y = all_models[model](pos, model_para[model][0], model_para[model][1]) y = all_models[model](pos, model_para[model][0], model_para[model][1])
elif model_para_num[model] == 3: elif model_para_num[model] == 3:
...@@ -107,7 +139,20 @@ class CurveModel(object): ...@@ -107,7 +139,20 @@ class CurveModel(object):
return y return y
def f_comb(self, pos, sample): def f_comb(self, pos, sample):
'''return the value of the f_comb when epoch = pos''' """return the value of the f_comb when epoch = pos
Parameters
----------
pos: int
the epoch number of the position you want to predict
sample: list
sample is a (1 * NUM_OF_FUNCTIONS) matrix, representing{w1, w2, ... wk}
Returns
-------
int
The expected matrix at pos with all the active function's prediction
"""
ret = 0 ret = 0
for i in range(self.effective_model_num): for i in range(self.effective_model_num):
model = self.effective_model[i] model = self.effective_model[i]
...@@ -116,7 +161,19 @@ class CurveModel(object): ...@@ -116,7 +161,19 @@ class CurveModel(object):
return ret return ret
def normalize_weight(self, samples): def normalize_weight(self, samples):
'''normalize weight ''' """normalize weight
Parameters
----------
samples: list
a collection of sample, it's a (NUM_OF_INSTANCE * NUM_OF_FUNCTIONS) matrix,
representing{{w11, w12, ..., w1k}, {w21, w22, ... w2k}, ...{wk1, wk2,..., wkk}}
Returns
-------
list
samples after normalize weight
"""
for i in range(NUM_OF_INSTANCE): for i in range(NUM_OF_INSTANCE):
total = 0 total = 0
for j in range(self.effective_model_num): for j in range(self.effective_model_num):
...@@ -126,7 +183,18 @@ class CurveModel(object): ...@@ -126,7 +183,18 @@ class CurveModel(object):
return samples return samples
def sigma_sq(self, sample): def sigma_sq(self, sample):
'''returns the value of sigma square, given the weight's sample''' """returns the value of sigma square, given the weight's sample
Parameters
----------
sample: list
sample is a (1 * NUM_OF_FUNCTIONS) matrix, representing{w1, w2, ... wk}
Returns
-------
float
the value of sigma square, given the weight's sample
"""
ret = 0 ret = 0
for i in range(1, self.point_num + 1): for i in range(1, self.point_num + 1):
temp = self.trial_history[i - 1] - self.f_comb(i, sample) temp = self.trial_history[i - 1] - self.f_comb(i, sample)
...@@ -134,13 +202,37 @@ class CurveModel(object): ...@@ -134,13 +202,37 @@ class CurveModel(object):
return 1.0 * ret / self.point_num return 1.0 * ret / self.point_num
def normal_distribution(self, pos, sample): def normal_distribution(self, pos, sample):
'''returns the value of normal distribution, given the weight's sample and target position''' """returns the value of normal distribution, given the weight's sample and target position
Parameters
----------
pos: int
the epoch number of the position you want to predict
sample: list
sample is a (1 * NUM_OF_FUNCTIONS) matrix, representing{w1, w2, ... wk}
Returns
-------
float
the value of normal distribution
"""
curr_sigma_sq = self.sigma_sq(sample) curr_sigma_sq = self.sigma_sq(sample)
delta = self.trial_history[pos - 1] - self.f_comb(pos, sample) delta = self.trial_history[pos - 1] - self.f_comb(pos, sample)
return np.exp(np.square(delta) / (-2.0 * curr_sigma_sq)) / np.sqrt(2 * np.pi * np.sqrt(curr_sigma_sq)) return np.exp(np.square(delta) / (-2.0 * curr_sigma_sq)) / np.sqrt(2 * np.pi * np.sqrt(curr_sigma_sq))
def likelihood(self, samples): def likelihood(self, samples):
'''likelihood''' """likelihood
Parameters
----------
sample: list
sample is a (1 * NUM_OF_FUNCTIONS) matrix, representing{w1, w2, ... wk}
Returns
-------
float
likelihood
"""
ret = np.ones(NUM_OF_INSTANCE) ret = np.ones(NUM_OF_INSTANCE)
for i in range(NUM_OF_INSTANCE): for i in range(NUM_OF_INSTANCE):
for j in range(1, self.point_num + 1): for j in range(1, self.point_num + 1):
...@@ -148,7 +240,19 @@ class CurveModel(object): ...@@ -148,7 +240,19 @@ class CurveModel(object):
return ret return ret
def prior(self, samples): def prior(self, samples):
'''priori distribution''' """priori distribution
Parameters
----------
samples: list
a collection of sample, it's a (NUM_OF_INSTANCE * NUM_OF_FUNCTIONS) matrix,
representing{{w11, w12, ..., w1k}, {w21, w22, ... w2k}, ...{wk1, wk2,..., wkk}}
Returns
-------
float
priori distribution
"""
ret = np.ones(NUM_OF_INSTANCE) ret = np.ones(NUM_OF_INSTANCE)
for i in range(NUM_OF_INSTANCE): for i in range(NUM_OF_INSTANCE):
for j in range(self.effective_model_num): for j in range(self.effective_model_num):
...@@ -159,7 +263,19 @@ class CurveModel(object): ...@@ -159,7 +263,19 @@ class CurveModel(object):
return ret return ret
def target_distribution(self, samples): def target_distribution(self, samples):
'''posterior probability''' """posterior probability
Parameters
----------
samples: list
a collection of sample, it's a (NUM_OF_INSTANCE * NUM_OF_FUNCTIONS) matrix,
representing{{w11, w12, ..., w1k}, {w21, w22, ... w2k}, ...{wk1, wk2,..., wkk}}
Returns
-------
float
posterior probability
"""
curr_likelihood = self.likelihood(samples) curr_likelihood = self.likelihood(samples)
curr_prior = self.prior(samples) curr_prior = self.prior(samples)
ret = np.ones(NUM_OF_INSTANCE) ret = np.ones(NUM_OF_INSTANCE)
...@@ -168,8 +284,7 @@ class CurveModel(object): ...@@ -168,8 +284,7 @@ class CurveModel(object):
return ret return ret
def mcmc_sampling(self): def mcmc_sampling(self):
''' """Adjust the weight of each function using mcmc sampling.
Adjust the weight of each function using mcmc sampling.
The initial value of each weight is evenly distribute. The initial value of each weight is evenly distribute.
Brief introduction: Brief introduction:
(1)Definition of sample: (1)Definition of sample:
...@@ -181,7 +296,11 @@ class CurveModel(object): ...@@ -181,7 +296,11 @@ class CurveModel(object):
Model is the function we chose right now. Such as: 'wap', 'weibull'. Model is the function we chose right now. Such as: 'wap', 'weibull'.
(4)Definition of pos: (4)Definition of pos:
Pos is the position we want to predict, corresponds to the value of epoch. Pos is the position we want to predict, corresponds to the value of epoch.
'''
Returns
-------
None
"""
init_weight = np.ones((self.effective_model_num), dtype=np.float) / self.effective_model_num init_weight = np.ones((self.effective_model_num), dtype=np.float) / self.effective_model_num
self.weight_samples = np.broadcast_to(init_weight, (NUM_OF_INSTANCE, self.effective_model_num)) self.weight_samples = np.broadcast_to(init_weight, (NUM_OF_INSTANCE, self.effective_model_num))
for i in range(NUM_OF_SIMULATION_TIME): for i in range(NUM_OF_SIMULATION_TIME):
...@@ -199,7 +318,18 @@ class CurveModel(object): ...@@ -199,7 +318,18 @@ class CurveModel(object):
self.weight_samples = new_values self.weight_samples = new_values
def predict(self, trial_history): def predict(self, trial_history):
'''predict the value of target position''' """predict the value of target position
Parameters
----------
trial_history: list
The history performance matrix of each trial.
Returns
-------
float
expected final result performance of this hyperparameter config
"""
self.trial_history = trial_history self.trial_history = trial_history
self.point_num = len(trial_history) self.point_num = len(trial_history)
self.fit_theta() self.fit_theta()
......
...@@ -17,9 +17,9 @@ ...@@ -17,9 +17,9 @@
# 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.
''' """
hyperband_tuner.py hyperband_advisor.py
''' """
from enum import Enum, unique from enum import Enum, unique
import sys import sys
...@@ -41,24 +41,39 @@ _KEY = 'STEPS' ...@@ -41,24 +41,39 @@ _KEY = 'STEPS'
@unique @unique
class OptimizeMode(Enum): class OptimizeMode(Enum):
''' """Oprimize Mode class"""
Oprimize Mode class
'''
Minimize = 'minimize' Minimize = 'minimize'
Maximize = 'maximize' Maximize = 'maximize'
def create_parameter_id(): def create_parameter_id():
''' """Create an id
Create an id
''' Returns
-------
int
parameter id
"""
global _next_parameter_id # pylint: disable=global-statement global _next_parameter_id # pylint: disable=global-statement
_next_parameter_id += 1 _next_parameter_id += 1
return _next_parameter_id - 1 return _next_parameter_id - 1
def create_bracket_parameter_id(brackets_id, brackets_curr_decay, increased_id=-1): def create_bracket_parameter_id(brackets_id, brackets_curr_decay, increased_id=-1):
''' """Create a full id for a specific bracket's hyperparameter configuration
Create a full id for a specific bracket's hyperparameter configuration
''' Parameters
----------
brackets_id: int
brackets id
brackets_curr_decay:
brackets curr decay
increased_id: int
increased id
Returns
-------
int
params id
"""
if increased_id == -1: if increased_id == -1:
increased_id = str(create_parameter_id()) increased_id = str(create_parameter_id())
params_id = '_'.join([str(brackets_id), params_id = '_'.join([str(brackets_id),
...@@ -67,11 +82,20 @@ def create_bracket_parameter_id(brackets_id, brackets_curr_decay, increased_id=- ...@@ -67,11 +82,20 @@ def create_bracket_parameter_id(brackets_id, brackets_curr_decay, increased_id=-
return params_id return params_id
def json2paramater(ss_spec, random_state): def json2paramater(ss_spec, random_state):
''' """Randomly generate values for hyperparameters from hyperparameter space i.e., x.
Randomly generate values for hyperparameters from hyperparameter space i.e., x.
ss_spec: hyperparameter space Parameters
random_state: random operator to generate random values ----------
''' ss_spec:
hyperparameter space
random_state:
random operator to generate random values
Returns
-------
Parameter:
Parameters in this experiment
"""
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']
...@@ -95,9 +119,24 @@ def json2paramater(ss_spec, random_state): ...@@ -95,9 +119,24 @@ def json2paramater(ss_spec, random_state):
return chosen_params return chosen_params
class Bracket(): class Bracket():
''' """A bracket in Hyperband, all the information of a bracket is managed by an instance of this class
A bracket in Hyperband, all the information of a bracket is managed by an instance of this class
''' Parameters
----------
s: int
The current SH iteration index.
s_max: int
total number of SH iterations
eta: float
In each iteration, a complete run of sequential halving is executed. In it,
after evaluating each configuration on the same subset size, only a fraction of
1/eta of them 'advances' to the next round.
R:
the budget associated with each stage
optimize_mode: str
optimize mode, 'maximize' or 'minimize'
"""
def __init__(self, s, s_max, eta, R, optimize_mode): def __init__(self, s, s_max, eta, R, optimize_mode):
self.bracket_id = s self.bracket_id = s
self.s_max = s_max self.s_max = s_max
...@@ -113,33 +152,37 @@ class Bracket(): ...@@ -113,33 +152,37 @@ class Bracket():
self.no_more_trial = False self.no_more_trial = False
def is_completed(self): def is_completed(self):
''' """check whether this bracket has sent out all the hyperparameter configurations"""
check whether this bracket has sent out all the hyperparameter configurations
'''
return self.no_more_trial return self.no_more_trial
def get_n_r(self): def get_n_r(self):
''' """return the values of n and r for the next round"""
return the values of n and r for the next round
'''
return math.floor(self.n / self.eta**self.i), self.r * self.eta**self.i return math.floor(self.n / self.eta**self.i), self.r * self.eta**self.i
def increase_i(self): def increase_i(self):
''' """i means the ith round. Increase i by 1"""
i means the ith round. Increase i by 1
'''
self.i += 1 self.i += 1
if self.i > self.bracket_id: if self.i > self.bracket_id:
self.no_more_trial = True self.no_more_trial = True
def set_config_perf(self, i, parameter_id, seq, value): def set_config_perf(self, i, parameter_id, seq, value):
''' """update trial's latest result with its sequence number, e.g., epoch number or batch number
update trial's latest result with its sequence number, e.g., epoch number or batch number
i: the ith round Parameters
parameter_id: the id of the trial/parameter ----------
seq: sequence number, e.g., epoch number or batch number i: int
value: latest result with sequence number seq the ith round
''' parameter_id: int
the id of the trial/parameter
seq: int
sequence number, e.g., epoch number or batch number
value: int
latest result with sequence number seq
Returns
-------
None
"""
if parameter_id in self.configs_perf[i]: if parameter_id in self.configs_perf[i]:
if self.configs_perf[i][parameter_id][0] < seq: if self.configs_perf[i][parameter_id][0] < seq:
self.configs_perf[i][parameter_id] = [seq, value] self.configs_perf[i][parameter_id] = [seq, value]
...@@ -148,10 +191,14 @@ class Bracket(): ...@@ -148,10 +191,14 @@ class Bracket():
def inform_trial_end(self, i): def inform_trial_end(self, i):
''' """If the trial is finished and the corresponding round (i.e., i) has all its trials finished,
If the trial is finished and the corresponding round (i.e., i) has all its trials finished,
it will choose the top k trials for the next round (i.e., i+1) it will choose the top k trials for the next round (i.e., i+1)
'''
Parameters
----------
i: int
the ith round
"""
global _KEY # pylint: disable=global-statement global _KEY # pylint: disable=global-statement
self.num_finished_configs[i] += 1 self.num_finished_configs[i] += 1
_logger.debug('bracket id: %d, round: %d %d, finished: %d, all: %d', self.bracket_id, self.i, i, self.num_finished_configs[i], self.num_configs_to_run[i]) _logger.debug('bracket id: %d, round: %d %d, finished: %d, all: %d', self.bracket_id, self.i, i, self.num_finished_configs[i], self.num_configs_to_run[i])
...@@ -181,10 +228,18 @@ class Bracket(): ...@@ -181,10 +228,18 @@ class Bracket():
return None return None
def get_hyperparameter_configurations(self, num, r, searchspace_json, random_state): # pylint: disable=invalid-name def get_hyperparameter_configurations(self, num, r, searchspace_json, random_state): # pylint: disable=invalid-name
''' """Randomly generate num hyperparameter configurations from search space
Randomly generate num hyperparameter configurations from search space
num: the number of hyperparameter configurations Parameters
''' ----------
num: int
the number of hyperparameter configurations
Returns
-------
list
a list of hyperparameter configurations. Format: [[key1, value1], [key2, value2], ...]
"""
global _KEY # pylint: disable=global-statement global _KEY # pylint: disable=global-statement
assert self.i == 0 assert self.i == 0
hyperparameter_configs = dict() hyperparameter_configs = dict()
...@@ -197,11 +252,15 @@ class Bracket(): ...@@ -197,11 +252,15 @@ class Bracket():
return [[key, value] for key, value in hyperparameter_configs.items()] return [[key, value] for key, value in hyperparameter_configs.items()]
def _record_hyper_configs(self, hyper_configs): def _record_hyper_configs(self, hyper_configs):
''' """after generating one round of hyperconfigs, this function records the generated hyperconfigs,
after generating one round of hyperconfigs, this function records the generated hyperconfigs,
creates a dict to record the performance when those hyperconifgs are running, set the number of finished configs creates a dict to record the performance when those hyperconifgs are running, set the number of finished configs
in this round to be 0, and increase the round number. in this round to be 0, and increase the round number.
'''
Parameters
----------
hyper_configs: list
the generated hyperconfigs
"""
self.hyper_configs.append(hyper_configs) self.hyper_configs.append(hyper_configs)
self.configs_perf.append(dict()) self.configs_perf.append(dict())
self.num_finished_configs.append(0) self.num_finished_configs.append(0)
...@@ -209,6 +268,13 @@ class Bracket(): ...@@ -209,6 +268,13 @@ class Bracket():
self.increase_i() self.increase_i()
def extract_scalar_reward(value, scalar_key='default'): def extract_scalar_reward(value, scalar_key='default'):
"""
Raises
------
RuntimeError
Incorrect final result: the final result should be float/int,
or a dict which has a key named "default" whose value is float/int.
"""
if isinstance(value, float) or isinstance(value, int): if isinstance(value, float) or isinstance(value, int):
reward = value reward = value
elif isinstance(value, dict) and scalar_key in value and isinstance(value[scalar_key], (float, int)): elif isinstance(value, dict) and scalar_key in value and isinstance(value[scalar_key], (float, int)):
...@@ -218,18 +284,21 @@ def extract_scalar_reward(value, scalar_key='default'): ...@@ -218,18 +284,21 @@ def extract_scalar_reward(value, scalar_key='default'):
return reward return reward
class Hyperband(MsgDispatcherBase): class Hyperband(MsgDispatcherBase):
''' """Hyperband inherit from MsgDispatcherBase rather than Tuner, because it integrates both tuner's functions and assessor's functions.
Hyperband inherit from MsgDispatcherBase rather than Tuner,
because it integrates both tuner's functions and assessor's functions.
This is an implementation that could fully leverage available resources, i.e., high parallelism. This is an implementation that could fully leverage available resources, i.e., high parallelism.
A single execution of Hyperband takes a finite budget of (s_max + 1)B. A single execution of Hyperband takes a finite budget of (s_max + 1)B.
'''
Parameters
----------
R: int
the maximum amount of resource that can be allocated to a single configuration
eta: int
the variable that controls the proportion of configurations discarded in each round of SuccessiveHalving
optimize_mode: str
optimize mode, 'maximize' or 'minimize'
"""
def __init__(self, R, eta=3, optimize_mode='maximize'): def __init__(self, R, eta=3, optimize_mode='maximize'):
''' """B = (s_max + 1)R"""
R: the maximum amount of resource that can be allocated to a single configuration
eta: the variable that controls the proportion of configurations discarded in each round of SuccessiveHalving
B = (s_max + 1)R
'''
super() super()
self.R = R # pylint: disable=invalid-name self.R = R # pylint: disable=invalid-name
self.eta = eta self.eta = eta
...@@ -254,26 +323,31 @@ class Hyperband(MsgDispatcherBase): ...@@ -254,26 +323,31 @@ class Hyperband(MsgDispatcherBase):
pass pass
def handle_initialize(self, data): def handle_initialize(self, data):
''' """data is search space
data is search space
''' Parameters
----------
data: int
number of trial jobs
"""
self.handle_update_search_space(data) self.handle_update_search_space(data)
send(CommandType.Initialized, '') send(CommandType.Initialized, '')
return True return True
def handle_request_trial_jobs(self, data): def handle_request_trial_jobs(self, data):
''' """
data: number of trial jobs Parameters
''' ----------
data: int
number of trial jobs
"""
for _ in range(data): for _ in range(data):
self._request_one_trial_job() self._request_one_trial_job()
return True return True
def _request_one_trial_job(self): def _request_one_trial_job(self):
''' """get one trial job, i.e., one hyperparameter configuration."""
get one trial job, i.e., one hyperparameter configuration.
'''
if not self.generated_hyper_configs: if not self.generated_hyper_configs:
if self.curr_s < 0: if self.curr_s < 0:
# have tried all configurations # have tried all configurations
...@@ -308,21 +382,28 @@ class Hyperband(MsgDispatcherBase): ...@@ -308,21 +382,28 @@ class Hyperband(MsgDispatcherBase):
return True return True
def handle_update_search_space(self, data): def handle_update_search_space(self, data):
''' """data: JSON object, which is search space
data: JSON object, which is search space
''' Parameters
----------
data: int
number of trial jobs
"""
self.searchspace_json = data self.searchspace_json = data
self.random_state = np.random.RandomState() self.random_state = np.random.RandomState()
return True return True
def handle_trial_end(self, data): def handle_trial_end(self, data):
''' """
data: it has three keys: trial_job_id, event, hyper_params Parameters
----------
data: dict()
it has three keys: trial_job_id, event, hyper_params
trial_job_id: the id generated by training service trial_job_id: the id generated by training service
event: the job's state event: the job's state
hyper_params: the hyperparameters (a string) generated and returned by tuner hyper_params: the hyperparameters (a string) generated and returned by tuner
''' """
hyper_params = json_tricks.loads(data['hyper_params']) hyper_params = json_tricks.loads(data['hyper_params'])
bracket_id, i, _ = hyper_params['parameter_id'].split('_') bracket_id, i, _ = hyper_params['parameter_id'].split('_')
hyper_configs = self.brackets[int(bracket_id)].inform_trial_end(int(i)) hyper_configs = self.brackets[int(bracket_id)].inform_trial_end(int(i))
...@@ -344,9 +425,17 @@ class Hyperband(MsgDispatcherBase): ...@@ -344,9 +425,17 @@ class Hyperband(MsgDispatcherBase):
return True return True
def handle_report_metric_data(self, data): def handle_report_metric_data(self, data):
''' """
data: it is an object which has keys 'parameter_id', 'value', 'trial_job_id', 'type', 'sequence'. Parameters
''' ----------
data:
it is an object which has keys 'parameter_id', 'value', 'trial_job_id', 'type', 'sequence'.
Raises
------
ValueError
Data type not supported
"""
value = extract_scalar_reward(data['value']) value = extract_scalar_reward(data['value'])
bracket_id, i, _ = data['parameter_id'].split('_') bracket_id, i, _ = data['parameter_id'].split('_')
bracket_id = int(bracket_id) bracket_id = int(bracket_id)
......
...@@ -21,11 +21,17 @@ from nni.assessor import Assessor, AssessResult ...@@ -21,11 +21,17 @@ from nni.assessor import Assessor, AssessResult
logger = logging.getLogger('medianstop_Assessor') logger = logging.getLogger('medianstop_Assessor')
class MedianstopAssessor(Assessor): class MedianstopAssessor(Assessor):
''' """MedianstopAssessor is The median stopping rule stops a pending trial X at step S
MedianstopAssessor is The median stopping rule stops a pending trial X at step S
if the trial’s best objective value by step S is strictly worse than the median value if the trial’s best objective value by step S is strictly worse than the median value
of the running averages of all completed trials’ objectives reported up to step S of the running averages of all completed trials’ objectives reported up to step S
'''
Parameters
----------
optimize_mode: str
optimize mode, 'maximize' or 'minimize'
start_step: int
only after receiving start_step number of reported intermediate results
"""
def __init__(self, optimize_mode='maximize', start_step=0): def __init__(self, optimize_mode='maximize', start_step=0):
self.start_step = start_step self.start_step = start_step
self.running_history = dict() self.running_history = dict()
...@@ -39,14 +45,29 @@ class MedianstopAssessor(Assessor): ...@@ -39,14 +45,29 @@ class MedianstopAssessor(Assessor):
logger.warning('unrecognized optimize_mode', optimize_mode) logger.warning('unrecognized optimize_mode', optimize_mode)
def _update_data(self, trial_job_id, trial_history): def _update_data(self, trial_job_id, trial_history):
"""update data
Parameters
----------
trial_job_id: int
trial job id
trial_history: list
The history performance matrix of each trial
"""
if trial_job_id not in self.running_history: if trial_job_id not in self.running_history:
self.running_history[trial_job_id] = [] self.running_history[trial_job_id] = []
self.running_history[trial_job_id].extend(trial_history[len(self.running_history[trial_job_id]):]) self.running_history[trial_job_id].extend(trial_history[len(self.running_history[trial_job_id]):])
def trial_end(self, trial_job_id, success): def trial_end(self, trial_job_id, success):
''' """trial_end
trial_end
''' Parameters
----------
trial_job_id: int
trial job id
success: bool
True if succssfully finish the experiment, False otherwise
"""
if trial_job_id in self.running_history: if trial_job_id in self.running_history:
if success: if success:
cnt = 0 cnt = 0
...@@ -61,9 +82,25 @@ class MedianstopAssessor(Assessor): ...@@ -61,9 +82,25 @@ class MedianstopAssessor(Assessor):
logger.warning('trial_end: trial_job_id does not in running_history') logger.warning('trial_end: trial_job_id does not in running_history')
def assess_trial(self, trial_job_id, trial_history): def assess_trial(self, trial_job_id, trial_history):
''' """assess_trial
assess_trial
''' Parameters
----------
trial_job_id: int
trial job id
trial_history: list
The history performance matrix of each trial
Returns
-------
bool
AssessResult.Good or AssessResult.Bad
Raises
------
Exception
unrecognize exception in medianstop_assessor
"""
curr_step = len(trial_history) curr_step = len(trial_history)
if curr_step < self.start_step: if curr_step < self.start_step:
return AssessResult.Good return AssessResult.Good
...@@ -74,7 +111,7 @@ class MedianstopAssessor(Assessor): ...@@ -74,7 +111,7 @@ class MedianstopAssessor(Assessor):
logger.warning('incorrect data type or value:') logger.warning('incorrect data type or value:')
logger.exception(error) logger.exception(error)
except Exception as error: except Exception as error:
logger.warning('unrecognized exception:') logger.warning('unrecognized exception in medianstop_assessor:')
logger.excpetion(error) logger.excpetion(error)
self._update_data(trial_job_id, num_trial_history) self._update_data(trial_job_id, num_trial_history)
......
...@@ -23,7 +23,18 @@ import json ...@@ -23,7 +23,18 @@ import json
import numpy as np 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
----------
file_path:
path to the file
Raises
------
TypeError
Error with the file path
"""
try: try:
with open(file_path, 'r') as file: with open(file_path, 'r') as file:
return json.load(file) return json.load(file)
...@@ -32,15 +43,34 @@ def get_json_content(file_path): ...@@ -32,15 +43,34 @@ def get_json_content(file_path):
return None return None
def generate_pcs(nni_search_space_content): 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 categorical {value_1, ..., value_N} [default value]
# parameter_name ordinal {value_1, ..., value_N} [default value] # parameter_name ordinal {value_1, ..., value_N} [default value]
# parameter_name integer [min_value, max_value] [default value] # parameter_name integer [min_value, max_value] [default value]
# parameter_name integer [min_value, max_value] [default value] log # 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]
# parameter_name real [min_value, max_value] [default value] log # parameter_name real [min_value, max_value] [default value] log
# https://automl.github.io/SMAC3/stable/options.html
''' 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
unsupported type or value error or incorrect search space
"""
categorical_dict = {} categorical_dict = {}
search_space = nni_search_space_content search_space = nni_search_space_content
with open('param_config_space.pcs', 'w') as pcs_fd: with open('param_config_space.pcs', 'w') as pcs_fd:
...@@ -92,34 +122,85 @@ def generate_pcs(nni_search_space_content): ...@@ -92,34 +122,85 @@ def generate_pcs(nni_search_space_content):
return None return None
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
# deterministic, 1/0 can be constructed either by providing an actual scenario-object, or by specifing the options in a scenario file.
# output_dir,
# paramfile, Reference: https://automl.github.io/SMAC3/stable/options.html
# run_obj, 'quality'
The format of the scenario file is one option per line:
OPTION1 = VALUE1
OPTION2 = VALUE2
...
Parameters
----------
abort_on_first_run_crash: bool
If true, SMAC will abort if the first run of the target algorithm crashes. Default: True,
because trials reported to nni tuner would always in success state
algo: function
Specifies the target algorithm call that SMAC will optimize. Interpreted as a bash-command.
Not required by tuner, but required by nni's training service for running trials
always_race_default:
Race new incumbents always against default configuration
cost_for_crash:
Defines the cost-value for crashed runs on scenarios with quality as run-obj. Default: 2147483647.0.
Trials reported to nni tuner would always in success state
cutoff_time:
Maximum runtime, after which the target algorithm is cancelled. `Required if *run_obj* is runtime`
deterministic: bool
If true, the optimization process will be repeatable.
execdir:
Specifies the path to the execution-directory. Default: .
Trials are executed by nni's training service
feature_file:
Specifies the file with the instance-features.
No features specified or feature file is not supported
initial_incumbent:
DEFAULT is the default from the PCS. Default: DEFAULT. Must be from: [‘DEFAULT’, ‘RANDOM’].
input_psmac_dirs:
For parallel SMAC, multiple output-directories are used.
Parallelism is supported by nni
instance_file:
Specifies the file with the training-instances. Not supported
intensification_percentage:
The fraction of time to be used on intensification (versus choice of next Configurations). Default: 0.5.
Not supported, trials are controlled by nni's training service and kill be assessor
maxR: int
Maximum number of calls per configuration. Default: 2000.
memory_limit:
Maximum available memory the target algorithm can occupy before being cancelled.
minR: int
Minimum number of calls per configuration. Default: 1.
output_dir:
Specifies the output-directory for all emerging files, such as logging and results.
Default: smac3-output_2018-01-22_15:05:56_807070.
overall_obj:
PARX, where X is an integer defining the penalty imposed on timeouts (i.e. runtimes that exceed the cutoff-time).
Timeout is not supported
paramfile:
Specifies the path to the PCS-file.
run_obj:
Defines what metric to optimize. When optimizing runtime, cutoff_time is required as well.
Must be from: [‘runtime’, ‘quality’].
runcount_limit: int
Maximum number of algorithm-calls during optimization. Default: inf.
Use default because this is controlled by nni
shared_model:
Whether to run SMAC in parallel mode. Parallelism is supported by nni
test_instance_file:
Specifies the file with the test-instances. Instance is not supported
tuner-timeout:
Maximum amount of CPU-time used for optimization. Not supported
wallclock_limit: int
Maximum amount of wallclock-time used for optimization. Default: inf.
Use default because this is controlled by nni
# the following keys use default value or empty Returns
# algo, not required by tuner, but required by nni's training service for running trials -------
# abort_on_first_run_crash, because trials reported to nni tuner would always in success state Scenario:
# always_race_default, The scenario-object (smac.scenario.scenario.Scenario) is used to configure SMAC and can be constructed
# cost_for_crash, trials reported to nni tuner would always in success state either by providing an actual scenario-object, or by specifing the options in a scenario file
# cutoff_time, """
# execdir, trials are executed by nni's training service
# feature_file, no features specified or feature file is not supported
# initial_incumbent, use default value
# input_psmac_dirs, parallelism is supported by nni
# instance_file, not supported
# intensification_percentage, not supported, trials are controlled by nni's training service and kill be assessor
# maxR, use default, 2000
# minR, use default, 1
# overall_obj, timeout is not supported
# shared_model, parallelism is supported by nni
# test_instance_file, instance is not supported
# tuner-timeout, not supported
# runcount_limit, default: inf., use default because this is controlled by nni
# wallclock_limit,default: inf., use default because this is controlled by nni
# please refer to https://automl.github.io/SMAC3/stable/options.html
'''
with open('scenario.txt', 'w') as sce_fd: with open('scenario.txt', 'w') as sce_fd:
sce_fd.write('deterministic = 0\n') sce_fd.write('deterministic = 0\n')
#sce_fd.write('output_dir = \n') #sce_fd.write('output_dir = \n')
......
...@@ -17,9 +17,9 @@ ...@@ -17,9 +17,9 @@
# 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.
''' """
smac_tuner.py smac_tuner.py
''' """
from nni.tuner import Tuner from nni.tuner import Tuner
...@@ -38,17 +38,19 @@ from smac.facade.epils_facade import EPILS ...@@ -38,17 +38,19 @@ from smac.facade.epils_facade import EPILS
@unique @unique
class OptimizeMode(Enum): class OptimizeMode(Enum):
''' """Oprimize Mode class"""
Oprimize Mode class
'''
Minimize = 'minimize' Minimize = 'minimize'
Maximize = 'maximize' Maximize = 'maximize'
class SMACTuner(Tuner): class SMACTuner(Tuner):
"""
Parameters
----------
optimize_mode: str
optimize mode, 'maximize' or 'minimize'
"""
def __init__(self, optimize_mode): def __init__(self, optimize_mode):
''' """Constructor"""
Constructor
'''
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,9 +63,13 @@ class SMACTuner(Tuner): ...@@ -61,9 +63,13 @@ class SMACTuner(Tuner):
self.categorical_dict = {} self.categorical_dict = {}
def _main_cli(self): def _main_cli(self):
''' """Main function of SMAC for CLI interface
Main function of SMAC for CLI interface
''' Returns
-------
instance
optimizer
"""
self.logger.info("SMAC call: %s" % (" ".join(sys.argv))) self.logger.info("SMAC call: %s" % (" ".join(sys.argv)))
cmd_reader = CMDReader() cmd_reader = CMDReader()
...@@ -123,11 +129,15 @@ class SMACTuner(Tuner): ...@@ -123,11 +129,15 @@ class SMACTuner(Tuner):
return optimizer return optimizer
def update_search_space(self, search_space): def update_search_space(self, search_space):
''' """TODO: this is urgly, we put all the initialization work in this method, because initialization relies
TODO: this is urgly, we put all the initialization work in this method, on search space, also because update_search_space is called at the beginning.
because initialization relies on search space, also because update_search_space is called at the beginning.
NOTE: updating search space is not supported. NOTE: updating search space is not supported.
'''
Parameters
----------
search_space:
search space
"""
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:
...@@ -140,9 +150,22 @@ class SMACTuner(Tuner): ...@@ -140,9 +150,22 @@ 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): def receive_trial_result(self, parameter_id, parameters, value):
''' """receive_trial_result
receive_trial_result
''' Parameters
----------
parameter_id: int
parameter id
parameters:
parameters
value:
value
Raises
------
RuntimeError
Received parameter id not in total_data
"""
reward = self.extract_scalar_reward(value) reward = self.extract_scalar_reward(value)
if self.optimize_mode is OptimizeMode.Maximize: if self.optimize_mode is OptimizeMode.Maximize:
reward = -reward reward = -reward
...@@ -156,12 +179,21 @@ class SMACTuner(Tuner): ...@@ -156,12 +179,21 @@ class SMACTuner(Tuner):
self.smbo_solver.nni_smac_receive_runs(self.total_data[parameter_id], reward) self.smbo_solver.nni_smac_receive_runs(self.total_data[parameter_id], reward)
def convert_loguniform_categorical(self, challenger_dict): def convert_loguniform_categorical(self, challenger_dict):
''' """Convert the values of type `loguniform` back to their initial range
Convert the values of type `loguniform` back to their initial range
Also, we convert categorical: Also, we convert categorical:
categorical values in search space are changed to list of numbers before, 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
----------
challenger_dict: dict
challenger dict
Returns
-------
dict
challenger dict
"""
for key, value in challenger_dict.items(): for key, value in challenger_dict.items():
# convert to loguniform # convert to loguniform
if key in self.loguniform_key: if key in self.loguniform_key:
...@@ -173,9 +205,18 @@ class SMACTuner(Tuner): ...@@ -173,9 +205,18 @@ class SMACTuner(Tuner):
return challenger_dict return challenger_dict
def generate_parameters(self, parameter_id): def generate_parameters(self, parameter_id):
''' """generate one instance of hyperparameters
generate one instance of hyperparameters
''' Parameters
----------
parameter_id: int
parameter id
Returns
-------
list
new generated parameters
"""
if self.first_one: if self.first_one:
init_challenger = self.smbo_solver.nni_smac_start() init_challenger = self.smbo_solver.nni_smac_start()
self.total_data[parameter_id] = init_challenger self.total_data[parameter_id] = init_challenger
...@@ -189,11 +230,18 @@ class SMACTuner(Tuner): ...@@ -189,11 +230,18 @@ class SMACTuner(Tuner):
return self.convert_loguniform_categorical(challenger.get_dictionary()) return self.convert_loguniform_categorical(challenger.get_dictionary())
def generate_multiple_parameters(self, parameter_id_list): def generate_multiple_parameters(self, parameter_id_list):
''' """generate mutiple instances of hyperparameters
generate mutiple instances of hyperparameters
'''
Parameters
----------
parameter_id_list: list
list of parameter id
Returns
-------
list
list of new generated parameters
"""
if self.first_one: if self.first_one:
params = [] params = []
for one_id in parameter_id_list: for one_id in parameter_id_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