一、GBDT
算法中有两个值,一个预测值,一个真实值,梯度提升树,减小残差,使梯度减小。
梯度提升回归树,裂分条件是:
- MSE
- 均方误差
- 是真实值, 预测值
- 梯度提升回归树,划分指标mse算法示例
-
for循环,计算所有的裂分方式的mse,找变化最大的,作为裂分条件!!!
为什么变化最大,最好的裂分条件???
因为,变化大,我们将相似的数据划归到相同的组中。
-
梯度提升树--gradient Boosting DecisionTree ----> GBDT
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)
# 梯度下降,降低残差
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)
# 梯度下降,降低残差
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)
# 第三颗树,还要进行梯度下降
# 梯度下降,降低残差
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)
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)
根据算法原理,进行计算
初始化算法,计算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)
第二颗树
_ = tree.plot_tree(clf[1,0],filled=True)
第三颗树
_ = tree.plot_tree(clf[2,0],filled=True)
第100颗
_ = tree.plot_tree(clf[99,0],filled=True)
根据算法原理,进行计算
步骤一,初始化算法
初始化算法,计算
# 二分类问题,类别 :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])