CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
test_search.py2967 linesDownload Raw Back to tests
1"""Test the search module"""
2
3import pickle
4import re
5import sys
6import warnings
7from collections.abc import Iterable, Sized
8from functools import partial
9from io import StringIO
10from itertools import chain, product
11from types import GeneratorType
12
13import numpy as np
14import pytest
15from scipy.stats import bernoulli, expon, uniform
16
17from sklearn import config_context
18from sklearn.base import BaseEstimator, ClassifierMixin, clone, is_classifier
19from sklearn.cluster import KMeans
20from sklearn.compose import ColumnTransformer
21from sklearn.datasets import (
22    make_blobs,
23    make_classification,
24    make_multilabel_classification,
25)
26from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
27from sklearn.dummy import DummyClassifier
28from sklearn.ensemble import HistGradientBoostingClassifier
29from sklearn.exceptions import FitFailedWarning
30from sklearn.experimental import enable_halving_search_cv  # noqa: F401
31from sklearn.feature_extraction.text import TfidfVectorizer
32from sklearn.impute import SimpleImputer
33from sklearn.linear_model import (
34    LinearRegression,
35    LogisticRegression,
36    Ridge,
37    SGDClassifier,
38)
39from sklearn.metrics import (
40    accuracy_score,
41    confusion_matrix,
42    f1_score,
43    make_scorer,
44    r2_score,
45    recall_score,
46    roc_auc_score,
47)
48from sklearn.metrics.pairwise import euclidean_distances
49from sklearn.model_selection import (
50    GridSearchCV,
51    GroupKFold,
52    GroupShuffleSplit,
53    HalvingGridSearchCV,
54    KFold,
55    LeaveOneGroupOut,
56    LeavePGroupsOut,
57    ParameterGrid,
58    ParameterSampler,
59    RandomizedSearchCV,
60    StratifiedKFold,
61    StratifiedShuffleSplit,
62    train_test_split,
63)
64from sklearn.model_selection._search import (
65    BaseSearchCV,
66    _yield_masked_array_for_each_param,
67)
68from sklearn.model_selection.tests.common import OneTimeSplitter
69from sklearn.naive_bayes import ComplementNB
70from sklearn.neighbors import KernelDensity, KNeighborsClassifier, LocalOutlierFactor
71from sklearn.pipeline import Pipeline, make_pipeline
72from sklearn.preprocessing import (
73    OneHotEncoder,
74    OrdinalEncoder,
75    SplineTransformer,
76    StandardScaler,
77)
78from sklearn.svm import SVC, LinearSVC
79from sklearn.tests.metadata_routing_common import (
80    ConsumingScorer,
81    _Registry,
82    check_recorded_metadata,
83)
84from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
85from sklearn.utils._array_api import (
86    _get_namespace_device_dtype_ids,
87    yield_namespace_device_dtype_combinations,
88)
89from sklearn.utils._mocking import CheckingClassifier, MockDataFrame
90from sklearn.utils._testing import (
91    MinimalClassifier,
92    MinimalRegressor,
93    MinimalTransformer,
94    _array_api_for_tests,
95    assert_allclose,
96    assert_allclose_dense_sparse,
97    assert_almost_equal,
98    assert_array_almost_equal,
99    assert_array_equal,
100    set_random_state,
101)
102from sklearn.utils.estimator_checks import _enforce_estimator_tags_y
103from sklearn.utils.fixes import CSR_CONTAINERS
104from sklearn.utils.validation import _num_samples
105
106
107# Neither of the following two estimators inherit from BaseEstimator,
108# to test hyperparameter search on user-defined classifiers.
109class MockClassifier(ClassifierMixin, BaseEstimator):
110    """Dummy classifier to test the parameter search algorithms"""
111
112    def __init__(self, foo_param=0):
113        self.foo_param = foo_param
114
115    def fit(self, X, Y):
116        assert len(X) == len(Y)
117        self.classes_ = np.unique(Y)
118        return self
119
120    def predict(self, T):
121        return T.shape[0]
122
123    def transform(self, X):
124        return X + self.foo_param
125
126    def inverse_transform(self, X):
127        return X - self.foo_param
128
129    predict_proba = predict
130    predict_log_proba = predict
131    decision_function = predict
132
133    def score(self, X=None, Y=None):
134        if self.foo_param > 1:
135            score = 1.0
136        else:
137            score = 0.0
138        return score
139
140    def get_params(self, deep=False):
141        return {"foo_param": self.foo_param}
142
143    def set_params(self, **params):
144        self.foo_param = params["foo_param"]
145        return self
146
147
148class LinearSVCNoScore(LinearSVC):
149    """A LinearSVC classifier that has no score method."""
150
151    @property
152    def score(self):
153        raise AttributeError
154
155
156X = np.array([[-1, -1], [-2, -1], [1, 1], [2, 1]])
157y = np.array([1, 1, 2, 2])
158
159
160def assert_grid_iter_equals_getitem(grid):
161    assert list(grid) == [grid[i] for i in range(len(grid))]
162
163
164@pytest.mark.parametrize("klass", [ParameterGrid, partial(ParameterSampler, n_iter=10)])
165@pytest.mark.parametrize(
166    "input, error_type, error_message",
167    [
168        (0, TypeError, r"Parameter .* a dict or a list, got: 0 of type int"),
169        ([{"foo": [0]}, 0], TypeError, r"Parameter .* is not a dict \(0\)"),
170        (
171            {"foo": 0},
172            TypeError,
173            r"Parameter (grid|distribution) for parameter 'foo' (is not|needs to be) "
174            r"(a list or a numpy array|iterable or a distribution).*",
175        ),
176    ],
177)
178def test_validate_parameter_input(klass, input, error_type, error_message):
179    with pytest.raises(error_type, match=error_message):
180        klass(input)
181
182
183def test_parameter_grid():
184    # Test basic properties of ParameterGrid.
185    params1 = {"foo": [1, 2, 3]}
186    grid1 = ParameterGrid(params1)
187    assert isinstance(grid1, Iterable)
188    assert isinstance(grid1, Sized)
189    assert len(grid1) == 3
190    assert_grid_iter_equals_getitem(grid1)
191
192    params2 = {"foo": [4, 2], "bar": ["ham", "spam", "eggs"]}
193    grid2 = ParameterGrid(params2)
194    assert len(grid2) == 6
195
196    # loop to assert we can iterate over the grid multiple times
197    for i in range(2):
198        # tuple + chain transforms {"a": 1, "b": 2} to ("a", 1, "b", 2)
199        points = set(tuple(chain(*(sorted(p.items())))) for p in grid2)
200        assert points == set(
201            ("bar", x, "foo", y) for x, y in product(params2["bar"], params2["foo"])
202        )
203    assert_grid_iter_equals_getitem(grid2)
204
205    # Special case: empty grid (useful to get default estimator settings)
206    empty = ParameterGrid({})
207    assert len(empty) == 1
208    assert list(empty) == [{}]
209    assert_grid_iter_equals_getitem(empty)
210    with pytest.raises(IndexError):
211        empty[1]
212
213    has_empty = ParameterGrid([{"C": [1, 10]}, {}, {"C": [0.5]}])
214    assert len(has_empty) == 4
215    assert list(has_empty) == [{"C": 1}, {"C": 10}, {}, {"C": 0.5}]
216    assert_grid_iter_equals_getitem(has_empty)
217
218
219def test_grid_search():
220    # Test that the best estimator contains the right value for foo_param
221    clf = MockClassifier()
222    grid_search = GridSearchCV(clf, {"foo_param": [1, 2, 3]}, cv=2, verbose=3)
223    # make sure it selects the smallest parameter in case of ties
224    old_stdout = sys.stdout
225    sys.stdout = StringIO()
226    grid_search.fit(X, y)
227    sys.stdout = old_stdout
228    assert grid_search.best_estimator_.foo_param == 2
229
230    assert_array_equal(grid_search.cv_results_["param_foo_param"].data, [1, 2, 3])
231
232    # Smoke test the score etc:
233    grid_search.score(X, y)
234    grid_search.predict_proba(X)
235    grid_search.decision_function(X)
236    grid_search.transform(X)
237
238    # Test exception handling on scoring
239    grid_search.scoring = "sklearn"
240    with pytest.raises(ValueError):
241        grid_search.fit(X, y)
242
243
244def test_grid_search_pipeline_steps():
245    # check that parameters that are estimators are cloned before fitting
246    pipe = Pipeline([("regressor", LinearRegression())])
247    param_grid = {"regressor": [LinearRegression(), Ridge()]}
248    grid_search = GridSearchCV(pipe, param_grid, cv=2)
249    grid_search.fit(X, y)
250    regressor_results = grid_search.cv_results_["param_regressor"]
251    assert isinstance(regressor_results[0], LinearRegression)
252    assert isinstance(regressor_results[1], Ridge)
253    assert not hasattr(regressor_results[0], "coef_")
254    assert not hasattr(regressor_results[1], "coef_")
255    assert regressor_results[0] is not grid_search.best_estimator_
256    assert regressor_results[1] is not grid_search.best_estimator_
257    # check that we didn't modify the parameter grid that was passed
258    assert not hasattr(param_grid["regressor"][0], "coef_")
259    assert not hasattr(param_grid["regressor"][1], "coef_")
260
261
262@pytest.mark.parametrize("SearchCV", [GridSearchCV, RandomizedSearchCV])
263def test_SearchCV_with_fit_params(SearchCV):
264    X = np.arange(100).reshape(10, 10)
265    y = np.array([0] * 5 + [1] * 5)
266    clf = CheckingClassifier(expected_fit_params=["spam", "eggs"])
267    searcher = SearchCV(clf, {"foo_param": [1, 2, 3]}, cv=2, error_score="raise")
268
269    # The CheckingClassifier generates an assertion error if
270    # a parameter is missing or has length != len(X).
271    err_msg = r"Expected fit parameter\(s\) \['eggs'\] not seen."
272    with pytest.raises(AssertionError, match=err_msg):
273        searcher.fit(X, y, spam=np.ones(10))
274
275    err_msg = "Fit parameter spam has length 1; expected"
276    with pytest.raises(AssertionError, match=err_msg):
277        searcher.fit(X, y, spam=np.ones(1), eggs=np.zeros(10))
278    searcher.fit(X, y, spam=np.ones(10), eggs=np.zeros(10))
279
280
281def test_grid_search_no_score():
282    # Test grid-search on classifier that has no score function.
283    clf = LinearSVC(random_state=0)
284    X, y = make_blobs(random_state=0, centers=2)
285    Cs = [0.1, 1, 10]
286    clf_no_score = LinearSVCNoScore(random_state=0)
287    grid_search = GridSearchCV(clf, {"C": Cs}, scoring="accuracy")
288    grid_search.fit(X, y)
289
290    grid_search_no_score = GridSearchCV(clf_no_score, {"C": Cs}, scoring="accuracy")
291    # smoketest grid search
292    grid_search_no_score.fit(X, y)
293
294    # check that best params are equal
295    assert grid_search_no_score.best_params_ == grid_search.best_params_
296    # check that we can call score and that it gives the correct result
297    assert grid_search.score(X, y) == grid_search_no_score.score(X, y)
298
299    # giving no scoring function raises an error
300    grid_search_no_score = GridSearchCV(clf_no_score, {"C": Cs})
301    with pytest.raises(TypeError, match="no scoring"):
302        grid_search_no_score.fit([[1]])
303
304
305def test_grid_search_score_method():
306    X, y = make_classification(n_samples=100, n_classes=2, flip_y=0.2, random_state=0)
307    clf = LinearSVC(random_state=0)
308    grid = {"C": [0.1]}
309
310    search_no_scoring = GridSearchCV(clf, grid, scoring=None).fit(X, y)
311    search_accuracy = GridSearchCV(clf, grid, scoring="accuracy").fit(X, y)
312    search_no_score_method_auc = GridSearchCV(
313        LinearSVCNoScore(), grid, scoring="roc_auc"
314    ).fit(X, y)
315    search_auc = GridSearchCV(clf, grid, scoring="roc_auc").fit(X, y)
316
317    # Check warning only occurs in situation where behavior changed:
318    # estimator requires score method to compete with scoring parameter
319    score_no_scoring = search_no_scoring.score(X, y)
320    score_accuracy = search_accuracy.score(X, y)
321    score_no_score_auc = search_no_score_method_auc.score(X, y)
322    score_auc = search_auc.score(X, y)
323
324    # ensure the test is sane
325    assert score_auc < 1.0
326    assert score_accuracy < 1.0
327    assert score_auc != score_accuracy
328
329    assert_almost_equal(score_accuracy, score_no_scoring)
330    assert_almost_equal(score_auc, score_no_score_auc)
331
332
333def test_grid_search_groups():
334    # Check if ValueError (when groups is None) propagates to GridSearchCV
335    # And also check if groups is correctly passed to the cv object
336    rng = np.random.RandomState(0)
337
338    X, y = make_classification(n_samples=15, n_classes=2, random_state=0)
339    groups = rng.randint(0, 3, 15)
340
341    clf = LinearSVC(random_state=0)
342    grid = {"C": [1]}
343
344    group_cvs = [
345        LeaveOneGroupOut(),
346        LeavePGroupsOut(2),
347        GroupKFold(n_splits=3),
348        GroupShuffleSplit(),
349    ]
350    error_msg = "The 'groups' parameter should not be None."
351    for cv in group_cvs:
352        gs = GridSearchCV(clf, grid, cv=cv)
353        with pytest.raises(ValueError, match=error_msg):
354            gs.fit(X, y)
355        gs.fit(X, y, groups=groups)
356
357    non_group_cvs = [StratifiedKFold(), StratifiedShuffleSplit()]
358    for cv in non_group_cvs:
359        gs = GridSearchCV(clf, grid, cv=cv)
360        # Should not raise an error
361        gs.fit(X, y)
362
363
364def test_classes__property():
365    # Test that classes_ property matches best_estimator_.classes_
366    X = np.arange(100).reshape(10, 10)
367    y = np.array([0] * 5 + [1] * 5)
368    Cs = [0.1, 1, 10]
369
370    grid_search = GridSearchCV(LinearSVC(random_state=0), {"C": Cs})
371    grid_search.fit(X, y)
372    assert_array_equal(grid_search.best_estimator_.classes_, grid_search.classes_)
373
374    # Test that regressors do not have a classes_ attribute
375    grid_search = GridSearchCV(Ridge(), {"alpha": [1.0, 2.0]})
376    grid_search.fit(X, y)
377    assert not hasattr(grid_search, "classes_")
378
379    # Test that the grid searcher has no classes_ attribute before it's fit
380    grid_search = GridSearchCV(LinearSVC(random_state=0), {"C": Cs})
381    assert not hasattr(grid_search, "classes_")
382
383    # Test that the grid searcher has no classes_ attribute without a refit
384    grid_search = GridSearchCV(LinearSVC(random_state=0), {"C": Cs}, refit=False)
385    grid_search.fit(X, y)
386    assert not hasattr(grid_search, "classes_")
387
388
389def test_trivial_cv_results_attr():
390    # Test search over a "grid" with only one point.
391    clf = MockClassifier()
392    grid_search = GridSearchCV(clf, {"foo_param": [1]}, cv=2)
393    grid_search.fit(X, y)
394    assert hasattr(grid_search, "cv_results_")
395
396    random_search = RandomizedSearchCV(clf, {"foo_param": [0]}, n_iter=1, cv=2)
397    random_search.fit(X, y)
398    assert hasattr(grid_search, "cv_results_")
399
400
401def test_no_refit():
402    # Test that GSCV can be used for model selection alone without refitting
403    clf = MockClassifier()
404    for scoring in [None, ["accuracy", "precision"]]:
405        grid_search = GridSearchCV(clf, {"foo_param": [1, 2, 3]}, refit=False, cv=2)
406        grid_search.fit(X, y)
407        assert (
408            not hasattr(grid_search, "best_estimator_")
409            and hasattr(grid_search, "best_index_")
410            and hasattr(grid_search, "best_params_")
411        )
412
413        # Make sure the functions predict/transform etc. raise meaningful
414        # error messages
415        for fn_name in (
416            "predict",
417            "predict_proba",
418            "predict_log_proba",
419            "transform",
420            "inverse_transform",
421        ):
422            outer_msg = f"has no attribute '{fn_name}'"
423            inner_msg = (
424                f"`refit=False`. {fn_name} is available only after "
425                "refitting on the best parameters"
426            )
427            with pytest.raises(AttributeError, match=outer_msg) as exec_info:
428                getattr(grid_search, fn_name)(X)
429
430            assert isinstance(exec_info.value.__cause__, AttributeError)
431            assert inner_msg in str(exec_info.value.__cause__)
432
433    # Test that an invalid refit param raises appropriate error messages
434    error_msg = (
435        "For multi-metric scoring, the parameter refit must be set to a scorer key"
436    )
437    for refit in [True, "recall", "accuracy"]:
438        with pytest.raises(ValueError, match=error_msg):
439            GridSearchCV(
440                clf, {}, refit=refit, scoring={"acc": "accuracy", "prec": "precision"}
441            ).fit(X, y)
442
443
444def test_grid_search_error():
445    # Test that grid search will capture errors on data with different length
446    X_, y_ = make_classification(n_samples=200, n_features=100, random_state=0)
447
448    clf = LinearSVC()
449    cv = GridSearchCV(clf, {"C": [0.1, 1.0]})
450    with pytest.raises(ValueError):
451        cv.fit(X_[:180], y_)
452
453
454def test_grid_search_one_grid_point():
455    X_, y_ = make_classification(n_samples=200, n_features=100, random_state=0)
456    param_dict = {"C": [1.0], "kernel": ["rbf"], "gamma": [0.1]}
457
458    clf = SVC(gamma="auto")
459    cv = GridSearchCV(clf, param_dict)
460    cv.fit(X_, y_)
461
462    clf = SVC(C=1.0, kernel="rbf", gamma=0.1)
463    clf.fit(X_, y_)
464
465    assert_array_equal(clf.dual_coef_, cv.best_estimator_.dual_coef_)
466
467
468def test_grid_search_when_param_grid_includes_range():
469    # Test that the best estimator contains the right value for foo_param
470    clf = MockClassifier()
471    grid_search = None
472    grid_search = GridSearchCV(clf, {"foo_param": range(1, 4)}, cv=2)
473    grid_search.fit(X, y)
474    assert grid_search.best_estimator_.foo_param == 2
475
476
477def test_grid_search_bad_param_grid():
478    X, y = make_classification(n_samples=10, n_features=5, random_state=0)
479    param_dict = {"C": 1}
480    clf = SVC(gamma="auto")
481    error_msg = re.escape(
482        "Parameter grid for parameter 'C' needs to be a list or "
483        "a numpy array, but got 1 (of type int) instead. Single "
484        "values need to be wrapped in a list with one element."
485    )
486    search = GridSearchCV(clf, param_dict)
487    with pytest.raises(TypeError, match=error_msg):
488        search.fit(X, y)
489
490    param_dict = {"C": []}
491    clf = SVC()
492    error_msg = re.escape(
493        "Parameter grid for parameter 'C' need to be a non-empty sequence, got: []"
494    )
495    search = GridSearchCV(clf, param_dict)
496    with pytest.raises(ValueError, match=error_msg):
497        search.fit(X, y)
498
499    param_dict = {"C": "1,2,3"}
500    clf = SVC(gamma="auto")
501    error_msg = re.escape(
502        "Parameter grid for parameter 'C' needs to be a list or a numpy array, "
503        "but got '1,2,3' (of type str) instead. Single values need to be "
504        "wrapped in a list with one element."
505    )
506    search = GridSearchCV(clf, param_dict)
507    with pytest.raises(TypeError, match=error_msg):
508        search.fit(X, y)
509
510    param_dict = {"C": np.ones((3, 2))}
511    clf = SVC()
512    search = GridSearchCV(clf, param_dict)
513    with pytest.raises(ValueError):
514        search.fit(X, y)
515
516
517@pytest.mark.parametrize("csr_container", CSR_CONTAINERS)
518def test_grid_search_sparse(csr_container):
519    # Test that grid search works with both dense and sparse matrices
520    X_, y_ = make_classification(n_samples=200, n_features=100, random_state=0)
521
522    clf = LinearSVC()
523    cv = GridSearchCV(clf, {"C": [0.1, 1.0]})
524    cv.fit(X_[:180], y_[:180])
525    y_pred = cv.predict(X_[180:])
526    C = cv.best_estimator_.C
527
528    X_ = csr_container(X_)
529    clf = LinearSVC()
530    cv = GridSearchCV(clf, {"C": [0.1, 1.0]})
531    cv.fit(X_[:180].tocoo(), y_[:180])
532    y_pred2 = cv.predict(X_[180:])
533    C2 = cv.best_estimator_.C
534
535    assert np.mean(y_pred == y_pred2) >= 0.9
536    assert C == C2
537
538
539@pytest.mark.parametrize("csr_container", CSR_CONTAINERS)
540def test_grid_search_sparse_scoring(csr_container):
541    X_, y_ = make_classification(n_samples=200, n_features=100, random_state=0)
542
543    clf = LinearSVC()
544    cv = GridSearchCV(clf, {"C": [0.1, 1.0]}, scoring="f1")
545    cv.fit(X_[:180], y_[:180])
546    y_pred = cv.predict(X_[180:])
547    C = cv.best_estimator_.C
548
549    X_ = csr_container(X_)
550    clf = LinearSVC()
551    cv = GridSearchCV(clf, {"C": [0.1, 1.0]}, scoring="f1")
552    cv.fit(X_[:180], y_[:180])
553    y_pred2 = cv.predict(X_[180:])
554    C2 = cv.best_estimator_.C
555
556    assert_array_equal(y_pred, y_pred2)
557    assert C == C2
558    # Smoke test the score
559    # np.testing.assert_allclose(f1_score(cv.predict(X_[:180]), y[:180]),
560    #                            cv.score(X_[:180], y[:180]))
561
562    # test loss where greater is worse
563    def f1_loss(y_true_, y_pred_):
564        return -f1_score(y_true_, y_pred_)
565
566    F1Loss = make_scorer(f1_loss, greater_is_better=False)
567    cv = GridSearchCV(clf, {"C": [0.1, 1.0]}, scoring=F1Loss)
568    cv.fit(X_[:180], y_[:180])
569    y_pred3 = cv.predict(X_[180:])
570    C3 = cv.best_estimator_.C
571
572    assert C == C3
573    assert_array_equal(y_pred, y_pred3)
574
575
576def test_grid_search_precomputed_kernel():
577    # Test that grid search works when the input features are given in the
578    # form of a precomputed kernel matrix
579    X_, y_ = make_classification(n_samples=200, n_features=100, random_state=0)
580
581    # compute the training kernel matrix corresponding to the linear kernel
582    K_train = np.dot(X_[:180], X_[:180].T)
583    y_train = y_[:180]
584
585    clf = SVC(kernel="precomputed")
586    cv = GridSearchCV(clf, {"C": [0.1, 1.0]})
587    cv.fit(K_train, y_train)
588
589    assert cv.best_score_ >= 0
590
591    # compute the test kernel matrix
592    K_test = np.dot(X_[180:], X_[:180].T)
593    y_test = y_[180:]
594
595    y_pred = cv.predict(K_test)
596
597    assert np.mean(y_pred == y_test) >= 0
598
599    # test error is raised when the precomputed kernel is not array-like
600    # or sparse
601    with pytest.raises(ValueError):
602        cv.fit(K_train.tolist(), y_train)
603
604
605def test_grid_search_precomputed_kernel_error_nonsquare():
606    # Test that grid search returns an error with a non-square precomputed
607    # training kernel matrix
608    K_train = np.zeros((10, 20))
609    y_train = np.ones((10,))
610    clf = SVC(kernel="precomputed")
611    cv = GridSearchCV(clf, {"C": [0.1, 1.0]})
612    with pytest.raises(ValueError):
613        cv.fit(K_train, y_train)
614
615
616class BrokenClassifier(BaseEstimator):
617    """Broken classifier that cannot be fit twice"""
618
619    def __init__(self, parameter=None):
620        self.parameter = parameter
621
622    def fit(self, X, y):
623        assert not hasattr(self, "has_been_fit_")
624        self.has_been_fit_ = True
625
626    def predict(self, X):
627        return np.zeros(X.shape[0])
628
629
630@pytest.mark.filterwarnings("ignore::sklearn.exceptions.UndefinedMetricWarning")
631def test_refit():
632    # Regression test for bug in refitting
633    # Simulates re-fitting a broken estimator; this used to break with
634    # sparse SVMs.
635    X = np.arange(100).reshape(10, 10)
636    y = np.array([0] * 5 + [1] * 5)
637
638    clf = GridSearchCV(
639        BrokenClassifier(), [{"parameter": [0, 1]}], scoring="precision", refit=True
640    )
641    clf.fit(X, y)
642
643
644def test_refit_callable():
645    """
646    Test refit=callable, which adds flexibility in identifying the
647    "best" estimator.
648    """
649
650    def refit_callable(cv_results):
651        """
652        A dummy function tests `refit=callable` interface.
653        Return the index of a model that has the least
654        `mean_test_score`.
655        """
656        # Fit a dummy clf with `refit=True` to get a list of keys in
657        # clf.cv_results_.
658        X, y = make_classification(n_samples=100, n_features=4, random_state=42)
659        clf = GridSearchCV(
660            LinearSVC(random_state=42),
661            {"C": [0.01, 0.1, 1]},
662            scoring="precision",
663            refit=True,
664        )
665        clf.fit(X, y)
666        # Ensure that `best_index_ != 0` for this dummy clf
667        assert clf.best_index_ != 0
668
669        # Assert every key matches those in `cv_results`
670        for key in clf.cv_results_.keys():
671            assert key in cv_results
672
673        return cv_results["mean_test_score"].argmin()
674
675    X, y = make_classification(n_samples=100, n_features=4, random_state=42)
676    clf = GridSearchCV(
677        LinearSVC(random_state=42),
678        {"C": [0.01, 0.1, 1]},
679        scoring="precision",
680        refit=refit_callable,
681    )
682    clf.fit(X, y)
683
684    assert clf.best_index_ == 0
685    # Ensure `best_score_` is disabled when using `refit=callable`
686    assert not hasattr(clf, "best_score_")
687
688
689def test_refit_callable_invalid_type():
690    """
691    Test implementation catches the errors when 'best_index_' returns an
692    invalid result.
693    """
694
695    def refit_callable_invalid_type(cv_results):
696        """
697        A dummy function tests when returned 'best_index_' is not integer.
698        """
699        return None
700
701    X, y = make_classification(n_samples=100, n_features=4, random_state=42)
702
703    clf = GridSearchCV(
704        LinearSVC(random_state=42),
705        {"C": [0.1, 1]},
706        scoring="precision",
707        refit=refit_callable_invalid_type,
708    )
709    with pytest.raises(TypeError, match="best_index_ returned is not an integer"):
710        clf.fit(X, y)
711
712
713@pytest.mark.parametrize("out_bound_value", [-1, 2])
714@pytest.mark.parametrize("search_cv", [RandomizedSearchCV, GridSearchCV])
715def test_refit_callable_out_bound(out_bound_value, search_cv):
716    """
717    Test implementation catches the errors when 'best_index_' returns an
718    out of bound result.
719    """
720
721    def refit_callable_out_bound(cv_results):
722        """
723        A dummy function tests when returned 'best_index_' is out of bounds.
724        """
725        return out_bound_value
726
727    X, y = make_classification(n_samples=100, n_features=4, random_state=42)
728
729    clf = search_cv(
730        LinearSVC(random_state=42),
731        {"C": [0.1, 1]},
732        scoring="precision",
733        refit=refit_callable_out_bound,
734    )
735    with pytest.raises(IndexError, match="best_index_ index out of range"):
736        clf.fit(X, y)
737
738
739def test_refit_callable_multi_metric():
740    """
741    Test refit=callable in multiple metric evaluation setting
742    """
743
744    def refit_callable(cv_results):
745        """
746        A dummy function tests `refit=callable` interface.
747        Return the index of a model that has the least
748        `mean_test_prec`.
749        """
750        assert "mean_test_prec" in cv_results
751        return cv_results["mean_test_prec"].argmin()
752
753    X, y = make_classification(n_samples=100, n_features=4, random_state=42)
754    scoring = {"Accuracy": make_scorer(accuracy_score), "prec": "precision"}
755    clf = GridSearchCV(
756        LinearSVC(random_state=42),
757        {"C": [0.01, 0.1, 1]},
758        scoring=scoring,
759        refit=refit_callable,
760    )
761    clf.fit(X, y)
762
763    assert clf.best_index_ == 0
764    # Ensure `best_score_` is disabled when using `refit=callable`
765    assert not hasattr(clf, "best_score_")
766
767
768def test_gridsearch_nd():
769    # Pass X as list in GridSearchCV
770    X_4d = np.arange(10 * 5 * 3 * 2).reshape(10, 5, 3, 2)
771    y_3d = np.arange(10 * 7 * 11).reshape(10, 7, 11)
772
773    def check_X(x):
774        return x.shape[1:] == (5, 3, 2)
775
776    def check_y(x):
777        return x.shape[1:] == (7, 11)
778
779    clf = CheckingClassifier(
780        check_X=check_X,
781        check_y=check_y,
782        methods_to_check=["fit"],
783    )
784    grid_search = GridSearchCV(clf, {"foo_param": [1, 2, 3]})
785    grid_search.fit(X_4d, y_3d).score(X, y)
786    assert hasattr(grid_search, "cv_results_")
787
788
789def test_X_as_list():
790    # Pass X as list in GridSearchCV
791    X = np.arange(100).reshape(10, 10)
792    y = np.array([0] * 5 + [1] * 5)
793
794    clf = CheckingClassifier(
795        check_X=lambda x: isinstance(x, list),
796        methods_to_check=["fit"],
797    )
798    cv = KFold(n_splits=3)
799    grid_search = GridSearchCV(clf, {"foo_param": [1, 2, 3]}, cv=cv)
800    grid_search.fit(X.tolist(), y).score(X, y)
801    assert hasattr(grid_search, "cv_results_")
802
803
804def test_y_as_list():
805    # Pass y as list in GridSearchCV
806    X = np.arange(100).reshape(10, 10)
807    y = np.array([0] * 5 + [1] * 5)
808
809    clf = CheckingClassifier(
810        check_y=lambda x: isinstance(x, list),
811        methods_to_check=["fit"],
812    )
813    cv = KFold(n_splits=3)
814    grid_search = GridSearchCV(clf, {"foo_param": [1, 2, 3]}, cv=cv)
815    grid_search.fit(X, y.tolist()).score(X, y)
816    assert hasattr(grid_search, "cv_results_")
817
818
819def test_pandas_input():
820    # check cross_val_score doesn't destroy pandas dataframe
821    types = [(MockDataFrame, MockDataFrame)]
822    try:
823        from pandas import DataFrame, Series
824
825        types.append((DataFrame, Series))
826    except ImportError:
827        pass
828
829    X = np.arange(100).reshape(10, 10)
830    y = np.array([0] * 5 + [1] * 5)
831
832    for InputFeatureType, TargetType in types:
833        # X dataframe, y series
834        X_df, y_ser = InputFeatureType(X), TargetType(y)
835
836        def check_df(x):
837            return isinstance(x, InputFeatureType)
838
839        def check_series(x):
840            return isinstance(x, TargetType)
841
842        clf = CheckingClassifier(check_X=check_df, check_y=check_series)
843
844        grid_search = GridSearchCV(clf, {"foo_param": [1, 2, 3]})
845        grid_search.fit(X_df, y_ser).score(X_df, y_ser)
846        grid_search.predict(X_df)
847        assert hasattr(grid_search, "cv_results_")
848
849
850def test_unsupervised_grid_search():
851    # test grid-search with unsupervised estimator
852    X, y = make_blobs(n_samples=50, random_state=0)
853    km = KMeans(random_state=0, init="random", n_init=1)
854
855    # Multi-metric evaluation unsupervised
856    scoring = ["adjusted_rand_score", "fowlkes_mallows_score"]
857    for refit in ["adjusted_rand_score", "fowlkes_mallows_score"]:
858        grid_search = GridSearchCV(
859            km, param_grid=dict(n_clusters=[2, 3, 4]), scoring=scoring, refit=refit
860        )
861        grid_search.fit(X, y)
862        # Both ARI and FMS can find the right number :)
863        assert grid_search.best_params_["n_clusters"] == 3
864
865    # Single metric evaluation unsupervised
866    grid_search = GridSearchCV(
867        km, param_grid=dict(n_clusters=[2, 3, 4]), scoring="fowlkes_mallows_score"
868    )
869    grid_search.fit(X, y)
870    assert grid_search.best_params_["n_clusters"] == 3
871
872    # Now without a score, and without y
873    grid_search = GridSearchCV(km, param_grid=dict(n_clusters=[2, 3, 4]))
874    grid_search.fit(X)
875    assert grid_search.best_params_["n_clusters"] == 4
876
877
878def test_gridsearch_no_predict():
879    # test grid-search with an estimator without predict.
880    # slight duplication of a test from KDE
881    def custom_scoring(estimator, X):
882        return 42 if estimator.bandwidth == 0.1 else 0
883
884    X, _ = make_blobs(cluster_std=0.1, random_state=1, centers=[[0, 1], [1, 0], [0, 0]])
885    search = GridSearchCV(
886        KernelDensity(),
887        param_grid=dict(bandwidth=[0.01, 0.1, 1]),
888        scoring=custom_scoring,
889    )
890    search.fit(X)
891    assert search.best_params_["bandwidth"] == 0.1
892    assert search.best_score_ == 42
893
894
895def test_param_sampler():
896    # test basic properties of param sampler
897    param_distributions = {"kernel": ["rbf", "linear"], "C": uniform(0, 1)}
898    sampler = ParameterSampler(
899        param_distributions=param_distributions, n_iter=10, random_state=0
900    )
901    samples = [x for x in sampler]
902    assert len(samples) == 10
903    for sample in samples:
904        assert sample["kernel"] in ["rbf", "linear"]
905        assert 0 <= sample["C"] <= 1
906
907    # test that repeated calls yield identical parameters
908    param_distributions = {"C": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}
909    sampler = ParameterSampler(
910        param_distributions=param_distributions, n_iter=3, random_state=0
911    )
912    assert [x for x in sampler] == [x for x in sampler]
913
914    param_distributions = {"C": uniform(0, 1)}
915    sampler = ParameterSampler(
916        param_distributions=param_distributions, n_iter=10, random_state=0
917    )
918    assert [x for x in sampler] == [x for x in sampler]
919
920
921def check_cv_results_array_types(
922    search, param_keys, score_keys, expected_cv_results_kinds
923):
924    # Check if the search `cv_results`'s array are of correct types
925    cv_results = search.cv_results_
926    assert all(isinstance(cv_results[param], np.ma.MaskedArray) for param in param_keys)
927    assert {
928        key: cv_results[key].dtype.kind for key in param_keys
929    } == expected_cv_results_kinds
930    assert not any(isinstance(cv_results[key], np.ma.MaskedArray) for key in score_keys)
931    assert all(
932        cv_results[key].dtype == np.float64
933        for key in score_keys
934        if not key.startswith("rank")
935    )
936
937    scorer_keys = search.scorer_.keys() if search.multimetric_ else ["score"]
938
939    for key in scorer_keys:
940        assert cv_results["rank_test_%s" % key].dtype == np.int32
941
942
943def check_cv_results_keys(cv_results, param_keys, score_keys, n_cand, extra_keys=()):
944    # Test the search.cv_results_ contains all the required results
945    all_keys = param_keys + score_keys + extra_keys
946    assert_array_equal(sorted(cv_results.keys()), sorted(all_keys + ("params",)))
947    assert all(cv_results[key].shape == (n_cand,) for key in param_keys + score_keys)
948
949
950def test_grid_search_cv_results():
951    X, y = make_classification(n_samples=50, n_features=4, random_state=42)
952
953    n_grid_points = 6
954    params = [
955        dict(
956            kernel=[
957                "rbf",
958            ],
959            C=[1, 10],
960            gamma=[0.1, 1],
961        ),
962        dict(
963            kernel=[
964                "poly",
965            ],
966            degree=[1, 2],
967        ),
968    ]
969
970    param_keys = ("param_C", "param_degree", "param_gamma", "param_kernel")
971    score_keys = (
972        "mean_test_score",
973        "mean_train_score",
974        "rank_test_score",
975        "split0_test_score",
976        "split1_test_score",
977        "split2_test_score",
978        "split0_train_score",
979        "split1_train_score",
980        "split2_train_score",
981        "std_test_score",
982        "std_train_score",
983        "mean_fit_time",
984        "std_fit_time",
985        "mean_score_time",
986        "std_score_time",
987    )
988    n_candidates = n_grid_points
989
990    search = GridSearchCV(SVC(), cv=3, param_grid=params, return_train_score=True)
991    search.fit(X, y)
992    cv_results = search.cv_results_
993    # Check if score and timing are reasonable
994    assert all(cv_results["rank_test_score"] >= 1)
995    assert (all(cv_results[k] >= 0) for k in score_keys if k != "rank_test_score")
996    assert (
997        all(cv_results[k] <= 1)
998        for k in score_keys
999        if "time" not in k and k != "rank_test_score"
1000    )
1001    # Check cv_results structure
1002    expected_cv_results_kinds = {
1003        "param_C": "i",
1004        "param_degree": "i",
1005        "param_gamma": "f",
1006        "param_kernel": "O",
1007    }
1008    check_cv_results_array_types(
1009        search, param_keys, score_keys, expected_cv_results_kinds
1010    )
1011    check_cv_results_keys(cv_results, param_keys, score_keys, n_candidates)
1012    # Check masking
1013    cv_results = search.cv_results_
1014
1015    poly_results = [
1016        (
1017            cv_results["param_C"].mask[i]
1018            and cv_results["param_gamma"].mask[i]
1019            and not cv_results["param_degree"].mask[i]
1020        )
1021        for i in range(n_candidates)
1022        if cv_results["param_kernel"][i] == "poly"
1023    ]
1024    assert all(poly_results)
1025    assert len(poly_results) == 2
1026
1027    rbf_results = [
1028        (
1029            not cv_results["param_C"].mask[i]
1030            and not cv_results["param_gamma"].mask[i]
1031            and cv_results["param_degree"].mask[i]
1032        )
1033        for i in range(n_candidates)
1034        if cv_results["param_kernel"][i] == "rbf"
1035    ]
1036    assert all(rbf_results)
1037    assert len(rbf_results) == 4
1038
1039
1040def test_random_search_cv_results():
1041    X, y = make_classification(n_samples=50, n_features=4, random_state=42)
1042
1043    n_search_iter = 30
1044
1045    params = [
1046        {"kernel": ["rbf"], "C": expon(scale=10), "gamma": expon(scale=0.1)},
1047        {"kernel": ["poly"], "degree": [2, 3]},
1048    ]
1049    param_keys = ("param_C", "param_degree", "param_gamma", "param_kernel")
1050    score_keys = (
1051        "mean_test_score",
1052        "mean_train_score",
1053        "rank_test_score",
1054        "split0_test_score",
1055        "split1_test_score",
1056        "split2_test_score",
1057        "split0_train_score",
1058        "split1_train_score",
1059        "split2_train_score",
1060        "std_test_score",
1061        "std_train_score",
1062        "mean_fit_time",
1063        "std_fit_time",
1064        "mean_score_time",
1065        "std_score_time",
1066    )
1067    n_candidates = n_search_iter
1068
1069    search = RandomizedSearchCV(
1070        SVC(),
1071        n_iter=n_search_iter,
1072        cv=3,
1073        param_distributions=params,
1074        return_train_score=True,
1075    )
1076    search.fit(X, y)
1077    cv_results = search.cv_results_
1078    # Check results structure
1079    expected_cv_results_kinds = {
1080        "param_C": "f",
1081        "param_degree": "i",
1082        "param_gamma": "f",
1083        "param_kernel": "O",
1084    }
1085    check_cv_results_array_types(
1086        search, param_keys, score_keys, expected_cv_results_kinds
1087    )
1088    check_cv_results_keys(cv_results, param_keys, score_keys, n_candidates)
1089    assert all(
1090        (
1091            cv_results["param_C"].mask[i]
1092            and cv_results["param_gamma"].mask[i]
1093            and not cv_results["param_degree"].mask[i]
1094        )
1095        for i in range(n_candidates)
1096        if cv_results["param_kernel"][i] == "poly"
1097    )
1098    assert all(
1099        (
1100            not cv_results["param_C"].mask[i]
1101            and not cv_results["param_gamma"].mask[i]
1102            and cv_results["param_degree"].mask[i]
1103        )
1104        for i in range(n_candidates)
1105        if cv_results["param_kernel"][i] == "rbf"
1106    )
1107
1108
1109@pytest.mark.parametrize(
1110    "SearchCV, specialized_params",
1111    [
1112        (GridSearchCV, {"param_grid": {"C": [1, 10]}}),
1113        (RandomizedSearchCV, {"param_distributions": {"C": [1, 10]}, "n_iter": 2}),
1114    ],
1115)
1116def test_search_default_iid(SearchCV, specialized_params):
1117    # Test the IID parameter  TODO: Clearly this test does something else???
1118    # noise-free simple 2d-data
1119    X, y = make_blobs(
1120        centers=[[0, 0], [1, 0], [0, 1], [1, 1]],
1121        random_state=0,
1122        cluster_std=0.1,
1123        shuffle=False,
1124        n_samples=80,
1125    )
1126    # split dataset into two folds that are not iid
1127    # first one contains data of all 4 blobs, second only from two.
1128    mask = np.ones(X.shape[0], dtype=bool)
1129    mask[np.where(y == 1)[0][::2]] = 0
1130    mask[np.where(y == 2)[0][::2]] = 0
1131    # this leads to perfect classification on one fold and a score of 1/3 on
1132    # the other
1133    # create "cv" for splits
1134    cv = [[mask, ~mask], [~mask, mask]]
1135
1136    common_params = {"estimator": SVC(), "cv": cv, "return_train_score": True}
1137    search = SearchCV(**common_params, **specialized_params)
1138    search.fit(X, y)
1139
1140    test_cv_scores = np.array(
1141        [
1142            search.cv_results_["split%d_test_score" % s][0]
1143            for s in range(search.n_splits_)
1144        ]
1145    )
1146    test_mean = search.cv_results_["mean_test_score"][0]
1147    test_std = search.cv_results_["std_test_score"][0]
1148
1149    train_cv_scores = np.array(
1150        [
1151            search.cv_results_["split%d_train_score" % s][0]
1152            for s in range(search.n_splits_)
1153        ]
1154    )
1155    train_mean = search.cv_results_["mean_train_score"][0]
1156    train_std = search.cv_results_["std_train_score"][0]
1157
1158    assert search.cv_results_["param_C"][0] == 1
1159    # scores are the same as above
1160    assert_allclose(test_cv_scores, [1, 1.0 / 3.0])
1161    assert_allclose(train_cv_scores, [1, 1])
1162    # Unweighted mean/std is used
1163    assert test_mean == pytest.approx(np.mean(test_cv_scores))
1164    assert test_std == pytest.approx(np.std(test_cv_scores))
1165
1166    # For the train scores, we do not take a weighted mean irrespective of
1167    # i.i.d. or not
1168    assert train_mean == pytest.approx(1)
1169    assert train_std == pytest.approx(0)
1170
1171
1172def test_grid_search_cv_results_multimetric():
1173    X, y = make_classification(n_samples=50, n_features=4, random_state=42)
1174
1175    n_splits = 3
1176    params = [
1177        dict(
1178            kernel=[
1179                "rbf",
1180            ],
1181            C=[1, 10],
1182            gamma=[0.1, 1],
1183        ),
1184        dict(
1185            kernel=[
1186                "poly",
1187            ],
1188            degree=[1, 2],
1189        ),
1190    ]
1191
1192    grid_searches = []
1193    for scoring in (
1194        {"accuracy": make_scorer(accuracy_score), "recall": make_scorer(recall_score)},
1195        "accuracy",
1196        "recall",
1197    ):
1198        grid_search = GridSearchCV(
1199            SVC(), cv=n_splits, param_grid=params, scoring=scoring, refit=False
1200        )

Showing the first 1,200 of 2967 lines. Download the file for the rest.

Aluode/PerceptionLabPortable · CoolFace