Commit 2a81b08c authored by QuanluZhang's avatar QuanluZhang Committed by Yan Ni
Browse files

support trial config deduplication in SMAC tuner (#1840)

parent f058cf29
...@@ -160,6 +160,7 @@ Similar to TPE, SMAC is also a black-box tuner which can be tried in various sce ...@@ -160,6 +160,7 @@ Similar to TPE, SMAC is also a black-box tuner which can be tried in various sce
**Requirement of classArgs** **Requirement of classArgs**
* **optimize_mode** (*maximize or minimize, optional, default = maximize*) - If 'maximize', the tuner will target to maximize metrics. If 'minimize', the tuner will target to minimize metrics. * **optimize_mode** (*maximize or minimize, optional, default = maximize*) - If 'maximize', the tuner will target to maximize metrics. If 'minimize', the tuner will target to minimize metrics.
* **config_dedup** (*True or False, optional, default = False*) - If True, the tuner will not generate a configuration that has been already generated. If False, a configuration may be generated twice, but it is rare for relatively large search space.
**Usage example** **Usage example**
......
...@@ -18,26 +18,31 @@ from smac.utils.io.cmd_reader import CMDReader ...@@ -18,26 +18,31 @@ from smac.utils.io.cmd_reader import CMDReader
from ConfigSpaceNNI import Configuration from ConfigSpaceNNI import Configuration
import nni
from nni.tuner import Tuner from nni.tuner import Tuner
from nni.utils import OptimizeMode, extract_scalar_reward from nni.utils import OptimizeMode, extract_scalar_reward
from .convert_ss_to_scenario import generate_scenario from .convert_ss_to_scenario import generate_scenario
logger = logging.getLogger('smac_AutoML')
class SMACTuner(Tuner): class SMACTuner(Tuner):
""" """
This is a wrapper of [SMAC](https://github.com/automl/SMAC3) following NNI tuner interface. This is a wrapper of [SMAC](https://github.com/automl/SMAC3) following NNI tuner interface.
It only supports ``SMAC`` mode, and does not support the multiple instances of SMAC3 (i.e., It only supports ``SMAC`` mode, and does not support the multiple instances of SMAC3 (i.e.,
the same configuration is run multiple times). the same configuration is run multiple times).
""" """
def __init__(self, optimize_mode="maximize"): def __init__(self, optimize_mode="maximize", config_dedup=False):
""" """
Parameters Parameters
---------- ----------
optimize_mode : str optimize_mode : str
Optimize mode, 'maximize' or 'minimize', by default 'maximize' Optimize mode, 'maximize' or 'minimize', by default 'maximize'
config_dedup : bool
If True, the tuner will not generate a configuration that has been already generated.
If False, a configuration may be generated twice, but it is rare for relatively large search space.
""" """
self.logger = logging.getLogger( self.logger = logger
self.__module__ + "." + self.__class__.__name__)
self.optimize_mode = OptimizeMode(optimize_mode) self.optimize_mode = OptimizeMode(optimize_mode)
self.total_data = {} self.total_data = {}
self.optimizer = None self.optimizer = None
...@@ -47,6 +52,7 @@ class SMACTuner(Tuner): ...@@ -47,6 +52,7 @@ class SMACTuner(Tuner):
self.loguniform_key = set() self.loguniform_key = set()
self.categorical_dict = {} self.categorical_dict = {}
self.cs = None self.cs = None
self.dedup = config_dedup
def _main_cli(self): def _main_cli(self):
""" """
...@@ -127,7 +133,7 @@ class SMACTuner(Tuner): ...@@ -127,7 +133,7 @@ class SMACTuner(Tuner):
search_space : dict search_space : dict
The format could be referred to search space spec (https://nni.readthedocs.io/en/latest/Tutorial/SearchSpaceSpec.html). The format could be referred to search space spec (https://nni.readthedocs.io/en/latest/Tutorial/SearchSpaceSpec.html).
""" """
self.logger.info('update search space in SMAC.')
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:
...@@ -225,9 +231,19 @@ class SMACTuner(Tuner): ...@@ -225,9 +231,19 @@ class SMACTuner(Tuner):
return self.param_postprocess(init_challenger.get_dictionary()) return self.param_postprocess(init_challenger.get_dictionary())
else: else:
challengers = self.smbo_solver.nni_smac_request_challengers() challengers = self.smbo_solver.nni_smac_request_challengers()
challengers_empty = True
for challenger in challengers: for challenger in challengers:
challengers_empty = False
if self.dedup:
match = [v for k, v in self.total_data.items() \
if v.get_dictionary() == challenger.get_dictionary()]
if match:
continue
self.total_data[parameter_id] = challenger self.total_data[parameter_id] = challenger
return self.param_postprocess(challenger.get_dictionary()) return self.param_postprocess(challenger.get_dictionary())
assert challengers_empty is False, 'The case that challengers is empty is not handled.'
self.logger.info('In generate_parameters: No more new parameters.')
raise nni.NoMoreTrialError('No more new parameters.')
def generate_multiple_parameters(self, parameter_id_list, **kwargs): def generate_multiple_parameters(self, parameter_id_list, **kwargs):
""" """
...@@ -261,9 +277,16 @@ class SMACTuner(Tuner): ...@@ -261,9 +277,16 @@ class SMACTuner(Tuner):
for challenger in challengers: for challenger in challengers:
if cnt >= len(parameter_id_list): if cnt >= len(parameter_id_list):
break break
if self.dedup:
match = [v for k, v in self.total_data.items() \
if v.get_dictionary() == challenger.get_dictionary()]
if match:
continue
self.total_data[parameter_id_list[cnt]] = challenger self.total_data[parameter_id_list[cnt]] = challenger
params.append(self.param_postprocess(challenger.get_dictionary())) params.append(self.param_postprocess(challenger.get_dictionary()))
cnt += 1 cnt += 1
if self.dedup and not params:
self.logger.info('In generate_multiple_parameters: No more new parameters.')
return params return params
def import_data(self, data): def import_data(self, data):
......
...@@ -53,14 +53,23 @@ common_schema = { ...@@ -53,14 +53,23 @@ common_schema = {
} }
} }
tuner_schema_dict = { tuner_schema_dict = {
('Anneal', 'SMAC'): { 'Anneal': {
'builtinTunerName': setChoice('builtinTunerName', 'Anneal', 'SMAC'), 'builtinTunerName': 'Anneal',
Optional('classArgs'): { Optional('classArgs'): {
'optimize_mode': setChoice('optimize_mode', 'maximize', 'minimize'), 'optimize_mode': setChoice('optimize_mode', 'maximize', 'minimize'),
}, },
Optional('includeIntermediateResults'): setType('includeIntermediateResults', bool), Optional('includeIntermediateResults'): setType('includeIntermediateResults', bool),
Optional('gpuIndices'): Or(int, And(str, lambda x: len([int(i) for i in x.split(',')]) > 0), error='gpuIndex format error!'), Optional('gpuIndices'): Or(int, And(str, lambda x: len([int(i) for i in x.split(',')]) > 0), error='gpuIndex format error!'),
}, },
'SMAC': {
'builtinTunerName': 'SMAC',
Optional('classArgs'): {
'optimize_mode': setChoice('optimize_mode', 'maximize', 'minimize'),
'config_dedup': setType('config_dedup', bool)
},
Optional('includeIntermediateResults'): setType('includeIntermediateResults', bool),
Optional('gpuIndices'): Or(int, And(str, lambda x: len([int(i) for i in x.split(',')]) > 0), error='gpuIndex format error!'),
},
('Evolution'): { ('Evolution'): {
'builtinTunerName': setChoice('builtinTunerName', 'Evolution'), 'builtinTunerName': setChoice('builtinTunerName', 'Evolution'),
Optional('classArgs'): { Optional('classArgs'): {
......
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