Перейти к содержанию

Шпаргалка: гиперпараметры

~8 минут чтения

Предварительно: Выбор модели | Отладка

Тюнинг гиперпараметров -- самый частый способ улучшить модель после baseline. Но 80% выигрыша дают 2-3 ключевых параметра: learning_rate + n_estimators для бустинга, C + kernel для SVM, max_depth для деревьев. Начинай с дефолтов (они обычно хороши), тюнь важнейшие параметры через RandomSearch, финишируй GridSearch на узком диапазоне. Cross-validation обязателен, логарифмическая шкала для learning_rate, C, alpha.

Общие принципы

Шаг Действие
1 Начинай с дефолтов -- они обычно неплохие
2 Tune самые важные параметры сначала
3 RandomSearch (широкий охват) -> GridSearch (финишировка)
4 Cross-validation обязателен (не holdout!)
5 Логарифмическая шкала для learning rate, C, alpha

Logistic Regression

Параметр Типичные значения Что делает
C 0.001, 0.01, 0.1, 1, 10, 100 Обратная сила регуляризации
penalty 'l1', 'l2', 'elasticnet' Тип регуляризации
solver 'lbfgs', 'liblinear', 'saga' Алгоритм оптимизации
max_iter 100-10000 Максимум итераций
from sklearn.linear_model import LogisticRegression

# Дефолтный
LogisticRegression()

# Для L1 (sparse)
LogisticRegression(penalty='l1', solver='liblinear', C=1.0)

# Для дисбаланса
LogisticRegression(class_weight='balanced')

# Grid
param_grid = {
    'C': [0.01, 0.1, 1, 10],
    'penalty': ['l1', 'l2'],
    'solver': ['liblinear']
}

Random Forest

Параметр Типичные значения Что делает
n_estimators 100, 200, 500, 1000 Количество деревьев
max_depth 5, 10, 20, None Глубина деревьев
min_samples_split 2, 5, 10, 20 Мин. для разбиения
min_samples_leaf 1, 2, 4, 8 Мин. в листе
max_features 'sqrt', 'log2', 0.5 Признаков на split
from sklearn.ensemble import RandomForestClassifier

# Дефолтный хороший
RandomForestClassifier(n_estimators=100, n_jobs=-1)

# Избежать переобучения
RandomForestClassifier(
    n_estimators=200,
    max_depth=10,
    min_samples_split=5,
    min_samples_leaf=2
)

# Grid
param_grid = {
    'n_estimators': [100, 200, 500],
    'max_depth': [5, 10, 20, None],
    'min_samples_split': [2, 5, 10],
    'max_features': ['sqrt', 'log2']
}

Правила для RF

Параметр Мало Оптимум Много
n_estimators Высокая variance 100-500 Лучше, но медленнее (после ~500 улучшение минимально)
max_depth Underfitting 10-20 None = переобучение
max_features Больше разнообразия деревьев 'sqrt' (классификация) 1.0 = все признаки, деревья похожи

XGBoost

Параметр Типичные значения Что делает
n_estimators 100, 500, 1000 Количество деревьев
max_depth 3, 5, 7, 9 Глубина деревьев
learning_rate 0.01, 0.05, 0.1, 0.3 Скорость обучения
subsample 0.6, 0.8, 1.0 Доля примеров
colsample_bytree 0.6, 0.8, 1.0 Доля признаков
reg_alpha 0, 0.01, 0.1, 1 L1 регуляризация
reg_lambda 0, 0.01, 0.1, 1 L2 регуляризация
min_child_weight 1, 3, 5, 7 Мин. вес в листе
gamma 0, 0.1, 0.2 Мин. потеря для split
import xgboost as xgb

# Хороший старт
model = xgb.XGBClassifier(
    n_estimators=500,
    max_depth=5,
    learning_rate=0.1,
    subsample=0.8,
    colsample_bytree=0.8,
    n_jobs=-1,
    random_state=42
)

# С early stopping
model.fit(
    X_train, y_train,
    eval_set=[(X_val, y_val)],
    early_stopping_rounds=50,
    verbose=False
)

# Grid для XGBoost
param_grid = {
    'max_depth': [3, 5, 7],
    'learning_rate': [0.01, 0.1],
    'n_estimators': [100, 500],
    'subsample': [0.8, 1.0],
    'colsample_bytree': [0.8, 1.0]
}

learning_rate и n_estimators -- связанная пара

Тюнить learning_rate без пропорционального изменения n_estimators бессмысленно. Уменьшили lr в 10 раз? Увеличьте деревья в 10 раз, иначе модель недообучится. Правило: lr * n_estimators ≈ const. Практика: сначала зафиксируйте lr=0.1 и подберите остальные параметры, потом уменьшите lr и пропорционально увеличьте n_estimators для финального качества.

Правила для XGBoost

Параметр Рекомендация
learning_rate + n_estimators Связанная пара: lr=0.1 + 300 деревьев (быстро) или lr=0.01 + 1000 (качественнее)
max_depth 3-7 (мелкие деревья!), для бустинга глубокие не нужны
subsample + colsample_bytree 0.8 -- хороший дефолт, добавляют случайность = регуляризация

LightGBM

Параметр Типичные значения Что делает
n_estimators 100, 500, 1000 Количество деревьев
num_leaves 31, 50, 100 Листьев в дереве
max_depth -1, 5, 10 Глубина (-1 = без ограничений)
learning_rate 0.01, 0.05, 0.1 Скорость обучения
feature_fraction 0.6, 0.8, 1.0 Доля признаков
bagging_fraction 0.6, 0.8, 1.0 Доля примеров
bagging_freq 1, 5 Как часто bagging
reg_alpha 0, 0.1, 1 L1
reg_lambda 0, 0.1, 1 L2
min_child_samples 20, 50, 100 Мин. в листе
import lightgbm as lgb

# Хороший старт
model = lgb.LGBMClassifier(
    n_estimators=500,
    num_leaves=31,
    learning_rate=0.1,
    feature_fraction=0.8,
    bagging_fraction=0.8,
    bagging_freq=5,
    n_jobs=-1,
    random_state=42
)

# С категориальными
model = lgb.LGBMClassifier(
    categorical_feature=['col1', 'col2']
)

LightGBM vs XGBoost

Аспект XGBoost LightGBM
Контроль сложности max_depth num_leaves (leaf-wise growth)
Соотношение max_depth=7 = до 128 листьев num_leaves=50-100 при max_depth=7
Категории Через encoding categorical_feature нативно
Скорость Стандарт Быстрее на больших данных

Правило: num_leaves < 2^max_depth, иначе переобучение.


CatBoost

Параметр Типичные значения Что делает
iterations 100, 500, 1000 Количество деревьев
depth 4, 6, 8, 10 Глубина деревьев
learning_rate 0.01, 0.05, 0.1 Скорость обучения
l2_leaf_reg 1, 3, 5, 10 L2 регуляризация
border_count 32, 64, 128 Бинов для числовых
cat_features list of indices Категориальные признаки
from catboost import CatBoostClassifier

model = CatBoostClassifier(
    iterations=500,
    depth=6,
    learning_rate=0.1,
    cat_features=['category_col'],
    verbose=False
)

SVM

Параметр Типичные значения Что делает
C 0.01, 0.1, 1, 10, 100 Штраф за ошибки
kernel 'linear', 'rbf', 'poly' Тип ядра
gamma 'scale', 'auto', 0.01, 0.1, 1 Ширина RBF ядра
degree 2, 3, 4 Степень poly ядра
from sklearn.svm import SVC

# Linear SVM
SVC(kernel='linear', C=1.0)

# RBF SVM (нелинейный)
SVC(kernel='rbf', C=1.0, gamma='scale')

# Grid для RBF
param_grid = {
    'C': [0.1, 1, 10, 100],
    'gamma': ['scale', 0.01, 0.1, 1]
}

Правила для SVM

Параметр Маленький Большой Дефолт
C Мягкий margin, underfitting Жёсткий margin, переобучение 1.0, range 0.1-100
gamma (RBF) Гладкая граница Сложная граница, переобучение 'scale' = \(1/(n_{features} \cdot \text{Var}(X))\)

Обязательно нормализовать данные перед SVM! Без нормализации признак с range [0, 10000] доминирует над [0, 1].


KNN

Параметр Типичные значения Что делает
n_neighbors 3, 5, 7, 11, 21 Количество соседей
weights 'uniform', 'distance' Веса соседей
metric 'euclidean', 'manhattan', 'minkowski' Метрика
p 1, 2 p для minkowski
from sklearn.neighbors import KNeighborsClassifier

# Дефолтный
KNeighborsClassifier(n_neighbors=5)

# С весами по расстоянию
KNeighborsClassifier(n_neighbors=7, weights='distance')

# Grid
param_grid = {
    'n_neighbors': [3, 5, 7, 11, 21],
    'weights': ['uniform', 'distance'],
    'metric': ['euclidean', 'manhattan']
}

Правила для KNN

Параметр Рекомендация
n_neighbors Нечётное (нет ничьих). Маленький k = переобучение, большой k = слишком гладкие границы. Rule of thumb: \(k \approx \sqrt{n}\)
weights 'distance' обычно лучше 'uniform' (ближние соседи важнее)
metric 'euclidean' (default) или 'manhattan' для high-dim / outliers

Обязательно нормализовать данные перед KNN! Расстояния зависят от масштаба признаков.


Neural Networks

Общие

Параметр Типичные значения Что делает
learning_rate 1e-4, 3e-4, 1e-3, 3e-3 Скорость обучения
batch_size 16, 32, 64, 128, 256 Размер батча
epochs 10-100+ Эпохи (с early stopping)
weight_decay 0, 1e-5, 1e-4, 1e-3 L2 регуляризация
dropout 0.1, 0.2, 0.3, 0.5 Dropout rate

Adam optimizer

optimizer = torch.optim.AdamW(
    model.parameters(),
    lr=1e-3,           # Начать с 1e-3 или 3e-4
    weight_decay=0.01  # Для AdamW
)

Learning Rate Schedule

# Cosine Annealing
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
    optimizer, T_max=100, eta_min=1e-6
)

# One Cycle
scheduler = torch.optim.lr_scheduler.OneCycleLR(
    optimizer, max_lr=0.01, epochs=10, steps_per_epoch=len(train_loader)
)

# Reduce on Plateau
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
    optimizer, mode='min', factor=0.1, patience=10
)

Правила для NN

Параметр Маленький Оптимум Большой
learning_rate Медленное обучение 1e-3 (Adam), 1e-5..1e-4 (fine-tuning) Loss скачет, не сходится
batch_size Лучше обобщение, шумнее 32-128 Стабильнее, быстрее эпоха, ограничен GPU
dropout Мало регуляризации 0.1-0.3 (transformer), 0.5 (FC) Underfitting

Clustering

K-Means

Параметр Типичные значения Что делает
n_clusters Определить Elbow/Silhouette Количество кластеров
init 'k-means++' Инициализация
n_init 10 Количество запусков
max_iter 300 Максимум итераций
# Выбор k через Elbow
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt

inertias = []
for k in range(1, 11):
    km = KMeans(n_clusters=k, random_state=42)
    km.fit(X)
    inertias.append(km.inertia_)

plt.plot(range(1, 11), inertias, 'o-')
plt.xlabel('k')
plt.ylabel('Inertia')

DBSCAN

Параметр Типичные значения Что делает
eps Зависит от данных Радиус окрестности
min_samples 5, 10, 15 Мин. точек для core
# Выбор eps через k-distance
from sklearn.neighbors import NearestNeighbors
import numpy as np

nn = NearestNeighbors(n_neighbors=5)
nn.fit(X)
distances, _ = nn.kneighbors(X)
distances = np.sort(distances[:, -1])

plt.plot(distances)  # Ищем "локоть"

Data leakage через preprocessing ДО cross-validation

Если вы вызвали StandardScaler().fit_transform(X) на ВСЁМ датасете, а потом делаете cross-validation -- это data leakage. Scaler видел тестовые фолды. Правильно: оберните preprocessing в Pipeline, тогда sklearn сам вызовет fit_transform только на train-фолдах внутри CV. Это касается любого preprocessing: imputation, PCA, feature selection.

from sklearn.model_selection import RandomizedSearchCV, GridSearchCV
from scipy.stats import randint, uniform

# RandomizedSearchCV (для начала)
param_dist = {
    'n_estimators': randint(100, 1000),
    'max_depth': randint(3, 15),
    'learning_rate': uniform(0.01, 0.3),
    'subsample': uniform(0.6, 0.4)
}

search = RandomizedSearchCV(
    model, param_dist,
    n_iter=50,
    cv=5,
    scoring='f1',
    n_jobs=-1,
    random_state=42
)
search.fit(X, y)

# GridSearchCV (для fine-tuning)
param_grid = {
    'max_depth': [4, 5, 6],
    'learning_rate': [0.05, 0.1, 0.15]
}

search = GridSearchCV(
    model, param_grid,
    cv=5,
    scoring='f1',
    n_jobs=-1
)
search.fit(X, y)

print(f"Best params: {search.best_params_}")
print(f"Best score: {search.best_score_:.4f}")

Optuna (лучший вариант)

import optuna

def objective(trial):
    params = {
        'n_estimators': trial.suggest_int('n_estimators', 100, 1000),
        'max_depth': trial.suggest_int('max_depth', 3, 15),
        'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.3, log=True),
        'subsample': trial.suggest_float('subsample', 0.6, 1.0)
    }

    model = xgb.XGBClassifier(**params)
    score = cross_val_score(model, X, y, cv=5, scoring='f1').mean()
    return score

study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)

print(f"Best params: {study.best_params}")

Вопросы для собеседования

Как правильно тюнить XGBoost? Назовите порядок и взаимозависимости параметров.

❌ «GridSearch по всем параметрам одновременно» -- комбинаторный взрыв, бессмысленно.

✅ Порядок: (1) Зафиксировать learning_rate=0.1, n_estimators=500 с early stopping. (2) Тюнить max_depth (3-7) и min_child_weight -- контроль сложности деревьев. (3) Тюнить subsample и colsample_bytree (0.6-1.0) -- стохастическая регуляризация. (4) Тюнить reg_alpha и reg_lambda -- L1/L2 регуляризация. (5) Финально: уменьшить learning_rate в 5-10 раз, пропорционально увеличить n_estimators. Ключевая зависимость: lr * n_estimators $\approx$ const.

RandomSearch vs GridSearch vs Bayesian (Optuna) -- когда что использовать?

❌ «GridSearch всегда лучше потому что полный перебор» -- неэффективно.

✅ GridSearch: финишировка на 2-3 параметрах с узким диапазоном (уже знаете примерный range). RandomSearch: начальная разведка, 50-100 trials покрывают 95% пространства при 5+ параметрах (Bergstra & Bengio, 2012). Optuna/Bayesian: лучшее для дорогих моделей (каждый trial = минуты/часы), использует Tree-structured Parzen Estimators, автоматически сужает пространство. Практика: RandomSearch 50 trials -> Optuna 100 trials -> GridSearch на финальных 2-3 параметрах.

Почему нельзя тюнить гиперпараметры на test set?

❌ «Мы же не обучаем модель на test set, только выбираем параметры» -- это и есть утечка.

✅ Выбор гиперпараметров по test set = оптимизация на нём = утечка. Модель адаптируется к особенностям test set, оценка оптимистична. Правильно: train/val/test split. GridSearch/Optuna работают ТОЛЬКО на val (через cross-validation на train). Test set используется ОДИН РАЗ в самом конце. Если test set маленький -- nested cross-validation: внешний CV для оценки, внутренний CV для тюнинга.

Самопроверка

  1. XGBoost тюнинг: Дан датасет 50K примеров, 30 признаков, бинарная классификация. Текущий baseline: XGBoost с дефолтами, F1=0.72. Напишите конкретный план тюнинга: какие параметры в каком порядке, какие диапазоны, какой метод поиска. Целевой F1 >= 0.80.

  2. Связанные параметры: XGBoost с learning_rate=0.1, n_estimators=300 даёт val F1=0.85. Вы хотите улучшить. Что будет, если просто уменьшить learning_rate до 0.01 без увеличения деревьев? Почему? Какой n_estimators выбрать?

  3. Nested CV: Объясните, зачем нужен nested cross-validation. Нарисуйте схему: что делает внешний CV, что внутренний. Когда nested CV обязателен, а когда достаточно train/val/test split?


See Also


Источники

  1. Scikit-learn Documentation -- GridSearchCV, RandomizedSearchCV, Pipeline
  2. XGBoost Documentation -- Parameters tuning guide
  3. LightGBM Documentation -- Parameters, categorical features
  4. CatBoost Documentation -- Training parameters
  5. Optuna Documentation -- Tree-structured Parzen Estimators
  6. Bergstra & Bengio (2012) -- "Random Search for Hyper-Parameter Optimization" (JMLR)