Skip to content

Commit 706aa7d

Browse files
authored
Large update (#76)
* Update XGB + SVM get meta info The Benchmarks XGB and SVM return now more information in their get_meta_information function. To do so, we updated the server / client connection, such that it can handle sending numpy random States. Those changes are affecting only the container versions of the Benchmarks XGB and SVM. All others still should work. * Add support for new extracted nasbench201 data. * Update Nasbench101 and 1shot1 - 1shot1 now supports selecting an seed (0, 1, 2). - updates the docstrings - improves the code logic * Update NasBench201 - Nasbench supports now the new created data (v1.3). Needs to be uploaded. - Remove old docstrings - Add a detailed description of the data sets. * Improve Check Style * Improve Check Style + Update Wrapper - Update Signatures - Simplify the abstract benchmark wrapper function * HPOBenchrc with yaml parser. - The new config file checks automatically if the version of the configuration file matches the hpobench version - If there is a mismatch, a warning is printed. * Update Doc String pybnn.py * Simplify the wrapper. * Update Configparser * Fix small merge errors * Fix Benchmark Encoder / Decoder * Update SVM and XGBOoostBenchmark - there was no condition to check if the fidelity is to small. - When the dataset fraction was 0, an error occured. We added a lower limit. * Improve readability of the wrapper function * Fix a bug in the json-encoder/decoder. * Simplify Server Client Communication * Fix small bug in the wrapper. * Fix double server output * Codestyle - enable pylint support * Codestyle * Fix small error in NAS-Bench201 datamanager. - remove cifar10 support - correct wrong file ending. * Add field task id to svm and xgboost container benchmark * Try to improve the shutdown behavior * Improve Signature + Move Pylint installation to 'codestyle' * Update Tests * Fix small bug in the wrapper. * Fix small bug in the wrapper. * Skip the nasbench 201 data tests.
1 parent 456f7ea commit 706aa7d

31 files changed

+981
-937
lines changed

ci_scripts/codestyle.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ if [[ "$RUN_CODESTYLE" == "true" ]]; then
1818
else
1919
echo "Flake8: No errors found"
2020
fi
21+
22+
# Enable the error W0221: Parameters differ from overridden method (arguments-differ)
23+
pylint --disable=all --enable=W0221 ./hpobench
24+
exit_code=$?
25+
if [[ "$exit_code" -eq 0 ]]; then
26+
echo "Pylint: No signature errors found"
27+
else
28+
echo "Pylint: Signature Failure!"
29+
exit 1
30+
fi
2131
else
2232
echo "Skip code style checking"
2333
fi

examples/w_optimizer/cartpole_bohb.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def __init__(self, seed, max_budget, *args, **kwargs):
3232
self.seed = seed
3333
self.max_budget = max_budget
3434

35+
# pylint: disable=arguments-differ
3536
def compute(self, config, budget, **kwargs):
3637
b = Benchmark(rng=self.seed)
3738
# Old API ---- NO LONGER SUPPORTED ---- This will simply ignore the fidelities

extra_requirements/tests.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"codestyle": ["pycodestyle","flake8"],
2+
"codestyle": ["pycodestyle","flake8","pylint"],
33
"pytest": ["pytest>=4.6","pytest-cov"],
44
"test_paramnet": ["tqdm"]
55
}

hpobench/abstract_benchmark.py

Lines changed: 88 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import abc
44
from typing import Union, Dict
5+
import functools
56

67
import logging
78
import ConfigSpace
@@ -12,9 +13,9 @@
1213
logger = logging.getLogger('AbstractBenchmark')
1314

1415

15-
class AbstractBenchmark(object, metaclass=abc.ABCMeta):
16+
class AbstractBenchmark(abc.ABC, metaclass=abc.ABCMeta):
1617

17-
def __init__(self, rng: Union[int, np.random.RandomState, None] = None):
18+
def __init__(self, rng: Union[int, np.random.RandomState, None] = None, **kwargs):
1819
"""
1920
Interface for benchmarks.
2021
@@ -38,9 +39,10 @@ def __init__(self, rng: Union[int, np.random.RandomState, None] = None):
3839
self.fidelity_space = self.get_fidelity_space()
3940

4041
@abc.abstractmethod
41-
def objective_function(self, configuration: Dict, fidelity: Union[Dict, None] = None,
42+
def objective_function(self, configuration: Union[ConfigSpace.Configuration, Dict],
43+
fidelity: Union[Dict, ConfigSpace.Configuration, None] = None,
4244
rng: Union[np.random.RandomState, int, None] = None,
43-
*args, **kwargs) -> dict:
45+
**kwargs) -> Dict:
4446
"""
4547
Objective function.
4648
@@ -67,12 +69,13 @@ def objective_function(self, configuration: Dict, fidelity: Union[Dict, None] =
6769
Dict
6870
Must contain at least the key `function_value` and `cost`.
6971
"""
70-
pass
72+
NotImplementedError()
7173

7274
@abc.abstractmethod
73-
def objective_function_test(self, configuration: Dict, fidelity: Union[Dict, None] = None,
75+
def objective_function_test(self, configuration: Union[ConfigSpace.Configuration, Dict],
76+
fidelity: Union[Dict, ConfigSpace.Configuration, None] = None,
7477
rng: Union[np.random.RandomState, int, None] = None,
75-
*args, **kwargs) -> Dict:
78+
**kwargs) -> Dict:
7679
"""
7780
If there is a different objective function for offline testing, e.g
7881
testing a machine learning on a hold extra test set instead
@@ -91,132 +94,95 @@ def objective_function_test(self, configuration: Dict, fidelity: Union[Dict, Non
9194
Dict
9295
Must contain at least the key `function_value` and `cost`.
9396
"""
94-
pass
97+
NotImplementedError()
9598

9699
@staticmethod
97-
def _check_configuration(foo):
100+
def check_parameters(wrapped_function):
98101
"""
99-
Decorator to enable checking the input configuration and, if given, fidelity parameters.
102+
Wrapper for the objective_function and objective_function_test.
103+
This function verifies the correctness of the input configuration and the given fidelity.
100104
101-
Uses the check_configuration of the ConfigSpace class to ensure
102-
that all specified values are valid, and no conditionals are violated
105+
It ensures that both, configuration and fidelity, don't contain any wrong parameters or any conditions are
106+
violated.
103107
104-
Can be combined with the _configuration_as_array decorator.
105-
"""
106-
def wrapper(self, configuration: Union[np.ndarray, ConfigSpace.Configuration, Dict], **kwargs):
107-
108-
try:
109-
if isinstance(configuration, np.ndarray):
110-
config_dict = {k: configuration[i] for (i, k) in enumerate(self.configuration_space)}
111-
config = ConfigSpace.Configuration(self.configuration_space, config_dict)
112-
elif isinstance(configuration, dict):
113-
config = ConfigSpace.Configuration(self.configuration_space, configuration)
114-
elif isinstance(configuration, ConfigSpace.Configuration):
115-
config = configuration
116-
else:
117-
config = None
118-
except Exception as e:
119-
logger.error('Error during the conversion of the provided configuration '
120-
'into a ConfigSpace.Configuration object')
121-
raise e
122-
123-
if config is None:
124-
raise TypeError(f'Configuration has to be from type np.ndarray, dict, or ConfigSpace.Configuration but '
125-
f'was {type(configuration)}')
126-
127-
self.configuration_space.check_configuration(config)
128-
return foo(self, configuration, **kwargs)
129-
return wrapper
108+
If the argument 'fidelity' is not specified or a single parameter is missing, then the corresponding default
109+
fidelities are filled in.
130110
131-
@staticmethod
132-
def _check_fidelity(foo):
111+
We cast them to a ConfigSpace.Configuration object to ensure that no conditions are violated.
133112
"""
134-
Decorator to enable checking the input fidelity parameters, if any. Wrapped functions are expected to contain
135-
an optional 'fidelity':keyword argument, which would in turn contain a dictionary of the requested fidelity
136-
parameters. If any specific parameter is missing or the entire argument is missing, the corresponding default
137-
values are filled in.
138113

139-
Uses the check_configuration of the ConfigSpace class to ensure that all specified values are valid, and no
140-
conditionals are violated.
141-
142-
Order independent from the _check_configuration decorator, but it does forward all fidelity parameters,
143-
regardless of input, as a dictionary in the 'fidelity' keyword argument.
144-
"""
145-
def wrapper(self, configuration: Union[np.ndarray, ConfigSpace.Configuration, Dict],
114+
# Copy all documentation from the underlying function except the annotations.
115+
@functools.wraps(wrapped=wrapped_function, assigned=('__module__', '__name__', '__qualname__', '__doc__',))
116+
def wrapper(self, configuration: Union[ConfigSpace.Configuration, Dict],
146117
fidelity: Union[Dict, ConfigSpace.Configuration, None] = None, **kwargs):
147118

148-
# Sanity check that there are no fidelities in **kwargs
149-
for f in self.fidelity_space.get_hyperparameters():
150-
if f.name in kwargs:
151-
raise ValueError(f'Fidelity parameter {f.name} should not be part of kwargs\n'
152-
f'Fidelity: {fidelity}\n Kwargs: {kwargs}')
153-
154-
# If kwargs contains the 'fidelity' arg, extract any fidelity parameters it contains and fill in
155-
# default values for the rest.
156-
default_fidelities = self.fidelity_space.get_default_configuration()
157-
try:
158-
if fidelity is None:
159-
fidelity = default_fidelities
160-
if isinstance(fidelity, dict):
161-
default_fidelities_cfg = default_fidelities.get_dictionary()
162-
fidelity = {k: fidelity.get(k, v) for k, v in default_fidelities_cfg.items()}
163-
fidelity = ConfigSpace.Configuration(self.fidelity_space, fidelity)
164-
elif isinstance(fidelity, ConfigSpace.Configuration):
165-
fidelity = fidelity
166-
else:
167-
fidelity = None
168-
except Exception as e:
169-
logger.error('Error during the conversion of the provided fidelities '
170-
'into a FidelitySpace (ConfigSpace.Configuration) object')
171-
raise e
172-
173-
if fidelity is None:
174-
raise TypeError(f'Configuration has to be from type np.ndarray, dict, or ConfigSpace.Configuration but '
175-
f'was {type(configuration)}')
176-
177-
# Ensure that the extracted fidelity values play well with the defined fidelity space
178-
self.fidelity_space.check_configuration(fidelity)
179-
180-
# All benchmarks should work on dictionaries. Cast the fidelity space object to a dictionary.
181-
fidelity = fidelity.get_dictionary()
182-
183-
return foo(self, configuration, fidelity=fidelity, **kwargs)
184-
return wrapper
185-
186-
@staticmethod
187-
def _configuration_as_array(foo, data_type=np.float):
188-
"""
189-
Decorator to allow the first input argument to 'objective_function' to
190-
be an array.
119+
configuration = AbstractBenchmark._check_and_cast_configuration(configuration, self.configuration_space)
191120

192-
For all continuous benchmarks it is often required that the input to
193-
the benchmark can be a (NumPy) array. By adding this to the objective
194-
function, both inputs types, ConfigSpace.Configuration and array,
195-
are possible.
121+
# Second, evaluate the given fidelities.
122+
# Sanity check that there are no fidelities in **kwargs
123+
fidelity = AbstractBenchmark._check_and_cast_fidelity(fidelity, self.fidelity_space, **kwargs)
196124

197-
Can be combined with the _check_configuration decorator.
198-
"""
199-
def wrapper(self, configuration, **kwargs):
200-
if isinstance(configuration, ConfigSpace.Configuration):
201-
config_array = np.array([configuration[k] for k in configuration], dtype=data_type)
202-
else:
203-
config_array = configuration
204-
return foo(self, config_array, **kwargs)
125+
# All benchmarks should work on dictionaries. Cast the both objects to dictionaries.
126+
return wrapped_function(self, configuration.get_dictionary(), fidelity.get_dictionary(), **kwargs)
205127
return wrapper
206128

207129
@staticmethod
208-
def _configuration_as_dict(foo):
209-
"""
210-
Decorator to cast the ConfigSpace.configuration to a dictionary. This allows the first argument of
211-
objective_function and objective_function_test to be a ConfigSpace.configuration.
130+
def _check_and_cast_configuration(configuration: Union[Dict, ConfigSpace.Configuration],
131+
configuration_space: ConfigSpace.ConfigurationSpace) \
132+
-> ConfigSpace.Configuration:
133+
""" Helper-function to evaluate the given configuration.
134+
Cast it to a ConfigSpace.Configuration and evaluate if it violates some boundaries.
135+
"""
136+
137+
if isinstance(configuration, dict):
138+
configuration = ConfigSpace.Configuration(configuration_space, configuration)
139+
elif isinstance(configuration, ConfigSpace.Configuration):
140+
configuration = configuration
141+
else:
142+
raise TypeError(f'Configuration has to be from type List, np.ndarray, dict, or '
143+
f'ConfigSpace.Configuration but was {type(configuration)}')
144+
configuration_space.check_configuration(configuration)
145+
return configuration
212146

213-
Can be combined with the _check_configuration decorator.
214-
"""
215-
def wrapper(self, configuration, **kwargs):
216-
if isinstance(configuration, ConfigSpace.Configuration):
217-
configuration = configuration.get_dictionary()
218-
return foo(self, configuration, **kwargs)
219-
return wrapper
147+
@staticmethod
148+
def _check_and_cast_fidelity(fidelity: Union[dict, ConfigSpace.Configuration, None],
149+
fidelity_space: ConfigSpace.ConfigurationSpace, **kwargs) \
150+
-> ConfigSpace.Configuration:
151+
""" Helper-function to evaluate the given fidelity object.
152+
Similar to the checking and casting from above, we validate the fidelity object. To do so, we cast it to a
153+
ConfigSpace.Configuration object.
154+
If the fidelity is not specified (None), then we use the default fidelity of the benchmark.
155+
If the benchmark is a multi-multi-fidelity benchmark and only a subset of the available fidelities is
156+
specified, we fill the missing ones with their default values.
157+
"""
158+
# Make a check, that no fidelities are in the kwargs.
159+
f_in_kwargs = []
160+
for f in fidelity_space.get_hyperparameters():
161+
if f.name in kwargs:
162+
f_in_kwargs.append(f.name)
163+
if len(f_in_kwargs) != 0:
164+
raise ValueError(f'Fidelity parameters {", ".join(f_in_kwargs)} should not be part of kwargs\n'
165+
f'Fidelity: {fidelity}\n Kwargs: {kwargs}')
166+
167+
default_fidelities = fidelity_space.get_default_configuration()
168+
169+
if fidelity is None:
170+
fidelity = default_fidelities
171+
if isinstance(fidelity, dict):
172+
default_fidelities_cfg = default_fidelities.get_dictionary()
173+
fidelity_copy = fidelity.copy()
174+
fidelity = {k: fidelity_copy.pop(k, v) for k, v in default_fidelities_cfg.items()}
175+
assert len(fidelity_copy) == 0, 'Provided fidelity dict contained unknown fidelity ' \
176+
f'values: {fidelity_copy.keys()}'
177+
fidelity = ConfigSpace.Configuration(fidelity_space, fidelity)
178+
elif isinstance(fidelity, ConfigSpace.Configuration):
179+
fidelity = fidelity
180+
else:
181+
raise TypeError(f'Fidelity has to be an instance of type None, dict, or '
182+
f'ConfigSpace.Configuration but was {type(fidelity)}')
183+
# Ensure that the extracted fidelity values play well with the defined fidelity space
184+
fidelity_space.check_configuration(fidelity)
185+
return fidelity
220186

221187
def __call__(self, configuration: Dict, **kwargs) -> float:
222188
""" Provides interface to use, e.g., SciPy optimizers """
@@ -240,9 +206,12 @@ def get_configuration_space(seed: Union[int, None] = None) -> ConfigSpace.Config
240206

241207
@staticmethod
242208
@abc.abstractmethod
243-
def get_fidelity_space() -> ConfigSpace.ConfigurationSpace:
209+
def get_fidelity_space(seed: Union[int, None] = None) -> ConfigSpace.ConfigurationSpace:
244210
""" Defines the available fidelity parameters as a "fidelity space" for each benchmark.
245-
211+
Parameters
212+
----------
213+
seed: int, None
214+
Seed for the fidelity space.
246215
Returns
247216
-------
248217
ConfigSpace.ConfigurationSpace

0 commit comments

Comments
 (0)