From 7936d0eecdf08f5fc66450c236fb08f00dfee764 Mon Sep 17 00:00:00 2001 From: JarnoRFB Date: Thu, 17 Jan 2019 18:43:33 +0100 Subject: [PATCH 1/2] Add function to calculate BOBH behavior This should be useful to calculate how many runs will be generated based on the hyperparameters of BOBH. --- hpbandster/utils.py | 115 ++++++++++++++++++++++++++++---------------- tests/test_utils.py | 16 ++++++ 2 files changed, 90 insertions(+), 41 deletions(-) diff --git a/hpbandster/utils.py b/hpbandster/utils.py index e29a25d..af4ab9c 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_bobh_run(min_budget, max_budget, eta, n_iterations): + """ + Prints the expected numbers of configurations, runs and budgets given BOBH's hyperparameters. + + Parameters + ---------- + min_budget + The smallest budget to consider. + max_budget + The largest budget to consider. + eta + The eta parameter. Determines how many configurations advance to the next round. + n_iterations + 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 BOBH 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..9d7f4ee 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_bobh_run(self): + stdout = StringIO() + sys.stdout = stdout + utils.predict_bobh_run(1, 9, eta=3, n_iterations=5) + expected = """Running BOBH 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() From 10d65beb4daabd8416dbc71bc1184e3051fd3dd4 Mon Sep 17 00:00:00 2001 From: JarnoRFB Date: Fri, 18 Jan 2019 09:29:43 +0100 Subject: [PATCH 2/2] Fix typos --- hpbandster/utils.py | 14 +++++++------- tests/test_utils.py | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/hpbandster/utils.py b/hpbandster/utils.py index af4ab9c..00b772b 100644 --- a/hpbandster/utils.py +++ b/hpbandster/utils.py @@ -52,19 +52,19 @@ def start_local_nameserver(host=None, port=0, nic_name=None): return (host, int(port)) -def predict_bobh_run(min_budget, max_budget, eta, n_iterations): +def predict_bohb_run(min_budget, max_budget, eta, n_iterations): """ - Prints the expected numbers of configurations, runs and budgets given BOBH's hyperparameters. + Prints the expected numbers of configurations, runs and budgets given BOHB's hyperparameters. Parameters ---------- - min_budget + min_budget : float The smallest budget to consider. - max_budget + max_budget : float The largest budget to consider. - eta + eta : int The eta parameter. Determines how many configurations advance to the next round. - n_iterations + n_iterations : int How many iterations of SuccessiveHalving to perform. """ s_max = -int(np.log(min_budget / max_budget) / np.log(eta)) + 1 @@ -83,7 +83,7 @@ def predict_bobh_run(min_budget, max_budget, eta, n_iterations): ns = [max(int(n0 * (eta ** (-i))), 1) for i in range(s + 1)] n_runs += sum(ns) - print('Running BOBH with these parameters will proceed as follows:') + 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)) diff --git a/tests/test_utils.py b/tests/test_utils.py index 9d7f4ee..df99b23 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -34,11 +34,11 @@ def test_local_nameserver_2(self): self.assertEqual(ns.host, '127.0.0.1') ns.shutdown() - def test_predict_bobh_run(self): + def test_predict_bohb_run(self): stdout = StringIO() sys.stdout = stdout - utils.predict_bobh_run(1, 9, eta=3, n_iterations=5) - expected = """Running BOBH with these parameters will proceed as follows: + 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].