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

一、GBDT

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

  • MSE
  • 均方误差
  • 是真实值, 预测值
  • 梯度提升回归树,划分指标mse算法示例
    • 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

计算 负梯度

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

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

# 前八个是一类
# 后两个是一类
# 分子
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

根据算法原理,进行计算

步骤一,初始化算法

初始化算法,计算

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

计算 导数就是梯度,负梯度

# 函数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

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

# 前八个是一类
# 后两个是一类
# 左边分支
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

三棵树,已然构造出来了,进行概率计算

# 计算第三颗的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])

你可能感兴趣的:((十六)梯度提升树--回归和分类的算法(gbdt)))