XGBoost全名叫(eXtreme Gradient Boosting)极端梯度提升,经常被用在一些项目中,其效果显著。它是大规模并行boosted tree的工具,它是目前最快最好的开源boosted tree工具包。XGBoost 所应用的算法就是GBDT(gradient boosting decision tree)的改进,既可以用于分类也可以用于回归问题中。
- 全称:eXtreme Gradient Boosting(极值梯度提升算法)
- 作者:陈天奇(华盛顿大学博士)
- 基础:GBDT
- 所属:boosting迭代型、树类算法。
- 适用范围:分类、回归等
- 优点:速度快、效果好、能处理大规模数据、支持多种语言、支持自定义损失函数等等。
目录
XGBoost和GBDT的区别
XGBoost作为GBDT的高效实现,对比原算法GBDT,XGBoost主要从下面几个方面做了优化:
- XGBoost的基学习器除了可以是CART也可以是线性分类器,而GBDT只能是CART;
- XGBoost在代价函数中加入了正则项,用于控制模型的复杂度(正则项的方式不同,GBDT是一种类似于缩减系数,而XGBoost类似于L2正则化项),可以防止过拟合,泛化能力更强。
- XGBoost借鉴了随机森林的做法,支持特征抽样,不仅防止过拟合,还能减少计算;
- XGBoost工具支持并行化;
- 对于缺失值的特征,通过枚举所有缺失值在当前节点是进入左子树还是右子树来决定缺失值的处理方式。
- 在算法的优化方式上,GBDT的损失函数只对误差部分做负梯度(一阶泰勒)展开,而XGBoost损失函数对误差部分做二阶泰勒展开,更加准确。
XGBoost与深度学习对比,不同的机器学习模型适用于不同类型的任务。深度神经网络通过对时空位置建模,能够很好地捕获图像、语音、文本等高维数据。而基于树模型的XGBoost则能很好地处理表格数据,同时还拥有一些深度神经网络所没有的特性(如:模型的可解释性、输入数据的不变性、更易于调参等)。
加法模型与前向分步算法
GBDT和XGBoost的算法核心都是:先构造一个(决策)树,然后不断在已有模型和实际样本输出的残差上再构造一颗树,依次迭代。算法都使用了前向分布算法的思路,从前向后,每一步学习一个基函数及其系数,最终逐步逼近优化目标函数式。
前向分布算法的前提,还需要介绍一下加法模型:
如果给定了损失函数L,所以前向分布算法考虑的问题是,如何求出所有的βm和γm,那我们的优化目标即为:
显然一次性求出所有的βm和γm基本不可能,所以前向分布算法给出的解决办法是:“利用贪心算法,每一步只学习一个弱模型及其系数,使得当前弱模型和之前所有的弱模型组合后目标表达式取得最优值,最终就可以使得所有弱模型组合后目标表达式取得最优值”。
下面通过一个具体的例子来说明:预测一个人是否喜欢电脑游戏,下图表明小男孩更喜欢打游戏,预测的结果为 tree1 和 tree 2 累加的结果2.9。
总之,提升方法告诉我们如何来求一个效果更好模型,那就是将多个弱模型组合起来,这仅仅是一个思路,而前向分布算法就具体告诉我们应该如何来做。
XGBoost目标损失函数推导
带正则项的Boosting Tree模型
其中,γγ为L1L1正则的惩罚项,λλ为L2L2正则的惩罚项
复杂度计算例子如下:
XGBoost损失函数推导
最优切分点划分算法
在实际训练过程中,当建立第 t 棵树时,一个非常关键的问题是如何找到叶子节点的最优切分点,XGBoost支持两种分裂节点的方法——贪心算法和近似算法。
(1)贪心算法
从树的深度为0开始:
- 对每个叶节点枚举所有的可用特征;
- 针对每个特征,把属于该节点的训练样本根据该特征值进行升序排列,通过线性扫描的方式来决定该特征的最佳分裂点,并记录该特征的分裂收益;
- 选择收益最大的特征作为分裂特征,用该特征的最佳分裂点作为分裂位置,在该节点上分裂出左右两个新的叶节点,并为每个新节点关联对应的样本集;
- 回到第1步,递归执行直到满足特定条件为止;
那么如何计算每个特征的分裂收益呢?
假设我们在某一节点完成特征分裂,则分裂前的目标函数可以写为:
分裂后的目标函数为:
则对于目标函数来说,分裂后的收益为:
注意: 该特征收益也可作为特征重要性输出的重要依据。
对于每次分裂,我们都需要枚举所有特征可能的分割方案,如何高效地枚举所有的分割呢?
假设我们要枚举某个特征所有 x<a 这样条件的样本,对于某个特定的分割点 a 我们要计算 a 左边和右边的导数和。
我们可以发现对于所有的分裂点 a ,只要做一遍从左到右的扫描就可以枚举出所有分割的梯度和GL、GR 。然后用上面的公式计算每个分割方案的收益就可以了。
观察分裂后的收益,我们会发现节点划分不一定会使得结果变好,因为我们有一个引入新叶子的惩罚项,也就是说引入的分割带来的增益如果小于一个阀值的时候,我们可以剪掉这个分割。
(2)近似算法
贪心算法可以得到最优解,但当数据量太大时则无法读入内存进行计算,近似算法主要针对贪心算法这一缺点给出了近似最优解。
对于每个特征,只考察分位点可以减少计算复杂度。
该算法首先根据特征分布的分位数提出候选划分点,然后将连续型特征映射到由这些候选点划分的桶中,然后聚合统计信息找到所有区间的最佳分裂点。
在提出候选切分点时有两种策略:
-
Global:学习每棵树前就提出候选切分点,并在每次分裂时都采用这种分割;
-
Local:每次分裂前将重新提出候选切分点。直观上来看,Local策略需要更多的计算步骤,而Global策略因为节点已有划分所以需要更多的候选点。
下图给出不同种分裂策略的AUC变化曲线,横坐标为迭代次数,纵坐标为测试集AUC,eps为近似算法的精度,其倒数为桶的数量。
我们可以看到 Global 策略在候选点数多时(eps 小)可以和 Local 策略在候选点少时(eps 大)具有相似的精度。此外我们还发现,在 eps 取值合理的情况下,分位数策略可以获得与贪婪算法相同的精度。
XGBoost的优缺点
优点:
(1)并行处理,支持并行化。boosting技术中下一棵树依赖上一棵树的残差进行训练和预测,所以树与树之间应该是只能串行。但是同层级节点可并行,具体的对于某个节点,节点内选择最佳分裂点,进行枚举的时候并行,(据说恰好这个也是树形成最耗时的阶段)。候选分裂点计算增益用多线程并行,训练速度快。
(2)高度的灵活性。XGBoost 允许用户定义自定义优化目标函数和评价标准。
(3)缺失值处理。XGBoost内置处理缺失值的规则。用户需要提供一个和其它样本不同的值,然后把它作为一个参数传进去,以此来作为缺失值的取值。XGBoost在不同节点遇到缺失值时采用不同的处理方法,并且会学习未来遇到缺失值时的处理方法。
(4)内置交叉验证。XGBoost允许在每一轮boosting迭代中使用交叉验证。因此,可以方便地获得最优boosting迭代次数。而GBM使用网格搜索,只能检测有限个值。early stop,当预测结果已经很好的时候可以提前停止建树,加快训练速度。
(5)XGBoost还特别设计了针对稀疏数据的算法
假设样本的第i个特征缺失时,无法利用该特征对样本进行划分,这里的做法是将该样本默认地分到指定的子节点,至于具体地分到哪个节点还需要某算法来计算,算法的主要思想是,分别假设特征缺失的样本属于右子树和左子树,而且只在不缺失的样本上迭代,分别计算缺失样本属于右子树和左子树的增益,选择增益最大的方向为缺失数据的默认方向(论文中“枚举”指的不是枚举每个缺失样本在左边还是在右边,而是枚举缺失样本整体在左边,还是在右边两种情况。 分裂点还是只评估特征不缺失的样本。);
(6)XGBoost还提出了三种防止过拟合的方法:Shrinkage and Column Subsampling、正则化项
Shrinkage方法就是在每次迭代中对树的每个叶子结点的分数乘上一个缩减权重η,这可以使得每一棵树的影响力不会太大,留下更大的空间给后面生成的树去优化模型。Column Subsampling类似于随机森林中的选取部分特征进行建树。其可分为两种,一种是按层随机采样,在对同一层内每个结点分裂之前,先随机选择一部分特征,然后只需要遍历这部分的特征,来确定最优的分割点。另一种是随机选择特征,则建树前随机选择一部分特征然后分裂就只遍历这些特征。一般情况下前者效果更好。
缺点
(1)虽然利用预排序和近似算法可以降低寻找最佳分裂点的计算量,但是xgBoosting采用预排序,在迭代之前,对结点的特征做预排序,需要遍历数据集选择最优分割点,数据量大,非常耗时;LightGBM方法采用histogram算法,占用的内存低,数据分割的复杂度更低;
(2)预排序过程的空间复杂度过高,不仅需要存储特征值,还需要存储特征对应样本的梯度统计值的索引,相当于消耗了两倍的内存。
(3)xgBoosting采用level-wise生成决策树,同时分裂同一层的叶子,从而进行多线程优化,不容易过拟合,但很多叶子节点的分裂增益较低,没必要进行跟进一步的分裂,这就带来了不必要的开销;LightGBM采用深度优化,leaf-wise生长策略,每次从当前叶子中选择增益最大的结点进行分裂,循环迭代,但会生长出更深的决策树,产生过拟合,因此引入了一个阈值进行限制,防止过拟合。
XGBoosting涉及的算法工程优化策略
(1) 对内存的优化(列分块)
在XGBoost模型计算过程中,特征值的排序与切分点的选择是最耗时的部分,文章中提出了一种划分块的优化方法,具体表现为如下流程:
- 整体训练数据可以看做一个n*m 的超大规模稀疏矩阵
- 按照mini-batch的方式横向分割,可以切成很多个“Block”
- 每一个“Block”内部采用一种Compress Sparse Column的稀疏短阵格式,每一列特征分别做好升序排列,便于搜索切分点,整体的时间复杂度有效降低。
(2)对CPU Cache的优化
针对一个具体的块(block),其中存储了排序好的特征值,以及指向特征值所属样本的索引指针,算法需要间接地利用索引指针来获得样本的梯度值。由于块中数据是按特征值来排序的,当索引指针指向内存中不连续的样本时,无法充分利用CPU缓存来提速。文章中作者提出来了两种优化思路。
- 提前取数(Prefetching)
对于精确搜索,利用多线程的方式,给每个线程划分一个连续的缓存空间,当training线程在按特征值的顺序计算梯度的累加时,prefetching线程可以提前将接下来的一批特征值对应的梯度加载到CPU缓存中。
- 合理设置分块大小
对于近似分桶搜索,按行分块时需要准确地选择块的大小。块太小会导致每个线程的工作量太少,切换线程的成本过高,不利于并行计算;块太大导致缓存命中率低,需要花费更多时间在读取数据上。经过反复实验,作者找到一个合理的block_size
。
(3)对IO的优化
当数据集太大,无法全部加载到内存时,主要的性能瓶颈就变成了磁盘IO,因此需要对IO进行优化。文章中主要提出来了两种优化思路。
- Block压缩优化
原始数据在磁盘上是以压缩格式存取的,读取的时候,现场解压 (decompress on-the-fly)
相当于牺牲一部分CPU时间,换取对磁盘IO的读取时间损耗。
- Block 分片优化
将压缩后的块存放在多个不同的磁盘中,每个磁盘开一个prefetching线程分别读取数据到各自的缓存,提供给一个training线程使用。
Xgboost算法训练参数
XGBoost可以把所有的参数分成了三类:
- 通用参数:宏观函数控制。
- Booster参数:控制每一步的booster(tree/regression)。
- 学习目标参数:控制训练目标的表现。
这些参数用来控制XGBoost的宏观功能。
booster[默认gbtree]
- 选择每次迭代的模型,有两种选择:
gbtree:基于树的模型
gbliner:线性模型
silent[默认0]
- 当这个参数值为1时,静默模式开启,不会输出任何信息。
- 一般这个参数就保持默认的0,因为这样能帮我们更好地理解模型。
nthread[默认值为最大可能的线程数]
- 这个参数用来进行多线程控制,应当输入系统的核数。
- 如果你希望使用CPU全部的核,那就不要输入这个参数,算法会自动检测它。
还有两个参数,XGBoost会自动设置,目前你不用管它。接下来咱们一起看booster参数。
尽管有两种booster可供选择,我这里只介绍tree booster,因为它的表现远远胜过linear booster,所以linear booster很少用到。
eta[默认0.3]
- 和GBM中的 learning rate 参数类似。
- 通过减少每一步的权重,可以提高模型的鲁棒性。
- 典型值为0.01-0.2。
min_child_weight[默认1]
- 决定最小叶子节点样本权重和。
- 和GBM的 min_child_leaf 参数类似,但不完全一样。XGBoost的这个参数是最小样本权重的和,而GBM参数是最小样本总数。
- 这个参数用于避免过拟合。当它的值较大时,可以避免模型学习到局部的特殊样本。
- 但是如果这个值过高,会导致欠拟合。这个参数需要使用CV来调整。
max_depth[默认6]
- 和GBM中的参数相同,这个值为树的最大深度。
- 这个值也是用来避免过拟合的。max_depth越大,模型会学到更具体更局部的样本。
- 需要使用CV函数来进行调优。
- 典型值:3-10
max_leaf_nodes
- 树上最大的节点或叶子的数量。
- 可以替代max_depth的作用。因为如果生成的是二叉树,一个深度为n的树最多生成n2n2个叶子。
- 如果定义了这个参数,GBM会忽略max_depth参数。
gamma[默认0]
- 在节点分裂时,只有分裂后损失函数的值下降了,才会分裂这个节点。Gamma指定了节点分裂所需的最小损失函数下降值。
- 这个参数的值越大,算法越保守。这个参数的值和损失函数息息相关,所以是需要调整的。
max_delta_step[默认0]
- 这参数限制每棵树权重改变的最大步长。如果这个参数的值为0,那就意味着没有约束。如果它被赋予了某个正值,那么它会让这个算法更加保守。
- 通常,这个参数不需要设置。但是当各类别的样本十分不平衡时,它对逻辑回归是很有帮助的。
- 这个参数一般用不到,但是你可以挖掘出来它更多的用处。
subsample[默认1]
- 和GBM中的subsample参数一模一样。这个参数控制对于每棵树,随机采样的比例。
- 减小这个参数的值,算法会更加保守,避免过拟合。但是,如果这个值设置得过小,它可能会导致欠拟合。
- 典型值:0.5-1
colsample_bytree[默认1]
- 和GBM里面的max_features参数类似。用来控制每棵随机采样的列数的占比(每一列是一个特征)。
- 典型值:0.5-1
colsample_bylevel[默认1]
- 用来控制树的每一级的每一次分裂,对列数的采样的占比。
- 我个人一般不太用这个参数,因为subsample参数和colsample_bytree参数可以起到相同的作用。但是如果感兴趣,可以挖掘这个参数更多的用处。
lambda[默认1]
- 权重的L2正则化项。(和Ridge regression类似)。
- 这个参数是用来控制XGBoost的正则化部分的。虽然大部分数据科学家很少用到这个参数,但是这个参数在减少过拟合上还是可以挖掘出更多用处的。
alpha[默认1]
- 权重的L1正则化项。(和Lasso regression类似)。
- 可以应用在很高维度的情况下,使得算法的速度更快。
scale_pos_weight[默认1]
- 在各类别样本十分不平衡时,把这个参数设定为一个正值,可以使算法更快收敛。
这个参数用来控制理想的优化目标和每一步结果的度量方法。
1、objective[默认reg:linear]
- 这个参数定义需要被最小化的损失函数。最常用的值有:
- binary:logistic 二分类的逻辑回归,返回预测的概率(不是类别)。
- multi:softmax 使用softmax的多分类器,返回预测的类别(不是概率)。 在这种情况下,你还需要多设一个参数:num_class(类别数目)。
- multi:softprob 和multi:softmax参数一样,但是返回的是每个数据属于各个类别的概率。
2、eval_metric[默认值取决于objective参数的取值]
- 对于有效数据的度量方法。
- 对于回归问题,默认值是rmse,对于分类问题,默认值是error。
- 典型值有:
- rmse 均方根误差(∑Ni=1ϵ2N−−−−−√∑i=1Nϵ2N)
- mae 平均绝对误差(∑Ni=1|ϵ|N∑i=1N|ϵ|N)
- logloss 负对数似然函数值
- error 二分类错误率(阈值为0.5)
- merror 多分类错误率
- mlogloss 多分类logloss损失函数
- auc 曲线下面积