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

Интуиция, стоящая за повышением градиента, похожа на игру в гольф. Первое попадание f0, которое вы сделали, является оценкой прогноза, сделанной первоначальным оценщиком. Второе попадание delta1, которое вы сделали по отношению к y, является предсказанным остатком первого дерева регрессии по отношению к истине. Остаток рассчитывается с помощью функции потерь на основе оценки прогноза на текущем шаге и истинности. Вы будете становиться все ближе и ближе после каждого шага, другими словами, потери поезда становятся все меньше и меньше с продолжением обучения.

В этом посте мы используем реализацию sklearn для обучения классификатора Gradient Boosting и графически для визуализации процедуры и результатов. В частности, содержание этого сообщения резюмируется следующим образом:

  1. используйте make_moons для создания примера двумерного набора данных
  2. как делаются первоначальные прогнозы с помощью Log Odds Estimator
  3. как рассчитывается остаточная стоимость каждой выборки на каждом шаге с помощью регрессора дерева решений
  4. как рассчитывается общая потеря тренировочного набора на каждом шаге
  5. как складываются оценка и остаток
  6. как преобразовать счет в класс предсказания
  7. сделать окончательный прогноз и провести границу решения

Во-первых, мы делаем наш пример двумерного набора данных для иллюстрации, используя метод sklearn make_moons. Данные о форме Луны не так легко подходят, чтобы мы могли более четко увидеть процесс обучения повышения градиента.

from sklearn.datasets import make_moons
X, y = make_moons(n_samples=50)
import plotly.offline as py
import plotly.graph_objs as go
py.init_notebook_mode(connected=True)
trace0 = go.Scatter(
    x = X[y==0][:,0],
    y = X[y==0][:,1],
    mode = 'markers+text',
    textposition = 'bottom center',
    text = np.where(y==0)[0],
    name = 'y=0'
)
trace1 = go.Scatter(
    x = X[y==1][:,0],
    y = X[y==1][:,1],
    mode = 'markers+text',
    textposition = 'bottom center',
    text = np.where(y==1)[0],
    name = 'y=1'
)
layout = go.Layout(
    title='example 2-d dataset',
    xaxis=dict(
        title='x[0]'
        ),
    yaxis=dict(
        title='x[1]'
    )
)
data = [trace0,trace1]
fig = go.Figure(data=data, layout=layout)
# Plot and embed in ipython notebook!
py.iplot(fig, filename='basic-scatter')

Затем мы обучаем классификатор повышения градиента с 30 деревьями (шагами). Максимальная глубина по умолчанию для каждого дерева равна 3. В процессе обучения мы рассчитываем потери поезда и используем метрику оценки ROC_AUC для оценки нашего процесса обучения.

import numpy as np
tree_num = 30
tree_depth = 3
from sklearn.ensemble import GradientBoostingClassifier
gb = GradientBoostingClassifier(n_estimators=tree_num, max_depth=tree_depth).fit(X, y)
from sklearn.metrics import roc_auc_score
pred0 = gb.init_.predict(X).ravel()
train_auc = [roc_auc_score(y, pred0)]
train_auc += [roc_auc_score(y, y_prob) for y_prob in gb.staged_decision_function(X)]
train_deviance = [gb.loss_(y, gb.init_.predict(X))]
train_deviance += [gb.loss_(y, y_pred) for y_pred in gb.staged_decision_function(X)]
x_list = list(range(tree_num+1))
# Create a trace
trace0 = go.Scatter(
    x = x_list,
    y = train_deviance,
    name = 'train_loss'
)
trace1 = go.Scatter(
    x = x_list,
    y = train_auc,
    name = 'train_auc'
)
data = [trace0,trace1]
layout = go.Layout(
    title='total loss / AUC score of training set in each step',
    xaxis=dict(title='step'),
    yaxis=dict(title='loss / auc_score')
)
fig = go.Figure(data=data, layout=layout)
py.iplot(fig, filename='loss-line')

Начальный прогноз делается init_estimator. В случае двухклассовой классификации init_estimator в биномиальном отклонении является оценщиком логарифмических шансов. Этот оценщик использует логарифмическое отношение количества выборок в двух целевых классах в качестве выходной оценки, чтобы сделать наивный прогноз. Auc_score на нулевом шаге, который имеет только init_estimator, равен 0,5, потому что у нас есть одинаковое количество выборок в двух целевых классах. Потери на каждом шаге вычисляются как 2 * отрицательное логарифмическое правдоподобие. В частности, в исходном коде это -2,0 * np.mean((y * pred) — np.logaddexp(0,0, pred)) где logaddexp — это логарифм суммы возведений в степень входных данных.

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

x_list = list(range(tree_num+1))
data = []
for x in X:
    score = [gb.init_.predict(x).ravel()[0]]
    residuals = [dt.predict([x])[0] for dt in gb.estimators_.ravel()]
    residual = 0
    for i,r in enumerate(residuals):
        residual += r*(lr)
        score += [residual]
    trace = go.Scatter(x = x_list, y = score)
    data += [trace]
layout = go.Layout(
    title='score of each sample in each step',
    xaxis=dict(title='step'),
    yaxis=dict(title='score')
)
fig = go.Figure(data=data, layout=layout)
py.iplot(fig, filename='instance-line')

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

# Log Odds Estimator output score
n0 = np.sum(y_train==0)
n1 = np.sum(y_train==1)
log_odds = np.log(n1/n0)
log_odds
0

На шаге 22 на этой линейной диаграмме синяя линия (образец 19) и красная линия (образец 43), которые близки к оси 0, перемещаются в противоположных направлениях. Это означает, что предсказания этих двух выборок меняются местами после шага 22, поскольку оценки используются в качестве входных данных для функции expit, которая является реализацией логистической сигмовидной функции. В этой функции оценка ниже 0 выводит 0 в качестве класса прогнозирования, а оценка выше 0 выводит 1 в качестве класса прогнозирования.

Остаточный результат — это прогнозируемое значение каждого дерева регрессии, глубина которого равна max_depth. В первом дереве регрессии оно имеет 6 конечных узлов, что означает, что оно имеет 6 различных значений остатка предсказания для разных выборок.

dt = gb.estimators_[0][0]
from sklearn.externals.six import StringIO
dot_data = StringIO()
from sklearn.tree import export_graphviz
export_graphviz(dt, out_file=dot_data, 
                rounded = True,
                rotate = True,
                filled = True)
import pydot
from IPython.display import Image
graph = pydot.graph_from_dot_data(dot_data.getvalue())
Image(graph[0].create_png())

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

Наконец, мы можем нарисовать границу решения окончательной модели. Это может объяснить, почему прогнозные оценки синей линии (образец 19) и красной линии (образец 43) изменились в обратном порядке на предыдущем линейном графике. На диаграмме границ решений мы обнаруживаем, что эти две выборки являются самыми сложными для изучения в обучающей выборке. Классификатор повышения градиента правильно подогнал их после 22-го шага. И после этого модель получает идеальный прогноз на тренировочном наборе.

h = 0.1
x0_min, x0_max = X[:, 0].min() - 1, X[:, 0].max() + 1
x1_min, x1_max = X[:, 1].min() - 1, X[:, 1].max() + 1
x0_ = np.arange(x0_min, x0_max, h)
x1_ = np.arange(x1_min, x1_max, h)
x0x0, x1x1 = np.meshgrid(x0_, x1_)
Z = gb.predict(np.c_[x0x0.ravel(), x1x1.ravel()]).reshape(x0x0.shape)
cmap_light =[[0, '#87c6f2'], [1, '#f2af74']]
p1 = go.Heatmap(x=x0_, y=x1_, z=Z,
                showscale=False,
                colorscale=cmap_light
               )
trace0 = go.Scatter(
    x = X[y==0][:,0],
    y = X[y==0][:,1],
    mode = 'markers+text',
    textposition = 'bottom center',
    text = np.where(y==0)[0],
    name = 'y=0'
)
trace1 = go.Scatter(
    x = X[y==1][:,0],
    y = X[y==1][:,1],
    mode = 'markers+text',
    textposition = 'bottom center',
    text = np.where(y==1)[0],
    name = 'y=1'
)
layout = go.Layout(
    title='example 2-d dataset',
    xaxis=dict(
        title='x[0]'
        ),
    yaxis=dict(
        title='x[1]'
    )
)
data = [trace0,trace1,p1]
fig = go.Figure(data=data, layout=layout)
# Plot and embed in ipython notebook!
py.iplot(fig,filename='basic-scatter')

Ура, спасибо, что дочитали до конца! Вот ссылки на этот пост: