Шпаргалка: гиперпараметры¶
~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.
Hyperparameter Search¶
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 для тюнинга.
Самопроверка
-
XGBoost тюнинг: Дан датасет 50K примеров, 30 признаков, бинарная классификация. Текущий baseline: XGBoost с дефолтами, F1=0.72. Напишите конкретный план тюнинга: какие параметры в каком порядке, какие диапазоны, какой метод поиска. Целевой F1 >= 0.80.
-
Связанные параметры: XGBoost с
learning_rate=0.1,n_estimators=300даёт val F1=0.85. Вы хотите улучшить. Что будет, если просто уменьшитьlearning_rateдо 0.01 без увеличения деревьев? Почему? Какойn_estimatorsвыбрать? -
Nested CV: Объясните, зачем нужен nested cross-validation. Нарисуйте схему: что делает внешний CV, что внутренний. Когда nested CV обязателен, а когда достаточно train/val/test split?
See Also¶
- Выбор модели -- какую модель выбрать
- Метрики -- как оценить результат тюнинга
- sklearn -- GridSearchCV, Pipeline
- Отладка -- если тюнинг не помогает
- Classical ML Interview Q&A -- развёрнутые ответы по гиперпараметрам
Источники¶
- Scikit-learn Documentation -- GridSearchCV, RandomizedSearchCV, Pipeline
- XGBoost Documentation -- Parameters tuning guide
- LightGBM Documentation -- Parameters, categorical features
- CatBoost Documentation -- Training parameters
- Optuna Documentation -- Tree-structured Parzen Estimators
- Bergstra & Bengio (2012) -- "Random Search for Hyper-Parameter Optimization" (JMLR)