赞
踩
XGBoost(eXtreme Gradient Boosting、XGB),是一种 Boosting 算法,他对 GBM 算法做了大量的理论和工程优化,准确率和运行效率都大大提升。XGBoost 通常使用 CART 作为基学习器。针对分类或回归问题,效果都非常好。在各种数据竞赛中大放异彩,而且在工业界也是应用广泛,主要是因为其效果优异,使用简单,速度快等优点。
xgb 是 boosting 算法中的 GBM 方法 的一种实现方式,主要是降低偏差,也就是降低模型的误差。因此它是采用多个基学习器,每个基学习器都比较简单(避免过拟合),下一个学习器是学习前面基学习器的结果
y
i
t
y^{t}_{i}
yit 和实际值
y
i
y_{i}
yi 的差值,通过多个学习器的学习,不断降低模型值和实际值的差。
XGB 的基学习器可以选择 CART 或者线性函数,通常都用 CART,这里就以 CART 为例。那么当基学习器为 CART 时,对应的
f
(
x
)
f(x)
f(x) 可以如下表示,这里去掉了脚标。
这个式子有点绕,解释下:
归纳下,XGB 的基本思路就是:
那在每一步如何选择/生成一个较优的树呢?那就是由下面的目标函数来决定。
目标函数由两部分组成:
因此,
泰勒公式可以参考:https://blog.csdn.net/qq_51392112/article/details/130645876。
上述公式即为:xgb第 t 步的目标函数。唯一的变量即为
f
t
f_{t}
ft ,此处的损失函数仍然是一个相对复杂的表达式,所以为了简化它,采用二阶泰勒展开来近似表达,即,这里对
f
t
(
x
i
)
=
y
^
i
t
−
1
f_t(x_i) = \hat y_{i}^{t-1}
ft(xi)=y^it−1 处进行泰勒展开。则上述损失函数转换为二阶导之后:
这里的缩减和 GBDT 里的缩减一样,都是为了降低单棵树的影响,给后面的树留有空间。因此引入一个学习率 n,用它去乘每步生成的 f t ( x ) f_t(x) ft(x)。
列抽样在 GBDT 和随机森林里都有用到,它可以牺牲偏差来降低方差。实现方法就是在分裂节点的时候只用一部分特征。
节点分裂的目标是找出一个特征,按照这个特征的某个节点分裂后,能够使目标函数降低最多。所以这里面有两层循环,外层是特征遍历,内层是特征值遍历。特征遍历没得说了,只能一个一个找,但是特征值遍历可以优化一些,缩短寻找最优分裂节点的耗时。我们分四步来看节点分裂:
首先了解下最基本的方法——精准贪心算法。
贪心算法分裂的方式就是一种暴力搜索的方式,遍历每一个特征,遍历该特征的每一个取值,计算分裂前后的增益,选择增益最大的特征取值作为分裂点。
精准贪心算法如下图,红线是分割线,在每一个地方都分割一次,计算一次
L
s
p
i
l
t
L_{spilt}
Lspilt 。分割线左侧的
g
,
h
g,h
g,h 分别加起来就是左侧
G
,
H
G, H
G,H ,分割线右侧的
g
,
h
g,h
g,h 分别加起来就是右侧
G
,
H
G, H
G,H 。
那么遍历一遍后选择目标函数降低最多的分裂点进行节点分裂,也就是在
L
s
p
i
l
t
L_{spilt}
Lspilt 最大的分裂点处进行节点分裂。
精准贪心算法的问题显而易见:
因此有了近似它的算法。最优切分点没必要找的那么精确,
那么基于这种想法,可以把这个候选节点范围缩小下,不再枚举,而是只选用分位点。比如下图选择了两个三分位点,也可以用四分位点或者其他更多的点。本质上就是把特征分到多个桶里,让数据粒度变粗,只跟桶打交道,降低计算量。
这里存在一个划分时机的问题,即,
两者都可以,但是后者需要的划分次数更少,因为后者对父节点用过的特征会分的更细。
综上,近似算法,其实就是分桶,目的是为了提升计算速度,降低遍历的次数,所以对特征进行分桶。就是将每一个特征的取值按照分位数划分到不同的桶中,利用桶的边界值作为分裂节点的候选集,每次遍历时不再是遍历所有特征取值,而是仅遍历该特征的几个桶(每个桶可以理解为该特征取值的分位数)就可以,这样可以降低遍历特征取值的次数。
前面用分位数选点的方法还有优化的空间。
如果能够在误差比较大的样本处分的更细一些,效果会更好。所以就有了加权分位数缩略图的想法,用权重来表示误差大小。
为什么选择 h h h 作为权重呢?可以从两个角度来看。
机器学习经常会碰到数据稀疏的问题,有三种情况会导致数据稀疏:
所以让算法能够感知数据的稀疏模式是非常重要的。XGBoost 的解决方法是在每个节点中设置一个默认方向,缺失值或者 0 元素直接走默认方向,不参与树模型的学习。比如在下图中就是直接走默认的红色虚线的方向。
这个默认方向该怎么选呢?两边都试试,看看哪边带来的
L
s
p
i
l
t
L_{spilt}
Lspilt 更大,就选哪边。具体来说就是在节点分裂的时候,把缺失值或 0 元素样本先分到左边的节点计算下
L
s
p
i
l
t
L_{spilt}
Lspilt ,然后再分到右边的节点计算下
L
s
p
i
l
t
L_{spilt}
Lspilt ,选择大的那边作为默认方向。
对于一个正在分裂的节点来说,因为所选特征对应的稀疏样本其实都被当成了一类,所以在找最优分裂点的时候是不需要考虑稀疏值的,只考虑非稀疏值即可,从而减小了计算量,缩短了运算耗时。
作者做了一个稀疏数据的实验,用了稀疏感知算法,速度能够提升 50 倍左右。
决策树学习过程中,最耗时的部分是分裂时对数据的反复排序。为了提高速度,XGBoost 采用了空间换时间的方法。对于一列特征,它创建了等量的指针,每个指针都跟一个特征值绑定,并指向对应样本,按照特征大小排序,把每一个特征的排好序的指针保存在块结构中,就实现了训练前对特征排序的目标,这样速度提升了,但是也因此需要额外空间存储指针。分条来说,块结构的设计有以下特点:
提出的块结构能够优化节点分裂的时间复杂度,但是在索引样本时,存在对内存的非连续访问问题。这是因为我们是按特征大小顺序对样本进行访问,但样本不按这个顺序。对内存的非连续访问将降低 CPU 的缓存命中率。XGBoost 针对精准贪心算法和近似算法设计了两种不同的优化策略。
对于精准贪心算法,采用缓存感知的预读取算法进行解决。在内存中开辟一个新的缓冲区(buffer),在 CPU 访问数据前,先把它要访问的数据存在这个缓冲区,把非连续的数据变成连续数据,这样就能提高 CPU 的缓存命中率,为 CPU 节省了一定的数据读取时间。
对于近似算法,包括加权分位数和稀疏感知这两种情况,通过降低块大小解决这一问题。缓存命中率低,一个原因是块太大了,没法全部存进缓存,如果把块的大小降到 CPU 缓存可以完全容纳的程度,就不会出现没命中了。但是块太小又会导致并行效率低。作者在尝试了多种块大小后,选择了最优的
2
16
2^{16}
216,每个块存储
2
16
2^{16}
216 个样本。实验结果如下图:
当数据没办法被一次全部读入内存时,就需要用到核外计算方法,单开一个线程从硬盘上读取需要用到的数据,解决内存不够地问题。但是从硬盘读取数据相对于处理器来说是很慢的,所以 XGBoost 采用了两种方法平衡两者速度:
对于存在某一维特征缺失的样本,xgb会尝试将其放到左子树计算一次增益,再放到右子树计算一次增益,对比放在左右子树增益的大小决定放在哪个子树。
XGB 提出了两种防止过拟合的方法:
【1】https://blog.csdn.net/qq_18293213/article/details/123965029
【2】https://zhuanlan.zhihu.com/p/360060567
【3】XGBoost: A Scalable Tree Boosting System https://dl.acm.org/doi/10.1145/2939672.2939785
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。