2
2
3
3
import abc
4
4
from typing import Union , Dict
5
+ import functools
5
6
6
7
import logging
7
8
import ConfigSpace
12
13
logger = logging .getLogger ('AbstractBenchmark' )
13
14
14
15
15
- class AbstractBenchmark (object , metaclass = abc .ABCMeta ):
16
+ class AbstractBenchmark (abc . ABC , metaclass = abc .ABCMeta ):
16
17
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 ):
18
19
"""
19
20
Interface for benchmarks.
20
21
@@ -38,9 +39,10 @@ def __init__(self, rng: Union[int, np.random.RandomState, None] = None):
38
39
self .fidelity_space = self .get_fidelity_space ()
39
40
40
41
@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 ,
42
44
rng : Union [np .random .RandomState , int , None ] = None ,
43
- * args , ** kwargs ) -> dict :
45
+ ** kwargs ) -> Dict :
44
46
"""
45
47
Objective function.
46
48
@@ -67,12 +69,13 @@ def objective_function(self, configuration: Dict, fidelity: Union[Dict, None] =
67
69
Dict
68
70
Must contain at least the key `function_value` and `cost`.
69
71
"""
70
- pass
72
+ NotImplementedError ()
71
73
72
74
@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 ,
74
77
rng : Union [np .random .RandomState , int , None ] = None ,
75
- * args , * *kwargs ) -> Dict :
78
+ ** kwargs ) -> Dict :
76
79
"""
77
80
If there is a different objective function for offline testing, e.g
78
81
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
91
94
Dict
92
95
Must contain at least the key `function_value` and `cost`.
93
96
"""
94
- pass
97
+ NotImplementedError ()
95
98
96
99
@staticmethod
97
- def _check_configuration ( foo ):
100
+ def check_parameters ( wrapped_function ):
98
101
"""
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.
100
104
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.
103
107
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.
130
110
131
- @staticmethod
132
- def _check_fidelity (foo ):
111
+ We cast them to a ConfigSpace.Configuration object to ensure that no conditions are violated.
133
112
"""
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.
138
113
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 ],
146
117
fidelity : Union [Dict , ConfigSpace .Configuration , None ] = None , ** kwargs ):
147
118
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 )
191
120
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 )
196
124
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 )
205
127
return wrapper
206
128
207
129
@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
212
146
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
220
186
221
187
def __call__ (self , configuration : Dict , ** kwargs ) -> float :
222
188
""" Provides interface to use, e.g., SciPy optimizers """
@@ -240,9 +206,12 @@ def get_configuration_space(seed: Union[int, None] = None) -> ConfigSpace.Config
240
206
241
207
@staticmethod
242
208
@abc .abstractmethod
243
- def get_fidelity_space () -> ConfigSpace .ConfigurationSpace :
209
+ def get_fidelity_space (seed : Union [ int , None ] = None ) -> ConfigSpace .ConfigurationSpace :
244
210
""" 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.
246
215
Returns
247
216
-------
248
217
ConfigSpace.ConfigurationSpace
0 commit comments