目标
介绍什么是梯度提升(Gradient Boost),如何运用梯度提升来作为回归和分类问题。以及其背后实现算法
基本要求
就是需要具有一定决策树的基础知识,随后接下来内容都是如何使用决策树来构建 GBDT 的模型,所以以必要了解决策树。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
准备数据
data = pd.read_csv("./data/data.csv")
data
这是今天的数据集,接下来内容都是基于这个数据集进行讲解,大家可以先简单了解一下这个数据集,身高、喜欢颜色和性别是数据特征,而体重是我们要预测的值。
我们随后将使用梯度提升(Gradient Boost)来进行预测体重值,也就是用梯度提升(Gradient Boost)来做回归(Regression)。要使用的梯度提升(Gradient Boost)与之前学习过的线性回归(Linear Rgression)虽然都是做回归问题,但是背后的方法却完全不同。
如果已经比较熟悉 AdaBoost,那么会有助于你学习梯度提升(Gradient Boost)。所以接下来我们会通过对比 AdaBoost ,来学习梯度提升(Gradient Boost)。
AdaBoost
- 在 AdaBoost 中,会首先构建一个规模不算大(也就是深度不算深)的决策树
- 每颗决策树的权重取决于他对之前估计误差值的补偿的程度
- 所以在 AdaBoost 每新创建一颗决策树,都是基于上一颗决策树的估计值和真实值之间的误差的基础之上
- 每一个决策树对误差补偿程度可以反映在其权重上,也就是补偿越好,他权重也就自然越大,所以在 AdaBoost 中每构建一颗决策树都是为了补偿上一颗决策树的误差
- 当 AdaBoost 中决策树的数量达到限制条件或者拟合数据已经达到预期效果,就可以不再构建新的决策树来完成训练
梯度提升(Gradient Boost)
- 相比于 AdaBoost ,梯度提升只是构建一个(叶)节点而不是一个决策树
- 构建出叶节点会对所有样本初始化一个权重,通常这个初始化权重就是预测值均值 73.3(体重)
- 接下来梯度提升就会开始构建决策树,与 AdaBoost 每次构建决策树都是基于上一颗决策树错误而构建新的规模不大决策树不同,梯度提升会构建一颗规模相对比较大决策树,但是并不是所没有限制(规模)。在今天例子,构建决策树的叶节点数量不会超过 4 个叶节点。但是在实际模型中,我们对叶节点数量限制可能在 8 到 32 之间。就每次构建出决策树规模上这一点梯度提升可能要比 AdaBoost 的规模大一些。在 AdaBoost 中,我们决策树的大小可能根据错误不同而不同,也就是说 AdaBoost 会根据上一次错误对新要构建决策树大小进行调整,而梯度提升中也是可以调整决策树的大小,不过不同这种大小调整会反映在每颗决策树上。
- 在梯度提升停止条件也是和 AdaBoost 比较类似
说了一大堆理论,我们还是动手在数据上来一次梯度提升模型比较直观,看看梯度提升是如何训练和进行预测(体重)
- 通过取均值作为预测值 71.2, 如果就此不再进行下去,那么我们对每个人体重预测值就是 71.2
- 接下来我们基于这个预测值误差来构建决策树,如何定义误差,我们将预测值(71.2)与数据样本中每一个数据值进行对比来计算误差,
这个实际值和预测值的差叫做伪残差(Pseudo Residual),这里伪残差就是我们梯度提升要做处理的部分。
# 读取数据
data = pd.read_csv("./data/data.csv",header=0)
# 显示数据列名称
data.columns
Index(['height', 'favorite', 'gender', 'weight'], dtype='object')
# 通过列名称(weight)获取列值,然后对其进行求取均值
c_avg = np.mean(data['weight'])
c_avg = np.round(c_avg,1)
c_avg
71.2
# 计算残差,我们通过 weight 减去均值得到残差
resual = data['weight'] - c_avg
resual
0 16.8
1 4.8
2 -15.2
3 1.8
4 5.8
5 -14.2
Name: weight, dtype: float64
data['residual'] = resual
data
# plt.scatter(np.linspace(1,6,1),data['weight'])
plt.scatter(np.linspace(0,5,6),data['weight'],color='blue')
身高(m) | 喜好颜色 | 性别 | 体重(预测) | 残差 |
---|---|---|---|---|
1.6 | Blue | Male | 88 | 16.8 |
1.6 | Green | Female | 76 | 4.8 |
1.5 | Blue | Female | 56 | -15.2 |
1.8 | Red | Male | 73 | 1.8 |
1.5 | Green | Male | 77 | 5.8 |
1.4 | Blue | Female | 57 | -14.2 |
构建决策树
通常我们都是预测目标值,而这一次我们通过拟合残差来达到推测预测值来达到训练的目的。我们将预测体重问题转移到预测残差上。
- 性别=F
- 身高 < 1.6
- 是否喜好蓝色(Blue)
- 身高 < 1.6
gender_female = data[data.gender =='Female']
gender_female
gender_male = data[data.gender =='Male']
gender_male
这里暂时不去解释为什么我们要构建这样树来预测残差。大家可能还记得我们对数规模进行限制,也就是最多只能有 4 个叶节点。
short_gender_female = gender_female[gender_female.height < 1.6]
short_gender_female
high_gender_female = gender_female[gender_female.height >= 1.6]
high_gender_female
- 性别=F
- 身高 < 1.6
- 是否喜好蓝色(Blue)
- 身高 < 1.6
现在我们通过对比之前预测值和决策树预测值
模型现在已经完全拟合训练数据,但是现在 Bias比较低,而且还具有较高的 Variance。在梯度提升是用过添加学习率来解决这个问题的。学习率是一个介于 0 到 1 之间的数。这个例子如果设置学习率为 0.1 那么就得到
因为添加了学习率,我们新的预测值不再能够完全拟合训练数据了。但是这一次预测结果要比上一次 71.2 也预测值更加接近观测值 88 了。通过学习率让我们模型一步一步以较小步幅迈向观测值。
# 通过列名称(weight)获取列值,然后对其进行求取均值
c_avg = np.mean(data['weight'])
c_avg = np.round(c_avg,1)
resual = data['weight'] - (c_avg+ 0.1 * (data['weight'] - c_avg))
data['residual'] = resual
data
plt.scatter(np.linspace(0,5,6),data['weight'],color='blue',label='we')
plt.scatter(np.linspace(0,5,6),data['residual'],color='red',label='residual')
plt.axhline(y=c_avg,linestyle='dashed')
pred = data['weight'] - data['residual']
print(pred)
plt.scatter(np.linspace(0,5,6),pred,color='green',label='residual')
plt.grid()
plt.show()
0 72.88
1 71.68
2 69.68
3 71.38
4 71.78
5 69.78
dtype: float64
决策树结构
- 性别=F
- 身高 < 1.6
- 是否喜好蓝色(Blue)
-
- 身高 < 1.6
身高(m) | 喜好颜色 | 性别 | 体重(预测) | 残差 |
---|---|---|---|---|
1.6 | Blue | Male | 88 | 13.6 |
1.6 | Green | Female | 76 | 3.9 |
1.5 | Blue | Female | 56 | -12.4 |
1.8 | Red | Male | 73 | 1.1 |
1.5 | Green | Male | 77 | 5.1 |
1.4 | Blue | Female | 57 | -11.4 |
每一次进行向前迭代都会不断减少我们预测值。我们通过不断添加新的决策树对模型做加法来减少残差。
身高(m) | 喜好颜色 | 性别 | 体重 |
---|---|---|---|
1.6 | Green | Female | ?? |
身高(m) | 喜好颜色 | 性别 | 体重 |
---|---|---|---|
1.6 | Green | Female | 70 |