暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

XGBoost和LightGBM

高級白 2021-08-16
1011

XGBoost

XGBoost由多棵CART(Classification And Regression Tree)组成,每一棵决策树学习的是目标值与之前所有树预测值之和的残差,多棵决策树共同决策,最后将所有树的结果累加起来作为最终的预测结果。在训练阶段,每棵新增加的树是在已训练完成的树的基础上进行训练的。

目标函数

XGBoost由个基模型组成的加法运算式:

其中为第个基模型,为第个样本的预测值。

损失函数可由预测值与真实值进行表示:

其中为样本数量。

模型的预测精度由模型的偏差和方差共同决定,损失函数代表了模型的偏差,想要方差小则需要简单的模型,所以目标函数由模型的损失函数 与抑制模型复杂度的正则项 组成,所以有:

其中, 为模型的正则项。目标函数 由两项组成:第一项为损失函数,用于评估模型预测值和真实值之间的损失或误差;第二项为正则化项,用来控制模型的复杂度,正则化项倾向于选择简单的模型,避免过拟合(降低方差)。

Boosting 模型是前向加法,以第 步的模型为例,模型对第 个样本 的预测为:

其中 由第 步的模型给出的预测值,是已知常数, 是我们这次需要加入的新模型的预测值,此时,目标函数可以写成:

求此时最优化目标函数,就相当于求解

根据泰勒公式把函数在点 处进行泰勒的二阶展开,可得:

我们把 视为 视为 ,故可将目标函数写为:

其中 为损失函数的一阶导, 为损失函数的二阶导,这里的导是对 求导。

以平方损失函数为例:

则:

由于在第 步时 其实是一个已知的值,所以 是一个常数,其对函数的优化不会产生影响,因此目标函数可以写成:

所以只需要求出每一步损失函数的一阶导和二阶导的值(由于前一步的 是已知的,所以这两个值就是常数),然后最优化目标函数,就可以得到每一步的 ,最后根据加法模型得到一个整体模型。

最优切分点划分算法

XGBoost的核心算法:

  1. 不断地添加树,不断地进行特征分裂来生长一棵树,每次添加一个树,其实是学习一个新函数,去拟合上次预测的残差。
  2. 当训练完成得到 棵树,要预测一个样本的分数,其实就是根据这个样本的特征,在每棵树中会落到对应的一个叶子节点,每个叶子节点就对应一个分数
  3. 最后只需要将每棵树对应的分数加起来就是该样本的预测值。

在决策树的生长过程中,一个非常关键的问题是如何找到叶子的节点的最优切分点,Xgboost 支持两种分裂节点的方法——贪心算法近似算法

贪心算法

  1. 从深度为 0 的树开始,对每个叶节点枚举所有的可用特征;

  2. 针对每个特征,把属于该节点的训练样本根据该特征值进行升序排列,通过线性扫描的方式来决定该特征的最佳分裂点,并记录该特征的分裂收益;

  3. 选择收益最大的特征作为分裂特征,用该特征的最佳分裂点作为分裂位置,在该节点上分裂出左右两个新的叶节点,并为每个新节点关联对应的样本集;

  4. 回到第 1 步,递归执行到满足特定条件为止。

近似算法

贪婪算法可以得到最优解,但当数据量太大时则无法读入内存进行计算,近似算法主要针对贪婪算法这一缺点给出了近似最优解。

对于每个特征,只考察分位点可以减少计算复杂度。

该算法会首先根据特征分布的分位数提出候选划分点,然后将连续型特征映射到由这些候选点划分的桶中,然后聚合统计信息找到所有区间的最佳分裂点。

在提出候选切分点时有两种策略:

  • Global:学习每棵树前就提出候选切分点,并在每次分裂时都采用这种分割;
  • Local:每次分裂前将重新提出候选切分点。

直观上来看,Local 策略需要更多的计算步骤,而 Global 策略因为节点没有划分所以需要更多的候选点。

工程实践

块结构设计

决策树的学习最耗时的一个步骤就是在每次寻找最佳分裂点时都需要对特征的值进行排序。而 XGBoost 在训练之前根据特征对数据进行了排序,然后保存到块结构中,并在每个块结构中都采用了稀疏矩阵存储格式(Compressed Sparse Columns Format,CSC)进行存储,后面的训练过程中会重复地使用块结构,可以大大减小计算量。

  • 每一个块结构包括一个或多个已经排序好的特征;
  • 缺失特征值将不进行排序;
  • 每个特征会存储指向样本梯度统计值的索引,方便计算一阶导和二阶导数值;

这种块结构存储的特征之间相互独立,方便计算机进行并行计算。在对节点进行分裂时需要选择增益最大的特征作为分裂,这时各个特征的增益计算可以同时进行,这也是 Xgboost 能够实现分布式或者多线程计算的原因。

缓存访问优化算法

块结构的设计可以减少节点分裂时的计算量,但特征值通过索引访问样本梯度统计值的设计会导致访问操作的内存空间不连续,这样会造成缓存命中率低,从而影响到算法的效率。

为了解决缓存命中率低的问题,XGBoost 提出了缓存访问优化算法:为每个线程分配一个连续的缓存区,将需要的梯度信息存放在缓冲区中,这样就是实现了非连续空间到连续空间的转换,提高了算法效率。

此外适当调整块大小,也可以有助于缓存优化。

“核外”块计算

当数据量过大时无法将数据全部加载到内存中,只能先将无法加载到内存中的数据暂存到硬盘中,直到需要时再进行加载计算,而这种操作必然涉及到因内存与硬盘速度不同而造成的资源浪费和性能瓶颈。为了解决这个问题,XGBoost 独立一个线程专门用于从硬盘读入数据,以实现处理数据和读入数据同时进行。

此外,XGBoost 还用了两种方法来降低硬盘读写的开销:

  • 块压缩: 对 Block 进行按列压缩,并在读取时进行解压;
  • 块拆分: 将每个块存储到不同的磁盘中,从多个磁盘读取可以增加吞吐量。

优缺点

优点

  1. 精度更高:GBDT 只用到一阶泰勒展开,而 XGBoost  对损失函数进行了二阶泰勒展开。XGBoost 引入二阶导一方面是为了增加精度,另一方面也是为了能够自定义损失函数,二阶泰勒展开可以近似大量损失函数;
  2. 灵活性更强:GBDT 以 CART 作为基分类器,XGBoost 不仅支持 CART 还支持线性分类器,(使用线性分类器的 XGBoost 相当于带 L1 和 L2 正则化项的逻辑斯蒂回归(分类问题)或者线性回归(回归问题))。此外,XGBoost 工具支持自定义损失函数,只需函数支持一阶和二阶求导;
  3. 正则化:XGBoost 在目标函数中加入了正则项,用于控制模型的复杂度。正则项里包含了树的叶子节点个数、叶子节点权重的 L2 范式。正则项降低了模型的方差,使学习出来的模型更加简单,有助于防止过拟合;
  4. Shrinkage(缩减):相当于学习速率。XGBoost 在进行完一次迭代后,会将叶子节点的权重乘上该系数,主要是为了削弱每棵树的影响,让后面有更大的学习空间;
  5. 列抽样:XGBoost 借鉴了随机森林的做法,支持列抽样,不仅能降低过拟合,还能减少计算;
  6. 缺失值处理:XGBoost 采用的稀疏感知算法极大的加快了节点分裂的速度;
  7. 可以并行化操作: 块结构可以很好的支持并行计算。

缺点

  1. 虽然利用预排序和近似算法可以降低寻找最佳分裂点的计算量,但在节点分裂过程中仍需要遍历数据集;
  2. 预排序过程的空间复杂度过高,不仅需要存储特征值,还需要存储特征对应样本的梯度统计值的索引,相当于消耗了两倍的内存。

LightGBM

LightGBM具有训练速度快、内存占用低的特点。

  1. 单边梯度抽样算法;
  2. 直方图算法;
  3. 互斥特征捆绑算法;
  4. 基于最大深度的 Leaf-wise 的垂直生长算法;
  5. 类别特征最优分割;
  6. 特征并行和数据并行;
  7. 缓存优化。

单边梯度抽样算法

GBDT 算法的梯度大小可以反应样本的权重,梯度越小说明模型拟合的越好,单边梯度抽样算法(Gradient-based One-Side Sampling, GOSS)利用这一信息对样本进行抽样,减少了大量梯度小的样本,在接下来的计算过程中只需关注梯度高的样本,极大的减少了计算量。

GOSS 算法保留了梯度大的样本,并对梯度小的样本进行随机抽样,为了不改变样本的数据分布,在计算增益时为梯度小的样本引入一个常数进行平衡。GOSS 算法将更多的注意力放在训练不足的样本上,另一方面通过乘上权重来防止采样对原始数据分布造成太大的影响。

直方图算法

直方图算法

直方图算法的基本思想是将连续的特征离散化为 个离散特征,同时构造一个宽度为 的直方图用于统计信息(含有 个 bin)。利用直方图算法无需遍历数据,只需要遍历 个 bin 即可找到最佳分裂点。

特征离散化的具有存储方便、运算更快、鲁棒性强、模型更加稳定等优点。虽然将特征离散化后无法找到精确的分割点,可能会对模型的精度产生一定的影响,但较粗的分割也起到了正则化的效果,一定程度上降低了模型的方差(减少过拟合)。

直方图加速

在构建叶节点的直方图时,可以通过父节点的直方图与相邻叶节点的直方图相减的方式构建,从而减少了一半的计算量。在实际操作过程中,还可以先计算直方图小的叶子节点,然后利用直方图作差来获得直方图大的叶子节点。

稀疏特征优化

XGBoost 在进行预排序时只考虑非零值进行加速,而 LightGBM 也采用类似策略:只用非零特征构建直方图。

互斥特征捆绑算法

高维特征往往是稀疏的,而且特征间可能是相互排斥的(如两个特征不同时取非零值),如果两个特征并不完全互斥(如只有一部分情况下是不同时取非零值),可以用互斥率表示互斥程度。互斥特征捆绑算法(Exclusive Feature Bundling, EFB)指出如果将一些特征进行融合绑定,则可以降低特征数量。

带深度限制的 Leaf-wise 算法

在建树的过程中有两种策略:

  • Level-wise:基于层进行生长,直到达到停止条件;
  • Leaf-wise:每次分裂增益最大的叶子节点,直到达到停止条件。

XGBoost 采用 Level-wise 的增长策略,方便并行计算每一层的分裂节点,提高了训练速度,但同时也因为节点增益过小增加了很多不必要的分裂,降低了计算量;LightGBM 采用 Leaf-wise 的增长策略减少了计算量,配合最大深度的限制防止过拟合,由于每次都需要计算增益最大的节点,所以无法并行分裂。

类别特征最优分割

LightGBM 原生支持类别特征,采用 many-vs-many 的切分方式将类别特征分为两个子集,实现类别特征的最优切分。

与XGBoost对比

内存更小

  • XGBoost 使用预排序后需要记录特征值及其对应样本的统计值的索引,而 LightGBM 使用了直方图算法将特征值转变为 bin 值,且不需要记录特征到样本的索引,极大的减少了内存消耗
  • LightGBM 采用了直方图算法将存储特征值转变为存储 bin 值,降低了内存消耗
  • LightGBM 在训练过程中采用互斥特征捆绑算法减少了特征数量,降低了内存消耗

速度更快

  • LightGBM 采用了直方图算法将遍历样本转变为遍历直方图,极大的降低了时间复杂度
  • LightGBM 在训练过程中采用单边梯度算法过滤掉梯度小的样本,减少了大量的计算
  • LightGBM 采用了基于 Leaf-wise 算法的增长策略构建树,减少了很多不必要的计算量
  • LightGBM 采用优化后的特征并行、数据并行方法加速计算,当数据量非常大的时候还可以采用投票并行的策略
  • LightGBM 对缓存也进行了优化,增加了 Cache hit 的命中率

总结

简单介绍XGBoost

GBDT是一种基于Boosting增强策略的加法模型,训练的时候采用前向分布算法进行贪婪的学习,每次迭代都学习一棵CART树来拟合之前 棵树的预测结果与训练样本真实值的残差

XGBoost对GBDT进行了一系列优化,比如损失函数进行了二阶泰勒展开、目标函数加入正则项、支持并行和默认缺失值处理等,在可扩展性和训练速度上有了巨大的提升,但其核心思想没有大的变化。

XGBoost与GBDT有什么不同

  • 基分类器:XGBoost的基分类器不仅支持CART决策树,还支持线性分类器,此时XGBoost相当于带L1和L2正则化项的Logistic回归(分类问题)或者线性回归(回归问题)。
  • 导数信息:XGBoost对损失函数做了二阶泰勒展开,GBDT只用了一阶导数信息,并且XGBoost还支持自定义损失函数,只要损失函数一阶、二阶可导。
  • 正则项:XGBoost的目标函数加了正则项, 相当于预剪枝,使得学习出来的模型更加不容易过拟合。
  • 列抽样:XGBoost支持列采样,与随机森林类似,用于防止过拟合。
  • 缺失值处理:对树中的每个非叶子结点,XGBoost可以自动学习出它的默认分裂方向。如果某个样本该特征值缺失,会将其划入默认分支
  • 并行化:注意不是tree维度的并行,而是特征维度的并行。XGBoost预先将每个特征按特征值排好序,存储为块结构,分裂结点时可以采用多线程并行查找每个特征的最佳分割点,极大提升训练速度。

XGBoost损失函数为什么使用二阶泰勒展开

  • 可扩展性:为了统一损失函数求导的形式以支持自定义损失函数。损失函数支持自定义,只需要新的损失函数二阶可导。
  • 精准性:二阶信息能让梯度收敛更快更准确,一阶导指引梯度方向,二阶导指引梯度方向如何变化。可以更为精准的逼近真实的损失函数。

XGBoost为什么可以并行训练

  • XGBoost的并行,并不是说每棵树可以并行训练,XGB本质上仍然采用boosting思想,每棵树训练前需要等前面的树训练完成才能开始训练。
  • XGBoost的并行,指的是特征维度的并行:在训练之前,每个特征按特征值对样本进行预排序,并存储为Block结构,在后面查找特征分割点时可以重复使用,而且特征已经被存储为一个个Block结构,那么在寻找每个特征的最佳分割点时,可以利用多线程对每个Block并行计算。

XGBoost为什么快

  • 分块并行:训练前每个特征按特征值进行排序并存储为Block结构,后面查找特征分割点时重复使用,并且支持并行查找每个特征的分割点
  • 候选分位点:每个特征采用常数个分位点作为候选分割点
  • CPU cache 命中优化:使用缓存预取的方法,对每个线程分配一个连续的buffer,读取每个block中样本的梯度信息并存入连续的Buffer中。
  • Block 处理优化:Block预先放入内存;Block按列进行解压缩;将Block划分到不同硬盘来提高吞吐

XGBoost防止过拟合的方法

  • 目标函数添加正则项:叶子节点个数+叶子节点权重的L2正则化
  • 列抽样:训练的时候只用一部分特征(不考虑剩余的block块即可)
  • 子采样:每轮计算可以不使用全部样本,使算法更加保守
  • shrinkage: 可以叫学习率或步长,为了给后面的训练留出更多的学习空间

XGBoost和LightGBM的区别

  • 树生长策略:XGB采用level-wise
    的分裂策略,LGB采用leaf-wise
    的分裂策略。XGB对每一层所有节点做无差别分裂,但是可能有些节点增益非常小,对结果影响不大,带来不必要的开销。Leaf-wise是在所有叶子节点中选取分裂收益最大的节点进行的,但是很容易出现过拟合问题,所以需要对最大深度做限制。

  • 分割点查找算法:XGB使用特征预排序算法,LGB使用基于直方图的切分点算法,其优势如下:

    • 减少内存占用,比如离散为256个bin时,只需要用8位整形就可以保存一个样本被映射为哪个bin(这个bin可以说就是转换后的特征),对比预排序的exact greedy算法来说(用int_32
      来存储索引+ 用float_32
      保存特征值),可以节省的空间。
    • 计算效率提高,预排序的Exact greedy对每个特征都需要遍历一遍数据,并计算增益。而直方图算法在建立完直方图后,只需要对每个特征遍历直方图即可。
    • LGB还可以使用直方图做差加速,一个节点的直方图可以通过父节点的直方图减去兄弟节点的直方图得到,从而加速计算
  • 支持离散变量:无法直接输入类别型变量,因此需要事先对类别型变量进行编码(例如独热编码),而LightGBM可以直接处理类别型变量。

  • 缓存命中率:XGB使用Block结构的一个缺点是取梯度的时候,是通过索引来获取的,而这些梯度的获取顺序是按照特征的大小顺序的,这将导致非连续的内存访问,可能使得CPU cache缓存命中率低,从而影响算法效率。而LGB是基于直方图分裂特征的,梯度信息都存储在一个个bin中,所以访问梯度是连续的,缓存命中率高。

  • 并行策略:LightGBM 与 XGboost 的并行策略不同:

    • 特征并行 :LGB特征并行的前提是每个worker留有一份完整的数据集,但是每个worker仅在特征子集上进行最佳切分点的寻找;worker之间需要相互通信,通过比对损失来确定最佳切分点;然后将这个最佳切分点的位置进行全局广播,每个worker进行切分即可。XGB的特征并行与LGB的最大不同在于XGB每个worker节点中仅有部分的列数据,也就是垂直切分,每个worker寻找局部最佳切分点,worker之间相互通信,然后在具有最佳切分点的worker上进行节点分裂,再由这个节点广播一下被切分到左右节点的样本索引号,其他worker才能开始分裂。二者的区别就导致了LGB中worker间通信成本明显降低,只需通信一个特征分裂点即可,而XGB中要广播样本索引。
    • 数据并行 :当数据量很大,特征相对较少时,可采用数据并行策略。LGB先对数据水平切分,每个worker上的数据先建立起局部的直方图,然后合并成全局的直方图,采用直方图相减的方式,先计算样本量少的节点的样本索引,然后直接相减得到另一子节点的样本索引,这个直方图算法使得worker间的通信成本降低一倍,因为只用通信以此样本量少的节点。XGB中的数据并行也是水平切分,然后单个worker建立局部直方图,再合并为全局,不同在于根据全局直方图进行各个worker上的节点分裂时会单独计算子节点的样本索引,因此效率贼慢,每个worker间的通信量也就变得很大。
    • 投票并行(LGB):当数据量和维度都很大时,选用投票并行,该方法是数据并行的一个改进。数据并行中的合并直方图的代价相对较大,尤其是当特征维度很大时。大致思想是:每个worker首先会找到本地的一些优秀的特征,然后进行全局投票,根据投票结果,选择top的特征进行直方图的合并,再寻求全局的最优分割点。

XGBoost的参数调优

参数名称作用
n_estimator提升迭代次数,及基模型个数,也就是树的棵树
eta和 GBM 中的 learning rate 参数类似。通过减少每一步的权重,可以提高模型的鲁棒性。
gamma在节点分裂时,只有分裂后损失函数的值下降了,才会分裂这个节点。Gamma 指定了节点分裂所需的最小损失函数下降值。这个参数的值越大,算法越保守。
max_depth和 GBM 中的参数相同,这个值为树的最大深度。这个值也是用来避免过拟合的。max depth 越大,模型会学到更具体更局部的样本。需要使用 CV 函数来进行调优。
min_child_weight决定最小叶子节点样本权重和,和 GBM 的 min child leaf 参数类似,但不完全一样。XGBoost 的这个参数是最小样本权重的和,而 GBM 参数是最小样本总数。这个参数用于避免过拟合。当它的值较大时,可以避免模型学习到局部的特殊样本。但是如果这个值过高,会导致欠拟合。
max_delta_step这参数限制毎棵树权重改变的最大步长,如果这个参数的值为 0,那就意味着没有约束。如果它被赋予了某个正值,那么它会让这个算法更加保守。通常,这个参数不需要设置。但是当各类别的样本十分不平衡时,它对逻辑回归是很有帮助的。这个参数一般用不到,但是你可以挖掘出来它更多的用处。
subsample和 GBM 中的 subsample 参数一模一样。这个参数控制对于每棵树,随机采样的比例。减小这个参数的值,算法会更加保守,避免过拟合。但是,如果这个值设置得过小,它可能会导致欠拟合。
colsample_bytree和 GBM 里面的 max features 参数类似。用来控制每棵随机采样的列数的占比(每一列是一个特征)。
colsample_bylevel用来控制树的每一级的每一次分裂,对列数的采样的占比。subsample 参数和 colsample bytree 参数可以起到相同的作用。但是如果感兴趣,可以挖掘这个参数更多的用处。
lambda权重的 L2 正则化项。(和 Ridge regression 类似)。这个参数是用来控制 XGBoost 的正则化部分的。虽然大部分数据科学家很少用到这个参数,但是这个参数在减少过拟合上还是可以挖掘出更多用处的。
alpha权重的 L1 正则化项。(和 Lasso regression类似)。可以应用在很高维度的情况下,使得算法的速度更快。

参考文献

[1] 统计学习方法

[2] 机器学习

[3] 百面机器学习

[4] 深度学习500问

[5] 概率论与数理统计

[6] 深入理解XGBoost

[7] https://zhuanlan.zhihu.com/p/87885678

[8] https://cloud.tencent.com/developer/article/1500914


文章转载自高級白,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论