scikit-learnMiddleCoding

Как работает GridSearchCV и чем он отличается от RandomizedSearchCV?

GridSearchCV перебирает все комбинации гиперпараметров (n_candidates = произведение длин), RandomizedSearchCV сэмплирует n_iter случайных — выгоднее при большом пространстве поиска.

GridSearchCV vs RandomizedSearchCV

GridSearchCV строит декартово произведение всех значений параметров и обучает модель на каждой комбинации с k-fold CV. Если у вас 3 параметра по 5 значений каждый и cv=5, это 5³ × 5 = 625 обучений. RandomizedSearchCV сэмплирует ровно n_iter случайных точек из пространства поиска, что даёт предсказуемый бюджет вычислений.

Когда выбирать

  • GridSearchCV — пространство параметров маленькое и все комбинации имеют смысл (например, 2-3 параметра, каждый по 3-4 значения).
  • RandomizedSearchCV — широкое пространство, непрерывные распределения (loguniform, uniform), или бюджет времени ограничен.

Пример с GridSearchCV

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.datasets import make_classification
import numpy as np

X, y = make_classification(n_samples=1000, n_features=20, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

pipeline = Pipeline([
    ("scaler", StandardScaler()),
    ("svc", SVC(probability=True)),
])

# Имена параметров: <step_name>__<param_name>
param_grid = {
    "svc__C": [0.1, 1.0, 10.0],
    "svc__kernel": ["rbf", "linear"],
    "svc__gamma": ["scale", "auto"],
}  # 3 * 2 * 2 = 12 комбинаций, cv=5 -> 60 обучений

grid_search = GridSearchCV(
    pipeline,
    param_grid,
    cv=5,
    scoring="roc_auc",
    n_jobs=-1,
    verbose=1,
    refit=True,  # переобучает лучшую модель на всём train
)
grid_search.fit(X_train, y_train)

print("Best params:", grid_search.best_params_)
print("Best CV ROC-AUC:", grid_search.best_score_)
print("Test ROC-AUC:", grid_search.score(X_test, y_test))

Пример с RandomizedSearchCV

from sklearn.model_selection import RandomizedSearchCV
from sklearn.ensemble import GradientBoostingClassifier
from scipy.stats import loguniform, randint

pipeline2 = Pipeline([
    ("scaler", StandardScaler()),
    ("gb", GradientBoostingClassifier(random_state=42)),
])

# Можно использовать scipy-распределения — GridSearchCV так не умеет
param_dist = {
    "gb__n_estimators": randint(50, 300),
    "gb__learning_rate": loguniform(1e-3, 0.5),
    "gb__max_depth": randint(2, 8),
    "gb__subsample": [0.6, 0.8, 1.0],
}

rand_search = RandomizedSearchCV(
    pipeline2,
    param_distributions=param_dist,
    n_iter=30,          # фиксированный бюджет: 30 * cv=5 = 150 обучений
    cv=5,
    scoring="roc_auc",
    n_jobs=-1,
    random_state=42,    # воспроизводимость
    refit=True,
)
rand_search.fit(X_train, y_train)

print("Best params:", rand_search.best_params_)
print("Best CV ROC-AUC:", rand_search.best_score_)

# Анализ результатов
import pandas as pd
results = pd.DataFrame(rand_search.cv_results_)
print(results[["param_gb__learning_rate", "param_gb__n_estimators",
               "mean_test_score", "std_test_score"]]
      .sort_values("mean_test_score", ascending=False)
      .head(5))

Halving-варианты (scikit-learn >= 1.0)

Для экономии времени используйте HalvingGridSearchCV и HalvingRandomSearchCV: они отсекают слабые кандидаты на ранних итерациях, выделяя больше ресурсов перспективным:

from sklearn.model_selection import HalvingRandomSearchCV

halving = HalvingRandomSearchCV(
    pipeline2,
    param_dist,
    factor=3,        # каждый раунд оставляет 1/3 кандидатов
    cv=5,
    scoring="roc_auc",
    random_state=42,
    n_jobs=-1,
)
halving.fit(X_train, y_train)

Подводные камни

  • Передача X_test в fit вместо X_train — leakage; GridSearchCV не предупредит, просто оценит слишком оптимистично.
  • Параметры Pipeline задаются через step__param; опечатка в имени шага не вызывает ошибку до fit, а иногда молча игнорируется.
  • refit=True (дефолт) переобучает на всём X_train; если нужна внешняя финальная оценка, передайте X_test только после best_estimator_.
  • n_jobs=-1 + Pipeline + Windows = проблемы с multiprocessing; на Linux работает надёжно.
  • При использовании loguniform из scipy минимальное значение не может быть 0 — передайте loguniform(1e-5, 1), иначе ValueError.
  • cv_results_['mean_test_score'] — это среднее по CV-фолдам, не метрика на holdout; не путать.
  • RandomizedSearchCV с маленьким n_iter может не покрыть хорошую область пространства — увеличивайте итерации или уменьшайте пространство поиска.
  • Кастомный scorer через make_scorer должен возвращать float (выше = лучше); знак инвертируется автоматически только для некоторых метрик.

Common mistakes

  • Объяснять gridsearch vs randomizedsearch только синтаксисом без shape, dtype, состояния или режима выполнения.
  • Игнорировать leakage, воспроизводимость, пустые входы и скрытые копии данных.
  • Не проверять production-симптомы: latency, память, ретраи, дрейф качества и несовпадение версий.

What the interviewer is testing

  • Может ли связать gridsearch vs randomizedsearch с реальным контрактом входов и выходов.
  • Упоминает ли тесты, метрики, reproducibility и диагностику ошибок.
  • Видит ли различие между demo-кодом в ноутбуке и production-пайплайном.

Sources

Related topics