大厂面试机器学习算法(4)提升树模型:GDBT、XGBoost、LightGBM(未完待续)

关系梳理

提升树:拟合残差的boosting树模型,是一类模型的思想概括;

GDBT:广义GDBT就是指提升树模型;狭义GDBT指最原始的提升树,使用一阶导数拟合;

XGBoost:使用二阶导数并加入正则项的提升树模型。
如果GDBT取广义,那么“XGBoost是GDBT的一种”;如果GDBT取狭义,那么“XGBoost是GDBT的改进”,两种说法都有道理。

LightGBM:XGBoost的改进版,包括:并行方案、基于梯度的单边检测、排他性特征捆绑等。
此外,GBDT是一种机器学习算法,而XGBoost和LightGBM是工程上实现的框架。

Xgboost

论文:https://arxiv.org/pdf/1603.02754.pdf

XGBoost的全称是eXtreme Gradient Boosting,是2014年3月陈天奇博士提出的,是基于CART树的一种boosting算法。

原理介绍

xgboost是集成了K棵树之后的集成模型,如下图所示。
在这里插入图片描述

  • 每个样本需要经过K棵决策树
  • 最终的结果是每棵树的结果(对应叶子节点分数)之和,如下图所示。
    在这里插入图片描述
    对应的有以下两个关键问题:
  • 针对每棵树,如何计算每个叶子的分数f(x)?
  • 针对每棵树,如何构建整棵树的结构?

计算叶子阶段的分数

1. 定义目标函数
XGboost采用的是加法训练,首先优化第一棵树,之后再优化第二棵树,直至优化完k棵树。
在优化第t棵树时,前t-1课已定, y t = y t − 1 + f t ( x ) y^t = y^{t-1}+f_t(x) yt=yt1+ft(x)
在这里插入图片描述
在这里插入图片描述
n为样本数量,Obj为预测值与真实值之间的损失 l ( y , y ^ ) l(y,\hat{y}) l(y,y^)(如MSE)与惩罚项 Ω \Omega Ω 之和。

2. 二阶泰勒展开

XGboost使用二阶泰勒展开进行优化。泰勒公式(二阶)
在这里插入图片描述

此处将 y ^ i t − 1 = f 1 , . . . , t − 1 ( x i ) \hat{y}_i^{t-1}=f_{1,...,t-1}(x_i) y^it1=f1,...,t1(xi)看做x, f t ( x i ) f_t(x_i) ft(xi)看做△x, l ( y i , y ^ i t − 1 ) l(y_i,\hat{y}_i^{t-1}) l(yi,y^it1)看做泰勒公式中的 f ( x ) f(x) f(x),得:
在这里插入图片描述
其中 l ( y i , y ^ i t − 1 ) l(y_i,\hat{y}_i^{t-1}) l(yi,y^it1)为常数项(前t-1棵树已经优化完了),所以可以去掉,对函数没有影响;

g i g_i gi h i h_i hi分别表示对x(即 y ^ t − 1 \hat{y}^{t-1} y^t1)的一阶、二阶导数(函数中没有 f t ( x ) f_t(x) ft(x),也是常数)。
在这里插入图片描述

3. 惩罚项量化
树的复杂度由叶子结点个数T和各个叶子结点的w(f(x))表示:
在这里插入图片描述
进一步将 f t ( x ) f_t(x) ft(x)替换为 w w w,其中 I j I_j Ij表示被分到第j个叶子结点下的样本的下标集合,求导得最终w(f(x)):
在这里插入图片描述
最终的目标函数即为:
在这里插入图片描述

构建第k棵树的结构

1. 贪心算法
构建树有多种算法,这里我们以贪心算法为例,说明构建树的过程。
算法流程

  1. 对每个节点枚举所有的可用特征;
  2. 针对每个特征,把属于该节点的训练样本根据该特征的值进行升序排列,通过线性扫描的方式来决定该特征的最佳分裂点,并记录该特征的分裂收益;
  3. 选择收益最大的特征作为分裂特征,用该特征的最佳分裂点作为分裂位置,在该节点上分裂出左右两个新的叶节点,并为每个新节点关联对应的样本集;
  4. 回到第1步,递归执行直到满足特定条件为止。

因为我们想要得到最小值,所以判断当前节点是否继续划分的标准是:划分后的score应比划分前的score要更小;确定划分点的标准为:选择(划分前score-划分后score)最大的切分点作为这一次的划分点。
I = I L ∪ I R I = I_L\cup I_R I=ILIR,则有:
在这里插入图片描述
2. 近似算法:分桶
用贪心算法来寻找最佳划分点,准确度非常不错,但是时间复杂度和空间复杂度都太高了,作者提出一种近似法分位法,先对数据进行分桶(Bucket),然后桶内的数据相加起来,作为一个代表来进行计算。
分桶有两种方式:

  1. 全局分桶(Global Bucket),可以一开始就对全部的数据进行分桶。后面进行划分的时候只需要使用分桶数据就可以了。
  2. 局部分桶(Local Bucket),每次需要对当前leaf node 进行划分的时候,对当前节点里面的数据进行分桶。然后再划分。当然这个时间复杂度也会变的比较高。

作者测试对几种不同的切分算法的AUC结果比较。可以看得出,当eps=0.05,也就是将数据分成20个Bucket的时候,AUC的分数跟精准的贪心算法一样。
在这里插入图片描述

常见面试问题

  1. 如何实现并行?
    Boosting不是一种串行的结构吗?怎么并行的?注意Xgboost的并行不是tree粒度的并行,Xgboost也是一次迭代完才能进行下一次迭代的(第t次迭代的损失函数里包含了前面t−1次迭代的预测值)。Xgboost的并行是在特征粒度上的。决策树的学习最耗时的一个步骤就是对特征的值进行排序(因为要确定最佳分割点),Xgboost在训练之前,预先对数据进行了排序,然后保存为block(块)结构,后面的迭代中重复地使用这个结构,大大减小计算量。这个block结构也使得并行成为了可能,在进行节点的分裂时,需要计算每个特征的增益,最终选增益最大的那个特征去做分裂,那么各个特征的增益计算就可以开多线程进行
    在这里插入图片描述
  2. 怎样防止过拟合
    ① 在损失函数中添加正则项
    ② 添加类似梯度下降优化问题中的学习率 η \eta η,在累加时用得分乘以这个学习率。这可以收缩每棵树的权重,让每棵树的生长更加稳定。
    ③ 对样本的特征随机抽样。每次生成树的时候,只用其中一部分抽样的特征。这样子也能搞比较好的降低过拟合的风险。而且有研究怎么特征抽样效果比样本抽样效果更好。
  3. 学习率的作用?在公式的什么地方?
    Shrinkage(缩减):相当于学习速率。XGBoost 在进行完一次迭代后,会将叶子节点的权重乘上该系数,主要是为了削弱每棵树的影响,让后面有更大的学习空间。传统GBDT的实现也有学习速率;
    公式=预测值+正则项,预测值和正则项两部分都包含学习率参数。
  4. 如何处理缺失值?
    手动处理:使用均值;统一使用标记值(如-1)
    自动处理:每次计算Gain的时候分别遍历将含有缺失值的结点分别加到左边或者右边,选择Gain最大的方向作为缺失值应该在的方向。
    在这里插入图片描述
  5. 不足
    xgboost本质上还是一个基于现有特征的分类器,并不能实现特征交叉的功能,需要手动手工的对特征进行交叉发现新的有效特征。
  6. XGboost相对于GDBT的改进?
    ① 传统的GBDT算法以CART作为基分类器,xgboost还可以支持线性分类器,相当于带L1和L2的逻辑回归或者线性回归。
    ② 传统的GBDT在优化的时候,使用的是一阶导数信息,xgboost则对代价函数进行了二阶泰勒展开,同时用到了一阶导数和二阶导数。顺便提一下,xgboost工具支持自定义代价函数,只要函数可一阶和二阶求导。
    ③ xgboost在代价函数中加入了正则项,用于控制模型的复杂度。正则项里面包括树的叶子节点的个数、每个叶子节点上输出的score的L2模的平方和。( 从Bias-variance tradeoff 的角度来说,正则化降低了模型的variance,使得到的模型更加简单,防止过拟合,这是xgboost优于传统的GBDT的一个特征)
    ④ 列抽样(column sampling),借鉴了随机森林的做法,支持列抽样可以降低过拟合,同时减少了计算量,这也是xgboost异于传统gbdt的一个特性,sklearn中已经实现行采样和列采样,同样xgboost也是可以实现的。
  7. XGBoost是否需要归一化?
    不需要。连续特征的归一化,作用是解决梯度下降时,等高线是椭圆导致迭代次数增多的问题。而xgboost等树模型是阶跃(不可导)的,不能进行梯度下降,而是通过寻找特征的最优分裂点来完成优化的。由于归一化不会改变分裂点的位置,因此xgboost不需要进行归一化。
    (此外,归一化也不会影响信息熵增益的值,因此在决策树模型中也不适用。)
  8. 怎样计算特征重要性?
    Python中直接调用xgboost库的feature_importance函数,原理是根据每个特征的增益进行排序。
    由损失函数:在这里插入图片描述
    得到信息增益Gain的公式:
    在这里插入图片描述
    实际计算使用每棵子树的所有信息增益的平均值,得到最终该特征的Gain。

Python API: xgboost

  1. xgboost.XGBClassifier().fit()和xgboost.train()的区别
    Python xgboost库中有两种训练模型的方法。
    import xgboost as xgb

① xgboost.XGBClassifier().fit()

xgm = xgb.XGBClassifier()
xgm.fit(X_train, y_train)   
y_pred = xgm.predict(X_test)  

② xgboost.train()

param = {'max_depth':2, 'eta':1, 'silent':1, 'objective':'binary:logistic' }
dtrain = xgb.DMatrix(X_train, y_train)
dtest = xgb.DMatrix(X_test)
bst = xgb.train(list(param), dtrain)
preds = bst.predict(dtest)
  1. xgb.train()中参数的解释
    参见这篇博客
  2. 官方文档
    XGboost documentation
  3. 类别型变量的处理
    有序型:LabelEncoder;无序型:onehot/embedding
    onehot:本质上被当做n个特征,每次分裂one vs rest,数据量较少时会导致切分数据不平衡;
    embedding:可以避免onehot的问题。

LightGBM

LightGBM相对于XGBoost的改进

LightGBM相对于XGBoost的改进包括:并行方案、基于梯度的单边检测、排他性特征捆绑等。

LightGBM的并行方案

LightGBM改进了XGBoost的特征并行方案,并且提出了数据并行方案和投票并行方案。

  1. 特征并行方案
    LightGBM并没有垂直切分(即按特征切分)数据集,而是每个worker都有全量的训练数据,避免了通讯开销过大的问题。
    最优的特征分裂结果不需要传输到其他worker中,只需要将最优特征以及分裂点告诉其他worker,worker随后本地自己进行数据分裂,生成决策树。

  2. 数据并行方案
    当数据量很大时,特征并行算法受限于特征分裂效率,此时推荐使用数据并行算法。
    为避免过大通讯开销,使用Reduce Scatter机制归并不同worker的不同特征子集的直方图,找到最优局部分裂信息,最终同步找到全局最优。(没懂)

  3. 基于投票的并行方案
    每个worker中选出top k个分裂特征,将所有top-k进行汇总,选出全局分裂特征进行分裂。
    可以证明,该方法有很大的概率选出实际最优的特征。

基于梯度的单边检测(GOSS)

GOSS是一个样本的采样算法,目的是丢弃一些对计算信息增益没有帮助的样本。梯度大的样本对信息增益有更大的影响,但直接抛弃拥有小梯度的实例数据会使据的分布发生改变。
因此,GOSS方法保持有较大梯度的实例,而在小梯度数据上运行随机采样。既减少了时间和空间开销,又保证了计算精度。

排他性特征捆绑(EFB)

在实际应用中,高维度特征具有稀疏性,且许多特征具有互斥性,如one-hot。捆绑互斥的特征可以减小特征数量。

常见面试问题

  1. LightGBM和XGBoost对比
    1)XGBoost使用基于预排序的决策树算法,每遍历一个特征就需要计算一次特征的增益,时间复杂度为O(datafeature)。
    而LightGBM使用基于直方图的决策树算法,直方图的优化算法只需要计算K次,时间复杂度为O(Kfeature)
    2)XGBoost使用按层生长(level-wise)的决策树生长策略,LightGBM则采用带有深度限制的按叶子节点(leaf-wise)算法。在分裂次数相同的情况下,leaf-wise可以降低更多的误差,得到更好的精度。leaf-wise的缺点在于会产生较深的决策树,产生过拟合。
    3)支持类别特征,不需要进行独热编码处理
    4)优化了特征并行和数据并行算法,除此之外还添加了投票并行方案
    5)采用基于梯度的单边采样来保持数据分布,减少模型因数据分布发生变化而造成的模型精度下降
    6)特征捆绑转化为图着色问题,减少特征数量
  2. LightGBM的直方图算法
    直方图算法的思想:将每个特征的连续浮点数映射为k个整数,并根据其出现频率构造直方图。从特征处理的角度即数据离散化。
    在这里插入图片描述
    优点:
    1)占用内存更小,不需要存储浮点数和索引,只需要存储整型;
    2)计算开销更小,XGBoost需要遍历所有特征值,LightGBM的时间复杂度从 O ( # d a t a ∗ # f e a t u r e ) O(\#data*\#feature) O(#data#feature)降低到 O ( k ∗ # f e a t u r e ) O(k*\#feature) O(k#feature) # d a t a > > k \#data>>k #data>>k
    3)使用直方图作差加快训练速度。我们只需要对其中一个子节点进行数据传输,其兄弟节点可以通过父节点和该节点作差得到。
    在这里插入图片描述
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值