Search
Write a publication
Pull to refresh

Градиентный бустинг для новичков

Reading time12 min
Views2.1K
Центр непрерывного образования

факультет компьютерных наук НИУ ВШЭ

В этой статье я рассмотрю общий принцип работы градиентного бустинга. Этот алгоритм считается достаточно продвинутым и эффективным, однако если рассмотреть его работу по шагам — можно увидеть, что он работает очень просто.

Сначала мы рассмотрим на простейшем примере принцип его работы, а потом посмотрим, как реализовать его с помощью Python.

Маргарита Бурова

Преподаватель и эксперт Центра непрерывного образования ФКН НИУ ВШЭ, руководитель Edtech-программ по DS и DA Wildberries&Russ

Градиентный бустинг — это алгоритм машинного обучения, в котором много простых, слабых моделей (обычно деревья решений) объединяются в одну сильную. Каждая новая модель помогает исправлять ошибки предыдущих, из‑за чего итоговый алгоритм становится всё точнее. Проще говоря, он учится на своих ошибках и с каждым шагом делает прогнозы лучше.

Слабые модели — это простые алгоритмы, которые чуть лучше случайного угадывания. В градиентном бустинге ими часто бывают неглубокие деревья решений, например, «пни» с одним разбиением. Они быстро обучаются и дают грубые предсказания, но при последовательном объединении, когда каждая следующая модель исправляет ошибку предыдущей, они могут дать очень точный результат — в этом и состоит идея бустинга.

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

Схему работы алгоритма можно увидеть на картинке ниже. Мы берем выборку, строим на ней решающее дерево, потом получаем остатки (ошибки), далее уже берем эти ошибки как целевые переменные, снова строим дерево и т. д.

Так шаг за шагом мы движемся в сторону наилучшего решения, напоминая процесс градиентного спуска, но не по отдельным параметрам, а по целым функциям, описывающим прогноз.

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

Чтобы понять принцип работы лучше — рассмотрим небольшой пример, иллюстрирующий работу градиентного бустинга в задаче регрессии — предсказания числового значения. Пусть мы пытаемся спрогнозировать количество продаж некоторого товара в зависимости от рекламного бюджета. У нас имеется очень небольшой набор данных (всего 6 наблюдений, чтобы можно было все посчитать вручную):

Рекламный бюджет (тыс. у.е.)

Фактические продажи (тыс. единиц)

0

4

1

6

2

8

3

12

4

18

5

24

Здесь первый столбец X — рекламные затраты (в тысячах у.е.), второй столбец y — реальные продажи (в тысячах единиц продукции). Видно, что с ростом бюджета продажи увеличиваются, но не строго линейно (особенно заметен скачок на высоких значениях бюджета).

Мы проведем вручную две итерации градиентного бустинга на этих данных. В качестве простой модели возьмём решающие пни (деревья решений глубины 1), поскольку с ними проще работать вручную. Будем использовать квадратичную функцию потерь (MSE, среднеквадратичную ошибку) — обычный выбор для задач регрессии.

Также введём гиперпараметр скорость обучения \alpha (learning rate) — коэффициент, на который мы умножаем предсказания каждого нового дерева перед добавлением в ансамбль. В нашем примере возьмём \alpha = 0.5 (достаточно высокая скорость, чтобы изменения были заметны, но всё же меньше 1, чтобы проиллюстрировать постепенное улучшение).

Шаг 1. Инициализация (начальный прогноз). На нулевой итерации градиентного бустинга мы создаём начальный прогноз F_0(x), который должен быть каким‑то простым приближением целевой переменной. Обычно для регрессии в качестве F_0 берут константу, равную среднему значению целевой переменной на обучающей выборке (это минимизирует MSE на обучающих данных). Посчитаем средние продажи в нашем наборе данных:

  • Сумма y: 4 + 6 + 8 + 12 + 18 + 24 = 72 тыс. единиц

  • Число точек n = 6

  • Среднее: \bar{y} = 72/6 = 12 тыс. единиц

Таким образом, начальный прогноз F_0 для любого значения бюджета равен 12. Иначе говоря, до обучения каких‑либо моделей мы просто предсказываем константу 12 (тыс. единиц продажи) для всех наблюдений. Дополняем нашу таблицу прогнозом:

Рекламный бюджет (тыс. у.е.)

Фактические продажи (тыс. единиц)

Первый прогноз продаж (тыс. единиц)

0

4

12

1

6

12

2

8

12

3

12

12

4

18

12

5

24

12

Уже можно увидеть, что пока модель сильно ошибается: например, для X=5 (бюджет 5 тыс.) фактические продажи 24 тыс., а мы предсказываем лишь 12 тыс., то есть ошибка составляет 12 единиц. Вот теперь‑то мы и будем пытаться исправить эти ошибки, добавляя следующие модели.

Шаг 2. Вычисление остатков (ошибок). Посчитаем остатки r_{i} — разницы между фактическими значениями y_{i} и текущим прогнозом модели F_0(x_i):

  • Для X=0: фактическое y=4, прогноз 12, остаток r = 4 - 12 = -8

  • Для X=1: y=18, прогноз 12, остаток r = 6 - 12 = -6

  • Для X=2: y=24, прогноз 12, остаток r = 8 - 12 = -4

  • Для X=3: y=12, прогноз 12, остаток r = 12 - 12 = 0

  • Для X=4: y=18, прогноз 12, остаток r = 18 - 12 = +6

  • Для X=5: y=24, прогноз 12, остаток r = 24 - 12 = +12

Итак, после нулевого (константного) прогноза у нас четыре неположительных остатка (для наблюдений с  X=4) и два положительных (для X=4,5). Отрицательный остаток означает, что наш прогноз был выше реального значения (мы переоценили), положительный — прогноз был ниже реального (недооценили). В частности, для X=5 мы сильно недооценили продажи (на 12 тыс. единиц). Теперь задача следующей модели — учесть эти остатки и постараться скорректировать прогнозы в нужную сторону.

Шаг 3. Обучение первого решающего пня на остатках. Теперь мы обучим первый слабый алгоритм (решающий пень) на наших данных. Поскольку мы хотим, чтобы новый алгоритм h_1(x) предсказывал остатки (т. е. ошибки текущей модели) — в качестве целевой переменной при его обучении выступают не исходные y, а рассчитанные остатки r. Наши пары данных для обучения первого пня будут: (X, r), где X — бюджет, r = y - 12 — остаток после начальной модели.

Решающий пень — дерево глубины 1 — разделит данные на две группы одним условием вида «X \leq c?». Алгоритм выберет такой порог, чтобы наилучшим образом приблизить предсказанием средние значения остатков в каждой группе. Интуитивно, он попробует разделить объекты на группы с примерно одинаковыми остатками. Давайте посмотрим на наши остатки: для X=0,1,2,3 они отрицательные или ноль, а для X=4,5 — положительные и достаточно большие. Логично ожидать, что дерево разделит данные примерно на эти две части. Предположим, алгоритм выбрал разделяющий порог X \leq 3 vs X > 3. Тогда:

  • Лист 1 (левая ветвь): объекты с X \leq 3 (бюджет 0, 1, 2, 3). Остатки этих точек: -8, -6, -4, 0. Среднее значение остатка на этом листе: (-8 + -6 + -4 + 0) / 4 = -18/4 = -4.5. Значит, первый пень будет предсказывать значение -4.5 для всех объектов, попадающих в эту группу. (Это означает: на этой группе он предполагает, что мы переоценили продажи примерно на 4.5 тыс., т. е. надо снизить прогноз.)

  • Лист 2 (правая ветвь): объекты с X > 3 (бюджет 4, 5). Остатки здесь: +6, +12. Среднее: (6 + 12) / 2 = 9. Соответственно, на этом листе пень будет предсказывать +9 (мы недооценили продажи примерно на 9 тыс., т. е. надо повысить прогноз для таких объектов).

Итак, наш первый слабый алгоритм h_1(x) можно описать правилом:

Он выдаёт поправку к предсказанию в виде константы, зависящей от того, в какую группу (левую или правую по условию X\leqslant3) попадает наблюдение x. Заметим, что h_1(x) как раз приблизил остатки текущей модели: для малых значений бюджета ошибка была отрицательная (переоценка), поэтому h_1 предсказывает отрицательную поправку; для больших бюджетов ошибка была положительная (недооценка), и h_1 даёт положительную поправку.

Шаг 4. Обновление прогноза с учётом первого дерева. Теперь мы обновим наш ансамбль: формируем новый прогноз F_1(x), добавив к предыдущему прогнозу F_0(x) поправку от первой модели h_1(x) с учётом скорости обучения \alpha. Формально можем записать это так:F_1(x)=F_0(x)+\alpha \cdot h_1(x).

В нашем случае F_0(x) = 12 (константа), \alpha = 0.5. Значения h_1(x) мы определили выше для двух групп. Посчитаем новые прогнозы F_1 для каждой точки:

  • Для X=0,1,2,3 (левая группа): F_1 = 12 + 0.5 \cdot (-4.5) = 12 - 2.25 = 9.75. То есть после добавления первого дерева наш прогноз продаж для бюджетов 0, 1, 2, 3 снизился с 12 до 9.75 тыс. единиц. (Мы скорректировали его вниз, поскольку изначально переоценивали продажи на этих небольших бюджетах.)

  • Для X=4,5 (правая группа): F_1 = 12 + 0.5 \cdot 9 = 12 + 4.5 = 16.5. Прогноз для больших бюджетов (4 и 5 тыс.) увеличился с 12 до 16.5 тыс. единиц. (Мы подняли предсказание, так как изначально сильно недооценивали продажи для этих точек.)

Хорошо видно, как первый шаг бустинга подправил базовый прогноз в верном направлении: там, где изначально прогноз был слишком высок (малые X), он снизился; там, где был слишком низок (большие X), — повысился.

Шаг 5. Пересчёт ошибок после первого дерева. Теперь снова вычислим остатки, но уже для обновлённого прогноза F_1(x). Это покажет, какие ошибки остаются после учёта первого корректирующего дерева:

Рекламный бюджет (тыс. у.е.)

Фактические продажи (тыс. единиц)

Первый прогноз продаж (тыс. единиц)

Второй прогноз(тыс. единиц) 

Остаток

0

4

12

9.75

-5.75

1

6

12

9.75

-3.75

2

8

12

9.75

-1.75

3

12

12

9.75

+2.25

4

18

12

16.5

+1.5

5

24

12

16.5

+7.5

Шаг 6. Вторая итерация бустинга (второе дерево). Повторяем процесс: теперь у нас текущий прогноз F_1(x) и остатки r после него. Обучаем второй решающий пень h_2(x) на новых остатках. Посмотрим на распределение текущих ошибок r:

  • Для X=0,1,2: остатки отрицательные (-5.75, -3.75, -1.75) — мы всё ещё немного переоцениваем продажи при низком бюджете.

  • Для X=3: остаток +2.25 (стали недооценивать на этой точке).

  • Для X=4: остаток +1.5 (слегка недооцениваем).

  • Для X=5: остаток +7.5 (значительно недооцениваем при самом крупном бюджете).

Заметим, что самые большие по модулю ошибки сейчас: X=5 (наибольший положительный остаток +7.5) и X=0 (наибольший отрицательный -5.75). Вероятно, новое дерево попытается особенно учесть точку X=5, так как там ошибка велика. Возможное разбиение — отделить самый большой бюджет от остальных. Предположим, второй пень выберет порог X > 4, то есть разделит данные на: группа 1 (X \le 4) и группа 2 (X > 4).

  • Лист 1 (X \le 4): объекты X=0,1,2,3,4 (пять точек). Их остатки: -5.75, -3.75, -1.75, +2.25, +1.5. Средний остаток на этой группе:

    \frac{-5.75 - 3.75 - 1.75 + 2.25 + 1.5}{5} = \frac{-7.5}{5} = -1.5.

    Пень h_2 предскажет-1.5 для этой группы (небольшая отрицательная поправка, т. е. слегка снизит прогнозы, поскольку в среднем мы чуть переоцениваем на этих точках).

  • Лист 2 (X > 4): объекты X=5 (только одна точка). Остаток: +7.5. Средний остаток = 7.5. Пень предскажет +7.5 для этой группы (существенная положительная поправка для X=5).

Получаем правило для второго дерева:

Шаг 7. Обновление прогноза с учётом второго дерева. Добавляем h_2(x) к текущему ансамблю:F_2(x)=F_1(x)+\alpha \cdot h_2(x),где \alpha = 0.5 по-прежнему. Обновим прогнозы:

Рекламный бюджет (тыс. у.е.)

Фактические продажи (тыс. единиц)

Первый прогноз продаж (тыс. единиц)

Второй прогноз(тыс. единиц) 

Третий прогноз (тыс. единиц) 

0

4

12

9.75

9.0

1

6

12

9.75

9.0

2

8

12

9.75

9.0

3

12

12

9.75

9.0

4

18

12

16.5

15.75

5

24

12

16.5

20.25

Шаг 8. Пересчёт ошибок после второго дерева. Найдём остатки r при новом прогнозе F_2:

  • X=0: фактическое 4, прогноз 9.0, остаток r = 4 - 9.0 = -5.0

  • X=1: фактическое 6, прогноз 9.0, остаток r = 6 - 9.0 = -3.0

  • X=2: фактическое 8, прогноз 9.0, остаток r = 8 - 9.0 = -1.0

  • X=3: фактическое 12, прогноз 9.0, остаток r = 12 - 9.0 = +3.0

  • X=4: фактическое 18, прогноз 15.75, остаток X=4

  • X=5: фактическое 24, прогноз 20.25, остаток r = 24 - 20.25 = +3.75

Ошибки продолжили уменьшаться: например, для X=5 остаток сократился с +7.5 до +3.75, для X=0 с -5.75 до -5.0, для X=4 с +1.5 до +2.25 (здесь чуть вырос, но был небольшой). Видим, что ни у одной точки теперь нет ошибки больше 5 (в сравнении с 12 изначально). Тем не менее, заметны группы: X=0,1,2 имеют небольшие отрицательные остатки,X=3,4,5 — положительные.

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

Важно подчеркнуть, что на каждой итерации мы уменьшали функцию потерь. В нашем случае функция потерь — сумма квадратов ошибок. После 1-го дерева сумма квадратов ошибок снизилась, после 2-го ещё снизилась, и дальше будет снижаться все больше. Градиентный бустинг гарантирует постепенное улучшение качества на обучающих данных, так как каждое новое дерево оптимизируется именно на остатках (градиентах ошибки) предыдущей модели.

Коэффициент скорости обучения \alpha при этом контролирует величину шага — насколько сильно каждое новое дерево исправляет ошибку предшественника. В нашем примере мы брали \alpha=0.5, т. е. каждое дерево исправляло ошибку лишь наполовину, оставляя пространство для последующих улучшений. Если бы мы взяли \alpha=1.0, каждое дерево полностью бы исправляло усреднённую ошибку на своих листах, и, возможно, за меньшее число итераций мы бы дошли до очень малой ошибки.

Теперь давайте рассмотрим, как можно реализовать градиентный бустинг с помощью Python!

Современные библиотеки машинного обучения предоставляют готовые и оптимизированные реализации градиентного бустинга, так что нет необходимости реализовывать его вручную для каждой задачи. Одной из наиболее распространённых является библиотека scikit‑learn, где есть класс GradientBoostingRegressor для решения задач регрессии (и аналогичный GradientBoostingClassifier для классификации). Также существуют специализированные библиотеки, такие как XGBoost, LightGBM, CatBoost, которые часто превосходят по скорости и дополнительным возможностям базовый GradientBoosting из scikit‑learn. Здесь мы покажем простой пример использования GradientBoostingRegressor из scikit‑learn на данных, похожих на наш ручной пример.

Проделаем следующие шаги:

  1. Подготовим данные. Воспользуемся теми же условными данными: рекламные бюджеты X и продажи y.

  2. Создадим модель. Настроим GradientBoostingRegressor с определёнными параметрами: например, возьмём n_estimators = 3 (три дерева, в ручном вычислении у нас было два), learning_rate = 0.5 (как в нашем примере), и ограничим глубину деревьев max_depth = 1 (решающие пни). В реальной задаче эти параметры подбираются тщательнее, но для демонстрации мы берём небольшие значения, чтобы легко сопоставить с ручными расчётами.

  3. Обучим модель на наших данных с помощью метода fit.

  4. Получим прогнозы и сравним их с фактическими значениями.

Ниже приведён код с комментариями для каждого шага:

import numpy as np
from sklearn.ensemble import GradientBoostingRegressor

# 1. Подготовка данных (наш небольшой набор X -> y)
X = np.array([[0], [1], [2], [3], [4], [5]])   # признак: бюджет (тыс. у.е.), форматируем как колонку
y = np.array([4, 6, 8, 12, 18, 24])           # целевая переменная: продажи (тыс. единиц)

# 2. Создание и настройка модели градиентного бустинга
model = GradientBoostingRegressor(
    n_estimators=3,    # число деревьев в ансамбле (итераций бустинга)
    learning_rate=0.5, # скорость обучения (вклад каждого дерева)
    max_depth=1,       # максимальная глубина деревьев (1 = решающий пень)
    random_state=0     # фиксируем случайность для воспроизводимости
)

# 3. Обучение модели на данных
model.fit(X, y)

# 4. Получение прогнозов модели
predictions = model.predict(X)
print("Прогнозы модели:", predictions)
print("Фактические значения:", y)

Давайте запустим этот код и посмотрим на результат:

Прогнозы модели: [ 7.3125  7.3125  7.3125 11.0625 17.8125 21.1875]  
Фактические значения: [ 4  6  8 12 18 24]

Модель выдала предсказания, довольно близкие к реальным продажам. Например, для X=5 (бюджет 5k) прогноз ~21.19 тыс. продаж против фактических 24; для X=0 прогноз ~7.31 против фактических 4; и т. д. Эти результаты похожи на то, что мы получили вручную.

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

  • n_estimators = 3 — мы запросили именно 3 дерева. Если указать больше, модель добавила бы больше слабых моделей, потенциально улучшив качество (до некоторого предела). В реальности имеет смысл подобрать n_estimators повыше (сотни и более) при малом learning_rate.

  • learning_rate = 0.5 — скорость обучения. Мы взяли относительно большой шаг 0.5 для наглядности. В прикладных задачах часто ставят 0.1 или 0.05; 0.5 — скорее верхняя граница, при которой нужно мало деревьев, но есть риск переобучения. Можно поэкспериментировать: уменьшив learning_rate до 0.1 и увеличив n_estimators до, скажем, 30, скорее всего модель даст более точные результаты на этом же примере

  • max_depth = 1 — ограничение глубины каждого дерева одним уровнем (решающие пни). Если мы разрешим более глубокие деревья (например, max_depth = 3), каждый из 3 деревьев может сильнее подгоняться под данные. Попробуйте мысленно: одно дерево глубины 3 на таком маленьком наборе могло бы почти идеально моделировать зависимость. В большинстве случаев max_depth = 3-5 — разумный диапазон для бустинга.

  • random_state = 0 — фиксирует генератор случайностей, чтобы результаты были воспроизводимы. В градиентном бустинге случайность может влиять, например, при равнозначных разбиениях или при использовании подвыборок. Фиксация состояния обеспечивает, что каждый запуск даст одинаковый результат (полезно для экспериментов).

После обучения мы использовали model.predict(X) для получения прогнозов на тех же данных. В реальности нас интересуют прогнозы на новых (тестовых) данных, но у нас их нет, и мы просто продемонстрировали, как модель выучила обучающие точки.

Итак, мы разобрали принцип работы градиентного бустинга и теперь вы знаете, как он работает в теории и как его реализовать в коде!

Конечно, для комфортного понимания этой темы важно уверенно владеть базовыми алгоритмами машинного обучения. Если вы только начинаете свой путь в Data Science и хотите пройти эти первые шаги, рекомендую бесплатный онлайн‑интенсив «Введение в Data Science: от Python до машинного обучения» от Центра непрерывного образования ФКН НИУ ВШЭ. Он поможет заложить фундамент для дальнейшего освоения более сложных алгоритмов, в том числе и бустинга.

Tags:
Hubs:
+3
Comments3

Articles