![9469c895fd443b6f00414e5c9c764d3b.png](https://i-blog.csdnimg.cn/blog_migrate/6ac81a49a83f5c95b49e4b87ba011944.jpeg)
本文是对华盛顿大学陈天奇在SIGKDD 2016 大会上发表的论文《XGBoost: A Scalable Tree Boosting System》学习后的总结。包含一下内容:
- xgboost的定义
- xgboost的模型
- 模型的学习
- 处理技巧
什么是XGBoost?
XGBoost是Exterme Gradient Boosting(极限梯度提升)的缩写,它是基于决策树的集成机器学习算法,它以梯度提升(Gradient Boost)为框架。XGBoost是由由GBDT发展而来,同样是利用加法模型与前向分步算法实现学习的优化过程,但与GBDT是有区别的。主要区别包括以下几点:
- 目标函数:XGBoost的损失函数添加了正则化项,使用正则用以控制模型的复杂度,正则项里包含了树的叶子节点个数、每个叶子节点权重(叶结点的socre值)的平方和。
- 优化方法:GBDT在优化时只使用了一阶导数信息,XGBoost在优化时使用了一、二介导数信息。
- 缺失值处理:XBGoost对缺失值进行了处理,通过学习模型自动选择最优的缺失值默认切分方向。
- 防止过拟合: XGBoost除了增加了正则项来防止过拟合,还支持行列采样的方式来防止过拟合。
- 结果:它可以在最短时间内用更少的计算资源得到更好的结果。
XGBoost被大量运用于竞赛中,比如Kaggle竞赛,在Kaggle2015年公布的29个获胜者中有17个使用了XGBoost,同样在KDDCup2015的竞赛中XGBoost也被大量使用。
XGBoost的基学习器
XGBoost的可以使用Regression Tree(CART)作为基学习器,也可以使用线性分类器作为基学习器。以CART作为基学习器时,其决策规则和决策树是一样的,但CART的每一个叶节点具有一个权重,也就是叶节点的得分或者说是叶节点的预测值。CART的示例如下图:
![25e9f7c2fcb92bc9e20e521f907db85b.png](https://i-blog.csdnimg.cn/blog_migrate/b5ee3c8a08677047917fd559c5987f19.png)
图中为两颗回归树(左右两个),其中树下方的输出值即为叶节点的权重(得分),当输出一个样本进行预测时,根据每个内部节点的决策条件进行划分节点,最终被划分到的叶节点的权重即为该样本的预测输出值。
XGBoost的模型
XGBoost模型的定义为:给定一个包含n个样本m个特征的数据集,
其中,
模型的学习
通常情况下,怎样去学习一个模型?
- 定义目标函数,即损失函数以及正则项。
- 优化目标函数。
按照这个套路,第一步定义XGBoost的目标函数。
其中
公式(2)右边第一部分是度量预测值与真实值之间的损失函数,第二部分表示对模型复杂度的惩罚项(正则项),在惩罚项中
第二步,优化目标函数。在通常的模型中针对这类目标函数可以使用梯度下降的方式进行优化,但注意到
使用前向分步算法优化目标函数。设
公式(3)表示样本i在t次迭代后的预测值=样本i在前t-1次迭代后的预测值+当前第t颗树预测值。则目标函数可以表示为:
式(4)表示贪心地添加使得模型提升最大的
式5中
定义
注意式7中
则我们的目标函数变成了公式8.可以看出目标函数只依赖于数据点的一阶和二阶导数。其中
接下来针对公式8进行细分。首先定义集合
对式9进行求导:
将(式10)带入(式9)中得到(式11):
令
到目前我们得到了(式12),可以做为得分值评价一颗树的好坏,那么评价一颗树的好坏有什么用呢?可以用于对的剪枝操作(防止过拟合),和决策树中的剪枝是一样的,给定一个损失函数,判断剪枝后,根据损失函数是否减小来决定是否执行剪枝,只是XGBoost是运用式12来作为损失函数判断的标准。注意到评价一颗树的还好的前提是我们能得到一颗树,上式也是基于给定一个树的前提下推导而来的,那么这颗树怎么来得到呢?
树的生成
在决策树的生成中,我们用ID3、C4.5、Gini指数等指标去选择最优分裂特征、切分点(CART时),XGBoost同样定义了特征选择和切分点选择的指标:
XGBoost中使用过(式13)判断切分增益,Gain值越大,说明分裂后能使目标函数减少越多,就越好。其中
分裂查找算法
关于最优特征以及最优切分点的选取XGBoost提供了两个算法。
- Basic Exact Greedy Algorithm (精确贪心算法)
- Approximate Algorithm(近似算法)
精确贪心算法类似于CART中最优特征与切分点的查找,通过遍历每个特征下的每个可能的切分点取值,计算切分后的增益,选择增益最大的特征及切分点。具体算法流程如下
![40e4281c864f8101e985502d0184c93d.png](https://i-blog.csdnimg.cn/blog_migrate/12188b374d36a19da4cdeebd2c77df43.jpeg)
因为精确贪心算法需要遍历所有特征和取值,当数据量非常大的时候,无法将所有数据同时加载进内存时,精确贪心算法会非常耗时,XGBoost的作者引进了近似算法。近似算法对特征值进行了近似处理,即根据每个特征k的特征值分布,确定出候选切分点
![157ca6bd7d40f35cb53142269ccf33fd.png](https://i-blog.csdnimg.cn/blog_migrate/66f1dab8919154261cc1ff79e7dd4f90.png)
划分好候选切分点之后,按照精确贪心算法描述的算法步骤进行选择最优切分特征与最优切分点,不同的是切分点被上述候选切分点所代替,但原理和操作过程是一样的。
在近似算法的伪代码图中,作者提到可以按照global的方式提出候选切分点,也可以按照local的方式提出候选切分点。
![5094dc4473ec5ecd6877f4fc31b8e08e.png](https://i-blog.csdnimg.cn/blog_migrate/fbc124ed3ed1743846e904fcc7370993.jpeg)
什么是global方式?什么是local方式?简单的说就是什么时候提取候选切分点,即在哪一个步骤进行候选切分点的提取。global表示在生成树之前进行候选切分点的提取,即开始之前为整颗树做一次提取即可,在每次的节点划分时都使用已经提取好的候选切分点。而local则是在每次节点划分时才进行候选切分点的提取。那么区别是什么呢?
- global方式进行候选切分点提取的次数少。因为只是在初始化的阶段进行一次即可,以后的节点切分均使用同一个,而local方式是在每次节点切分时才进行,需要很多次的提取。
- global方式需要更多的候选点,即对候选点提取数量比local更多,因为没有像local方式一样每次节点划分时,对当前节点的样本进行细化,local方式更适合树深度较大的情况。
![28c340aac4da4d3e91dfd3e37f500b9c.png](https://i-blog.csdnimg.cn/blog_migrate/a342892e86ba84411a7f508d18233660.jpeg)
上图是作者在Higgs boson 数据集上对两种方式的测试,可以看出local需要更少的候选切分点,当global方式有足够多的候选点时正确率与local相当。
下面对近似算法举个栗子做为说明。
![cb42f01b9182d606fed55a017ad851e0.png](https://i-blog.csdnimg.cn/blog_migrate/08a180d9af8187a61bb47129ea046821.jpeg)
图示中特征值被切分成三个候选切分点,位于0~
注意到栗子在对样本进行划分时,考虑的是样本的个数,即每个桶中样本个数相同为出发点来划分的,如果样本有权重呢?直观上的想法是,那就考虑权重而不是个数呗。那么每个样本应该赋予什么样的权重?又怎样去处理这个权重?
加权分位数略图(Weighted Quantile Sketch)
为了处理带权重的候选切分点的选取,作者提出了Weighted Quantile Sketch算法。加权分位数略图算法提出一种数据结构,这种数据结构支持merge和prune操作。作者在论文中给出了该算法的详细描述和证明链接(ps:已经失效),这里不做详细介绍。可以参考链接加权分位数略图定义及证明说明。简单介绍加权分位数略图侯选点的选取方式。
设数据集
(式14)表示数据集中第k个特征值小于z的样本所在比例(公式看起来貌似有点奇怪,简单来说就是特征值小于z的样本的权重和,占所有样本权重总和的百分比)。我们的目标是找到一个候选切分点(也就是说怎么去划分候选点的问题),可以根据下式进行侯选点的选取
简单的说(式15)表示落在两个相邻的候选切分点之间样本占比小于某个值
由(式14)看的样本是以二阶导数作为加权考虑占比的,那么问题来了,为什么使用二阶导数作为加权呢?
对目标函数(式8)进行改写,过程如下:
(式16)可以看成权重 为
其他优化方法
- 稀疏值处理( Sparsity-aware Split Finding)。实际工程中一般会出现输入值稀疏的情况。比如数据的缺失、one-hot编码都会造成输入数据稀疏。论文中作者提出了关于稀疏值的处理,思路是:对于缺失数据让模型自动学习默认的划分方向。算法具体的方法如下:
![68f1f47df4299c0c81e6e7e19e4ac0f9.png](https://i-blog.csdnimg.cn/blog_migrate/c8a3182021a57e2fe2436add7a904a7c.jpeg)
从算法中可以看出,作者采用的是在每次的切分中,让缺失值分别被切分到左节点以及右节点,通过计算得分值比较两种切分方法哪一个更优,则会对每个特征的缺失值都会学习到一个最优的默认切分方向。乍一看这个算法会多出相当于一倍的计算量,但其实不是的。因为在算法的迭代中只考虑了非缺失值数据的遍历,缺失值数据直接被分配到左右节点,所需要遍历的样本量大大减小。
![1a8c268e0463cc44c374c19afae83c7c.png](https://i-blog.csdnimg.cn/blog_migrate/d5347226ca96e1e8e4e1de6135a1e1b7.png)
作者通过在Allstate-10K数据集上进行了实验,从结果可以看到稀疏算法比普通算法在处理数据上快了超过50倍。
- 分块并行(Column Block for Parallel Learning)。在树生成过程中,需要花费大量的时间在特征选择与切分点选择上,并且这部分时间中大部分又花费在了对特征值得排序上。那么怎么样减小这个排序时间开销呢?作者提出通过按特征进行分块并排序,在块里面保存排序后的特征值及对应样本的引用,以便于获取样本的一阶、二阶导数值。具体方式如图:
![f7a2f60d83efa6201bb6cebb05042260.png](https://i-blog.csdnimg.cn/blog_migrate/d26dc13424a39fa2b83abb378c4d92aa.jpeg)
通过顺序访问排序后的块遍历样本特征的特征值,方便进行切分点的查找。此外分块存储后多个特征之间互不干涉,可以使用多线程同时对不同的特征进行切分点查找,即特征的并行化处理。注意到,在顺序访问特征值时,访问的是一块连续的内存空间,但通过特征值持有的索引(样本索引)访问样本获取一阶、二阶导数时,这个访问操作访问的内存空间并不连续,这样可能造成cpu缓存命中率低,影响算法效率。那么怎么解决这个问题呢?缓存访问 Cache-aware Access 。
- 缓存访问(Cache-aware Access)。为了减小非连续内存的访问带来缓存命中率低问题,作者提出了缓存访问优化机制。解决思路是:既然是非连续内存访问带来问题,那么去掉非连续内存访问就可以解决。那么怎么能去掉非连续内存空间的访问呢?转非连续为连续----缓存预取。即提起将要访问的非连续内存空间中的梯度统计信息(一阶、二阶导数),放置到连续的内存空间中。具体的操作上就是为每个线程在内存空间中分配一个连续的buffer缓存区,将需要的梯度统计信息存放在缓冲区中。这种方式对数据量大的时候很有用,因为大数据量时,不能把所有样本都加入到内存中,因此可以动态的将相关信息加入到内存中。
![454dd88301fe50e6537b0c3eb6bdd1ff.png](https://i-blog.csdnimg.cn/blog_migrate/1f263eaeb6f35004bdbbb986413c17bc.jpeg)
上图给出了在Higgs数据集上使用缓存访问和不使用缓存访问的式样对比。可以发现在数据量大的时候,基于精确的贪心算法使用缓存预取得处理速度几乎是普通情况下的两倍。那么对于block块应该选择多大才合理呢?作者通过实验证明选择每个块存放
![972f8a5fd9532593f95af158c9e3affb.png](https://i-blog.csdnimg.cn/blog_migrate/d9dda396d81ace211ea6808d073013c5.jpeg)
- "核外"块计算(Blocks for Out-of-core Computation)。当数据量非常大的是时候我们不能把所有数据都加载内存中,因为装不下。那么就必须的将一部分需要加载进内存的数据先存放在硬盘中,当需要时在加载进内存。这样操作具有很明显的瓶颈,即硬盘的IO操作速度远远低于内存的处理速度,那么肯定会存在大量等待硬盘IO操作的情况。针对这个问题作者提出了“核外”计算的优化方法。具体操作为,将数据集分成多个块存放在硬盘中,使用一个独立的线程专门从硬盘读取数据,加载到内存中,这样算法在内存中处理数据就可以和从硬盘读取数据同时进行。为了加载这个操作过程,作者提出了两种方法。
- 块压缩(Block Compression)。论文使用的是按列进行压缩,读取的时候用另外的线程解压。对于行索引,只保存第一个索引值,然后用16位的整数保存与该block第一个索引的差值。作者通过测试在block设置为
个样本大小时,压缩比率几乎达到26%
29%(貌似没说使用的是什么压缩方法.......)。
- 块分区(Block Sharding )。块分区是将特征block分区存放在不同的硬盘上,以此来增加硬盘IO的吞吐量。
- 防止过拟合。从XGBoost的模型上可以看到,为了防止过拟合加入了两项惩罚项
、
,除此之外XGBoost还有另外两个防止过拟合的方法。
,其中
就是学习率,通常取0.1。
参考资料
- 陈天奇论文原文《XGBoost: A Scalable Tree Boosting System》
- 李航博士 书籍《统计学习方法》
- 加权分位数 XGBoost解读(2)--近似分割算法
- https://zhuanlan.zhihu.com/p/62670784
https://www.cnblogs.com/pinard/p/6053344.html
- https://www.hrwhisper.me/machine-learning-xgboost/