Почему работает Boosting

Повышение градиента - один из самых эффективных методов машинного обучения. В этом посте я расскажу, почему бустинг работает. TL; DL Boosting исправляет ошибки предыдущих учеников, подбирая шаблоны в остатках.

Повышение

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

Чтобы создать интуицию для ускорения, я создам простой бустер, используя реализацию дерева решений Scikit learn. Само собой разумеется, что метод повышения градиента - отличный пакет XGboost. Этот пост должен помочь вам лучше понять, почему повышение так эффективно при решении задач прогнозного моделирования.

На высоком уровне повышение относится к семейству алгоритмов машинного обучения Ensemble. Повышение квалификации включает в себя последовательное обучение слабых учащихся - где слабый учащийся является оценщиком с низким уровнем систематической ошибки - для предсказания некоторого результата. Интересно то, что каждый учащийся не предсказывает исходную цель. Вместо этого каждый учащийся пытается предсказать ошибки предыдущего учащегося. На практике это означает, что учащийся-1 будет пытаться предсказать целевой результат напрямую, а учащийся-2 попытается предсказать остаток учащегося- 1. Этот процесс прогнозирования остатков продолжается до последнего учащегося. Затем можно сделать окончательный прогноз, взяв сумму всех отдельных учащихся. Это чрезвычайно эффективный способ предсказывать вещи, но интуиция не всегда ясна. Я надеюсь сделать эту интуицию более доступной в этом посте.

Начнем с импорта некоторых зависимостей

from sklearn.tree import DecisionTreeRegressor
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np

sns.set_style("whitegrid")
n = 100
X = np.linspace(0, 10, n) 
y = X**2 + 10 - (20 * np.random.random(n))
X = X[:, np.newaxis]

plt.figure(figsize=(15, 4))
plt.scatter(X, y, alpha=.7);

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

Я постарался, чтобы перечисленные ниже функции были очень простыми. many_trees возвращает список деревьев решений, boost последовательно подбирает деревья решений, сначала предсказывая целевой результат y с деревом-0, затем по дереву-1- n предсказывает остатки и predict выполняет итерацию по списку подходящих деревья решений и возвращает каждое предсказание деревьев. plot_fits - это функция согласования, которая суммирует прогноз n деревьев и возвращает подобранную линию и остатки.

Реализация повышения с использованием деревьев решений

def many_trees(n_trees, clf=False, **kwargs):
    trees = [DecisionTreeRegressor(**kwargs) for i in range(n_trees)]
    return trees

def boost(trees, X, y):
    fitted = []
    for tree in trees:
        tree.fit(X, y)
        yhat = tree.predict(X)
        y = (y-yhat) 
        fitted.append(tree)
    return fitted
        
def predict(trees, X):
    return np.array([tree.predict(X) for tree in trees]).T

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

learners = many_trees(30, max_depth=1, clf=False)
fitted = boost(learners, X, y, clf=False)
boosted_yhat = predict(fitted, X)

xfit = np.linspace(0, 10, 100).reshape(-1, 1)
  
def plot_fits(n_trees, row):
    preds_t = boosted_yhat[:, :n_trees]
    boosted_pred = preds_t.sum(1)
    res = boosted_pred-y
    axes[row, 0].plot(xfit, boosted_pred, c='red')
    axes[row, 0].scatter(X, y)
    axes[row, 0].set_title(f"Fit after {n_trees} trees", fontsize=15)
    axes[row, 1].scatter(sample_ix, res, alpha=0.7)
    axes[row, 1].plot(res, color='r', alpha=0.7)
    axes[row, 1].set_title(f"Residuals after {n_trees} trees", fontsize=15)

Дело в остатках!

Повышение работает путем подгонки последовательных моделей к остаткам предыдущей модели. Обычно в процессе оценки модели наносят на график остатки. Обычно вы проверяете остатки, чтобы убедиться, что они случайны и нет очевидных закономерностей. Если в остатках есть закономерности, это признак того, что вы упускаете ключевую информацию о целевой переменной и не соответствуете данным. По сути, шаблоны в остатках представляют информацию о взаимосвязи между X и Y, которую можно смоделировать.

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

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

fig, axes = plt.subplots(nrows=6, ncols=2, figsize=(20,25))
plot_fits(1, 0)
plot_fits(5, 1)
plot_fits(15, 2)
plot_fits(20, 3)
plot_fits(25, 4)
plot_fits(30, 5)

fig.tight_layout()

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

plt.figure(figsize=(15, 4))

pred = 0
for i in range(len(learners)):
    pred += boosted_yhat[:, i]
    plt.plot(xfit, pred)
plt.plot(xfit, predict(learners, X).sum(1))
plt.xlabel("X")
plt.ylabel("y");

plt.scatter(X, y, alpha=.4)

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

learners = many_trees(30, max_depth=4, clf=False)
fitted = boost(learners, X, y, clf=False)
boosted_yhat = predict(fitted, X)

plt.figure(figsize=(15, 4))
plt.scatter(X, y, alpha=.4)
pred = 0
for i in range(len(learners)):
    pred += boosted_yhat[:, i]
    plt.plot(xfit, pred)
plt.plot(xfit, predict(learners, X).sum(1))
plt.xlabel("X")
plt.ylabel("y");

sample_ix = np.arange(X.shape[0])
plt.figure(figsize=(15, 4))
plt.scatter(sample_ix, pred-y)
plt.plot(pred-y, color='r', alpha=0.4)

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

Надеюсь, вы нашли это полезным. Обращайтесь ниже с любыми вопросами или мыслями.