深度解析xgboost

Xgboost是GBDT算法的高效实现,在工业界的传统算法中,Xgboost几乎占据了半壁江山。这里,我们将深度探讨xgboost原理以及其高效实现。
原理部分参考集成学习

目标函数

事实上,如果不考虑工程实现、解决问题上的一些差异,xgboost与gbdt比较大的不同就是目标函数的定义。xgboost的目标函数如下所示:
\[ \begin{aligned}\text{obj}^{(t)} & = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t)}) + \sum_{i=1}^t\Omega(f_i) \\ & = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t-1)} + f_t(x_i)) + \Omega(f_t) + \mathrm{constant}\end{aligned} \]
通过二阶泰勒展开,可得:
\[ \text{obj}^{(t)} = \sum_{i=1}^n [l(y_i, \hat{y}_i^{(t-1)}) + g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t) + \mathrm{constant} \]
其中:
\[ \begin{aligned}g_i &= \partial_{\hat{y}_i^{(t-1)}} l(y_i, \hat{y}_i^{(t-1)})\\ h_i &= \partial_{\hat{y}_i^{(t-1)}}^2 l(y_i, \hat{y}_i^{(t-1)})\end{aligned} \]

泰勒展开:$f(x+\Delta x)\approx f(x)+f'(x)\Delta x+\frac{1}{2}f''(x) {\Delta x}^2 $;在展开时 $ x $ 对应目标函数里的 $ \hat{y}_i^{(t-1)} $, $ \Delta x $对应 $ f_t(x_i) $

最终的目标函数只依赖于每个数据点在误差函数上的一阶导数和二阶导数。

另外,对CART树正则项作一番定义:
\[ f_t(x) = w_{q(x)}, w \in R^T, q:R^d\rightarrow \{1,2,\cdots,T\} . \]
需要解释下这个定义,首先,一棵树有\(T\)个叶子节点,这\(T\)个叶子节点的值组成了一个\(T\)维向量\(w\)\(q(x)\)是一个映射,用来将样本映射成1到\(T\)的某个值,也就是把它分到某个叶子节点,\(q(x)\)其实就代表了CART树的结构。\(w_q(x)\)自然就是这棵树对样本\(x\)的预测值了。
xgboost使用了如下的正则化项:
\[ \Omega(f) = \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2 \]
Note:其中, \(T\)表示叶子节点的个数,\(w\)表示叶子节点的分数 。直观上看,目标要求预测误差尽量小,且叶子节点\(T\)尽量少(\(γ\)控制叶子结点的个数),节点数值\(w\)尽量不极端(\(λ\)控制叶子节点的分数不会过大),防止过拟合。
至此,我们关于第t棵树的优化目标已然很清晰,下面我们对它做如下变形
\[ \begin{aligned}\text{obj}^{(t)} &\approx \sum_{i=1}^n [g_i w_{q(x_i)} + \frac{1}{2} h_i w_{q(x_i)}^2] + \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2\\ &= \sum^T_{j=1} [(\sum_{i\in I_j} g_i) w_j + \frac{1}{2} (\sum_{i\in I_j} h_i + \lambda) w_j^2 ] + \gamma T\end{aligned} \]
其中\(I_j\)代表一个集合,集合中每个值代表一个训练样本的序号,整个集合就是被第\(t\)棵CART树分到了第\(j\)个叶子节点上的训练样本。
进一步,我们可以做如下简化:
\[ \text{obj}^{(t)} = \sum^T_{j=1} [G_jw_j + \frac{1}{2} (H_j+\lambda) w_j^2] +\gamma T \]
通过对\(w\)求导等于0,可以得到:
\[ \begin{aligned}w_j^\ast &= -\frac{G_j}{H_j+\lambda}\\ \text{obj}^\ast &= -\frac{1}{2} \sum_{j=1}^T \frac{G_j^2}{H_j+\lambda} + \gamma T\end{aligned} \]

实质是把样本分配到叶子结点会对应一个obj,优化过程就是obj优化。也就是分裂节点到叶子不同的组合,不同的组合对应不同obj,所有的优化围绕这个思想展开。

\(Obj\)代表了当我们指定一个树的结构的时候,我们在目标上面最多减少多少。我们可以把它叫做 结构分数(structure score)

[结构分数][base64str1]

分裂节点

对于一个叶子节点如何进行分裂,xgboost作者在其原始论文中给出了两种分裂节点的方法。

枚举所有不同树结构的贪心法

贪心法,即从树深度0开始,每一节点都遍历所有的特征,比如年龄、性别等等,然后对于某个特征,先按照该特征里的值进行排序,然后线性扫描该特征进而确定最好的分割点,最后对所有特征进行分割后,我们选择所谓的增益Gain最高的那个特征,而Gain如何计算呢?
在上面我们得到
\[ \begin{aligned} \text{obj}^\ast &= -\frac{1}{2} \sum_{j=1}^T \frac{G_j^2}{H_j+\lambda} + \gamma T\end{aligned} \]
其中,目标函数中的\(\frac{G_j^2}{H_j+\lambda}\)部分,表示着每一个叶子节点对当前模型损失的贡献程度,融合一下,得到Gain的计算表达式,如下所示:

[Gain的计算表达式][base64str2]

另外,要注意“对于某个特征,先按照该特征里的值进行排序”。比如设置一个值a,然后枚举所有\(x < a\)\(a < x\)这样的条件(\(x\)代表某个特征比如年龄age,把age从小到大排序:假定从左至右依次增大,则比\(a\)小的放在左边,比\(a\)大的放在右边),对于某个特定的分割\(a\),我们要计算\(a\)左边和右边的导数和。
第二个值得注意的事情就是引入分割不一定会使得情况变好,所以我们有一个引入新叶子的惩罚项。优化这个目标对应了树的剪枝, 当引入的分割带来的增益小于一个阀值\(γ\)的时候,则忽略这个分割。
下面是论文中的算法

[算法1][base64str3]

近似算法

主要针对数据太大,不能直接进行计算

[算法2][base64str4]

把样本从根分配到叶子结点,就是个排列组合。不同的组合对应的cost不同。求最好的组合你就要try,一味穷举是不可能的,所以才出来贪婪法。不看从头到尾 就看当下节点怎么分配最好。这才有了那个exact greddy方法,后来还想加速才有了histogram的做法。

总而言之,XGBoost使用了和CART回归树一样的想法,利用贪婪算法,遍历所有特征的所有特征划分点,不同的是使用的目标函数不一样。具体做法就是分裂后的目标函数值比单子叶子节点的目标函数的增益,同时为了限制树生长过深,还加了个阈值,只有当增益大于该阈值才进行分裂。

xgboost特征重要性排名 具体实现?

参考:通俗理解kaggle比赛大杀器xgboost

http://link.zhihu.com/?target=http%3A//djjowfy.com

https://zhuanlan.zhihu.com/p/34534004

https://www.zhihu.com/question/41354392/answer/98658997

https://zhuanlan.zhihu.com/p/31654000

猜你喜欢

转载自www.cnblogs.com/hellojamest/p/11569706.html