Gradient Boosting Machine

GBM(Gradient Boosting Machine)算法是Boosting(提升)算法的一种。主要思想是,串行地生成多个弱学习器,每个弱学习器的目标是拟合先前累加模型的损失函数的负梯度, 使加上该弱学习器后的累积模型损失往负梯度的方向减少。 且它用不同的权重将基学习器进行线性组合,使表现优秀的学习器得到重用。 GBM属于加法模型,也称 MART(muliple additive regression trees)。

最常用的基学习器为树模型,采用决策树的GBM称为GBDT(Gradient Boosting Decision Tree)。而XgBoost、LightGBM、CatBoost等都是从GBDT(采用CART树)派生出来的具体实现工具。

算法思想:

由于负梯度方向是下降最快的方向,因此将累积模型的误差loss对函数的负梯度作为当前模型的拟合目标。

采用平方损失的 L2 Boosting算法如下:

l2-boosting

如果将损失函数换成 log-loss,则算法成为 BinomialBoost;如果是指数损失,则算法演变成 AdaBoost,还可以采用 Huber loss 等更加 robust 的损失函数。

应用场景:各种分类、回归场景,如CTR预估等。

GBM 问答

为什么 GBM 常常与决策树相结合?

理论上,GBM可以选择各种不同的学习算法作为基学习器。现实中,用得最多的基学习器是决策树。

  1. 模型较弱,不稳定,方差大。
    决策树本身是一种不稳定的学习器(训练数据的一点波动可能给结果带来较大的影响),从统计学的角度单棵决策树的方差比较大。而在集成学习中,弱学习器间方差越大,弱学习器本身泛化性能越好,则集成学习模型的泛化性能就越好。
  2. 可解释性强、快速。
    这与决策树算法自身的优点有很大的关系。决策树可以认为是if-then规则的集合,易于理解,可解释性强,预测速度快。同时,决策树算法相比于其他的算法需要更少的特征工程,比如可以不用做特征标准化,可以很好的处理字段缺失的数据,也可以不用关心特征间是否相互依赖等。决策树能够自动组合多个特征,它可以毫无压力地处理特征间的交互关系并且是非参数化的,因此你不必担心异常值或者数据是否线性可分。

使用boosted tree作为学习算法的优势:

  • 使用不同类型的数据时,不需要做特征标准化/归一化
  • 可以很容易平衡运行时效率和精度;比如,使用boosted tree作为在线预测的模型可以在机器资源紧张的时候截断参与预测的树的数量从而提高预测效率
  • 学习模型可以输出特征的相对重要程度,可以作为一种特征选择的方法
  • 模型可解释性好
  • 对数据字段缺失不敏感
  • 能够自动做多组特征间的interaction,具有很好的非性线性

GBM 所使用的梯度提升与常用的梯度下降是什么关系?

在梯度下降中,模型是以参数化形式表示,从而模型的更新等价于参数的更新。而在梯度提升中,模型并不需要进行参数化表示,而是直接定义在函数空间中,从而大大扩展了可以使用的模型种类。

  • 同: 每一轮迭代中都是利用损失函数的负梯度信息来对当前模型进行更新.
  • 异: 梯度不同, 梯度下降是对参数w求导(参数空间); 梯度提升是对函数值f进行求导(函数空间).

预测时并行

为什么模型训练是串行的而预测时则可以并行调用模型?

训练的时候下一轮的目标值依赖上一轮的结果,需要迭代训练,不能并行。而预测的时候每棵树都已经建好,输入是原始数据,输出是把每棵树的预测值按权相加,可以并行。

对于GBM、AdaBoost等加法模型boosting算法,在预测时均可以进行模型并行。

GBDT

GBDT 公式

初始化:估计使损失函数最小化的常数值\(\rho\), 采用只有一个根节点的树\(F_0\)

\[F_0=\arg \min_\rho \sum_{i=1}^N L(y_i, \rho) \]

迭代M次,训练M个决策树:

\[\begin{align} \text{for m=1:M do:} \\ \rm{负梯度}\ \delta_i &=-[{\partial L(y_i,F(x_i))\over \partial F(x_i)}]_{F(x)=F_{m-1}(x)}, i=1,...,N \\ \rm{分割点}\ a_m &= \arg \min_{a,\beta}\sum_{i=1}^N [\delta_i-\beta h(x_i:a)]^2 \\ \rm{模型权重}\ \rho_m &=\arg \min_\rho\sum_{i=1}^N L(y_i, F_{m-1}(x_i)+\rho h(x_i:a_m)) \\ \rm{加权模型}\ F_m(x) &= F_{m-1}(x)+\rho_m h(x:a_m) \end{align} \]

GBDT 优缺点

优点:

  1. 预测阶段计算速度快, 树之间可并行化计算,然后按顺序做加法, 注意训练时不可以并行化.
  2. 适合稠密数据集
  3. 能够自动发现特征间的高阶关系, 具有决策树模型的优势(可解释性, 鲁棒性, 不需要对数据进行归一化等预处理)

缺点:

  1. 不适合稀疏(通常是高维的)的数据, 不如SVM, 神经网络
  2. 不太适合文本分类(没有处理数值特征上的优势)
  3. 训练时只能串行训练, 只能在决策树内部进行局部的并行化(特征并行).

XGBoost

XGBoost 是陈天奇等人开发的一个开源机器学习项目:https://xgboost.ai/

from GBDT to XGBoost

xgboost是从决策树一步步发展而来的。

from 决策树 to xgboost 发展路线

⟶⟶ 对样本重抽样,然后多个树平均 ⟶⟶ Tree bagging

⟶⟶ 再同时对特征进行随机挑选 ⟶⟶ 随机森林

⟶⟶ 自适应增强,加权平均⟶⟶ Boosing (Adaboost, GradientBoost)

⟶⟶ 正则化(提前剪枝),泰勒展开逼近 ⟶⟶ xgboosting

Xgboost对GradientBoost的改进-Xgboost和GBDT的区别?

  1. 增加了正则项(相当于剪枝提前),针对树的复杂度,防止模型过度复杂,降低了过拟合的可能性。而GradientBoosting并没有惩罚项。正则化项与树的叶子节点的数量T和叶子节点的值有关。
  2. 损失函数是用泰勒展式二项逼近,采用了二阶导数(Hessian矩阵),支持自定义代价函数(提升了工具的扩展性)。而GradientBoosting只是用了一阶导数(梯度)。
  3. 节点分裂的方式不同,gbdt是用的gini系数,xgboost是经过优化推导后的
  4. 【工程优化】优化了计算过程,缩短了运行时间。采用了并行计算、块处理、稀疏矩阵处理技术等等。

key point-1: 正则项+DART

xgboost的目标函数是损失函数+惩罚项。树越复杂,惩罚越重。

loss

树的复杂度定义如下,叶节点的数量和叶节点的得分越高,树就越复杂。

正则项

Dropout是deep learning里很常用的正则化技巧,很自然的我们会想能不能把Dropout用到GBDT模型上呢?

可以,XGBoost 已根据论文 “DART: Dropouts meet Multiple Additive Regression Trees.” JMLR 进行了实现,提供了DART的开关选项。

思路:串行模型训练中前边的模型相比后边的模型重要性更高,作用更大。DART通过随机删除tree的方式进行平衡。

由于在添加新树时拟合的是之前累积数的残差,如果随机删掉了(k个)树,则当前树拟合的目标量级是不采用drop方式的k倍,可将新树乘以权重1/k 来还原原本的量级。这种缩放的方式与Dropout类似。

key point-2: 泰勒展开

为什么xgboost 要进行泰勒二阶展开?

作用一:收敛更快,并减少计算量,主要目的.

二阶信息本身就能让梯度收敛更快更准确,这一点在优化算法里的牛顿法里已经证实了。可以简单认为一阶导指引梯度方向,二阶导指引梯度方向如何变化。xgboost展开到二阶,实际上等于在使用牛顿法进行每一轮的模型拟合和更新。

在构造决策树时需要选择每个特征的每个划分点的最优值, 不同的划分得到不同的决策树, 需要通过损失值来选择最优的划分, xgboost通过泰勒二阶展开减少了损失值的计算次数, 每次只需重新计算当前树的叶子输出.

泰勒展开

通过泰勒展开之后,对于每一轮迭代,在进行节点分裂选择最优分割点时某些项只需要计算一次(式子中constant的部分)便可被重复利用。

\[\mathcal{L}^{(t)}\approx \sum_{i=1}^n \underbrace{\ell(y_i,\hat{y}_i^{(t-1)})}_{\text{constant}}+\underbrace{g_i}_{\text{constant}}f_t(\mathbf{x}_i)+\frac{1}{2}\underbrace{h_i}_{\text{constant}}f_t^2(\mathbf{x}_i)+\Omega(f_t) \]

作用二:损失函数扩展

xgboost官网上说,用泰勒级数展开是因为损失函数的梯度并不总是容易求得。方便实现应该是最直接的原因。泰勒展开式跟算法效果没关系,用泰勒展开式只是为了对所有二阶可导的损失函数都可以做近似替换,与采用MSE的推导公式统一。

关键在于模块化可以很方便地自定义损失函数,只要这个损失函数可以求一阶和二阶导

  • xgboost 是在 MSE 基础上推导出来的,在 MSE 的情况下,xgboost 的目标函数展开就是一阶项+二阶项的形式,而其他类似 log loss 这样的目标函数不能表示成这种形式。为了后续推导的统一,所以将目标函数进行二阶泰勒展开,就可以直接自定义损失函数了,只要损失函数是二阶可导的即可,增强了模型的扩展性。

参考:

Xgboost 公式推导

每颗树拟合前一棵树的负梯度, 采用简单的加法模型, 拟合当前树时之前的树不再改变, 预测输出值为:

\[\hat{y}_i = \sum_{k=1}^K f_k(x_i), f_k \in \mathcal{F} \]

F 是所有的 CARTs 树, K 是树的个数.

目标函数: n个所有样本的误差损失+t棵树的正则项约束

\[\text{obj} = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t)}) + \sum_{i=1}^t\Omega(f_i) \]

第t棵树的预测输出值(加法模型)为:

\[\begin{split}\hat{y}_i^{(0)} &= 0\\ \hat{y}_i^{(t)} &= \sum_{k=1}^t f_k(x_i)= \hat{y}_i^{(t-1)} + f_t(x_i)\end{split} \]

因此目标函数可以写作:

\[\begin{split}\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{split} \]

对损失函数进行泰勒展开(到二阶):

\[\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} \]

移除常量:

\[\sum_{i=1}^n [g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t) \]

可以看出计算量上减少的优势: 原本需要计算样本所有特征的每个划分点的误差损失, 会包含一些重复的公共计算,而新的目标函数将公共计算提取出来了作为常量只需计算一次,剩余项也很容易并行计算.

模型复杂度定义:

\[\Omega(f) = \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2 \]

f与叶子节点的值w有关,

继续对目标函数改写,设 \(I_j = \{i|q(x_i)=j\}\)是分配给第 j 个叶子节点的下标:

\[\begin{split} \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{split} \]

\(G_j = \sum_{i\in I_j} g_i, H_j = \sum_{i\in I_j} h_i\),则有:

\[\text{obj}^{(t)} = \sum^T_{j=1} [G_jw_j + \frac{1}{2} (H_j+\lambda) w_j^2] +\gamma T \]

对w求导,令导数为0, 解得最小化损失函数的情况下各个叶子节点上的预测值\(w^*_j\). 而 \(\text{obj}^\ast\) 能够衡量树的结构有多好.

\[\begin{split}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{split} \]

参考https://xgboost.readthedocs.io/en/latest/tutorials/model.html

其它工程上的优化

  1. 列抽样(column subsampling)。xgboost借鉴了随机森林的做法,支持列抽样,不仅能降低过拟合,还能减少计算,这也是xgboost异于传统gbdt的一个特性。
  2. 对缺失值的处理。对于特征的值有缺失的样本,xgboost可以自动学习出它的分裂方向。
  3. 增加并行,包括特征并行、近似直方图算法选择分割点。

xgboost在实现时还做了许多优化:

  1. 在寻找最佳分割点时,考虑传统的枚举每个特征的所有可能分割点的贪心法效率太低,xgboost实现了一种近似的算法。大致的思想是根据百分位法列举几个可能成为分割点的候选者,然后从候选者中根据上面求分割点的公式计算找出最佳的分割点。
  2. xgboost考虑了训练数据为稀疏值的情况,可以为缺失值或者指定的值指定分支的默认方向,这能大大提升算法的效率,paper提到50倍。
  3. xgboost借鉴了随机森林中的列(特征)采样技术,即在某个节点分裂时,不是在当前节点中所有属性中选取最佳分裂属性,而是在当前属性集合中的某些属性中来选择最优分裂属性。这种方法降低了过拟合的可能性。
  4. 特征列排序后以块的形式存储在内存中,在迭代中可以重复使用;虽然boosting算法迭代必须串行,但是在处理每个特征列时可以做到并行。
  5. 按照特征列方式存储能优化寻找最佳的分割点,但是当以行计算梯度数据时会导致内存的不连续访问,严重时会导致cache miss,降低算法效率。paper中提到,可先将数据收集到线程内部的buffer,然后再计算,提高算法的效率。
  6. xgboost还考虑了当数据量比较大,内存不够时怎么有效的使用磁盘,主要是结合多线程、数据压缩、分片的方法,尽可能的提高算法的效率。

XGBoost模型加权方式

Q:xgb是像AdaBoost那样各棵树加权吗?

答:有加权。

xgb的Shrinkage(缩减),相当于学习速率(xgboost中的eta)。xgboost在进行完一次迭代后,会将叶子节点的权重乘上该系数,主要是为了削弱每棵树的影响,让后面有更大的学习空间。实际应用中,一般把eta设置得小一点,然后迭代次数设置得大一点。(传统GBDT的实现也有学习速率)

gbdt/xgboost 多分类方式

二分类: 叶子节点上得到类别的分数, 通过logistic得到类别概率.
多分类是训练多颗二分类树(K个类别), 然后通过Softmax.

LightGBM

LightGBM 的改进点

1. 速度和内存使用的优化

许多提升工具对于决策树的学习使用基于 pre-sorted 的算法 (例如,在xgboost中默认的算法) ,这是一个简单的解决方案,但是不易于优化。

LightGBM 利用基于 histogram 的算法,通过将连续特征(属性)值分段为 discrete bins 来加快训练的速度并减少内存的使用。 如下的是基于 histogram 算法的优点:

  • 减少分割增益的计算量
    • Pre-sorted 算法需要 O(#data) 次的计算
    • Histogram 算法只需要计算 O(#bins) 次, 并且 #bins 远少于 #data
      • 这个仍然需要 O(#data) 次来构建直方图, 而这仅仅包含总结操作
  • 通过直方图的相减来进行进一步的加速
    • 在二叉树中可以通过利用叶节点的父节点和相邻节点的直方图的相减来获得该叶节点的直方图
    • 所以仅仅需要为一个叶节点建立直方图 (其 #data 小于它的相邻节点)就可以通过直方图的相减来获得相邻节点的直方图,而这花费的代价(O(#bins))很小。
  • 减少内存的使用
    • 可以将连续的值替换为 discrete bins。 如果 #bins 较小, 可以利用较小的数据类型来存储训练数据, 如 uint8_t。
    • 无需为 pre-sorting 特征值存储额外的信息
  • 减少并行学习的通信代价

2. 准确率的优化

Leaf-wise (Best-first) 的决策树生长策略

大部分决策树的学习算法通过 level(depth)-wise 策略生长树,
LightGBM 通过 leaf-wise (best-first) 策略来生长树。它将选取具有最大 delta loss 的叶节点来生长。 当生长相同的 #leaf,leaf-wise 算法可以比 level-wise 算法减少更多的损失。

#data 较小的时候,leaf-wise 可能会造成过拟合。 所以,LightGBM 可以利用额外的参数 max_depth 来限制树的深度并避免过拟合(树的生长仍然通过 leaf-wise 策略)。

详细内容参考: http://lightgbm.apachecn.org/cn/latest/Features.html

直方图算法-LightGBM

LightGBM的直方图算法是代替Xgboost的预排序算法的,虽然直方图的算法思路不算是Lightgbm的亮点,毕竟xgboost里面的近似算法也是用的这种思想,但是这种思路对于xgboost的预排序本身也是一种优化,所以Lightgbm本着快的原则,也采用了这种直方图的思想。

直方图算法说白了就是把连续的浮点特征离散化为k个整数(也就是分桶bins的思想), 比如[0, 0.1) ->0, [0.1, 0.3)->1。 并根据特征所在的bin对其进行梯度累加和个数统计,在遍历数据的时候,根据离散化后的值作为索引在直方图中累积统计量,当遍历一次数据后,直方图累积了需要的统计量,然后根据直方图的离散值,遍历寻找最优的分割点。

Histogram

这样在遍历到该特征的时候,只需要根据直方图的离散值,遍历寻找最优的分割点即可,由于bins的数量是远小于样本不同取值的数量的,所以分桶之后要遍历的分裂点的个数会少了很多,这样就可以减少计算量,同时也降低了内存存储量。

  • ○ 内存:直方图算法的内存消耗为(#data * #features * 4Bytes), 因为 xgboost 既要保存原始 feature 的值,也要保存这个值的顺序索引,这些值需要 32 位的浮点数来保存。
  • ○ 计算:预排序算法在选择好分裂特征计算分裂收益时需要遍历所有样本的特征值,时间为 (#data),而直方图算法只需要遍历桶就行了,时间为(#bin)

缺点: 不能找到很精确的分割点,训练误差没有 pre-sorted 好。但从实验结果来看,histogram 算法在测试集的误差和 pre-sorted 算法差异并不是很大,甚至有时候效果更好。

实际上可能决策树对于分割点的精确程度并不太敏感,而且较“粗”的分割点也自带正则化的效果。

与其他算法相比:

  1. scikit-learn GBDT、gbm in R 使用的是基于 pre-sorted 的算法。
  2. pGBRT 使用的是基于 histogram 的算法。
  3. xgboost 既提供了基于 pre-sorted 的算法,又提供了基于 histogram 的算法。
  4. lightgbm 使用的是基于 histogram 的算法。

直方图算法-XGBoost

xgboost不是后面的近似分割算法也进行了分桶吗? 为啥会比lightgbm的直方图算法慢这么多呢

xgboost那里的分桶是基于什么? 那个算法叫做Weight Quantile Sketch算法,考虑的是对loss的影响权值,用的每个样本的二阶导的分布去找候选分割点,这样带来的一个问题就是每一层划分完了之后,下一次依然需要构建这样的直方图,毕竟样本被划分到了不同的节点中,二阶导分布也就变了。 所以xgboost在每一层都得动态构建直方图, 因为它这个直方图算法不是针对某个特定的feature的,而是所有feature共享一个直方图(每个样本权重的二阶导)。

而lightgbm对每个特征都有一个直方图,只构建一次。 故xgboost的直方图算法是不如lightgbm的直方图算法快。

直方图算法的比较

XGBoost 近似直方图算法: 对所有feature每一层建立一个直方图。LightGBM 对每一个feature建立一个直方图, 只需一次。LightGBM直方图分桶将特征离散化了, XGBoost 还是全局的预排序。

树模型-特征重要度

怎么求GBDT、RF 等树模型的特征重要性?

Friedman在GBM的论文中提出的方法:

特征 j 的全局重要度通过特征 j 在每颗树中的重要度的平均值来衡量:

\[\hat{J_{j}^2}=\frac1M \sum_{m=1}^M\hat{J_{j}^2}(T_m) \]

其中,M是树的数量。特征 j 在单颗树中的重要度的如下:

\[\hat{J_{j}^2}(T)=\sum\limits_{t=1}^{L-1} \hat{i_{t}^2} 1(v_{t}=j) \]

其中,L 为树的叶子节点数量,L-1 即为树的非叶子节点数量(构建的树都是具有左右孩子的二叉树),重要度定义为按相应特征进行分裂之后平方损失的减少值。这里也不一定是平方损失, 可以计算所有的非叶子节点在分裂时加权不纯度的减少,减少得越多说明特征越重要。

简单说 GBDT 特征重要度的计算方法:所有回归树中通过特征 i 分裂后平方损失的减少值平均值得到特征重要性。

RF 有两种方法:

  1. 通过计算 Gini 系数的减少量判断特征重要性,越大越重要。
  2. 对于一颗树,先使用 OOB 样本计算测试误差 a,再随机打乱 OOB 样本中第 i 个特征(上下打乱特征矩阵第 i 列的顺序)后计算测试误差 b,a 与 b 差距越大特征 i 越重要。

开源工具实现:

  • 在 sklearn 中,GBDT 和 RF 的特征重要性计算方法是相同的,都是基于单棵树计算每个特征的重要性, 探究每个特征在每棵树上做了多少的贡献,再取个平均值。

Xgb

  • Xgboost 主要有三种计算方法:
    • a. importance_type=weight(默认值),特征重要性使用特征在所有树中作为划分属性的次数。

    • b. importance_type=gain,特征重要性使用特征在作为划分属性时 loss 平均的降低量。

    • c. importance_type=cover,特征重要性使用特征在作为划分属性时对样本的覆盖度。

posted @ 2022-05-27 22:42  康行天下  阅读(982)  评论(0编辑  收藏  举报