diff --git a/hpbandster/utils.py b/hpbandster/utils.py index e29a25d..00b772b 100644 --- a/hpbandster/utils.py +++ b/hpbandster/utils.py @@ -1,58 +1,91 @@ import os.path import json -import threading +import threading +import numpy as np import Pyro4 import Pyro4.naming - from hpbandster.core.result import Result from hpbandster.core.base_iteration import Datum - def nic_name_to_host(nic_name): - """ translates the name of a network card into a valid host name""" - from netifaces import ifaddresses, AF_INET - host = ifaddresses(nic_name).setdefault(AF_INET, [{'addr': 'No IP addr'}] )[0]['addr'] - return(host) - + """ translates the name of a network card into a valid host name""" + from netifaces import ifaddresses, AF_INET + host = ifaddresses(nic_name).setdefault(AF_INET, [{'addr': 'No IP addr'}])[0]['addr'] + return (host) def start_local_nameserver(host=None, port=0, nic_name=None): - """ - starts a Pyro4 nameserver in a daemon thread - - Parameters: - ----------- - host: str - the hostname to use for the nameserver - port: int - the port to be used. Default =0 means a random port - nic_name: str - name of the network interface to use - - Returns: - -------- - tuple (str, int): - the host name and the used port - """ - - if host is None: - if nic_name is None: - host = 'localhost' - else: - host = nic_name_to_host(nic_name) - - uri, ns, _ = Pyro4.naming.startNS(host=host, port=port) - host, port = ns.locationStr.split(':') - - - thread = threading.Thread(target=ns.requestLoop, name='Pyro4 nameserver started by HpBandSter') - thread.daemon=True - - thread.start() - return(host, int(port)) + """ + starts a Pyro4 nameserver in a daemon thread + + Parameters: + ----------- + host: str + the hostname to use for the nameserver + port: int + the port to be used. Default =0 means a random port + nic_name: str + name of the network interface to use + + Returns: + -------- + tuple (str, int): + the host name and the used port + """ + + if host is None: + if nic_name is None: + host = 'localhost' + else: + host = nic_name_to_host(nic_name) + + uri, ns, _ = Pyro4.naming.startNS(host=host, port=port) + host, port = ns.locationStr.split(':') + + thread = threading.Thread(target=ns.requestLoop, name='Pyro4 nameserver started by HpBandSter') + thread.daemon = True + + thread.start() + return (host, int(port)) + + +def predict_bohb_run(min_budget, max_budget, eta, n_iterations): + """ + Prints the expected numbers of configurations, runs and budgets given BOHB's hyperparameters. + + Parameters + ---------- + min_budget : float + The smallest budget to consider. + max_budget : float + The largest budget to consider. + eta : int + The eta parameter. Determines how many configurations advance to the next round. + n_iterations : int + How many iterations of SuccessiveHalving to perform. + """ + s_max = -int(np.log(min_budget / max_budget) / np.log(eta)) + 1 + + n_runs = 0 + n_configurations = [] + initial_budgets = [] + for iteration in range(n_iterations): + s = s_max - 1 - (iteration % s_max) + initial_budget = (eta ** -s) * max_budget + initial_budgets.append(initial_budget) + n0 = int(np.floor(s_max / (s + 1)) * eta ** s) + n_configurations.append(n0) + ns = [max(int(n0 * (eta ** (-i))), 1) for i in range(s + 1)] + n_runs += sum(ns) + print('Running BOHB with these parameters will proceed as follows:') + print(' {} iterations of SuccessiveHalving will be executed.'.format(n_iterations)) + print(' The iterations will start with a number of configurations as {}.'.format(n_configurations)) + print(' With the initial budgets as {}.'.format(initial_budgets)) + print(' A total of {} unique configurations will be sampled.'.format(sum(n_configurations))) + print(' A total of {} runs will be executed.'.format(n_runs)) diff --git a/tests/test_utils.py b/tests/test_utils.py index 3220cb7..df99b23 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,5 +1,7 @@ import unittest import logging +from io import StringIO +import sys logging.basicConfig(level=logging.WARNING) @@ -32,5 +34,19 @@ def test_local_nameserver_2(self): self.assertEqual(ns.host, '127.0.0.1') ns.shutdown() + def test_predict_bohb_run(self): + stdout = StringIO() + sys.stdout = stdout + utils.predict_bohb_run(1, 9, eta=3, n_iterations=5) + expected = """Running BOHB with these parameters will proceed as follows: + 5 iterations of SuccessiveHalving will be executed. + The iterations will start with a number of configurations as [9, 3, 3, 9, 3]. + With the initial budgets as [1.0, 3.0, 9, 1.0, 3.0]. + A total of 27 unique configurations will be sampled. + A total of 37 runs will be executed. +""" + self.assertEqual(stdout.getvalue(), expected) + + if __name__ == '__main__': unittest.main()