Source code for aup.Proposer.HyperbandProposer

"""
..
  Copyright (c) 2018 LG Electronics Inc.
  SPDX-License-Identifier: GPL-3.0-or-later
  
aup.Proposer.HyperbandProposer
==============================

The code is based on `hyperband github commit a632209 <https://github.com/zygmuntz/hyperband>`_.

See `license <https://github.com/zygmuntz/hyperband/blob/master/LICENSE>`_ for redistribution.

Configuration
-------------

General parameters
~~~~~~~~~~~~~~~~~~

============= ============== ========================================
Name          Default value  Explanation
============= ============== ========================================
proposer      -              hyperband
random_seed   0              [Optional] seed for random generator
max_iter      81             Max iterations (e.g. epochs) per configuration
eta           3              downsampling rate, choose 3 for training from scratch
skip_last     0              whether skip last element
engine        random         engine to generate configurations for hyperband
============= ============== ========================================

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
======= ==========================================================================


In the returned job_config, it has ``n_iterations`` and ``tid`` for tracking the HPO process.
``n_iterations`` marks how much resource should be allocated for this round of training (e.g. epochs);
``tid`` is used to recover the previous trained model if needed (i.e. finetune)

APIs
----
"""
import logging
import random
from math import log, ceil

from six.moves import input

from aup.Proposer import get_proposer
from .AbstractProposer import AbstractProposer
from ..utils import set_default_keyvalue, get_from_options

logger = logging.getLogger(__name__)


[docs]class HyperbandProposer(AbstractProposer): def __init__(self, config): super(HyperbandProposer, self).__init__(config) self.verify_config(config) self.target = -1 if config["target"] == "max" else 1 self.max_iter = config["max_iter"] self.eta = config["eta"] self.skip_last = config["skip_last"] self.s_max = int(log(self.max_iter)/log(self.eta)) self.B = (self.s_max+1)*self.max_iter self.results = [] self.best_counter = -1 self.s = self.s_max+1 self.config = config self.nSamples = 0 for s in reversed(range(self.s_max + 1)): self.nSamples += int(ceil(self.B / self.max_iter / (s + 1) * self.eta ** s)) logger.info("Total number of samples is %d"%self.nSamples) set_default_keyvalue("random_seed", 0, config) random.seed(config["random_seed"]) self.t = 0 self.i = 0 self.n = 0 self.test_set = [] self.r = 0 self.n_configs = 0 self.n_iterations = 0 self.scores = {} self.setup(self.s_max)
[docs] def setup(self, s): # Follow the hyperband paper, set up internal variables based on s_max self.s = s self.n = int(ceil(self.B / self.max_iter / (s + 1) * self.eta ** s)) self.r = self.max_iter * self.eta ** (-s) gen_config = self.config.copy() gen_config["n_samples"] = self.n gen_config["random_seed"] = random.randint(0, 100) gen_config["proposer"] = gen_config["engine"] proposer = get_proposer(gen_config['proposer'])(gen_config) self.test_set = [proposer.get for _ in range(self.n)] self.t = 0 self.i = 0 self.n_configs = self.n * self.eta ** (-self.i) self.n_iterations = self.r * self.eta ** self.i
[docs] def verify_config(self, config): for i in config["parameter_config"]: if i['name'] == "tid": msg = "`tid` is reserved for Hyperband" logger.fatal(msg) raise KeyError(msg) set_default_keyvalue("max_iter", 81, config, log=logger) set_default_keyvalue("eta", 3, config, log=logger) set_default_keyvalue("skip_last", 0, config, log=logger) set_default_keyvalue("engine", "random", config, log=logger)
[docs] def get_param(self): if self.t == len(self.test_set): if len(self.scores) != self.t: self.current_proposal = None return None tids = sorted(self.scores, key=self.scores.get) tids = tids[0:int(self.n_configs / self.eta)] self.test_set = [self.test_set[i] for i in tids] self.scores = {} self.i += 1 self.t = 0 self.n_configs = self.n * self.eta ** (-self.i) self.n_iterations = self.r * self.eta ** self.i if self.i == (self.s+1-int(self.skip_last)): self.i = 0 self.setup(self.s-1) logger.debug("\n*** {} configurations x {:.1f} iterations each".format( self.n_configs, self.n_iterations)) if type(self.test_set[self.t]) != dict: self.test_set[self.t] = self.test_set[self.t]() self.test_set[self.t]['tid'] = self.t self.test_set[self.t]["n_iterations"] = self.n_iterations self.t += 1 return self.test_set[self.t-1] # return parameter configuration
[docs] def update(self, score, job): self.scores[job.config["tid"]] = score * self.target
[docs] def failed(self, job): super(HyperbandProposer, self).failed(job) raise NotImplementedError("HyperbandProposer does not support failed jobs")
[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] @staticmethod def setup_config(): # pragma: no cover config = dict() config['max_iter'] = int(input("max iteration [81]:") or 81) config['eta'] = int(input("ita [3]") or 3) config["skip_last"] = int(input("skip last [0]") or '0') config["engine"] = get_from_options("Hyperparameter sampling engine", ["random", "sequence"]) config.update(get_proposer(config['engine']).setup_config()) return config