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

Внимание: в тексте задания произошли изменения - поменялось число деревьев (теперь 50), правило изменения величины шага в задании 3 и добавился параметр random_state у решающего дерева. Правильные ответы не поменялись, но теперь их проще получить. Также исправлена опечатка в функции gbm_predict.

В этом задании будет использоваться датасет boston из sklearn.datasets. Оставьте последние 25% объектов для контроля качества, разделив X и y на X_train, y_train и X_test, y_test.

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


In [1]:
from sklearn import cross_validation, datasets, metrics, tree, ensemble, learning_curve

import numpy as np
import pandas as pd


/Users/educationalprogram/anaconda/lib/python2.7/site-packages/sklearn/cross_validation.py:44: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. Also note that the interface of the new CV iterators are different from that of this module. This module will be removed in 0.20.
  "This module will be removed in 0.20.", DeprecationWarning)
/Users/educationalprogram/anaconda/lib/python2.7/site-packages/sklearn/learning_curve.py:23: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the functions are moved. This module will be removed in 0.20
  DeprecationWarning)

In [2]:
%pylab inline


Populating the interactive namespace from numpy and matplotlib

In [3]:
boston = datasets.load_boston()

X_train = boston.data[:379]
X_test = boston.data[379:]
y_train = boston.target[:379]
y_test = boston.target[379:]

Задание 1

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

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

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

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


In [4]:
def grad(y,z):
    return y - z

Задание 2

Заведите массив для объектов DecisionTreeRegressor (будем их использовать в качестве базовых алгоритмов) и для вещественных чисел (это будут коэффициенты перед базовыми алгоритмами).

В цикле от обучите последовательно 50 решающих деревьев с параметрами max_depth=5 и random_state=42 (остальные параметры - по умолчанию). В бустинге зачастую используются сотни и тысячи деревьев, но мы ограничимся 50, чтобы алгоритм работал быстрее, и его было проще отлаживать (т.к. цель задания разобраться, как работает метод). Каждое дерево должно обучаться на одном и том же множестве объектов, но ответы, которые учится прогнозировать дерево, будут меняться в соответствие с полученным в задании 1 правилом.

Попробуйте для начала всегда брать коэффициент равным 0.9. Обычно оправдано выбирать коэффициент значительно меньшим - порядка 0.05 или 0.1, но т.к. в нашем учебном примере на стандартном датасете будет всего 50 деревьев, возьмем для начала шаг побольше.

В процессе реализации обучения вам потребуется функция, которая будет вычислять прогноз построенной на данный момент композиции деревьев на выборке X:

def gbm_predict(X):
    return [sum([coeff * algo.predict([x])[0] for algo, coeff in zip(base_algorithms_list, coefficients_list)]) for x in X]
(считаем, что base_algorithms_list - список с базовыми алгоритмами, coefficients_list - список с коэффициентами перед алгоритмами)

Эта же функция поможет вам получить прогноз на контрольной выборке и оценить качество работы вашего алгоритма с помощью mean_squared_error в sklearn.metrics.

Возведите результат в степень 0.5, чтобы получить RMSE. Полученное значение RMSEответ в пункте 2.


In [5]:
def gbm_predict(X):
    return [sum([coeff * algo.predict([x])[0] for algo, coeff in zip
                 (base_algorithms_list, coefficients_list)]) for x in X]
#(считаем, что base_algorithms_list - список с базовыми алгоритмами,
 #coefficients_list - список с коэффициентами перед алгоритмами)
base_algorithms_list = []
coefficients_list = []
error_2 = []

estimator = tree.DecisionTreeRegressor(max_depth=5, random_state = 42)    
estimator.fit(X_train, y_train)
base_algorithms_list.append(estimator)
coefficients_list.append(0.9)
err = np.sqrt(metrics.mean_squared_error(y_test, gbm_predict(X_test)))
error_2.append(err)
    
for i in range(1, 50):
    estimator = tree.DecisionTreeRegressor(max_depth=5,random_state = 42)    
    
    y_pred = gbm_predict(X_train)
    
    estimator.fit(X_train, grad(y_train, y_pred))
    base_algorithms_list.append(estimator)
    coefficients_list.append(0.9)
    
    err = np.sqrt(metrics.mean_squared_error(y_test, gbm_predict(X_test)))
    error_2.append(err)
    print 'error № ',i,' = ', err,'\n'


error №  1  =  4.67115371425 

error №  2  =  4.902307279 

error №  3  =  4.96414761873 

error №  4  =  5.22591814058 

error №  5  =  5.15731316944 

error №  6  =  5.26203169472 

error №  7  =  5.31037728018 

error №  8  =  5.49735236302 

error №  9  =  5.50976262651 

error №  10  =  5.51536443259 

error №  11  =  5.50874833155 

error №  12  =  5.50754530432 

error №  13  =  5.51337953929 

error №  14  =  5.44512320786 

error №  15  =  5.47672020639 

error №  16  =  5.47542645699 

error №  17  =  5.4638455167 

error №  18  =  5.47292361429 

error №  19  =  5.47237372531 

error №  20  =  5.47663342962 

error №  21  =  5.47389832656 

error №  22  =  5.47462444855 

error №  23  =  5.47809994824 

error №  24  =  5.47969742317 

error №  25  =  5.48095722855 

error №  26  =  5.47970381231 

error №  27  =  5.48104693831 

error №  28  =  5.49604397771 

error №  29  =  5.47733948699 

error №  30  =  5.47798717367 

error №  31  =  5.47802828313 

error №  32  =  5.47637513571 

error №  33  =  5.47620399489 

error №  34  =  5.47602454125 

error №  35  =  5.47696019446 

error №  36  =  5.47841604605 

error №  37  =  5.47772991619 

error №  38  =  5.47614603779 

error №  39  =  5.47679470442 

error №  40  =  5.47669503426 

error №  41  =  5.47632897204 

error №  42  =  5.47574966183 

error №  43  =  5.47601272123 

error №  44  =  5.47612194832 

error №  45  =  5.47673970866 

error №  46  =  5.47674382025 

error №  47  =  5.47681407471 

error №  48  =  5.47676640788 

error №  49  =  5.47665097417 


In [6]:
ans1 = error_2[49]
print ans1
with open('grad_boost_ans1.txt', 'w') as file_out:
        file_out.write(str(ans1))


5.47665097417

Задание 3

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

Попробуйте уменьшать вес перед каждым алгоритмом с каждой следующей итерацией по формуле 0.9 / (1.0 + i), где i - номер итерации (от 0 до 49). Используйте качество работы алгоритма как ответ в пункте 3.

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


In [7]:
base_algorithms_list = []
coefficients_list = []
error_3 = []

estimator = tree.DecisionTreeRegressor(max_depth=5, random_state = 42)    
estimator.fit(X_train, y_train)
base_algorithms_list.append(estimator)
coefficients_list.append(0.9)
err = np.sqrt(metrics.mean_squared_error(y_test, gbm_predict(X_test)))
error_3.append(err)
    
for i in range(1, 50):
    estimator = tree.DecisionTreeRegressor(max_depth=5,random_state = 42)    
    
    y_pred = gbm_predict(X_train)
    
    estimator.fit(X_train, grad(y_train, y_pred))
    base_algorithms_list.append(estimator)
    coefficients_list.append(0.9/(1. + i))
    
    err = np.sqrt(metrics.mean_squared_error(y_test, gbm_predict(X_test)))
    error_3.append(err)
    print 'error № ',i,' = ', err,'\n'


error №  1  =  4.41159494834 

error №  2  =  4.39118317166 

error №  3  =  4.45874508257 

error №  4  =  4.49862725958 

error №  5  =  4.51678863168 

error №  6  =  4.55049937011 

error №  7  =  4.60844761848 

error №  8  =  4.63333747349 

error №  9  =  4.65090865256 

error №  10  =  4.65853296944 

error №  11  =  4.66516497966 

error №  12  =  4.68676877506 

error №  13  =  4.7042338802 

error №  14  =  4.70790404385 

error №  15  =  4.72596972469 

error №  16  =  4.73740159456 

error №  17  =  4.74223331219 

error №  18  =  4.74675518764 

error №  19  =  4.75332257595 

error №  20  =  4.75947997184 

error №  21  =  4.76345795617 

error №  22  =  4.7635949072 

error №  23  =  4.76186700767 

error №  24  =  4.76187942782 

error №  25  =  4.76105843061 

error №  26  =  4.75686234322 

error №  27  =  4.76717333036 

error №  28  =  4.77047187654 

error №  29  =  4.77893655078 

error №  30  =  4.77514295762 

error №  31  =  4.78222265584 

error №  32  =  4.7850000048 

error №  33  =  4.78690822047 

error №  34  =  4.7856983121 

error №  35  =  4.79090520221 

error №  36  =  4.79195203191 

error №  37  =  4.7978781996 

error №  38  =  4.80244390205 

error №  39  =  4.80228245136 

error №  40  =  4.80151458777 

error №  41  =  4.80245728121 

error №  42  =  4.80389666243 

error №  43  =  4.80323339564 

error №  44  =  4.80213993236 

error №  45  =  4.80320212453 

error №  46  =  4.80730227795 

error №  47  =  4.80663144813 

error №  48  =  4.80727023114 

error №  49  =  4.81089328026 


In [8]:
ans2 = error_3[49]
print ans2
with open('grad_boost_ans2.txt', 'w') as file_out:
        file_out.write(str(ans2))


4.81089328026

Задание 4

Реализованный вами метод - градиентный бустинг над деревьями - очень популярен в машинном обучении. Он представлен как в самой библиотеке sklearn, так и в сторонней библиотеке XGBoost, которая имеет свой питоновский интерфейс. На практике XGBoost работает заметно лучше GradientBoostingRegressor из sklearn, но для этого задания вы можете использовать любую реализацию.

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

1. С увеличением числа деревьев, начиная с некоторого момента, качество работы градиентного бустинга не меняется существенно.

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

3. С ростом глубины деревьев, начиная с некоторого момента, качество работы градиентного бустинга на тестовой выборке начинает ухудшаться.

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

In [9]:
# iteration
err_list = []
coefficients_list = []
base_algorithms_list = []

estimator = tree.DecisionTreeRegressor(max_depth=5, random_state = 42)    
estimator.fit(X_train, y_train)
base_algorithms_list.append(estimator)
coefficients_list.append(0.9)
err = np.sqrt(metrics.mean_squared_error(y_test, gbm_predict(X_test)))
err_list.append(err)
    
for i in range(1, 100):
    estimator = tree.DecisionTreeRegressor(max_depth=5,random_state = 42)    
    
    y_pred = gbm_predict(X_train)
    
    estimator.fit(X_train, grad(y_train, y_pred))
    base_algorithms_list.append(estimator)
    coefficients_list.append(0.9/(1. + i))
    
    err = np.sqrt(metrics.mean_squared_error(y_test, gbm_predict(X_test)))
    err_list.append(err)
    print i,


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

In [10]:
plt.plot(range(0, 100), err_list)


Out[10]:
[<matplotlib.lines.Line2D at 0x117f10f50>]

In [11]:
# max_depth
err_list = []
coefficients_list = []
base_algorithms_list = []
depths = range(2, 2*50 + 2, 2)

estimator = tree.DecisionTreeRegressor(max_depth=depths[0], random_state = 42)    
estimator.fit(X_train, y_train)
base_algorithms_list.append(estimator)
coefficients_list.append(0.9)
err = np.sqrt(metrics.mean_squared_error(y_test, gbm_predict(X_test)))
err_list.append(err)
    
for i in range(1, 50):
    estimator = tree.DecisionTreeRegressor(max_depth=depths[i],random_state = 42)    
    
    y_pred = gbm_predict(X_train)
    
    estimator.fit(X_train, grad(y_train, y_pred))
    base_algorithms_list.append(estimator)
    coefficients_list.append(0.9/(1.0 + i))
    
    err = np.sqrt(metrics.mean_squared_error(y_test, gbm_predict(X_test)))
    err_list.append(err)
    print i,


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

In [12]:
plt.plot(depths, err_list)


Out[12]:
[<matplotlib.lines.Line2D at 0x11b3acf50>]

In [17]:
ans3 = '2 3'
with open('grad_boost_ans3.txt', 'w') as file_out:
        file_out.write(ans3)

Задание 5

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

Для этого обучите LinearRegression из sklearn.linear_model (с параметрами по умолчанию) на обучающей выборке и оцените для прогнозов полученного алгоритма на тестовой выборке RMSE. Полученное качество - ответ в пункте 5.

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


In [14]:
from sklearn.linear_model import LinearRegression

In [15]:
LineReg = LinearRegression().fit(X_train, y_train)
y_pred = LineReg.predict(X_test)
ans5 = np.sqrt(metrics.mean_squared_error(y_test, y_pred))
print ans5
with open('grad_boost_ans4.txt', 'w') as file_out:
        file_out.write(str(ans5))


8.27046803494

In [ ]: