Passed
Pull Request — master (#110)
by
unknown
01:48
created

GridSearch._check_param_grid()   C

Complexity

Conditions 9

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 13
nop 2
dl 0
loc 21
rs 6.6666
c 0
b 0
f 0
1
"""Grid search optimizer."""
2
3
from collections.abc import Sequence
4
5
import numpy as np
6
7
from sklearn.model_selection import ParameterGrid, ParameterSampler, check_cv
8
9
from hyperactive.base import BaseOptimizer
10
11
12
class GridSearch(BaseOptimizer):
13
    """Grid search optimizer.
14
15
    Parameters
16
    ----------
17
    experiment : BaseExperiment, optional
18
        The experiment to optimize parameters for.
19
        Optional, can be passed later in ``add_search``.
20
    param_grid : dict[str, list]
21
        The search space to explore. A dictionary with parameter
22
        names as keys and a numpy array as values.
23
    error_score : float, default=np.nan
24
        The score to assign if an error occurs during the evaluation of a parameter set.
25
26
    Example
27
    -------
28
    Grid search applied to scikit-learn parameter tuning:
29
30
    1. defining the experiment to optimize:
31
    >>> from hyperactive.experiment.integrations import SklearnCvExperiment
32
    >>> from sklearn.datasets import load_iris
33
    >>> from sklearn.svm import SVC
34
    >>>
35
    >>> X, y = load_iris(return_X_y=True)
36
    >>>
37
    >>> sklearn_exp = SklearnCvExperiment(
38
    ...     estimator=SVC(),
39
    ...     X=X,
40
    ...     y=y,
41
    ... )
42
43
    2. setting up the grid search optimizer:
44
    >>> from hyperactive.opt import GridSearch
45
    >>> param_grid = {
46
    ...     "C": [0.01, 0.1, 1, 10],
47
    ...     "gamma": [0.0001, 0.01, 0.1, 1, 10],
48
    ... }
49
    >>> grid_search = GridSearch(sklearn_exp, param_grid=param_grid)
50
51
    3. running the grid search:
52
    >>> best_params = grid_search.run()
53
54
    Best parameters can also be accessed via the attributes:
55
    >>> best_params = grid_search.best_params_
56
    """
57
58
    def __init__(
59
        self,
60
        experiment=None,
61
        param_grid=None,
62
        error_score=np.nan,
63
    ):
64
        self.experiment = experiment
65
        self.param_grid = param_grid
66
        self.error_score = error_score
67
68
        super().__init__()
69
70
    def _check_param_grid(self, param_grid):
71
        """_check_param_grid from sklearn 1.0.2, before it was removed."""
72
        if hasattr(param_grid, "items"):
73
            param_grid = [param_grid]
74
75
        for p in param_grid:
76
            for name, v in p.items():
77
                if isinstance(v, np.ndarray) and v.ndim > 1:
78
                    raise ValueError("Parameter array should be one-dimensional.")
79
80
                if isinstance(v, str) or not isinstance(v, (np.ndarray, Sequence)):
81
                    raise ValueError(
82
                        f"Parameter grid for parameter ({name}) needs to"
83
                        f" be a list or numpy array, but got ({type(v)})."
84
                        " Single values need to be wrapped in a list"
85
                        " with one element."
86
                    )
87
88
                if len(v) == 0:
89
                    raise ValueError(
90
                        f"Parameter values for parameter ({name}) need "
91
                        "to be a non-empty sequence."
92
                    )
93
94
    def _run(self, experiment, param_grid, error_score):
95
        """Run the optimization search process."""
96
        self._check_param_grid(param_grid)
97
        candidate_params = list(ParameterGrid(param_grid))
98
99
        scores = []
100
        for candidate_param in candidate_params:
101
            try:
102
                score = experiment(**candidate_param)
103
            except Exception:  # noqa: B904
104
                # Catch all exceptions and assign error_score
105
                score = error_score
106
            scores.append(score)
107
108
        best_index = np.argmin(scores)
109
        best_params = candidate_params[best_index]
110
111
        self.best_index_ = best_index
112
        self.best_params_ = best_params
113
        self.best_score_ = scores[best_index]
114
115
        return best_params
116
117
    @classmethod
118
    def get_test_params(cls, parameter_set="default"):
119
        """Return testing parameter settings for the skbase object.
120
121
        ``get_test_params`` is a unified interface point to store
122
        parameter settings for testing purposes. This function is also
123
        used in ``create_test_instance`` and ``create_test_instances_and_names``
124
        to construct test instances.
125
126
        ``get_test_params`` should return a single ``dict``, or a ``list`` of ``dict``.
127
128
        Each ``dict`` is a parameter configuration for testing,
129
        and can be used to construct an "interesting" test instance.
130
        A call to ``cls(**params)`` should
131
        be valid for all dictionaries ``params`` in the return of ``get_test_params``.
132
133
        The ``get_test_params`` need not return fixed lists of dictionaries,
134
        it can also return dynamic or stochastic parameter settings.
135
136
        Parameters
137
        ----------
138
        parameter_set : str, default="default"
139
            Name of the set of test parameters to return, for use in tests. If no
140
            special parameters are defined for a value, will return `"default"` set.
141
142
        Returns
143
        -------
144
        params : dict or list of dict, default = {}
145
            Parameters to create testing instances of the class
146
            Each dict are parameters to construct an "interesting" test instance, i.e.,
147
            `MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
148
            `create_test_instance` uses the first (or only) dictionary in `params`
149
        """
150
        from hyperactive.experiment.integrations import SklearnCvExperiment
151
152
        sklearn_exp = SklearnCvExperiment.create_test_instance()
153
        param_grid = {
154
            "C": [0.01, 0.1, 1, 10],
155
            "gamma": [0.0001, 0.01, 0.1, 1, 10],
156
        }
157
        params_sklearn = {
158
            "experiment": sklearn_exp,
159
            "param_grid": param_grid,
160
        }
161
162
        from hyperactive.experiment.toy import Ackley
163
164
        ackley_exp = Ackley.create_test_instance()
165
        param_grid = {
166
            "x0": np.linspace(-5, 5, 10),
167
            "x1": np.linspace(-5, 5, 10),
168
        }
169
        params_ackley = {
170
            "experiment": ackley_exp,
171
            "param_grid": param_grid,
172
        }
173
        
174
        return [params_sklearn, params_ackley]
175