个人总结:从 GBDT 到 XGBoost和LightGBM 用统一的视角看待它们

上次讲到GBDT,就不得不提到现在应用最广泛,效果也是数一数二的XGBoost,我们可以用统一的视角看待它们,因为它们本质上其实是一样的,可以把XGBoost看作GBDT的工程实现。

 如果非要说一些区别的话,最明显的是,GBDT使用了一阶泰勒公式展开(梯度下降/最速下降),而XGBoost使用了二阶泰勒公式展开(牛顿法),但是这些并不影响它们其实是同样的东西。如果你很熟悉下面的基础知识,那么可以直接跳到XGB部分。

从泰勒公式说起

泰勒公式是一个用函数在某点的信息描述其附近取值的公式。

基本形式:

一阶泰勒展开:

二阶泰勒展开:

假设 ,将处进行泰勒展开

,针对这个公式进行不同处理,就能分别得到梯度下降和牛顿法。

梯度下降

在机器学习任务中,需要最小化损失函数L(θ),其中θ是要求解的模型参数。梯度下降是一种迭代求解方法:选取初值,不断迭代,更新θ的值,进行损失函数极小化。

迭代公式:

处进行泰勒一阶展开:

要使得,可取,平方乘以一个负数一定是负数嘛。

很巧,形式正好就是“学习率 x 负梯度”,则

学习率(或者步长)可以用线性搜索的方法确定,但是一般是直接赋一个比较小的值。

牛顿法

处进行泰勒二阶展开:

假设参数是标量(一维):

可将一阶导数和二阶导数分别记为g和h:

,求该函数的极小值

注:(1)函数极小值的一阶必要条件:若x*是f(x)的一个极小点,则必有 f ' (x*) = 0

(2)函数极小值的二阶必要条件:若x*是f(x)的一个极小点,则必有 f ' (x*) = 0 && f '' (x*) > 0

 即让极小,可令

,所以,此时h为二阶导数,g为一阶导数。

假设参数是高维:

这时θ为n维列向量\theta ^{t} = \begin{pmatrix} \\ \theta^{t}_{1} \\ ... \\ \theta^{t}_{n} \end{pmatrix},同时也是n维列向量。

{L}'(\theta^{t - 1}) = \begin{pmatrix} \\ \frac{\partial L(\theta^{t}) }{\partial \theta^{t-1}_1} \\ ... \\ \frac{\partial L(\theta^{t}) }{\partial \theta^{t-1}_n} \end{pmatrix}_{n \times 1}  也就是  \bigtriangledown L(\theta^{t-1}),让g代替它。

{L}''(\theta^{t - 1}) = \begin{pmatrix} \frac{\partial^2 L(\theta^{t})}{\partial \theta^{t-1}_{1} \theta^{t-1}_{1}} &... &\frac{\partial^2 L(\theta^{t})}{\partial \theta^{t-1}_{1} \theta^{t-1}_{n}} \\ \vdots & \ddots & \vdots \\ \frac{\partial^2 L(\theta^{t})}{\partial \theta^{t-1}_{n} \theta^{t-1}_{1}} & ... & \frac{\partial^2 L(\theta^{t})}{\partial \theta^{t-1}_{n} \theta^{t-1}_{n}} \end{pmatrix}_{n\times n} 也就是海森矩阵\bigtriangledown^{2} L(\theta^{t-1}):是由目标函数在点\theta^{t-1}处的二阶偏导数组成的nxn阶对称矩阵,让H代替它。

于是有相应的二阶泰勒展开为

L(\theta^{t}) = L(\theta^{t - 1}) + g \Delta\theta + {\frac{1}{2}}\Delta\theta^{T} H\Delta\theta      (因为这里也是n维列向量)

此时与前面思路相同,寻找函数最小值,对进行求导,这里就涉及矩阵求导了。

同理,我们得出

\Delta \theta = -\frac{g}{H} 即\Delta \theta = -H^{-1}g。此处H为海森矩阵,g为一阶梯度。

 

思维转化

GBDT在函数空间利用梯度下降法进行优化,于是想到XGBoost在函数空间利用牛顿法进行优化。

 

XGBoost的流程(如何将其与GBDT进行统一)

首先还是得有目标函数,这个目标函数其实和GBDT一致,只不过多了最右边的正则化项

\hat{y}^{(t-1)}处进行二阶泰勒展开:

这个就是L(k+\Delta ) = L(k) +f'(k)\Delta + \frac{f''(k)\Delta ^{2}}{2},gi和hi就是梯度和海森矩阵。

第一项为常数项,去掉后为:

而正则化内部为:

,以叶子节点个数(T)和叶节点权重(w)进行正则化,可以理解为内部正则化。(GBDT正则化为shrinkage,为前向加法的每一步设置一个步长,可以理解为外部正则化,XGBoost也可以使用shrinkage。)

同时,,其中\omega_{q(x)}为叶子节点q的权重。

将它们代入后得到:

n为样本数量,前半部分为样本的累加,T为当前树的叶子节点数量,最后一部分为对叶子节点权重的累加。

使用叶子节点数量进行统一:

这里,,

则为使目标函数最小,使其导数为0,解出来最优权重为:

但是这时已经可以和GBDT构成一部分统一了!
GBDT里我们在对叶子节点进行拟合时(若是采用的是对数损失),用近似值(newton-raphson公式)进行代替:

所以实际上GBDT里面的叶子节点和XGB(除开正则化项)里面的权重是一模一样的!XGB相当于整合了gbdt生成树,以及对树进行拟合得到叶子节点权重的两步。

 

XGB的特征选择

此时已经算出了叶子节点的最优权重,但是树的结构仍未确定。

GBDT在内部使用CART回归树,所以树节点分裂使用的是最小方差和。而XGB使用贪心法每次尝试分裂一个叶节点,计算分裂前后的增益,选择增益最大的特征和特征点。

分裂前后的增益:

  • ID3采用信息增益
  • C4.5采用信息增益比
  • CART离散特征采用Gini系数,连续特征采用最小方差和

XGBoost和的分裂函数和上述有些许不同:

先看把最优预测分数放进损失函数后得到的最小损失:

标红的部分是每个叶子节点对总体损失的贡献,损失越小,则标红部分越大。

定义分裂前后的增益,我们希望该增益最大化,左右子树的一阶和二阶导数分别为G_L,H_L,G_R,H_R:

该公式内部结构为:

,I代表目前的节点(特征)。

具体做法举例:

假设选择年龄这个特征的值a作为分裂点,则可以得到左子树2人,右子树3人。这样可以分别计算出左右子树的一阶和二阶导,进而求出最终的上式的值:

通过选择除了a的其他值可以得到其他组合,遍历所有特征所有可能的分割点,计算这个Gain,选取最大值对应的分割点(feature, value)来进行分割。

对于稀疏值,XGBoost采取one-hot编码的方式。
如果训练样本中没有缺失值,而预测过程中出现了缺失值,那么样本会被默认分到右子树。

 

XGBoost的工程运行效率优化

  • Boosting没办法做到并行,在单个弱学习器里最耗费时间的是特征的分裂过程,Xgb针对这个分裂做了较大的并行优化,对不同特征的特征划分点,在不同线程内并行选择分裂的最大增益。
  • 对训练的每个特征排序,以块的方式存储在内存中,特征预排序,方便后面迭代使用。计算时首先默认所有样本都在右子树,然后从小到大迭代依次放入左子树,寻找最优的分裂点。这个结构加速了特征分裂的速度,只需在建树前排序一次,后面节点分裂时只需在当前的基础上进行加减,不需要再扫描所有数据。
  • 当出现缺失值时,XGBoost可以学习出默认的节点分裂方向,在处理带缺失值的特征时,先对非缺失的样本进行排序,对该特征缺失的样本先不处理,然后在遍历每个分裂点时,将这些缺失样本分别划入左子树和右子树来计算损失然后求最优
  • XGBoost先从顶到底建立所有可以建立的子树,再从底到顶反向进行剪枝。比起GBDT,这样不容易陷入局部最优解。
  • 行抽样:抽取部分样本子采样,列抽样:抽取部分特征(借鉴随机森林),为了防止过拟合。
  • shrinkage(即学习速率,将学习速率调小,迭代次数增加,有正则化作用),与GBDT相同。
  • 支持自定义损失函数(需二阶可导)。

 

XGBoost调参

XGBoost最重要的3个参数:booster,n_estimators和objective

1) booster决定了XGBoost使用的弱学习器类型,可以是默认的gbtree, 也就是CART决策树,还可以是线性弱学习器gblinear以及DART。一般来说,我们使用gbtree就可以了,不需要调参。

2) n_estimators是非常重要的需要调参的参数,它代表了我们使用的弱学习器的数量,关系到模型的复杂度。这个参数对应GBDT的n_estimators。这个值太小,容易欠拟合太大,模型过于复杂,一般需要调参选择一个适中的数值。

3) objective代表了我们要解决的目标问题是分类还是回归,或其他问题,以及对应的损失函数。在回归问题一般是使用reg:squarederror,即MSE均方误差。二分类问题一般使用binary:logistic,多分类问题一般使用multi:softmax。

 

XGB弱学习器参数

这里只讨论gbtree。调参的参数主要是决策树的相关参数如下:

1) max_depth: 控制树深度。数据少或者特征少可以不管这个值。如果模型样本量多,特征也多,需要限制最大深度。具体取值需要进行网格调参。这个参数也对应GBDT的max_depth。

2) min_child_weight最小子节点权重阈值。如果某个树节点权重小于这个阈值,则不会再分裂子树,即这个树节点就是叶子节点。这里树节点的权重使用的是该节点所在样本的二阶导数的和,即XGBoost原理篇的

  

这个值也是通过网格搜索寻找最优值,在sklearn GBDT里没有完全对应的参数,不过min_samples_split从另一个角度起到了阈值限制。

3) gamma: XGBoost决策树分裂所带来的损失减小阈值,也就是我们在尝试树结构分裂的最大的gain,这个值大于gamma才能继续分裂。这个值也需要网格搜索寻找最优值。

4) subsample子采样参数,这个也是不放回抽样,和sklearn GBDT的subsample作用一样。选择小于1的比例可以减少方差,即防止过拟合,但是会增加样本拟合的偏差。初期可以取值为1,如果发现过拟合则可以网格搜索找一个相对小的值

5) colsample_bytree/colsample_bylevel/colsample_bynode:这三个参数都是用于控制特征采样比例的,默认是不做采样,即使用所有的特征建树。colsample_bytree控制整棵树的特征采样比例,colsample_bylevel控制某一层的特征采样比例,而colsample_bynode控制某一个树节点的特征采样比例。比如我们一共64个特征,则假设colsample_bytree,colsample_bylevel和colsample_bynode都是0.5,则某一个树节点分裂时会随机采样8个特征来尝试分裂子树。

6) reg_alpha/reg_lambda: 这2个是XGBoost的正则化参数。

reg_alpha是L1正则化系数,reg_lambda是L2正则化系数。

上面这些参数都是需要调参的,不过一般先调max_depth,min_child_weight和gamma。如果发现有过拟合的情况,再调后面几个参数。

 

XGB其他参数

XGBoost还有一些其他参数需注意,主要是learning rate

learning rate控制每个弱学习器的权重衰减系数,和sklearn的GBDT learning rate类似。较小的learning rate意味我们需要更多的弱学习器的迭代次数。通常用步长和迭代最大次数一起来决定算法的拟合效果。所以这两个参数n_estimators和learning rate要一起调才有效果。当然可以先固定一个learning rate,然后调完n_estimators,再调完其他所有参数后,最后再来调learning rate和n_estimators。

n_jobs控制算法的并发线程数scale_pos_weight用于类别不平衡的时候,负例和正例的比例。类似于sklearn中的class_weight。importance_type可以查询各个特征的重要性程度。可以选择“gain”,“weight”,“cover”,“total_gain”。最后可以通过调用booster的get_score方法获取对应的特征权重。“gain”和“total_gain”通过分别计算特征被选中做分裂特征时带来的平均增益和总增益来计算重要性。“cover”和“total_cover”通过计算特征被选中做分裂时的平均样本覆盖度总体样本覆盖度来计算重要性。“weight”通过特征被选中作为分裂特征的计数来计算重要性。

 

从XGBoost到LightGBM

LightGBM在内存和运行速度上又做了许多优化,但是从算法本身的角度来看并没有相对于Xgboost更多的优化点。

直方图算法

连续的浮点特征离散化为k个整数,同时构造一个宽度为k的直方图,根据离散化的值作为索引在直方图中累积统计量,遍历一次数据后,直方图累积了所需要的统计量,然后根据直方图的离散值寻找最优分隔点

这个做法减少了内存的使用,浮点数变为了整数,同时bin的数量的减少,使得计算复杂度由O(data)降低到O(bin)。

直方图差加速

一个叶子的直方图可以由它的父亲节点的直方图和它兄弟节点的直方图做差得到,提升一倍速度

 

建树过程的两种方法:level-wise -》 leaf-wise

 

并行优化

垂直切分

传统的特征并行(boosting的特性无法做到树并行,只能特征并行),在不同的线程中并行选择分裂的最大增益:

  1. 垂直切分数据,每个worker(线程)只有部分特征
  2. 每个worker找到局部最佳切分点(feature, threshold)
  3. worker之间互相通信,找到全局最佳切分点
  4. 具有全局最佳切分特征的worker进行节点分裂,然后广播切分后左右子树的instance indices
  5. 其他worker根据广播的instance indices进行节点分裂

缺点: 由于传统的计算复杂度为O(data),当数据量大时会比较慢;网络通信代价大,需要广播instance indices

LGB的特征并行

  1. 每个worker保存所有数据集
  2. 每个worker在其特征子集上寻找最佳切分点
  3. worker之间互相通信,找到全局最佳切分点
  4. 每个worker根据全局最佳切分点进行节点分裂

优点:避免了广播instance indices,减少网络通信量。

缺点:分裂的计算复杂度没有减少,当数据量大时单个worker存储所有数据代价高。

 

水平切分

传统的特征并行:

  1. 水平切分数据,每个worker只有部分数据
  2. 每个worker根据本地数据统计局部直方图
  3. 合并所有局部直方图得到全局直方图
  4. 根据全局直方图进行节点分裂

缺点:网络通信代价大

LGB的特征并行:

  1. 不同的worker合并不同特征的局部直方图
  2. 采用直方图做差算法,只需要通信一个节点的直方图

优点:通信量减少

其他改进

Gradient-based One Side Sampling(GOSS)

每次迭代时对样本进行采样,对误差大(梯度绝对值大)的数据保留;对误差小的数据采样一个子集,给这个子集的数据一个权重,让这个子集可以近似到误差小的数据的全集。这样可以在减少数据的同时不改变训练数据的分布,从而在几乎不影响精度的情况下加速训练。

Exclusive Feature Bundling

特征维度很大时,特征空间一般是稀疏的。利用这个特征,可以无损地降低GBDT算法中需要遍历的特征数量(降低构造特征直方图需要遍历的特征数量)。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值