Defaults are rarely optimal, and searching by hand on the test set produces over-optimistic results.
GridSearchCV(estimator, param_grid, cv=5, scoring=...) tries every combination in param_grid, cross-validates each, and keeps the best. After fit:
best_params_andbest_score_give the winner and its mean CV score,best_estimator_is that configuration refitted on all the data you passed (becauserefit=True), so the search object itself canpredict,cv_results_holds every candidate's scores;pd.DataFrame(search.cv_results_)is the easiest way to read it.
With a pipeline, keys use the step names: {"model__C": [0.1, 1, 10]}. A list of dicts searches several sub-grids, for example different models in the same "model" step.
Grids explode combinatorially, so for more than two or three parameters use RandomizedSearchCV, which samples n_iter candidates from lists or distributions such as scipy.stats.loguniform(1e-3, 1e2). Random search finds good regions with far fewer fits when only some parameters matter. HalvingGridSearchCV and HalvingRandomSearchCV (experimental) save more time by giving all candidates a little data and only the best ones more.
Remember that best_score_ is optimistically biased: it is the maximum of many noisy estimates. Confirm on a held-out test set, or with nested cross-validation.
Example
from scipy.stats import loguniform
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import GridSearchCV, RandomizedSearchCV
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
X, y = load_breast_cancer(return_X_y=True)
pipe = make_pipeline(StandardScaler(), SVC())
grid = GridSearchCV(pipe, {"svc__C": [0.1, 1, 10], "svc__kernel": ["linear", "rbf"]}, cv=5)
grid.fit(X, y)
print(grid.best_params_, round(grid.best_score_, 3))
# {'svc__C': 10, 'svc__kernel': 'rbf'} 0.977
rand = RandomizedSearchCV(pipe, {"svc__C": loguniform(1e-2, 1e2),
"svc__gamma": loguniform(1e-4, 1e-1)},
n_iter=20, cv=5, random_state=0, n_jobs=-1).fit(X, y)
print(round(rand.best_score_, 3)) # 0.974
print(rand.predict(X[:3])) # [0 0 0] (uses best_estimator_)Remember
- Pipeline parameters are searched as
step__param best_estimator_is refitted on all the data you passed tofit- Use randomized search with log-uniform distributions for several parameters
Watch out for
- Reporting
best_score_as the expected performance on new data.