机器学习笔记(13)-XGBoost
机器学习笔记(13)-XGBoost
XGBoost陈天奇等人开发的一个开源机器学习项目,高效地实现了GBDT算法并进行了算法和工程上的许多改进,被广泛应用在Kaggle竞赛及其他许多机器学习竞赛中并取得了不错的成绩。可以说在深度学习热门前和当年SVM一样属于比赛中的明星算法了。
Bagging和Boosting
介绍XGBoost之前,首先简单介绍下Bagging和Boosting的区别,我们知道这两个都是集成学习的框架,主要针对一系列弱分类器,使它们在集成后成为一个效果比较好的强分类器。比如随机森林就是一个采用bagging的学习算法,而我们这次介绍的XGBoost就是一个Boosting算法。
Bagging:是由多个过拟合的弱分类器组合而成,通过每个弱分类器的权重来判断结果。通俗的理解就是Bagging算法是由多个领域专家(弱分类器)组成,单个专家只在该领域(数据分布)下时预测准确率高,当符合某个领域时,会挑选出该领域的专家(权重最高)来判断(加权组合)。
Boosting:与Bagging相同的时它也是由多个弱分类器组成,不同点在于每个弱分类器都是欠拟合的,同样是协同所有弱分类器一起判断结果。
XGBoost顾名思义,采用的是Boosting的思想,训练多棵树模型,来组合判断结果,但是不同于随机森林,它的每一个分类器采用残差的方式来训练每棵树,使得每棵树的结果之和为预测结果,并且在每棵树的建立算法上也和传统的决策树分枝算法(C4.5)不同。
举个例子就是,比如有一个预测样本的值为10:
- 训练第一个弱分类器预测的结果为6,得到残差为:10-6=4;
- 接下来训练第二个弱分类器预测的结果为2,得到残差为:4-2=2;
- 再训练第三个弱分类器预测的结果为1,得到残差为:2-1=1;
- 最后训练第四个弱分类器预测的结果为1;
- 我们把四个弱分类器的预测结果累加就是我们真实的预测值10。
这就是XGBoost利用Boosting算法来集成的强学习器。
XGBoost算法推导
上一节讲了XGBoost采用了Boosting通过残差的方式来求和拟合结果。XGBoost的每个弱分类器都是一棵决策树,分枝算法先放到之后再说,通过训练一棵树来预测结果,得到残差后再训练第二棵树,依此类推,训练若干个弱分类器后,模型收敛完成训练。
这就是XGBoost的算法思想,真的非常简单,下面我们来推导过程。
残差损失
之前说了XGBoost是根据每棵树的预测结果和样本真实值的残差来迭代优化的,假设有:
- 数据样本\(X=\{(x_{1},y_{1}),(x_{2},y_{2}),\cdots,(x_{n},y_{n})\}^N\),其中\(x_{i}\in\mathbb{R}^p,y_{i}\in\mathbb{R}\);
- 模型总共需要构建K棵树,每棵树预测值表示为:\(f_{k}(x_{i})\)
于是我们得到单个样本的预测值为:
那么我们就可以进一步得到目标函数:
其中\(\mathcal{L}(y_{i},\hat y_{i})\)为样本真实值和预测值的损失函数,\(\Omega(f_{k})\)为每棵树的复杂度(可以理解为正则项)。
事实上,对于一棵树,复杂度一般采用以下几项来表示:
- 树的深度
- 叶节点个数
- 叶节点值
树的深度很好理解,树越深表示节点越多,也就是复杂度越高,越容易过拟合;
叶节点个数和深度表示的含义基本一样;
叶节点值表示如果我的预测值是100,原来每棵树叶节点值为50,那么我用两棵树就可以完成,当我们设置每棵树的叶节点值不超过10,那么我们至少需要10棵树才能拟合真实值。而树越多,越不容易过拟合。
训练第k棵树
有了目标函数后,我们需要进一步来简化问题,XGBoost是根据我们每次预测值和真实值的残差来提升模型的,也就是假设我们已知前k-1棵树后,我们通过模型迭代训练来得到第k棵树,依次把所有k棵树全部训练完成的。
于是我们令:
- 第0棵树:\(\hat y^{(0)}_{i}=0\);
- 第1棵树:\(\hat y^{(1)}_{i}=f_{1}(x_{i})=\hat y^{(0)}_{i}+f_{1}(x_{i})\);
- 第2棵树:\(\hat y^{(2)}_{i}=f_{1}(x_{i})+f_{2}(x_{i})=\hat y^{(1)}_{i}+f_{2}(x_{i})\);
- 第k棵树:\(\hat y^{(k)}_{i}=\sum_{k=1}^{K}f_{k}(x_{i})=\hat y^{(k-1)}_{i}+f_{k}(x_{i})\);
我们把结论代入到目标函数中,就得到:
观察上式,我们因为已知前k-1棵树,求第k棵树,所以前k-1棵树的项都应该是已知常数,我们令:
- \(\hat y^{(k-1)}_{i}=C_{1}\)
- \(\sum_{j=1}^{K-1}\Omega(f_{j})=C_{2}\)
得到:
泰勒级数
大学高等数学课中,我们知道当一个函数为连续函数时(函数处处可导),我们可以采用泰勒展开式近似该函数,XGBoost正是采用了泰勒展开式中的二阶导数来近似表示函数的。引用下百度百科:
事实上当到二阶导数时,已经非常接近原函数了。于是有二阶泰勒展开式(Taylor Expansion)近似:
XGBoost利用了该思想,它把前k-1棵树预测的值看成时\(f(x)\),加上第k棵树预测的值看成\(f(x+\Delta x)\)。
也就是:
代入到目标函数也就是:
上式同样有几个已知常数项,我们简化一下令:
目标函数再次简化形式:
上式目标函数,我们要令该函数的值最小,只保留和\(f_{k}\)有关的项,求得:\(argmin\;Obj\):
定义树的表示
下面我们要想一个办法来表示我们的\(f_{k}(x_{i})\)和\(\Omega(f_{k})\),最好可以用一种统一的参数形式来表示。
现在考虑在一棵树中,我们尝试使用样本在树中的叶节点位置和叶节点的值来定义\(f_{k}(x_{i})\)和\(\Omega(f_{k})\)。
我们令:
- \(I\):叶节点总个数
- \(q_{x_{i}}\):每个样本\(x_{i}\)在树中的位置为
- \(W_{q_{x_{i}}}\):每个样本在叶节点上的值
- \(\gamma T\):其中\(T\)表示树的高度,\(\gamma\)是超参数,用于调节高度的权重
先来看复杂度项\(\Omega(f_{k})\),我们采用树的高度加上叶节点的值作为复杂度项,就有:
其中\(\lambda\)是叶节点的值的超参数,同样用于控制权重。定义每个叶节点的值的平方作为复杂度项。
再来看树的预测函数\(f_{k}(x_{i})\):
我们这么幸苦地采用这个形式来改变形式,其实核心思想就是原本目标函数是通过数据样本的个数来表示的,我们现在想采用数的叶节点个数来表示目标函数,其中多个数据样本可能落在同一个叶子节点上。
也就是说:所有的N个数据样本分布在\(I\)个叶节点上,每个叶节点中假设有M个样本,即:
代入到目标函数中得到:
我们再把常数项简化表示,令:
代入得:
对\(W\)求导得:
把最优解代入目标函数得到:
这里我们就得到了我们要得最小化目标函数值最优解。
每棵树得构建
前面我们其实是得到了一个结论:当我们已知前k-1棵树预测值时,可以很方便地计算出第k棵树预测值。现在来思考每棵树的构建方式。
前面的章节我已经介绍过决策树的构建流程,依赖于分枝算法,我们以信息增益(ID3)为例回顾下流程:
通过原来的信息熵减去分枝后的熵就是信息增益,我们需要求它的最大值作为分枝点。XGBoost也是这个思想,不过采用的不是信息增益,而是我们之前的目标函数\(Obj^*\)最优解。
假设数据集为:
- 数据集\(D=\{(x_{1},y_{1}),(x_{2},y_{2}),\cdots,(x_{n},y_{n})\}\),
- 属性集A=\(\{a_{1},a_{2},\cdots,a_{p}\}\)其中每个数据样本\(x_{i}\)有p个属性
构造流程和决策树一样,只是分枝算法进行了替换。
总结
可以看到XGBoost算法主要依赖:
- 利用Boost残差的思想构造多个若分类器逐步迭代优化来逐渐逼近目标值;
- 采用泰勒二阶展开式来近似弱分类器;
- 利用已知前k-1棵树,可以计算第k棵树的思想,作为树的分枝算法来建树。
最后说一下如果是分类问题的话,那么拟合的就是概率值,也就是要把预测值和真实值转换为类别的概率,迭代过程就是让预测概率不断接近真实概率。将\(\mathcal{L}(y_{i},\hat y_{i})\)转化为对数损失函数来做,回归就采用最小二乘。