Как правильно настроить валидацию при использовании стекинга и блендинга?

Ответ

Главная задача — избежать утечки данных из целевой переменной (y) в мета-признаки. Для этого используется двухуровневая процедура, часто реализуемая через k-фолдовую кросс-валидацию.

Схема корректной валидации для стекинга:

  1. Исходный набор данных делится на обучающую (Train) и финальную тестовую (Hold-out) выборки. Hold-out откладывается до самого конца и не участвует в создании мета-признаков.
  2. На обучающей выборке (Train) выполняется k-фолдовая кросс-валидация для каждой базовой модели (например, RandomForest, XGBoost, SVM).
  3. На каждом шаге CV модель обучается на k-1 фолдах, а ее предсказания для оставшегося фолда (out-of-fold predictions, OOF) сохраняются. В итоге получается полный вектор OOF-предсказаний по всем объектам Train.
  4. Эти OOF-предсказания от всех базовых моделей становятся мета-признаками для обучения мета-модели (например, линейной регрессии или логистической регрессии).
  5. Мета-модель обучается на этих мета-признаках и истинных значениях y_train.
  6. Для получения финальных предсказаний на Hold-out или новых данных:
    • Каждая базовая модель, обученная на всем Train, делает предсказание.
    • Эти предсказания подаются как входные признаки в обученную мета-модель.

Пример кода для создания OOF-признаков:

import numpy as np
from sklearn.model_selection import KFold
from sklearn.ensemble import RandomForestRegressor
from sklearn.linear_model import LinearRegression

# Исходные данные (без Hold-out)
X_train, y_train = ...
kf = KFold(n_splits=5, shuffle=True, random_state=42)

oof_predictions = np.zeros((X_train.shape[0], len(base_models)))

# Для каждой базовой модели
for model_idx, base_model in enumerate(base_models):
    col_oof = np.zeros(X_train.shape[0])

    for fold, (train_idx, val_idx) in enumerate(kf.split(X_train, y_train)):
        X_tr, X_val = X_train[train_idx], X_train[val_idx]
        y_tr = y_train[train_idx]

        # Обучаем базовую модель на фолде
        model = clone(base_model)
        model.fit(X_tr, y_tr)
        # Предсказываем на валидационном фолде
        col_oof[val_idx] = model.predict(X_val)

    oof_predictions[:, model_idx] = col_oof

# OOF-предсказания — это мета-признаки для Train
meta_features_train = oof_predictions

# Обучаем мета-модель на этих признаках
meta_model = LinearRegression()
meta_model.fit(meta_features_train, y_train)

Блендинг — это упрощенная версия стекинга, где мета-признаки создаются путем предсказания базовых моделей на одной отдельной валидационной выборке (обычно 10-20% от Train), а не на всех фолдах. Метод проще, но менее устойчив, чем стекинг с кросс-валидацией.

Ответ 18+ 🔞

Давай разберём эту всю хрень со стекингом и блендингом, а то народ постоянно наступает на одни и те же грабли, а потом удивляется, почему модель нихуя не работает, а на тесте всё накрывается медным тазом.

Слушай, главная проблема, из-за которой всё летит в пизду — это утечка данных из целевой переменной y в мета-признаки. Представь, ты учишь модель на данных, где она уже знает ответы. Это как списать у самого себя на экзамене — результат будет овердохуища, но на реальной задаче ты окажешься полным лузером. Чтобы этого не было, нужна двухуровневая процедура, обычно через k-фолдовую кросс-валидацию. Запомни это, как «Отче наш».

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

  1. Разделяй и властвуй. Берёшь все данные и сразу откладываешь финальную тестовую выборку (Hold-out) в самый тёмный угол. К ней не прикасаешься, пока не закончишь всю основную работу. Это святое.
  2. Работаем с обучающей выборкой (Train). На ней делаешь k-фолдовую кросс-валидацию для каждой своей базовой модели — будь то RandomForest, XGBoost или какая-нибудь мартышлюшка в виде SVM.
  3. Вот тут магия. На каждом шаге CV ты учишь модель на k-1 фолдах, а потом заставляешь её предсказывать на том единственном фолде, который она не видела (out-of-fold predictions, OOF). Эти предсказания ты аккуратно собираешь. В итоге у тебя получается полный вектор OOF-предсказаний по всем объектам Train. Это и есть твои честные мета-признаки, безо всякой утечки, ёпта!
  4. Собираем пазл. Эти OOF-предсказания от всех твоих базовых моделей становятся входом для мета-модели (ну, там линейная регрессия или что-то подобное). Её ты и учишь на этих признаках и на реальных значениях y_train.
  5. Финальный аккорд. Когда надо предсказать что-то на отложенном Hold-out или на новых данных, ты делаешь так:
    • Каждая базовая модель, которую ты заново обучил на всём Train, делает своё предсказание.
    • Эти предсказания ты скармливаешь уже обученной мета-модели, и она выдаёт итоговый вердикт.

Смотри, как это выглядит в коде, чтобы не было подозрения, что я тебя развожу:

import numpy as np
from sklearn.model_selection import KFold
from sklearn.ensemble import RandomForestRegressor
from sklearn.linear_model import LinearRegression

# Допустим, у тебя уже есть данные (Hold-out мы уже откусили и спрятали)
X_train, y_train = ...
kf = KFold(n_splits=5, shuffle=True, random_state=42)

# Массив, куда будем складывать OOF-предсказания от каждой модели
oof_predictions = np.zeros((X_train.shape[0], len(base_models)))

# Гоняем каждую базовую модель
for model_idx, base_model in enumerate(base_models):
    col_oof = np.zeros(X_train.shape[0])

    # А вот и наша k-fold CV
    for fold, (train_idx, val_idx) in enumerate(kf.split(X_train, y_train)):
        X_tr, X_val = X_train[train_idx], X_train[val_idx]
        y_tr = y_train[train_idx]

        # Клонируем модель, учим на фолде
        model = clone(base_model)
        model.fit(X_tr, y_tr)
        # Предсказываем на валидационном фолде — то, что модель не видела
        col_oof[val_idx] = model.predict(X_val)

    # Записываем столбец OOF-предсказаний для этой модели
    oof_predictions[:, model_idx] = col_oof

# Вуаля! oof_predictions — это и есть наши честные мета-признаки для Train
meta_features_train = oof_predictions

# Теперь учим мета-модель на этой красоте
meta_model = LinearRegression()
meta_model.fit(meta_features_train, y_train)

А теперь про блендинг. Это, по сути, ленивая упрощённая версия стекинга для тех, у кого терпения ноль ебать. Вместо того чтобы городить всю эту ёперный театр с фолдами, ты просто откусываешь от Train одну валидационную выборку (скажем, 10-20%), учишь на остатке базовые модели, а предсказания на этой валидашке используешь как мета-признаки. Метод проще, быстрее, но, ясное дело, менее устойчивый. Если тебе не жалко данных и хочется побыстрее — можно и так, но знай, что идешь по охуенно тонкому льду.