Passed
Push — master ( 73f6be...3a14a5 )
by Simon
01:53
created

_BaseGFOadapter._solve()   A

Complexity

Conditions 2

Size

Total Lines 30
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 30
rs 9.8
c 0
b 0
f 0
cc 2
nop 3
1
"""Adapter for gfo package."""
2
3
# copyright: hyperactive developers, MIT License (see LICENSE file)
4
5
from skbase.utils.stdout_mute import StdoutMute
6
7
from hyperactive.base import BaseOptimizer
8
9
__all__ = ["_BaseGFOadapter"]
10
11
12
class _BaseGFOadapter(BaseOptimizer):
13
    """Adapter base class for gradient-free-optimizers.
14
15
    * default tag setting
16
    * default _run method
17
    * default get_search_config
18
    * default get_test_params
19
    * Handles defaults for "initialize" parameter
20
    * extension interface: _get_gfo_class, docstring, tags
21
    """
22
23
    _tags = {
24
        "authors": "SimonBlanke",
25
        "python_dependencies": ["gradient-free-optimizers>=1.5.0"],
26
    }
27
28
    def __init__(self):
29
        super().__init__()
30
31
        if self.initialize is None:
32
            self._initialize = {"grid": 4, "random": 2, "vertices": 4}
33
        else:
34
            self._initialize = self.initialize
35
36
    def _get_gfo_class(self):
37
        """Get the GFO class to use.
38
39
        Returns
40
        -------
41
        class
42
            The GFO class to use. One of the concrete GFO classes
43
        """
44
        raise NotImplementedError("This method should be implemented in a subclass.")
45
46
    def get_search_config(self):
47
        """Get the search configuration.
48
49
        Returns
50
        -------
51
        dict with str keys
52
            The search configuration dictionary.
53
        """
54
        search_config = super().get_search_config()
55
        search_config["initialize"] = self._initialize
56
        del search_config["verbose"]
57
58
        search_config = self._handle_gfo_defaults(search_config)
59
60
        search_config["search_space"] = self._to_dict_np(search_config["search_space"])
61
62
        return search_config
63
64
    def _handle_gfo_defaults(self, search_config):
65
        """Handle default values for GFO search configuration.
66
67
        Temporary measure until GFO handles defaults gracefully.
68
69
        Parameters
70
        ----------
71
        search_config : dict with str keys
72
            The search configuration dictionary to handle defaults for.
73
74
        Returns
75
        -------
76
        search_config : dict with str keys
77
            The search configuration dictionary with defaults handled.
78
        """
79
        if "sampling" in search_config and search_config["sampling"] is None:
80
            search_config["sampling"] = {"random": 1000000}
81
82
        if "tree_para" in search_config and search_config["tree_para"] is None:
83
            search_config["tree_para"] = {"n_estimators": 100}
84
85
        return search_config
86
87
    def _to_dict_np(self, search_space):
88
        """Coerce the search space to a format suitable for gfo optimizers.
89
90
        gfo expects dicts of numpy arrays, not lists.
91
        This method coerces lists or tuples in the search space to numpy arrays.
92
93
        Parameters
94
        ----------
95
        search_space : dict with str keys and iterable values
96
            The search space to coerce.
97
98
        Returns
99
        -------
100
        dict with str keys and 1D numpy arrays as values
101
            The coerced search space.
102
        """
103
        import numpy as np
104
105
        def coerce_to_numpy(arr):
106
            """Coerce a list or tuple to a numpy array."""
107
            if not isinstance(arr, np.ndarray):
108
                return np.array(arr)
109
            return arr
110
111
        coerced_search_space = {k: coerce_to_numpy(v) for k, v in search_space.items()}
112
        return coerced_search_space
113
114
    def _solve(self, experiment, **search_config):
115
        """Run the optimization search process.
116
117
        Parameters
118
        ----------
119
        experiment : BaseExperiment
120
            The experiment to optimize parameters for.
121
        search_config : dict with str keys
122
            identical to return of ``get_search_config``.
123
124
        Returns
125
        -------
126
        dict with str keys
127
            The best parameters found during the search.
128
            Must have keys a subset or identical to experiment.paramnames().
129
        """
130
        n_iter = search_config.pop("n_iter", 100)
131
        max_time = search_config.pop("max_time", None)
132
133
        gfo_cls = self._get_gfo_class()
134
        gfopt = gfo_cls(**search_config)
135
136
        with StdoutMute(active=not self.verbose):
137
            gfopt.search(
138
                objective_function=experiment.score,
139
                n_iter=n_iter,
140
                max_time=max_time,
141
            )
142
        best_params = gfopt.best_para
143
        return best_params
144
145
    @classmethod
146
    def get_test_params(cls, parameter_set="default"):
147
        """Return testing parameter settings for the skbase object.
148
149
        ``get_test_params`` is a unified interface point to store
150
        parameter settings for testing purposes. This function is also
151
        used in ``create_test_instance`` and ``create_test_instances_and_names``
152
        to construct test instances.
153
154
        ``get_test_params`` should return a single ``dict``, or a ``list`` of ``dict``.
155
156
        Each ``dict`` is a parameter configuration for testing,
157
        and can be used to construct an "interesting" test instance.
158
        A call to ``cls(**params)`` should
159
        be valid for all dictionaries ``params`` in the return of ``get_test_params``.
160
161
        The ``get_test_params`` need not return fixed lists of dictionaries,
162
        it can also return dynamic or stochastic parameter settings.
163
164
        Parameters
165
        ----------
166
        parameter_set : str, default="default"
167
            Name of the set of test parameters to return, for use in tests. If no
168
            special parameters are defined for a value, will return `"default"` set.
169
170
        Returns
171
        -------
172
        params : dict or list of dict, default = {}
173
            Parameters to create testing instances of the class
174
            Each dict are parameters to construct an "interesting" test instance, i.e.,
175
            `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
176
            `create_test_instance` uses the first (or only) dictionary in `params`
177
        """
178
        import numpy as np
179
180
        from hyperactive.experiment.integrations import SklearnCvExperiment
181
182
        sklearn_exp = SklearnCvExperiment.create_test_instance()
183
        params_sklearn = {
184
            "experiment": sklearn_exp,
185
            "search_space": {
186
                "C": np.array([0.01, 0.1, 1, 10]),
187
                "gamma": np.array([0.0001, 0.01, 0.1, 1, 10]),
188
            },
189
            "n_iter": 100,
190
        }
191
192
        from hyperactive.experiment.toy import Ackley
193
194
        ackley_exp = Ackley.create_test_instance()
195
        params_ackley = {
196
            "experiment": ackley_exp,
197
            "search_space": {
198
                "x0": np.linspace(-5, 5, 10),
199
                "x1": np.linspace(-5, 5, 10),
200
            },
201
            "n_iter": 100,
202
        }
203
        params_ackley_list = {
204
            "experiment": ackley_exp,
205
            "search_space": {
206
                "x0": list(np.linspace(-5, 5, 10)),
207
                "x1": list(np.linspace(-5, 5, 10)),
208
            },
209
            "n_iter": 100,
210
        }
211
        return [params_sklearn, params_ackley, params_ackley_list]
212