Как регуляризуется градиентный бустинг?

Ответ

При работе с градиентным бустингом (XGBoost, LightGBM, CatBoost) я применяю несколько уровней регуляризации, чтобы управлять сложностью модели и предотвращать переобучение.

Стратегии регуляризации, которые я использую:

  1. Регуляризация самих деревьев (базовых learners):

    • max_depth / num_leaves: Жёстко ограничиваю глубину или количество листьев.
    • min_child_weight (XGB) / min_data_in_leaf (LGBM): Задаю минимальную сумму весов (или количество объектов) в листе, чтобы не создавать слишком специфичные разделения.
    • gamma (XGB): Минимальное снижение потерь для выполнения split.
  2. L1 и L2 регуляризация на веса листьев:

    • reg_alpha (L1) и reg_lambda (L2) в XGBoost. lambda_l1, lambda_l2 в LightGBM.
    • L1 может занулять веса некоторых листьев, действуя как отбор признаков на уровне дерева.
  3. Стохастические методы:

    • subsample: Обучаю каждое дерево на случайной подвыборке данных (например, 0.8).
    • colsample_bytree, colsample_bylevel, colsample_bynode: Случайно выбираю подмножество признаков для всего дерева, уровня или каждого разделения.
  4. Контроль скорости обучения:

    • Небольшой learning_rate (например, 0.01-0.1) в сочетании с большим числом n_estimators. Это требует больше итераций, но делает процесс обучения более плавным и устойчивым.
  5. Ранняя остановка (Early Stopping):

    • Это мой основной инструмент. Я всегда выделяю валидационную выборку и останавливаю обучение, когда её метрика перестаёт улучшаться.

Пример настройки регуляризованной модели в XGBoost:

import xgboost as xgb
from sklearn.model_selection import train_test_split

X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2)

dtrain = xgb.DMatrix(X_train, label=y_train)
dval = xgb.DMatrix(X_val, label=y_val)

params = {
    'objective': 'binary:logistic',
    'max_depth': 4,               # Неглубокие деревья
    'min_child_weight': 5,        # Минимум объектов в листе
    'subsample': 0.8,             # Стохастичность по объектам
    'colsample_bytree': 0.8,      # Стохастичность по признакам
    'eta': 0.05,                  # Малая скорость обучения
    'reg_alpha': 0.1,             # L1 регуляризация
    'reg_lambda': 1.5,            # L2 регуляризация
    'eval_metric': 'logloss'
}

evals = [(dtrain, 'train'), (dval, 'eval')]
model = xgb.train(params, dtrain, num_boost_round=1000,
                  evals=evals, early_stopping_rounds=50, verbose_eval=50)

Ответ 18+ 🔞

А, регуляризация в бустингах! Ну это, блядь, святое дело, если не хочешь, чтобы модель на тебя смотрела как на идиота и переобучилась так, что даже твою бабушку по IP-адресу распознает.

Слушай, я тебе как на духу. Когда дело доходит до XGBoost, LightGBM или этого кота с бустом, я регуляризацию впихиваю везде, куда только можно, как будто от этого зависит моя зарплата. Потому что иначе получается пиздопроебибна ситуация: модель на трейне показывает точность 99.9%, а на новых данных — хуй с горы.

Вот моя философия, по пунктам, чтобы не охуеть от сложности:

  1. Деревья сами по себе. Первое правило — не дай им вырасти до небес. max_depth или num_leaves жму как последнюю копейку. Зачем дереву 15 уровней? Чтобы каждый лист соответствовал одному конкретному наблюдению? Да похуй. Лучше 4-5, и будет счастье. Ещё min_child_weight — это чтобы лист не создавался ради двух-трёх ушлёпков с краю датасета. Без этого — доверия ебать ноль.

  2. L1 и L2 — мои лучшие друзья. Это reg_alpha и reg_lambda. L1, тот вообще красавец, может вес листа в ноль загнать, по сути как отбор фич внутри дерева. L2 — просто штрафует за большие веса, чтобы дерево не думало, что оно самый умный и может одним листом весь таргет предсказать. Без них — хитрая жопа получится, а не модель.

  3. Стохастика — наше всё. subsample и всякие colsample_by*. Суть проста: каждое новое дерево учится не на всём датасете и не на всех фичах, а на случайной их подвыборке. Это как заставить модель смотреть на мир не одним глазом, а каждый раз немного по-новому. Итог — ансамбль получается устойчивее, терпения ноль ебать к шуму в данных.

  4. Скорость обучения (learning_rate или eta). Вот тут многие охуевают и ставят 0.3. А потом удивляются, почему модель скачет как коза и сходится в какую-то хуйню. Я жму на 0.01-0.05, но зато n_estimators ставлю овердохуища. Медленно, плавно, зато точно в цель. Это как не гнать на спуске с горы, а аккуратно тормозить.

  5. Ранняя остановка (Early Stopping). Это вообще манда с ушами, если её не использовать. Я всегда, блядь, оставляю валидационную выборку и смотрю, когда метрика на ней перестаёт расти. Как только улучшений нет 20-50 раундов — всё, стоп машина, дальше учиться нечему, только переобучение качается.

Вот тебе пример, как это выглядит в коде, чтобы не быть голословным:

import xgboost as xgb
from sklearn.model_selection import train_test_split

X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2)

dtrain = xgb.DMatrix(X_train, label=y_train)
dval = xgb.DMatrix(X_val, label=y_val)

params = {
    'objective': 'binary:logistic',
    'max_depth': 4,               # Глубоко не лезем, а то вылезем в переобучение
    'min_child_weight': 5,        # Чтобы лист не для пары чудаков создавался
    'subsample': 0.8,             # Берём 80% данных для каждого дерева
    'colsample_bytree': 0.8,      # И 80% фич
    'eta': 0.05,                  # Ползи, улитка, ползи
    'reg_alpha': 0.1,             # L1, зануляй лишнее
    'reg_lambda': 1.5,            # L2, успокой горячие головы-листья
    'eval_metric': 'logloss'
}

evals = [(dtrain, 'train'), (dval, 'eval')]
model = xgb.train(params, dtrain, num_boost_round=1000,
                  evals=evals, early_stopping_rounds=50, verbose_eval=50)

Смотри, что здесь происходит: мы даём модели возможность сделать аж 1000 деревьев, но благодаря ранней остановке она, скорее всего, упрётся в потолок валидационной метрики где-то на 300-400. А все эти регуляризаторы не дают каждому отдельному дереву взъебать данные и выучить тренировочный шум. В итоге получается сбалансированная, крепкая модель, которая не бздит на новых данных. Красота, ёпта!