"""
..
Copyright (c) 2018 LG Electronics Inc.
SPDX-License-Identifier: GPL-3.0-or-later
aup.Proposer.SpearmintProposer
==============================
Re-implementation of Spearmint. Most of the Spearmint code has not been changed
Mainly wrap main.py
Be aware - all variables are vectorized except size=1 case. (different from spearmint original implementation)
The original source is forked from `Spearmint github commit 70309f0
<https://github.com/JasperSnoek/spearmint/tree/master/spearmint>`_.
Configuration
-------------
General parameters
~~~~~~~~~~~~~~~~~~
============= ============== ========================================
Name Default value Explanation
============= ============== ========================================
proposer - spearmint
engine GPEIOptChooser
engine_config - Options for spearmint chooser
grid_size 20000 Option for spearmint
n_samples - Total number of trials to sample
random_seed 0 [Optional] seed for random generator
spearmint_dir spearmint Spearmint working directory
============= ============== ========================================
Specific parameters for ``parameter_config``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
======= ==========================================================================
Name Explanation
======= ==========================================================================
name name of the variable, will be used in the job config, i.e. training code
type type of the parameter to be sampled: choose from "float","int","choice"
range range of the parameter. For "choice", list all the feasible values
======= ==========================================================================
APIs
----
"""
import importlib
import logging
import os
import shutil
from six.moves import input
from .AbstractProposer import AbstractProposer
from .spearmint import ExperimentGrid
from ..aup import BasicConfig
from ..utils import set_default_keyvalue, check_missing_key, get_from_options
logger = logging.getLogger(__name__)
[docs]class SpearmintProposer(AbstractProposer):
def __init__(self, config):
super(SpearmintProposer, self).__init__(config)
if os.path.isdir(config["workingdir"]): # local folder exist
set_default_keyvalue("spearmint_dir",
os.path.join(config["workingdir"], "spearmint"),
config, log=logger)
else: # local folder not exist (esp. run remotely)
set_default_keyvalue("spearmint_dir", "spearmint", config, log=logger)
self.expt_dir = config["spearmint_dir"]
set_default_keyvalue("random_seed", 0, config, log=logger)
set_default_keyvalue("engine", "GPEIChooser", config, log=logger)
set_default_keyvalue("engine_config", {}, config, log=logger)
set_default_keyvalue("grid_size", 20000, config, log=logger)
self.nSamples = config["n_samples"]
self.seed = config["random_seed"]
self.target = -1 if config["target"] == "max" else 1
self.variables = []
self.grid_size = config["grid_size"]
for param in config["parameter_config"]:
p = self.parse_param_config(param)
if "job_id" == p['name']:
msg = "`job_id` is preserved for HPO"
logger.fatal(msg)
raise ValueError(msg)
if p["type"] == "choice":
p["type"] = "enum"
set_default_keyvalue('size', 1, p)
self.variables.append(BasicConfig(**p))
try:
module = importlib.import_module(".spearmint.chooser." + config["engine"], package='aup.Proposer')
self.chooser = module.init(self.expt_dir, config["engine_config"])
self.verify_config(config)
except ImportError:
msg = "%s doesn't exist in spearmint" % config["engine"]
logger.fatal(msg)
raise KeyError(msg)
[docs] @staticmethod
def setup_config(): # pragma: no cover
config = dict()
logger.critical("The following step only setup the basic configuration, edit file direct for advanced tuning.")
config['engine'] = get_from_options("HPO Engine, `engine`,", ["GPEIOptChooser"])
config['engine_config'] = dict()
config['grid_size'] = int(input("Grid size for hyperparameters, `grid_size`, [20000]:") or 20000)
config['n_samples'] = int(input("number of model samples to draw randomly, `n_samples`, [1]:") or 1)
config['random_seed'] = int(input("random seed, `random_seed`, [0]:") or 0)
config.update(AbstractProposer.setup_config())
for i in config['parameter_config']:
i.update({'size': 1})
return config
[docs] def get_param(self, **kwargs):
expt_grid = ExperimentGrid(self.expt_dir, self.variables, self.grid_size, self.seed)
grid, values, durations = expt_grid.get_grid()
candidates = expt_grid.get_candidates()
pending = expt_grid.get_pending()
complete = expt_grid.get_complete()
job_id = self.chooser.next(grid, values, durations, candidates, pending, complete)
if isinstance(job_id, tuple):
(job_id, candidate) = job_id
job_id = expt_grid.add_to_grid(candidate)
expt_grid.set_submitted(job_id, 0)
job_config = expt_grid.get_params(job_id)
for i in job_config:
if len(job_config[i]) == 1: # spearmint returns list, other methods return value.
job_config[i] = job_config[i][0]
else:
raise NotImplementedError("Parameter with dimension larger than 1 is not supported yet")
job_config["job_id"] = job_id
expt_grid.set_running(job_id)
self.current_proposal = job_config
return self.current_proposal
[docs] def save(self, path):
msg = "Save and restore not supported yet"
logger.fatal(msg)
raise NotImplementedError(msg)
[docs] def reload(self, path):
msg = "Save and restore not supported yet"
logger.fatal(msg)
raise NotImplementedError(msg)
[docs] def update(self, score, job):
ExperimentGrid.job_complete(self.expt_dir, job.config["job_id"], score * self.target, 0)
[docs] def failed(self, job):
super(SpearmintProposer, self).failed(job)
ExperimentGrid.job_broken(self.expt_dir, job.config["job_id"])
[docs] def verify_config(self, config):
check_missing_key(config, "n_samples", "Specify number of samples to randomly draw", log=logger)
if os.path.exists(self.expt_dir):
logger.warning("Removing folder %s" % self.expt_dir)
shutil.rmtree(self.expt_dir)
os.mkdir(self.expt_dir)
return config