这篇论文是Tianqi Chen2016年在arXiv上发表的。
陈天奇是机器学习领域著名青年学者,本科就读于上海交大ACM班,华盛顿大学计算机系博士,开发了XGBoost、MXNet、TVM等知名机器学习工具。
(因为原文内容较多,很多针对系统而非算法本身的内容,故没有逐句翻译,仅放上了每部分的核心内容,如想阅读全文翻译可以阅读参考网址。)
Abstract
提升树是一种高效且被广泛使用的机器学习方法。在本文中,我们描述了一个可扩展的端对端的提升树系统,叫做XGBoost,该系统被数据科学家广泛使用,在许多机器学习任务中取得了显著效果。针对稀疏数据,我们提出一种新的稀疏数据感知算法。我们也提出了分布式加权分位数略图(weighted quantile sketch)来近似实现树模型的学习。更重要的是,我们陈述了缓存访问模式、数据压缩和分片的见解以构建可扩展的提升树系统。通过结合这些知识,XGBoost可以使用比现有系统少得多的资源就能够扩展数十亿的实例。
Keywords
Large-scale Machine Learning 大规模机器学习
1. INTRODUCTION
机器学习在许多领域变得重要,成功应用的两个因素:有效的模型(捕捉复杂数据相关性)和可扩展的系统(处理大数据)。
梯度提升树算法在机器学习领域中闪闪发光,其变种LambdaMART算法也在排序问题中有最先进的表现。
XGBoost在机器学习大赛中效果显著,在网页文本分类、顾客行为预测、情感挖掘、广告点击率预测、恶意软件分类、物品分类、风险评估、大规模在线课程退学率预测等很广泛的问题里都能获得很好的结果。
XGBoost成功背后的最重要因素是其在所有情况下的可扩展性。该系统在单台机器上的运行速度比现有流行解决方案快十倍以上,并可在分布式或内存有限的环境中扩展到数十亿个示例。XGBoost的可扩展性是由于几个重要的系统和算法优化。这些创新包括:一种新颖的树学习算法,用于处理稀疏数据;理论上合理的加权分位数略图程序能够在近似树学习中处理实例权重。并行和分布式计算使得学习速度更快,从而加快了模型的探索。更重要的是,XGBoost利用外核计算,使数据科学家能够在桌面上处理数百万个示例。最后,将这些技术结合起来,使用最少的集群资源扩展到更大的数据的端到端系统更为令人兴奋。本文主要贡献如下:
- 我们设计和构建高度可扩展的端到端提升树系统。
- 我们提出了一个理论上合理的加权分位数略图。
- 我们引入了一种新颖的稀疏感知算法用于并行树学习。
- 我们提出了一个有效的用于树模型的核外学习(out-of-core tree learning)的缓存感知块结构(cache-aware block structure)。
除了这些主要的贡献之外,我们还提出了一个改进正则化学习的方法。
2. TREE BOOSTING IN A NUTSHELL
我们在这一节中介绍梯度提升树算法。
(这一章是算法概述。近乎全文翻译)
2.1 Regularized Learning Objective
对于给定的数据集有n个样本m个特征
,树集成算法使用个数为K的加法模型(如图1)来预测输出。
其中
是回归树(也叫做CART)的空间。
表示将样本映射到叶节点的树的结构。
是每棵树叶子的数量。每个
对应了独立的树结构
和叶权值
。与决策树不同,每棵回归树的每个叶子上包含连续的连续值打分,我们用
表示第
个叶子的打分。对于一个给定的例子,我们将使用树中的决策规则(由
给出)将其分类到叶子节点中,并通过对相应叶子中的分数求和来计算最终预测(由
给出)。为了学习模型中使用的函数集合,我们最小化下面的正则化了的项。
这里
是一个可微的凸损失函数,它表示预测
和目标
之间的差值。第二项
是惩罚项,惩罚模型的复杂度(即回归树模型)。附加正则化项会有助于使最终学习到的权值更加平滑,避免过拟合。直观地说,带有正则化的目标函数倾向于选择简单的预测模型。类似的正则化技术已被用于正则化贪心森林算法(RGF)模型中。我们的目标函数和相应的学习算法比RGF更简单,更容易实现并行化。当正则化参数被设置为零时,目标函数退化为传统的梯度提升树。
2.2 Gradient Tree Boosting
公式(2)中的树集成模型中包含函数作为参数的情况,不能使用欧氏空间中的传统优化方法来优化。替代的方法是模型以累加的方式训练。形式上,
是第
次迭代中第
个实例的预测,我们把
加到最小化目标中。
这意味着我们根据公式(2)贪婪地将
加到了目标函数中,这对我们模型提升最大(因为是沿梯度下降的)。一般情况下,二阶近似(即泰勒二阶展开近似)可以用于快速优化目标函数。(因为有二阶信息,所以优化起来比一阶速度快。例如,牛顿法就比传统的梯度下降快)
其中
,分别为损失函数一阶和二阶的梯度值。在第
步迭代中,我们可以去掉常数项以简化目标函数。
(意思是针对第
步来说,之前的
步已成定局,也就是常数项了,所以针对这一步优化来说可以去掉)
定义
为叶子节点
里的样本,我们可以通过扩展
来重写公式(3):
对于一个固定结构
,我们可以计算叶节点
的最优权
:
并通过下式计算相应的最优值:
公式(6)可以作为一个评估方程去评价一棵树的结构
。这个打分就像评估决策树的杂质分数,不同的是它是为了更广泛的目标函数导出的。图2示出了如何计算这个分数。
通常来说不可能枚举出所有的树结构
,而是用贪心算法,从一个叶子开始分裂,反复给树添加分支。假设
和
是分裂后左右节点中包含的样本集合。使
,通过下式分裂后会使损失函数降低。
这个公式用于评价候选分裂节点的好坏。
2.3 Shrinking and Column Subsampling
除了2.1提到的用正则项来防止过拟合,这里还用到两种别的技术:
- 收缩(shrinking)。在每一次提升树训练迭代后,在前面乘一个因子ηηη来收缩其权重(也就是我们说的学习率,或者叫步长)。与随机优化中的学习率类似,收缩减少了每棵树的影响,并为将来的树模型留出了改进模型的空间。
- 列(特征)子采样(Column Subsampling)。这个技术用于随机森林中,在商业软件TreeNet4中实现,用于梯度增强,但未在现有的开源包中实现。根据用户反馈,使用列子采样可以比传统的行子采样(也支持)更能防止过度采样。列子采样还能加速稍后描述的并行算法。
3. SPLIT FINDING ALGORITHMS
这一章讲分裂点寻找算法。全文核心。
3.1 Basic Exact Greedy Algorithm
传统方法即贪心法,暴力地遍历所有可能的分割点,我们也支持这种做法。详细描述如算法1:
3.2 Approximate Algorithm
当数据不能完全读入内存时和分布式环境中,简单的贪心算法就不会很有效率。为了有效支持这两种环境中的提升树,我们需要一种近似算法。算法具体描述如Alg.2:
总结来说,该算法首先根据特征分布的百分位数提出可能的候选分裂点(具体的准则在3.3中给出)。然后算法将连续特征值映射到候选分割点分割出的箱子(buckets)中。计算出每个箱子中数据的统计量(这里的统计量指的是公式(7)中的g和h),然后根据统计量找到最佳的分割点。
该算法有两种变体(variants),区别为分裂点的准则(proposal)何时给出。全局选择在树构造的初始阶段要求给出所有候选分裂点,并且在树的所有层中使用相同的分裂节点用于分裂。局部选择在分裂后重新给出分裂候选节点。全局方法(The global variant)比局部方法(The local variant)需要更少的步骤。
然而,通常在全局选择中需要更多的候选点,因为在每次分裂后候选节点没有被更新。局部选择在分裂后更新候选节点,并且可能更适合于深度更深的树。
图3给出了基于希格斯玻色子数据集的不同算法的比较。我们发现,局部选择确实需要更少的候选人。当给出足够的候选节点,全局选择可以达到与局部选择一样的准确率。
直接构造梯度统计量的近似直方图也是可行的。也可以使用分箱策略(binning strategies)来代替分位数划分。分位数策略的优点是可分配和可重计算,我们将在下一节中详细说明。从图3中,我们还发现,当设置合理的近似水平,分位数策略可以得到与贪心算法相同的精度。
3.3 Weighted Quantile Sketch
(这一部分不太懂,因此全部翻译)
近似算法中很重要的一步是列出候选的分割点。通常特征的百分位数作为候选分割点的分布会比较均匀。具体来说,设 表示样本的第 个特征的取值和其二阶梯度统计量。我们可以定义一个排序方程
上式表示样本中第
个特征的取值小于
的比例(直译过来确实是这样,不过公式表达的是取值小于z的二阶梯度统计量的比例)。我们的目标是找到候选的分割节点
。
这里
是一个近似因子(别被名字吓倒了,其实就是衡量两者的差距)。直观的说,大概有
个分割点(这应该好理解吧,如果从0-1之间分割,分割点之间差距小于0.2,那么就是大概有5个分割点)。这里每一个数据点用
h_i$能代表权重,我们可以把公式(3)重写为:
这实际上是权值为
,标签为
的加权平方损失。对于大数据集来说,找到满足标准的候选分割点是非常不容易的。当每个实例具有相等的权重时,一个现存的叫分位数草图的算法解决了这个问题。然而,对于加权的数据集没有现存的分位数草图算法。因此,大部分现存的近似算法要么对可能失败的数据的随机子集进行排序,要么使用没有理论保证的启发式算法。
为了解决这个问题,我们引入了一种新的分布式加权分位数草图算法,该算法可以处理加权数据,并且可以从理论上证明。通常的做法是提出一种支持合并和修建操作的数据结构,每个操作都是可以被证明保持一定准确度的。附录中给出了算法的详细描述以及证明。
3.4 Sparsity-aware Split Finding
实际问题中,数据稀疏十分常见。有很多可能的因素造成:
1)数据中存在缺失值;
2)有些统计数值常常为0;
3)特征工程的结果,如独热编码。
所以,让算法意识到数据的稀疏是很重要的。为了做到这一点,我们建议在每个树节点中添加一个默认的方向,如图4所示。当稀疏矩阵x中的值丢失时,实例被分类为默认的方向。
(简单地说,就是把所有缺失值丢到左边,算出一个分数;然后把所有缺失值丢到右边,算出一个分数,取分数高的那种做法。)
伪代码如下:
在每个分支中有两种默认方向。最优的默认方向是从数据中学习出来的。(如上)
XGBoost以统一的方式处理所有稀疏模式。更重要的是,我们的方法利用稀疏性,使得计算的复杂度与输入中的非缺失数据的数量成线性关系。
在数据集Allstate-10K(此数据集在第6部分描述)上的比较发现稀疏感知算法的运行速度比常规版本快50倍。(图5)
4. SYSTEM DESIGN
这一章主要讲系统设计。
提炼一下就是,XGBoost将所有的列数据都预先排了序,以压缩形式分别存到block里,不同的block可以分布式存储,甚至存到硬盘里。在特征选择的时候,可以并行的处理这些列数据,XGBoost就是在这实现的并行化,用多线程来实现加速。同时这里还用cache加了一个底层优化:当数据排序后,索引值是乱序的,可能指向了不同的内存地址,找的时候数据是不连续的,这里加了个缓存,让以后找的时候能找到小批量的连续地址,以实现加速,即,使用预读取(prefetching)将下一块将要读取的数据预先放进内存里面。这里是在每个线程里申请了一个internal buffer来实现的。其实就是多开一个线程,该线程与训练的线程独立并负责数据读取。这个优化在小数据下看不出来,数据越多越明显。
4.1 Column Block for Parallel Learning
树学习中最耗时的部分是数据排序。为了减少排序的成本,我们提出将数据存储在内存单元中,称之为block。每个block中的数据每列根据特征取值排序,并以压缩列(CSC)格式储存。这种输入数据布局只需要在训练前计算一次,可以在后续迭代中重复使用。
在贪婪算法中,我们将整个数据集存储在单个block中,并通过对预排序的数据进行线性扫描来实现分割点搜索。我们集体对所有叶子进行分割查找,这样只需扫描一次block就可以得到所有叶子节点处所有候选分裂节点的统计信息。
当使用近似算法时,使用排序结构,分位数查找步骤在完成排序的列上就变成了线性扫描。这对于在每个分支中频繁更新候选分割点的本地优先算法非常有价值。直方图聚合中的二分搜索也变成了线性时间合并样式算法。
收集每列统计信息这一步骤可以实现并行化,这也给我们提供了一种寻找分割点的并行算法。
( 因此XGB的并行计算的粒度不在树上,而是在特征上,尤其是不同分支节点上(leaf-wise)。 当然这也成为XGB的一个问题所在,需要额外的空间存储pre-sort的数据。而且每次分支后,我们都要找处落在下一个子节点上的样本,并组织好它。后来就有了LightGBM。)
4.2 Cache-aware Access
虽然block结构有助于优化分割点查找的时间复杂度,但是算法需要通过行索引间接提取梯度统计量,因为这些值是按特征的顺序访问的,这是一种非连续的内存访问(意思就是按值排序以后指针就乱了)。 分割点枚举的简单实现在累积和非连续内存提取之间引入了即时读/写依赖性(参见图8)。 当梯度统计信息不适合CPU缓存进而发生缓存未命中时,这会减慢分割点查找的速度。
对于贪心算法,我们可以通过缓存感知预取算法来缓解这个问题。 具体来说,我们在每个线程中分配一个内部缓冲区,获取梯度统计信息并存入,然后以小批量方式执行累积。
对于近似算法,我们通过选择正确的block尺寸来解决问题。我们将block尺寸定义为block中包含的最大样本数,因为这反映了梯度统计量的高速缓存存储成本。 选择过小的block会导致每个线程的工作量很小,并行计算的效率很低。 另一方面,过大的block会导致高速缓存未命中现象,因为梯度统计信息不适合CPU高速缓存。良好的block尺寸平衡了这两个因素。
4.3 Blocks for Out-of-core Computation
除处理器和内存外,利用磁盘空间处理不适合主内存的数据也很重要。
为了实现核外计算,我们将数据分成多个块并将每个块存储在磁盘上。在计算过程中,使用独立的线程将块预取到主存储器缓冲区是非常重要的,因为计算可以因此在磁盘读取的情况下进行。但是,这并不能完全解决问题,因为磁盘读取会占用了大量计算时间。减少开销并增加磁盘IO的吞吐量非常重要。 我们主要使用两种技术来改进核外( Out-of-core)计算:
- Block Compression 块压缩。该块从列方向压缩,并在加载到主存储器时通过独立的线程进行解压。这可以利用解压过程中的一些计算与磁盘读取成本进行交换。我们使用通用的压缩算法来压缩特征值。
以压缩、解压代价换区磁盘I/O。 - Block Sharding 块分片/分区。为每个磁盘分配一个实现预取的线程,并将数据提取到内存缓冲区中。然后,训练线程交替地从每个缓冲区读取数据。当有多个磁盘可用时,这有助于提高磁盘读取的吞吐量。
将数据分片在在多个磁盘上,一个预取线程对应一个磁盘。
5. RELATED WORKS
虽然大多数现有的工作都集中在并行化的算法方面,但我们的工作在两个未经探索的方面得到了成果:核外计算和缓存感知学习。这让我们对联合优化系统和算法的有了深刻的理解,并构建了一个端到端系统,可以在非常有限的计算资源下处理大规模问题。在表1中,我们还总结了我们的系统与现存开源系统的对比。
本文提出的加权分位数草图是第一个解决在加权数据上找分位数的方法。 加权分位数摘要也不是专门针对树模型学习的,可以在将来服务于数据科学和机器学习中的其他应用。
6. END TO END EVALUATIONS
6.1 System Implementation
我们以开源软件包的形式实现了XGBoost。
分布式版本构建在rabit库上,用于allreduce。XGBoost的可移植性使其可用于许多生态系统,而不仅仅是绑定在特定平台。分布式XGBoost可以轻松运行在Hadoop,MPI Sun Grid引擎上。
6.2 Dataset and Setup
我们在实验中使用了四个数据集。分别是Allstate保险索赔数据集、高能物理学的希格斯玻色子数据集、Yahoo! learning to rank比赛数据集、criteo百万级别的点击日志数据集。
前三个数据集用于单机并行环境中,最后一个数据集用于分布式和核外计算的环境。
在所有实验中,我们统一设置提升树的最大深度等于8,学习率等于0.1,除非明确指定否则不进行列子采样。当我们将最大深度设置为其他时,我们可以得到相似的结果。
6.3 Classification
我们在Higgs-1M数据集上通过对比其他两种常用的基于贪心算法的提升树,评估基于贪心算法的XGBoost的性能。由于scikit-learn只能处理非稀疏输入,我们选择密集Higgs数据集进行比较。
在实验中,我们还发现列子采样后的结果略差于使用所有特征训练的结果。这可能是因为此数据集中的重要特征很少,贪心算法的精确结果会更好。
6.4 Learning to Rank
我们接下来评估XGBoost在learning to rank问题上的表现。我们与pGBRT进行比较,pGBRT是以前此类任务中表现最好的系统。XGBoost使用贪心算法,而pGBRT仅支持近似算法。
我们发现XGBoost运行速度更快。有趣的是,列采样不仅可以缩短运行时间,还能提高准确性。原因可能是由于子采样有助于防止过拟合,这是许多用户观察出来的。
6.5 Out-of-core Experiment
压缩将计算速度提高了三倍,并且分成两个磁盘进一步加速了2倍。对于此类实验,非常重要的一点是使大数据集来排空系统文件缓存以实现真正的核外环境。
6.6 Distributed Experiment
将我们的系统与两个生产力级别的分布式系统进行比较:Spark MLLib和H2O。
XGBoost的运行速度比基线系统快。更重要的是,它能够利用核外计算的优势,在给定有限的计算资源的情况下平稳扩展到所有17亿个样本。
随着我们添加更多机器,我们可以发现XGBoost的性能呈线性变化。
7. CONCLUSION
XGBoost是一个可扩展的提升树系统,被数据科学家广泛使用,并在很多问题上有很好的表现。
我们提出了一种用于处理稀疏数据的新型稀疏感知算法和用于近似学习的理论上合理的加权分位数草图算法。我们的经验表明,缓存访问模式,数据压缩和分片是构建可扩展的端到端系统以实现提升树的基本要素。
通过结合这些经验,XGBoost能够使用最少量的资源解决大规模的实际问题。
8. REFERENCES
略。
参考网址:
XGBoost 论文翻译+个人注释(非常好,总结得深入浅出)
XGBoost原论文阅读翻译(全文翻译,非常详细)
XGBoost论文阅读及其原理