Hyperparameter search

scikit-learn · Practical · 12 min

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_ and best_score_ give the winner and its mean CV score,
  • best_estimator_ is that configuration refitted on all the data you passed (because refit=True), so the search object itself can predict,
  • 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 to fit
  • Use randomized search with log-uniform distributions for several parameters

Watch out for

  • Reporting best_score_ as the expected performance on new data.

Check yourself

  1. Why is RandomizedSearchCV usually preferred over GridSearchCV for 5 hyperparameters?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: scikit-learn.org/stable/modules/grid_search.html