Фаза 02 · урок 11

Ансамблевые методы

Цель урока: Одно дерево решений быстро обучается и легко интерпретируется, но переобучается. Одна линейная модель недообучается на сложных границах. Можно потратить дни на проектирование идеальной архитектуры модели. А можно объединить множество…

Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.

Курс
AI Engineering from Scratch
Фаза
Основы машинного обучения
Чтение
14 мин.
Проверено
Содержание урока
  1. Цели обучения
  2. Проблема
  3. Концепция
  4. Почему ансамбли работают
  5. Бэггинг (Bootstrap Aggregating)
  6. Бустинг (последовательное исправление ошибок)
  7. AdaBoost
  8. Градиентный бустинг
  9. XGBoost: почему он доминирует на табличных данных
  10. Стекинг (метаобучение)
  11. Голосование
  12. Соберите это
  13. Шаг 1: решающий пень (базовый обучающийся алгоритм)
  14. Шаг 2: AdaBoost с нуля
  15. Шаг 3: градиентный бустинг с нуля
  16. Шаг 4: сравните со sklearn
  17. Используйте это
  18. Когда использовать каждый метод
  19. Производственный стек для табличных данных
  20. Внедрите это
  21. Упражнения
  22. Ключевые термины
  23. Дополнительное чтение

Группа слабых обучающихся моделей, правильно объединённая, становится сильной моделью. Это не метафора, а теорема.

Тип: Создание Язык: Python Предварительные требования: Фаза 2, урок 10 (компромисс между смещением и дисперсией) Время: ~120 минут

Цели обучения

  • Реализовать AdaBoost и градиентный бустинг с нуля и объяснить, как бустинг последовательно уменьшает смещение
  • Построить ансамбль бэггинга и показать, как усреднение декоррелированных моделей снижает дисперсию, не увеличивая смещение
  • Сравнить бэггинг, бустинг и стекинг по тому, на какой компонент ошибки нацелен каждый метод
  • Оценивать разнообразие ансамбля и объяснить, почему точность голосования большинством растёт с числом независимых слабых моделей

Проблема

Одно дерево решений быстро обучается и легко интерпретируется, но переобучается. Одна линейная модель недообучается на сложных границах. Можно потратить дни на проектирование идеальной архитектуры модели. А можно объединить множество несовершенных моделей и получить результат лучше каждой из них по отдельности.

Именно это делают ансамблевые методы. Это самый надёжный способ выигрывать соревнования Kaggle на табличных данных, они лежат в основе большинства производственных ML-систем и наглядно демонстрируют компромисс между смещением и дисперсией. Бэггинг уменьшает дисперсию. Бустинг уменьшает смещение. Стекинг учится определять, какой модели доверять на каких входах.

Концепция

Почему ансамбли работают

Предположим, у вас есть N независимых классификаторов, каждый с точностью p > 0.5. Точность голосования большинством равна:

P(majority correct) = sum over k > N/2 of C(N,k) * p^k * (1-p)^(N-k)

Для 21 классификатора с точностью 60% точность голосования большинством составляет около 74%. Для 101 классификатора она возрастает до 84%. Ошибки компенсируют друг друга, когда модели совершают разные ошибки.

Ключевое требование — разнообразие. Если все модели совершают одни и те же ошибки, их объединение ничего не даст. Ансамбли работают, поскольку создают разнообразные модели с помощью:

  • Разных подмножеств обучающих данных (бэггинг)
  • Разных подмножеств признаков (случайные леса)
  • Последовательного исправления ошибок (бустинг)
  • Разных семейств моделей (стекинг)

Бэггинг (Bootstrap Aggregating)

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

Диаграмма к уроку «Ансамблевые методы»

Бутстреп-выборка извлекается из исходных данных с возвращением и имеет тот же размер, что и исходный набор. В каждой бутстреп-выборке присутствуют около 63,2% уникальных примеров. Оставшиеся 36,8% (out-of-bag-примеры) образуют бесплатный валидационный набор.

Бэггинг уменьшает дисперсию, почти не увеличивая смещение. Каждое отдельное дерево переобучается на свою бутстреп-выборку, но это переобучение различно у каждого дерева, поэтому усреднение устраняет шум.

Случайные леса (Random Forests) — это бэггинг с дополнительным приёмом: в каждом разбиении рассматривается только случайное подмножество признаков. Это принуждает деревья к ещё большему разнообразию. Типичное число кандидатов в признаки: sqrt(n_features) для классификации и n_features / 3 для регрессии.

Бустинг (последовательное исправление ошибок)

Бустинг обучает модели последовательно. Каждая новая модель сосредоточивается на примерах, в которых ошиблись предыдущие модели.

Диаграмма к уроку «Ансамблевые методы»

Бустинг уменьшает смещение. Каждая новая модель исправляет систематические ошибки ансамбля на текущий момент. Финальное предсказание — взвешенная сумма всех моделей, при которой лучшим моделям назначается больший вес.

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

AdaBoost

AdaBoost (Adaptive Boosting) был первым практическим алгоритмом бустинга. Он работает с любым базовым обучающимся алгоритмом; обычно это решающие пни (деревья глубины 1).

Алгоритм:

1. Initialize sample weights: w_i = 1/N for all i

2. For t = 1 to T:
   a. Train weak learner h_t on weighted data
   b. Compute weighted error:
      err_t = sum(w_i * I(h_t(x_i) != y_i)) / sum(w_i)
   c. Compute model weight:
      alpha_t = 0.5 * ln((1 - err_t) / err_t)
   d. Update sample weights:
      w_i = w_i * exp(-alpha_t * y_i * h_t(x_i))
   e. Normalize weights to sum to 1

3. Final prediction: H(x) = sign(sum(alpha_t * h_t(x)))

Модели с меньшей ошибкой получают большее значение alpha. Неверно классифицированные примеры получают больший вес, поэтому следующая модель сосредоточивается на них.

Градиентный бустинг

Градиентный бустинг обобщает бустинг на произвольные функции потерь. Вместо перевзвешивания примеров он подгоняет каждую новую модель к остаткам (отрицательному градиенту функции потерь) текущего ансамбля.

1. Initialize: F_0(x) = argmin_c sum(L(y_i, c))

2. For t = 1 to T:
   a. Compute pseudo-residuals:
      r_i = -dL(y_i, F_{t-1}(x_i)) / dF_{t-1}(x_i)
   b. Fit a tree h_t to the residuals r_i
   c. Find optimal step size:
      gamma_t = argmin_gamma sum(L(y_i, F_{t-1}(x_i) + gamma * h_t(x_i)))
   d. Update:
      F_t(x) = F_{t-1}(x) + learning_rate * gamma_t * h_t(x)

3. Final prediction: F_T(x)

Для квадратичной функции потерь псевдоостатки — это просто фактические остатки: r_i = y_i - F_{t-1}(x_i). Каждое дерево буквально подгоняет ошибки предыдущего ансамбля.

Скорость обучения (shrinkage) управляет вкладом каждого дерева. При меньшей скорости обучения требуется больше деревьев, но обобщение лучше. Типичные значения: от 0,01 до 0,3.

XGBoost: почему он доминирует на табличных данных

XGBoost (eXtreme Gradient Boosting) — градиентный бустинг с инженерными оптимизациями, которые делают его быстрым, точным и устойчивым к переобучению:

  • Регуляризованная целевая функция: штрафы L1 и L2 на веса листьев не позволяют отдельным деревьям быть слишком уверенными
  • Аппроксимация второго порядка: использует первую и вторую производные функции потерь, что улучшает решения о разбиениях
  • Разбиения с учётом разреженности: нативно обрабатывает пропущенные значения, обучаясь лучшему направлению для пропусков в каждом разбиении
  • Подвыборка столбцов: как и случайные леса, выбирает признаки в каждом разбиении для разнообразия
  • Взвешенный эскиз квантилей: эффективно находит точки разбиения для непрерывных признаков в распределённых данных
  • Блочная структура с учётом кэша: компоновка памяти оптимизирована под строки кэша CPU

На табличных данных XGBoost (и его преемник LightGBM) стабильно превосходит нейронные сети. В ближайшее время это не изменится. Если ваши данные укладываются в таблицу со строками и столбцами, начните с градиентного бустинга.

Стекинг (метаобучение)

Стекинг использует предсказания нескольких базовых моделей в качестве признаков для метаобучающей модели.

Диаграмма к уроку «Ансамблевые методы»

Метаобучающая модель учится выбирать, какой базовой модели доверять на каких входах. Если случайный лес лучше в одних областях, а SVM — в других, метаобучающая модель научится соответствующим образом направлять предсказания.

Чтобы избежать утечки данных, предсказания базовых моделей должны генерироваться с помощью кросс-валидации на обучающем наборе. Нельзя одновременно обучать базовые модели и генерировать метапризнаки на одних и тех же данных.

Голосование

Самый простой ансамбль: просто объедините предсказания напрямую.

  • Жёсткое голосование: голосование большинством по меткам классов.
  • Мягкое голосование: усредните предсказанные вероятности и выберите класс с наибольшей средней вероятностью. Обычно это лучше, поскольку метод использует информацию об уверенности.

Соберите это

Шаг 1: решающий пень (базовый обучающийся алгоритм)

Код в code/ensembles.py реализует всё с нуля. Начнём с решающего пня: дерева с единственным разбиением.

class DecisionStump:
    def __init__(self):
        self.feature_idx = None
        self.threshold = None
        self.polarity = 1
        self.alpha = None

    def fit(self, X, y, weights):
        n_samples, n_features = X.shape
        best_error = float("inf")

        for f in range(n_features):
            thresholds = np.unique(X[:, f])
            for thresh in thresholds:
                for polarity in [1, -1]:
                    pred = np.ones(n_samples)
                    pred[polarity * X[:, f] < polarity * thresh] = -1
                    error = np.sum(weights[pred != y])
                    if error < best_error:
                        best_error = error
                        self.feature_idx = f
                        self.threshold = thresh
                        self.polarity = polarity

    def predict(self, X):
        n = X.shape[0]
        pred = np.ones(n)
        idx = self.polarity * X[:, self.feature_idx] < self.polarity * self.threshold
        pred[idx] = -1
        return pred

Шаг 2: AdaBoost с нуля

class AdaBoostScratch:
    def __init__(self, n_estimators=50):
        self.n_estimators = n_estimators
        self.stumps = []
        self.alphas = []

    def fit(self, X, y):
        n = X.shape[0]
        weights = np.full(n, 1 / n)

        for _ in range(self.n_estimators):
            stump = DecisionStump()
            stump.fit(X, y, weights)
            pred = stump.predict(X)

            err = np.sum(weights[pred != y])
            err = np.clip(err, 1e-10, 1 - 1e-10)

            alpha = 0.5 * np.log((1 - err) / err)
            weights *= np.exp(-alpha * y * pred)
            weights /= weights.sum()

            stump.alpha = alpha
            self.stumps.append(stump)
            self.alphas.append(alpha)

    def predict(self, X):
        total = sum(a * s.predict(X) for a, s in zip(self.alphas, self.stumps))
        return np.sign(total)

Шаг 3: градиентный бустинг с нуля

class GradientBoostingScratch:
    def __init__(self, n_estimators=100, learning_rate=0.1, max_depth=3):
        self.n_estimators = n_estimators
        self.lr = learning_rate
        self.max_depth = max_depth
        self.trees = []
        self.initial_pred = None

    def fit(self, X, y):
        self.initial_pred = np.mean(y)
        current_pred = np.full(len(y), self.initial_pred)

        for _ in range(self.n_estimators):
            residuals = y - current_pred
            tree = SimpleRegressionTree(max_depth=self.max_depth)
            tree.fit(X, residuals)
            update = tree.predict(X)
            current_pred += self.lr * update
            self.trees.append(tree)

    def predict(self, X):
        pred = np.full(X.shape[0], self.initial_pred)
        for tree in self.trees:
            pred += self.lr * tree.predict(X)
        return pred

Шаг 4: сравните со sklearn

Код проверяет, что наши реализации с нуля дают точность, сопоставимую с AdaBoostClassifier и GradientBoostingClassifier из sklearn, а также сравнивает все методы рядом друг с другом.

Используйте это

Когда использовать каждый метод

Метод Уменьшает Лучше всего для На что обратить внимание
Бэггинг / случайный лес Дисперсию Шумные данные, много признаков Не помогает со смещением
AdaBoost Смещение Чистые данные, простые базовые модели Чувствителен к выбросам и шуму
Градиентный бустинг Смещение Табличные данные, соревнования Медленно обучается, легко переобучается без настройки
XGBoost / LightGBM Оба Производственный табличный ML Много гиперпараметров
Стекинг Оба Получение последних 1–2% точности Сложен, риск переобучения метамодели
Голосование Дисперсию Быстрое объединение разнообразных моделей Помогает, только если модели разнообразны

Производственный стек для табличных данных

Для большинства задач табличного предсказания пробуйте в таком порядке:

  1. LightGBM или XGBoost с параметрами по умолчанию
  2. Настройте n_estimators, learning_rate, max_depth, min_child_weight
  3. Если нужны последние 0,5%, постройте стекинг-ансамбль из 3–5 разнообразных моделей
  4. На всём протяжении используйте кросс-валидацию

Нейронные сети на табличных данных почти всегда хуже градиентного бустинга, несмотря на продолжающиеся попытки исследований. TabNet, NODE и похожие архитектуры иногда сравниваются с ним, но редко превосходят хорошо настроенный XGBoost.

Внедрите это

Этот урок создаёт outputs/prompt-ensemble-selector.md — промпт, помогающий выбрать подходящий ансамблевый метод для заданного набора данных. Опишите свои данные (размер, типы признаков, уровень шума, баланс классов) и решаемую задачу. Промпт проведёт вас по контрольному списку решений, порекомендует метод, предложит начальные гиперпараметры и предупредит о распространённых ошибках этого метода. Также создаётся outputs/skill-ensemble-builder.md с полным руководством по выбору.

Упражнения

  1. Измените реализацию AdaBoost так, чтобы она отслеживала точность на обучении после каждого раунда. Постройте график точности в зависимости от числа оценщиков. Когда она сходится?

  2. Реализуйте случайный лес с нуля, добавив случайную подвыборку признаков в регрессионное дерево. Обучите 100 деревьев с max_features=sqrt(n_features) и усредните предсказания. Сравните снижение дисперсии с одиночным деревом.

  3. Добавьте в реализацию градиентного бустинга раннюю остановку: отслеживайте валидационную функцию потерь после каждого раунда и останавливайтесь, если она не улучшалась 10 последовательных раундов. Сколько деревьев в действительности требуется?

  4. Постройте стекинг-ансамбль из трёх базовых моделей (логистическая регрессия, дерево решений, k ближайших соседей) и метаобучающей логистической регрессии. Используйте 5-блочную кросс-валидацию для создания метапризнаков. Сравните с каждой базовой моделью по отдельности.

  5. Запустите XGBoost на том же наборе данных с параметрами по умолчанию. Сравните его точность со своим градиентным бустингом, реализованным с нуля. Измерьте время обоих. Насколько велика разница в скорости?

Ключевые термины

Термин Как обычно говорят Что это действительно означает
Бэггинг «Обучайте на случайных подмножествах» Bootstrap aggregating: обучайте модели на бутстреп-выборках и усредняйте предсказания, чтобы уменьшить дисперсию
Бустинг «Сосредоточьтесь на сложных примерах» Последовательно обучайте модели, каждая исправляет ошибки текущего ансамбля, чтобы уменьшить смещение
AdaBoost «Перевзвешивайте данные» Бустинг через обновление весов примеров; неверно классифицированные точки получают больший вес для следующей модели
Градиентный бустинг «Подгоняйте остатки» Бустинг, в котором каждая новая модель подгоняется к отрицательному градиенту функции потерь
XGBoost «Оружие Kaggle» Градиентный бустинг с регуляризацией, оптимизацией второго порядка и системными приёмами ускорения
Стекинг «Модели поверх моделей» Используйте предсказания базовых моделей как входные признаки для метаобучающей модели
Случайный лес «Множество рандомизированных деревьев» Бэггинг с деревьями решений и добавлением случайной подвыборки признаков в каждом разбиении для разнообразия
Разнообразие ансамбля «Совершайте разные ошибки» Чтобы ансамбль улучшил отдельные модели, их ошибки должны быть некоррелированными
Out-of-bag ошибка «Бесплатная валидация» Примеры, не попавшие в бутстреп-выборку (~36,8%), служат валидационным набором без выделения holdout-части

Дополнительное чтение


Источник: Ensemble Methods 02.10 — Компромисс между смещением и дисперсией · Фаза 2 — Основы машинного обучения · Полный каталог · 02.12 — Настройка гиперпараметров