(十六)梯度提升树--回归和分类的算法(gbdt))

一、GBDT

算法中有两个值,一个预测值,一个真实值,梯度提升树,减小残差,使梯度减小。
梯度提升回归树,裂分条件是:

  • MSE
  • 均方误差
  • \text{MSE}(y, \hat{y}) = \frac{1}{n_\text{samples}} \sum\limits_{i=0}^{n_\text{samples} - 1} (y_i - \hat{y}_i)^2.
  • y_i 是真实值,\hat{y_i} 预测值
  • <font color = red>梯度提升回归树,划分指标mse算法示例</font>
    • mse.png
    • for循环,计算所有的裂分方式的mse,找变化最大的,作为裂分条件!!!

    • 为什么变化最大,最好的裂分条件???

    • 因为,变化大,我们将相似的数据划归到相同的组中。


梯度提升树--gradient Boosting DecisionTree ----> GBDT


ensemble.png

GBDT也是集成学习Boosting家族的成员,但是却和传统的Adaboost有很大的不同。回顾下Adaboost,我们是利用前一轮迭代弱学习器的误差率来更新训练集的权重,这样一轮轮的迭代下去。GBDT也是迭代,使用了前向分布算法,但是弱学习器限定了只能使用CART回归树模型,同时迭代思路和Adaboost也有所不同。

在GBDT的迭代中,假设我们前一轮迭代得到的强学习器是ft−1(x)ft−1(x), 损失函数是L(y,ft−1(x))L(y,ft−1(x)), 我们本轮迭代的目标是找到一个CART回归树模型的弱学习器ht(x)ht(x),让本轮的损失损失L(y,ft(x))=L(y,ft−1(x)+ht(x))L(y,ft(x))=L(y,ft−1(x)+ht(x))最小。也就是说,本轮迭代找到决策树,要让样本的损失尽量变得更小。

GBDT的思想可以用一个通俗的例子解释,假如有个人30岁,我们首先用20岁去拟合,发现损失有10岁,这时我们用6岁去拟合剩下的损失,发现差距还有4岁,第三轮我们用3岁拟合剩下的差距,差距就只有一岁了。如果我们的迭代轮数还没有完,可以继续迭代下面,每一轮迭代,拟合的岁数误差都会减小。

二、代码算例(回归)

import numpy as np

from sklearn.ensemble import GradientBoostingRegressor

import matplotlib.pyplot as plt

from sklearn import tree
### 实际问题,年龄预测,回归问题
# 简单的数据,算法原理,无论简单数据,还是复杂数据,都一样
X = np.array([[600,0.8],[800,1.2],[1500,10],[2500,3]])
y = np.array([14,16,24,26])
# loss  = ls 最小二乘法
gbdt = GradientBoostingRegressor(n_estimators=3,loss = 'ls',
                                 learning_rate=0.1)#learning_rate 学习率
gbdt.fit(X,y)#训练
y_ = gbdt.predict(X)#预测
y_
array([18.374, 18.916, 21.084, 21.626])
# 目标值,真实值,算法,希望,预测,越接近真实,模型越好!!!
print(y)
# 求平均,这个平均值就是算法第一次预测的基准,初始值
print(y.mean())
[14 16 24 26]
20.0
# 残差:真实值,和预测值之间的差
residual = y - y.mean()
residual
# 残差,越小越好
# 如果残差是0,算法完全准确的把数值预测出来!
array([-6., -4.,  4.,  6.])
# 第一颗树,分叉时,friedman-mse (就是均方误差)= 26
# print('均方误差:',((y - y.mean())**2).mean())
plt.figure(figsize=(8,8))
_ = tree.plot_tree(gbdt[0,0],filled=True)
output_5_0.png
# 梯度下降,降低残差
residual = residual - learning_rate*residual
residual
array([-5.4, -3.6,  3.6,  5.4])
# 第二颗树
plt.figure(figsize=(8,8))
_ = tree.plot_tree(gbdt[1,0],filled=True)
output_7_0.png
# 梯度下降,降低残差
residual = residual - learning_rate*residual
residual
array([-4.86, -3.24,  3.24,  4.86])
# 第三颗树
plt.figure(figsize=(8,8))
_ = tree.plot_tree(gbdt[2,0],filled=True)
output_9_0.png
# 第三颗树,还要进行梯度下降
# 梯度下降,降低残差
residual = residual - learning_rate*residual
residual
array([-4.374, -2.916,  2.916,  4.374])
# 使用残差一步步,计算的结果
y_ = y - residual
y_
array([18.374, 18.916, 21.084, 21.626])
# 使用算法,预测
gbdt.predict(X)
array([18.374, 18.916, 21.084, 21.626])

三、代码算例(分类)

import numpy as np

from sklearn.ensemble import GradientBoostingClassifier

'''loss函数:Log-loss;
回归树的分裂准则:MSE;
树的深度:1(即决策树桩)
学习率:0.1;'''
X = np.arange(1,11).reshape(-1,1)
X
array([[ 1],
       [ 2],
       [ 3],
       [ 4],
       [ 5],
       [ 6],
       [ 7],
       [ 8],
       [ 9],
       [10]])
y = np.array([0,0,0,1,1]*2)
y
array([0, 0, 0, 1, 1, 0, 0, 0, 1, 1])
clf = GradientBoostingClassifier(n_estimators=2,max_depth=1)
clf.fit(X,y)
clf.predict(X)
array([0, 0, 0, 0, 0, 0, 0, 0, 1, 1])
# 梯度提升分类树中,只有一颗树
# 这一颗树预测值是
clf[0,0].predict(X)
array([-0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625,
        2.5  ,  2.5  ])
from sklearn import tree
_ = tree.plot_tree(clf[0,0],filled=True)
output_7_0.png
clf[0,0].predict(X)
array([-0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625,
        2.5  ,  2.5  ])
clf[1,0].predict(X)
array([-0.57052111, -0.57052111, -0.57052111, -0.57052111, -0.57052111,
       -0.57052111, -0.57052111, -0.57052111,  2.16820117,  2.16820117])
_ = tree.plot_tree(clf[1,0],filled=True)
output_10_0.png

根据算法原理,进行计算

初始化算法,计算F0

F_0 = np.log(4/6)
F_0
-0.40546510810816444

计算\widetilde{y} 负梯度

y_d0 = y - 1/(1 + np.exp(-F_0))
y_d0
array([-0.4, -0.4, -0.4,  0.6,  0.6, -0.4, -0.4, -0.4,  0.6,  0.6])

拟合第一棵树

# 分裂标准 mse
for i in range(1,11):
    if i ==10:
        mse = ((y_d0 - y_d0.mean())**2).mean()
    else:
        left_mse = ((y_d0[:i] - y_d0[:i].mean())**2).mean()
        right_mse = ((y_d0[i:] - y_d0[i:].mean())**2).mean()
        mse = left_mse*i/10 + right_mse*(10-i)/10
    print('从第%d个进行切分'%(i),mse)
从第1个进行切分 0.22222222222222224
从第2个进行切分 0.2
从第3个进行切分 0.17142857142857143
从第4个进行切分 0.22499999999999998
从第5个进行切分 0.23999999999999994
从第6个进行切分 0.23333333333333336
从第7个进行切分 0.20952380952380956
从第8个进行切分 0.15
从第9个进行切分 0.2
从第10个进行切分 0.24

计算左、右两侧叶子的预测值,由公式:

\gamma_{mj} = \frac{\sum_{x_i \in R_{mj}}\widetilde{y_i}}{\sum_{x_i \in R_{mj}}(y_i - \widetilde{y_i})(1 - y_i + \widetilde{y_i})}

# 前八个是一类
# 后两个是一类
# 分子
f1 = y_d0[:8].sum()
# print(f1)
# 分母
f2 = ((y[:8] - y_d0[:8])*(1 - y[:8] + y_d0[:8])).sum()
f2
gamma1 = np.round(f1/f2,3)
print('左边决策树分支,预测值:',gamma1)

# 右边分支
gamma2 =np.round(y_d0[8:].sum()/((y[8:] - y_d0[8:])*(1 - y[8:] + y_d0[8:])).sum(),3)
print('右边决策树分支,预测值:',gamma2)
左边决策树分支,预测值: -0.625
右边决策树分支,预测值: 2.5
gamma = np.array([-0.625]*8 + [2.5]*2)
gamma
array([-0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625,
        2.5  ,  2.5  ])
F_1 =np.round( F_0 + gamma*0.1,4)
F_1
array([-0.468 , -0.468 , -0.468 , -0.468 , -0.468 , -0.468 , -0.468 ,
       -0.468 , -0.1555, -0.1555])

四、梯度提升树--原理

import numpy as np

from sklearn.ensemble import GradientBoostingClassifier

from sklearn import tree

以下是树参数

'''loss函数:Log-loss;
回归树的分裂准则:MSE;
树的深度:1(即决策树桩)
学习率:0.1;'''
'loss函数:Log-loss;\n回归树的分裂准则:MSE;\n树的深度:1(即决策树桩)\n学习率:0.1;'

构造数据

X = np.arange(1,11).reshape(-1,1)
y = np.array([0,0,0,1,1]*2)
display(X,y)
array([[ 1],
       [ 2],
       [ 3],
       [ 4],
       [ 5],
       [ 6],
       [ 7],
       [ 8],
       [ 9],
       [10]])



array([0, 0, 0, 1, 1, 0, 0, 0, 1, 1])

声明算法,进行训练和预测

# 默认情况下,损失函数就是Log-loss == 交叉熵!
clf = GradientBoostingClassifier(n_estimators=100,learning_rate=0.1,max_depth=1)
clf.fit(X,y)
y_ = clf.predict(X)
print('真实的类别:',y)
print('算法的预测:',y_)
真实的类别: [0 0 0 1 1 0 0 0 1 1]
算法的预测: [0 0 0 1 1 0 0 0 1 1]

第一颗树

# 梯度提升分类树中,只有一颗树
# 这一颗树预测值是
_ = tree.plot_tree(clf[0,0],filled=True)
output_8_0.png

第二颗树

_ = tree.plot_tree(clf[1,0],filled=True)
output_10_0.png

第三颗树

_ = tree.plot_tree(clf[2,0],filled=True)
output_12_0.png

第100颗

_ = tree.plot_tree(clf[99,0],filled=True)
output_14_0.png

<font color =red>根据算法原理,进行计算</font>

步骤一,初始化算法

初始化算法,计算\rho = F_0(x)

\rho =F_0(x)= log\frac{\sum\limits_{i=1}^Ny_i}{\sum\limits_{i=1}^N(1 -y_i)}

# 二分类问题,类别 :0,1
# [0 0 0 1 1 0 0 0 1 1]
F0 = np.log(4/6)
F0
-0.40546510810816444

\widetilde{y} = y - \frac{1}{1+exp(-F(x))} = -\psi'(y,F(x))

计算\widetilde{y} 导数就是梯度,负梯度

\psi'(y,F(x)) = -y + \frac{1}{1 + exp(-F(x))}

# 函数F(X) 初始值F0的负梯度
yderivative0 = y - 1/(1 + np.exp(-F0))
yderivative0
array([-0.4, -0.4, -0.4,  0.6,  0.6, -0.4, -0.4, -0.4,  0.6,  0.6])

步骤二,梯度提升树

拟合第一棵树

# 分裂标准 mse
for i in range(1,11):
    if i ==10:
        mse = ((yderivative0 - yderivative0.mean())**2).mean()
    else:
        left_mse = ((yderivative0[:i] - yderivative0[:i].mean())**2).mean()
        right_mse = ((yderivative0[i:] - yderivative0[i:].mean())**2).mean()
        mse = left_mse*i/10 + right_mse*(10-i)/10
    print('从第%d个进行切分'%(i),np.round(mse,4))
# 从第八个样本这里进行分类,最优的选择,和算法第一颗画图的结果一致
从第1个进行切分 0.2222
从第2个进行切分 0.2
从第3个进行切分 0.1714
从第4个进行切分 0.225
从第5个进行切分 0.24
从第6个进行切分 0.2333
从第7个进行切分 0.2095
从第8个进行切分 0.15
从第9个进行切分 0.2
从第10个进行切分 0.24

计算左、右两侧叶子的预测值,由公式:

\gamma_{mj} = \frac{\sum_{x_i \in R_{mj}}\widetilde{y_i}}{\sum_{x_i \in R_{mj}}(y_i - \widetilde{y_i})(1 - y_i + \widetilde{y_i})}

# 前八个是一类
# 后两个是一类
# 左边分支
gamma1 = np.round(yderivative0[:8].sum()/((y[:8] - yderivative0[:8])*(1 - y[:8] + yderivative0[:8])).sum(),3)
print('左边决策树分支,预测值:',gamma1)

# 右边分支
gamma2 =np.round(yderivative0[8:].sum()/((y[8:] - yderivative0[8:])*(1 - y[8:] + yderivative0[8:])).sum(),3)
print('右边决策树分支,预测值:',gamma2)
左边决策树分支,预测值: -0.625
右边决策树分支,预测值: 2.5

一颗树就拟合完成了,自己计算的,和算法中的第一颗树(画图显示),完全一样

---------------------------------------------------------------------------------------------------------------------------------------------------------------

拟合第二颗树

# 第一颗预测的结果
gamma = np.array([-0.625]*8 + [2.5]*2)
gamma
array([-0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625,
        2.5  ,  2.5  ])
# F(x) 随着梯度提升树,提升,发生变化
learning_rate = 0.1
F1 =np.round( F0 + gamma*learning_rate,4) #保留4位小数
F1
array([-0.468 , -0.468 , -0.468 , -0.468 , -0.468 , -0.468 , -0.468 ,
       -0.468 , -0.1555, -0.1555])
# 计算F1(x)的负梯度
yderivative1 = np.round(y - 1/(1 + np.exp(-F1)),4)
yderivative1
array([-0.3851, -0.3851, -0.3851,  0.6149,  0.6149, -0.3851, -0.3851,
       -0.3851,  0.5388,  0.5388])
# 第二颗树分裂标准 mse
for i in range(1,11):
    if i ==10:
        mse = ((yderivative1 - yderivative1.mean())**2).mean()
    else:
        left_mse = ((yderivative1[:i] - yderivative1[:i].mean())**2).mean()
        right_mse = ((yderivative1[i:] - yderivative1[i:].mean())**2).mean()
        mse = left_mse*i/10 + right_mse*(10-i)/10
    print('从第%d个进行切分'%(i),np.round(mse,4))
# 第二棵树也是从第八个样本这里进行分类,最优的选择,和算法第二颗画图的结果一致
从第1个进行切分 0.2062
从第2个进行切分 0.1856
从第3个进行切分 0.1592
从第4个进行切分 0.2106
从第5个进行切分 0.2224
从第6个进行切分 0.2187
从第7个进行切分 0.1998
从第8个进行切分 0.15
从第9个进行切分 0.1904
从第10个进行切分 0.2227
# 计算第二颗树的预测值
# 前八个是一类
# 后两个是一类
# 左边分支
gamma1 = np.round(yderivative1[:8].sum()/((y[:8] - yderivative1[:8])*(1 - y[:8] + yderivative1[:8])).sum(),3)
print('第二棵树左边决策树分支,预测值:',gamma1)

# 右边分支
gamma2 =np.round(yderivative1[8:].sum()/((y[8:] - yderivative1[8:])*(1 - y[8:] + yderivative1[8:])).sum(),3)
print('第二棵树右边决策树分支,预测值:',gamma2)
第二棵树左边决策树分支,预测值: -0.571
第二棵树右边决策树分支,预测值: 2.168

拟合第三颗树,第二棵树的基础上进行了提升

# 第二棵树预测值
gamma = np.array([-0.571]*8 + [2.168]*2)
gamma
array([-0.571, -0.571, -0.571, -0.571, -0.571, -0.571, -0.571, -0.571,
        2.168,  2.168])
# F(x) 随着梯度提升树,提升,发生变化
learning_rate = 0.1
F2 =np.round( F1 + gamma*learning_rate,4) #保留4位小数
F2
array([-0.5251, -0.5251, -0.5251, -0.5251, -0.5251, -0.5251, -0.5251,
       -0.5251,  0.0613,  0.0613])
# 计算F2(x)的负梯度
yderivative2 = np.round(y - 1/(1 + np.exp(-F2)),4)
yderivative2
array([-0.3717, -0.3717, -0.3717,  0.6283,  0.6283, -0.3717, -0.3717,
       -0.3717,  0.4847,  0.4847])
# 第三颗树分裂标准 mse
for i in range(1,11):
    if i ==10:
        mse = ((yderivative2 - yderivative2.mean())**2).mean()
    else:
        left_mse = ((yderivative2[:i] - yderivative2[:i].mean())**2).mean()
        right_mse = ((yderivative2[i:] - yderivative2[i:].mean())**2).mean()
        mse = left_mse*i/10 + right_mse*(10-i)/10
    print('从第%d个进行切分'%(i),np.round(mse,4))
# 第三棵树从第三个样本这里进行裂分,最优的选择,和算法第三颗画图的结果一致
从第1个进行切分 0.1935
从第2个进行切分 0.1744
从第3个进行切分 0.1498
从第4个进行切分 0.199
从第5个进行切分 0.208
从第6个进行切分 0.2067
从第7个进行切分 0.1918
从第8个进行切分 0.15
从第9个进行切分 0.1827
从第10个进行切分 0.2088
#  计算第三颗树的预测值
# 前三个是一类
# 后七个是一类
# 左边分支
gamma1 = np.round(yderivative2[:3].sum()/((y[:3] - yderivative2[:3])*(1 - y[:3] + yderivative2[:3])).sum(),3)
print('第三棵树左边决策树分支,预测值:',gamma1)

# 右边分支
gamma2 =np.round(yderivative2[3:].sum()/((y[3:] - yderivative2[3:])*(1 - y[3:] + yderivative2[3:])).sum(),3)
print('第三棵树右边决策树分支,预测值:',gamma2)
第三棵树左边决策树分支,预测值: -1.592
第三棵树右边决策树分支,预测值: 0.666

<font color = red>三棵树,已然构造出来了,进行概率计算</font>

p = \frac{1}{1 + exp(-F(x))} = sigmoid

# 计算第三颗的F3(x)
# 第三颗树预测值
gamma = np.array([-1.592]*3 + [0.666]*7)
gamma
array([-1.592, -1.592, -1.592,  0.666,  0.666,  0.666,  0.666,  0.666,
        0.666,  0.666])
# F(x) 随着梯度提升树,提升,发生变化
learning_rate = 0.1
F3 =np.round( F2 + gamma*learning_rate,4) #保留4位小数
F3
array([-0.6843, -0.6843, -0.6843, -0.4585, -0.4585, -0.4585, -0.4585,
       -0.4585,  0.1279,  0.1279])
proba = 1/(1 + np.exp(-F3))
# 类别:0,1,如果这个概率大于等于0.5类别1,小于0.5类别0
(proba >= 0.5).astype(np.int8)
array([0, 0, 0, 0, 0, 0, 0, 0, 1, 1], dtype=int8)
clf.predict(X)
array([0, 0, 0, 1, 1, 0, 0, 0, 1, 1])
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350

推荐阅读更多精彩内容