欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

xgboost 正则项_深入理解Boosting算法(4)-XGBoost

发布时间:2025/4/17 编程问答 55 豆豆
生活随笔 收集整理的这篇文章主要介绍了 xgboost 正则项_深入理解Boosting算法(4)-XGBoost 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

导语:记得第一次使用XGBoost的时候还是2016年,那会的XGBoost工具还不完善,深度模型还没有怎么样火起来,大家用的最多的还是Sklearn里面的GBDT,或者R语言的GBM。后来在工作中也把这工具用在搜索点击率预估上,然后就时不时收到预测超时等性能问题。后来,又出现了LightGBM, 跑树模型基本上也就基本上告别了这个工具,但是很多人第一个集成树模型应该是XGBoost,它使得GBDT模型在大数据时代大规模的普及使用起到了重要作用,本文简单纪念一下它。

本文大纲如下:

  • GBDT模型训练复杂度分析
  • 正则化的损失函数(总)
  • 第次迭代的损失函数(分)
  • 从损失函数到树的评分函数
  • 特征最优切分点算法
  • 稀疏特征处理方式

GBDT模型训练复杂度分析

对于神经网络模型,可以通过利用mini-batch的形式将数据小批量地输入进行模型训练,而对于传统的机器学习模型,如GBDT模型在一次训练过程中需要使用整个训练数据集,而且在构建树的过程中还需要对整个数据集进行多轮的遍历,因此从近几年才发表的XGBoost, LightGBM的引用论文中就可以发现,从1998年开始,就有很多论文在开始研究如何在大规模数据下高效训练模型的方法。 在GBDT训练过程中,最耗时的部分在于回归树的构建过程中最优分裂特征,分裂阈值的查找过程。目前主要有以下几大类方式:

  • 特征预排序: 对特征的取值进行预先排序,枚举出可能的分裂点,如使用贪心算法进行枚举(并不高效,但是可以找到最优分裂点),或者利用特征的分位点进行近似的近似算法等(XGBoost中使用的方式,本文将进行介绍);
  • 基于特征直方图:对于连续型的特征将其进行离散化,得到特征的离散取值(bin),用于构建关于特征信息的(一阶导数,二阶导数)直方图;使用直方图的优势在于可以不需要对特征进行排序,毕竟排序算法很耗时的, 在LightGBM算法中将进行详细介绍该方法。

​关于计算复杂度的简单计算,如果利用

表示样本个数, 表示特征个数,特征被切分的bin的个数为, 那么在对于特征预排序算法其复杂度为 , 查找最佳分裂点的复杂度为, 而对于基于特征直方图的方式,在构建直方图时的复杂度为,查找最佳分裂点的复杂度为, 很显然 远远小于 。

有了上述的复杂度的基本认识,就知道从哪些方向进行优化,如如何在训练过程中减少训练样本,以及减少特征个数等,在LightGBM里面将详细介绍(hmm, 咋一直跑题)。为了保证更好的理解以及以后方便查阅,本文搞了很多公式,但是机器学习里面的公式都是加加减减,多看几遍就真香了,本文包含的内容:

  • XGBoost在损失函数上的设计,从整个模型的损失计算到第次迭代过程的推导计算;
  • XGBoost最优特征分裂方式,增益计算
  • XGBoost针对稀疏特征的学习方式

本文不包含系统设计,虽然大规模机器学习系统是一个很有魅力的方向,对其中涉及到的分布式算法,通信等复杂问题需要补上N篇论文才能说的清楚,有兴趣的同学可以参考这个链接进行学习(https://github.com/mercari/ml-system-design-pattern)。

正则化的损失函数(总)

XGBoost是Boosting算法家族中的一员,因此其形式上也是有

个基模型组成的一个加法模型,对于给定的个样本个特征的的数据集, 其预测过程如下公式所示:

其中的假设空间中的所有CART回归树模型

可以表示为。从公式表述中可以知道,单颗树有两部分信息组成:
  • 树的结构(树的深度,叶子节点个数)以及特征分裂阈值,
  • 叶子节点的权重(特别说明GBDT,XGBoost等使用的回归树,因为叶子节点存在权重信息)

单颗树的结构由符号

表示,它按照树的结构,将输入离散化映射到个叶子节点之中某一个上,因此的输出是叶子节点的索引编号值,个叶子节点的权重数组用表示。

有了上述定义符号表示,就要介绍XGBoost中使用的正则化的损失函数了,相比GBDT模型,引入了正则项用于防止过拟合,其形式如下所示(其实很早就有了类似的思想):

从损失函数上不难理解,在给定

个树模型之后,只需要利用可微分的损失函数计算样本的预测值与真实的差异之和, 再利用颗树计算树的正则项部分,正则项主要有两部分构成:
  • 叶子节点权重的范数,目的是使得权重值更平滑,连续;
  • 叶子节点的个数,在XGBoost中树的生长方式是Level-wise的方式,每一层都需要同时进行分裂,有可能导致不必要的特征参与分裂,如下图对比所示(后续的文章中会加入LigthGBM的原理,会有更详细的对比)

图1:两种决策树生长方式

次迭代的损失函数(分)

假设在完成第

次迭代后,即前颗树的结构以及参数都是已知的了,根据前向分步加法,下一次迭代时即次,模型对于样本的预测值为:

此时,将损失函数

可以展开从以下形式,

完成了在第

颗树学习时的损失函数推导; 细节信息如下:
  • 上述公式的第一步中,跟的区别是,正则项只有前颗树,因为此时才迭代到第颗树,后面的树还需要在学习;
  • 从第二步到第三步,发现已经将正则项,从累加变成了第颗树的正则项,以及常数,因此在此时前面第颗树是已知的,所以其正则项之后也是已知的,用表示即可,方便后续推导;
  • 从损失函数到树的评分函数

    众所周知,XGBoost利用二阶泰勒展开来近似表示第

    次迭代的损失函数,回忆一下泰勒公式的二阶展开公式, 把函数在点处进行泰勒二阶展开,可以得到如下形式:

    将上述公式与XGBoost的在第

    次迭代的损失函数进行对比,一一对应起来,如下表所示(可以忽略损失函数中的项,毕竟它一直是个常数,真实的标签值)

    因此,可以定义损失函数

    在 出的一阶导数为, 二阶导数为, (如果你有疑问为啥是对求导数,参考泰勒公式,其对应的是),因此,我们可以将损失函数进一步展开,

    上述公式中,常数

    ,以及也为常数,同时都是已知数,公式中只有为未知数,表示在样本划分过程中,落在相同叶子节点的样本, 而目标是最小化, 根据一元函数的求极值得过程,只要让其导数为0即可,

    即可求出,最优值为:

    带回损失函数(6)中,得到如下损失函数的显示表达式(将常数部分去除),

    有了该公式,我们可以用于评价生成的树的得分,得分越小,说明树的结构越好。到目前为止,我们知道了叶子节点的最优取值(解析解),以及最小的损失函数取值,然而,仔细回顾一下,我们还没有确定树的结构,公式也没有告诉我们该怎么得到最优的树的结构,下面部分将解答该问题。

    特征最优切分点算法

    树长成啥样还不知道,我们只是有了评价树结构好不好的公式。最近简单的办法,就是暴力枚举所有可能的结构,但这肯定是不能被接受的。那就要贪心法上场了,每次尝试分裂一个节点,计算分裂前后的增益,选择增益最大的那次分裂即可,依次循环下去。注意,贪心法不是XGBoost才有,从最经典的ID3,C4.5,CART树开始就有了,只不过,其使用的评估指标不一样而已;

    • ID3, 信息增益
    • C4.5, 信息增益比
    • CART, Gini系数,平方损失
    • XGboost, 由一阶导数,二阶导数构成的打分函数

    如果对这部分知识点有点疑问,可以参考此前的文章中关于回归树的生长分裂过程。在XGboost中具体实现中,有两种特征切分点算法, 贪心法和近似算法。

    贪心算法

    对一个特征最佳特征分裂阈值的选择,是用分裂之前的评估指标值,减去分裂后的值,找到增益最大的,即完成本次分裂搜索,对于XGboost而言,一次分裂之后,会将该节点上的样本划分到两个不相交的样本空间, 用

    , 分别表示分布在右子树的样本,左子树的样本, 表示该节点上总的样本个数,因此,我们可以用符号分别表示左,右节点两侧的一阶导数之和,二阶导数之和

    每次特征分裂的增益计算: 分裂前:

    分裂后:

    所以,分裂之后的增益为:

    贪心算法的流程如下图所示:

    贪心算法对每个特征都做线性搜索,根据样本特征的排序值,逐步将更多的样本加入到左子树,根据增益函数得到的最大的点,即为最优的分割点。 步骤:

  • 从当前节点出发,依次为个特征,将属于该节点上的样本按照特征的取值进行升序排列,依次枚举出分裂点,根据增益函数得到该分裂点的增益值,保存中间结果;
  • 选择增益最大的分裂点,作为当前节点的最佳分裂点
  • 回到第一步,并行地为其他的节点选择最佳的分裂点(layer-wise树生长方式容易并行);
  • 近似算法

    由于贪心算法需要对每个特征进行排序,因此成为计算速度的瓶颈。近似算法就是根据特征的分布提前计算得到

    个分位点,, 有了这些分位点,就可以将样本映射到对应的区间内,然后再次聚合区间内的信息,得到所有区间的最佳分裂点。很直观, 这种方式免去了贪心算法需要多次对每个特征进行排序的操作。

    从算法流程图中,可以看到,第一步是提前计算,或者每次分裂的时候计算各个特征的分位点,而在第二个for循环中,是分别为每个特征,统计得到落在各个区间的样本的一阶导数和二阶导数汇总起来,得到对应区间的

    。对比一下贪心算法中的第二个for循环,是在样本级别统计分在当前节点的左,右子树的一阶导数和二阶导之和;有了这样的比较,相信你很快就感受到了这样的操作对于提升速度无疑是起到了帮助。 下图,是从网上找到一张示意图, 用于直观的描述近似算法是如何找到最佳分裂点的过程;

  • 根据特征的分布,找到3分位点,将当前节点下的样本分成了3份;
  • 根据损失函数,得到各个样本在某次迭代过程中的一阶导数和二阶导数;
  • 将各个区间内的样本的一阶导数和二阶导数进行求和汇总,分别得到, ,
  • 利用与贪心算法相同的方法,寻找区间最佳的分裂点,由于只有3个区间,因此只有2两组合方式,1|2,3, 或者 1, 2 | 3
  • 简单总结贪心算法与近似算法

  • 贪心算法需要为每个特征进行线性搜索,枚举出所有的分裂点,而近似算法只需要统计分位点信息,枚举分位的取值即可;
  • 贪心算法在样本级别寻找特征的最佳分裂点,而近似算法在区间级别;查找的次数大大减少
  • 论文中也对两种的算法的精度做了比较,这里先不赘述

    补充:常用损失函数的一阶导数,二阶导数推导

    以下对平方损失函数,对数损失函数进行求导:

    • 平方损失函数
    即时
    • 损失函数为对数损失
    令 ,表示前颗树对于样本的预测值,由对数损失函数的定义可知,

    对数损失函数的一阶导数,以及二阶导数的的推导,由于对数损失函数是有两部分组成,因此在二分类时(

    ),可以分情况进行求导:当时,
    当时,

    而对于二阶导数,只需要在对

    求导即可,从上述结果可以看出,不论 或者,求导的结果是一样的,再次回忆一下对sigmoid函数的求导,即可得到我们的结果:

    稀疏特征处理方式

    所谓的稀疏特征就是特征

    中存在大量的缺失值,或者取值为0的样本占大多数(可能来源于数据本身,或者对类别型特征进行onehot编码之后出现大量取值为0的特征),数据中如果存在这样的模式,对于训练来讲可能存在着大量的信息,因此利用算法捕捉这样的模式对于提升模型的区分能力有着重要的帮助。 在XGBoost中,在每个节点分裂时,额外增加了一个针对缺失特征的分裂方向的判断,即对于存在取值缺失的样本,应该被分裂到左子树,还是右子树的判断,如下图所示得到的最终结果:

    至于各个特征应该往哪个方向偏,也是利用数据进行训练得到的,选择增益最大的方向, 训练过程如下图所示(以特征

    为例),

  • 对于特征, 选择取值为非空的样本,构成样本集合, 利用近似算法进行特征最优阈值查找;
  • 假定将缺失特征样本全部分裂到右子树,计算得到该特征的增益值;
  • 假定将缺失特征样本全部分裂到左子树,计算得到该特征的增益值;
  • 根据特征增益值,选择取值最大的方向作为该特征的默认最大值
  • 在训练的过程中,可以将稀疏特征处理方式和非稀疏特征整合在一起。

    本系列其他文章:

    质数:深入理解Boosting算法(1)-基础知识回顾​zhuanlan.zhihu.com质数:深入理解Boosting算法(2)-AdaBoost​zhuanlan.zhihu.com质数:深入理解Boosting算法(3)-GBDT​zhuanlan.zhihu.com

    总结

    以上是生活随笔为你收集整理的xgboost 正则项_深入理解Boosting算法(4)-XGBoost的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。