Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add function to calculate BOHB behavior #43

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 74 additions & 41 deletions hpbandster/utils.py
Original file line number Diff line number Diff line change
@@ -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))
16 changes: 16 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import unittest
import logging
from io import StringIO
import sys

logging.basicConfig(level=logging.WARNING)

Expand Down Expand Up @@ -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()