十大机器学习算法-XGBoost

简介

XGBoost是陈天奇等人开发的一个开源机器学习项目,高效地实现了GBDT算法并进行了算法和工程上的许多改进,被广泛应用在许多机器学习竞赛中取得不错的成绩。

基本思想

XGboost模型的损失函数包含两部分,模型的经验损失以及正则化项:
L ( ϕ ) = i l ( y ^ i , y i ) + k Ω ( f k )  where  Ω ( f ) = γ T + 1 2 λ w 2 \begin{array}{l}{\mathcal{L}(\phi)=\sum_{i} l\left(\hat{y}_{i}, y_{i}\right)+\sum_{k} \Omega\left(f_{k}\right)} \\ {\text { where } \Omega(f)=\gamma T+\frac{1}{2} \lambda\|w\|^{2}}\end{array}
其中 T T 为基模型CART树的叶子数。如何优化上面的目标函数?我们不能用诸如梯度下降的方法,因为 f f 是树,而非数值型的向量。我们采用前向分步算法,即贪心法找局部最优解模型,每一步找一个使得我们的损失函数降低最大的f(贪心法体现在这)
y ^ i ( t ) = j = 1 t f j ( x i ) = y ^ i ( t 1 ) + f t ( x i ) \hat{y}_{i}^{(t)}=\sum_{j=1}^{t} f_{j}\left(x_{i}\right)=\hat{y}_{i}^{(t-1)}+f_{t}\left(x_{i}\right)
对目标函数进行改写,得到第t次迭代的损失函数:
L ( t ) = i = 1 n l ( y i , y ^ i ( t 1 ) + f t ( x i ) ) + Ω ( f t ) \mathcal{L}^{(t)}=\sum_{i=1}^{n} l\left(y_{i}, \hat{y}_{i}^{(t-1)}+f_{t}\left(\mathbf{x}_{i}\right)\right)+\Omega\left(f_{t}\right)

二阶泰勒展开

假设损失函数使用的是平方损失,则上式写为:
L ( t ) = i = 1 N ( y i ( y ^ i ( t 1 ) + f t ( x i ) ) ) 2 + Ω ( f t ) = i = 1 N ( y i y ^ i ( t 1 ) f t ( x i ) ) 2 + Ω ( f t ) \begin{aligned} \mathcal{L}^{(t)} &=\sum_{i=1}^N \left(y_i – \left(\hat y_i^{(t-1)} + f_t({\bf x_i})\right)\right)^2 + \Omega(f_t) \\ &= \sum_{i=1}^N (\underbrace {y_i – \hat y_i^{(t-1)}}_{残差} – f_t({\bf x_i}))^2 + \Omega(f_t) \end{aligned}
这就是之前我们GBDT中使用平方损失,然后每一轮拟合的残差。

更一般的,我们之前使用“负梯度”。这里利用二阶泰勒展开
f ( x + Δ x ) f ( x ) + f ( x ) Δ x + 1 2 f ( x ) Δ x 2 f(x+\Delta x) \simeq f(x)+f^{\prime}(x) \Delta x+\frac{1}{2} f^{\prime \prime}(x) \Delta x^{2}
我们得到近似的目标函数:
L ( t ) i = 1 n [ l ( y i , y ^ ( t 1 ) ) + g i f t ( x i ) + 1 2 h i f t 2 ( x i ) ] + Ω ( f t ) \mathcal{L}^{(t)} \simeq \sum_{i=1}^{n}\left[l\left(y_{i}, \hat{y}^{(t-1)}\right)+g_{i} f_{t}\left(\mathbf{x}_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(\mathbf{x}_{i}\right)\right]+\Omega\left(f_{t}\right)
其中如果我们定义损失函数为平方损失, g i g_i h i h_i 分别为每个数据点在损失函数上的一阶导数和二阶导数,则有:
g i = y ^ ( t 1 ) ( y ^ ( t 1 ) y i ) 2 = 2 ( y ^ ( t 1 ) y i ) h i = y ^ ( t 1 ) 2 ( y i y ^ ( t 1 ) ) 2 = 2 g_{i}=\partial_{\hat{y}^{(t-1)}}\left(\hat{y}^{(t-1)}-y_{i}\right)^{2}=2\left(\hat{y}^{(t-1)}-y_{i}\right) \quad h_{i}=\partial_{\hat{y}^{(t-1)}}^{2}\left(y_{i}-\hat{y}^{(t-1)}\right)^{2}=2
为什么只用到二阶泰勒展开呢?因为在平方损失时,三阶展开已经为0。

此时移除对当前t轮来说是常数项的 l ( y i , y ^ i ( t 1 ) ) l\left(y_{i}, \hat{y}_{i}^{(t-1)}\right) 得到:
L ( t ) = i = 1 N ( g i f t ( x i ) + 1 2 h i f t 2 ( x i ) ) + Ω ( f t ) \mathcal{L}^{(t)}=\sum_{i=1}^{N}\left(g_{i} f_{t}\left(\mathbf{x}_{\mathbf{i}}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(\mathbf{x}_{i}\right)\right)+\Omega\left(f_{t}\right)
目标函数只依赖每个数据点在误差函数上的一阶导数和二阶导数

正则项

XGBoost采用衡量树复杂度的方式为:一棵树里面叶子节点的个数T,以及每棵树叶子节点上面输出分数w的平方和(相当于L2正则):
Ω ( f t ) = γ T + 1 2 λ j = 1 T w j 2 \Omega(f_t) =\gamma T +\frac{1}{2}\lambda\sum_{j=1}^{T} w_j^2
举例如下:

完整目标函数

进一步,对XGBoost来说,每一个数据点 x i x_i 最终都会落到一个叶子结点上。对于落在同一个叶子节点上的数据点来说,其输出都是一样的。假设我们共有 J J 个叶子结点,每个叶子结点对应的输出为 w j w_j w j w_j 也是我们要求解的最优权重), I j = { i q ( x i ) = j } I_j = \{ i|q(x_i) = j\} 为落在叶子结点 j j 的实例集合。则目标函数可以进一步改写(已知 y i y_i y ^ i \hat{y}_i 所以第一项为常数项可忽略):
L ~ ( t ) = i = 1 n [ g i f t ( x i ) + 1 2 h i f t 2 ( x i ) ] + γ T + 1 2 λ j = 1 T w j 2 = j = 1 T [ ( i I j g i ) w j + 1 2 ( i I j h i + λ ) w j 2 ] + γ T = j = 1 T [ G j w j + 1 2 ( H j + λ ) w j 2 ] + γ T \begin{aligned} \tilde{\mathcal{L}}^{(t)} &=\sum_{i=1}^{n}\left[g_{i} f_{t}\left(\mathbf{x}_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(\mathbf{x}_{i}\right)\right]+\gamma T+\frac{1}{2} \lambda \sum_{j=1}^{T} w_{j}^{2} \\ &=\sum_{j=1}^{T}\left[\left(\sum_{i \in I_{j}} g_{i}\right) w_{j}+\frac{1}{2}\left(\sum_{i \in I_{j}} h_{i}+\lambda\right) w_{j}^{2}\right]+\gamma T \\&= \sum_{j=1}^{T}\left[G_{j} w_{j}+\frac{1}{2}\left(H_{j}+\lambda\right) w_{j}^{2}\right]+\gamma T \end{aligned}
其中 G j = i I j g i H j = i I j h i G_j = \sum_{i \in I_j} g_i \quad H_j = \sum_{i \in I_j} h_i

因此,现在要做的是两件事:

  1. 确定树的结构, 这样,这一轮的目标函数就确定了下来
  2. 求使得当前这一轮(第t轮)的目标函数最小的叶结点分数w。(Obj代表了当我们指定一个树的结构的时候,我们在目标上面最多减少多少,也称为结构分数,structure score)

对于给定的树结构 q ( x ) q(\bf x) 可以计算得到 w j w_j 的最优权重为:
w j = G j H j + λ w_{j}^{*}=– \frac{G_j}{H_j+\lambda}
将最优权重带回目标函数,目标函数变为:

(2-8) L ~ ( t ) ( q ) = j = 1 T ( G j w j + 1 2 ( H j + λ ) w j 2 ) + γ T = j = 1 T ( G j 2 H j + λ + 1 2 G j 2 H j + λ ) + γ T = 1 2 j = 1 T ( G j 2 H j + λ ) + γ T \begin{aligned} \tilde{\mathcal{L}}^{(t)}(q) &=\sum_{j=1}^T \left(G_jw_j + \frac{1}{2} (H_j + \lambda) w_j^2\right) +\gamma T\\ &=\sum_{j=1}^T \left(- \frac{G_j^2}{H_j+\lambda} + \frac{1}{2} \frac{G_j^2}{H_j+\lambda} \right) +\gamma T\\ &=- \frac{1}{2}\sum_{j=1}^T \left({\color{red}{\frac{G_j^2}{H_j+\lambda}} } \right) +\gamma T \tag{2-8} \end{aligned}
所表示的目标函数越小越好。

树的结构确定

接下来要解决的就是上面提到的问题,即如何确定树的结构。

暴力枚举所有的树结构,然后选择结构分数最小的。 树的结构太多了,这样枚举一般不可行。

通常采用贪心法,每次尝试分裂一个叶节点,计算分裂后的增益,选增益最大的。这个方法在之前的决策树算法中大量被使用。而增益的计算方式比如ID3的信息增益,C4.5的信息增益率,CART的Gini系数等。那XGBoost呢?

回想式子2-8标红色的部分,衡量了每个叶子节点对总体损失的贡献,我们希望目标函数越小越好,因此红色的部分越大越好。

XGBoost使用下面的公式计算增益:
(2-9) G a i n = 1 2 [ G L 2 H L + λ + G R 2 H R + λ ( G L + G R ) 2 H L + H R + λ ] γ Gain = \frac{1}{2}[\underbrace{\frac{G_L^2}{H_L+\lambda}}_{左子树分数} + \underbrace{\frac{G_R^2}{H_R+\lambda}}_{右子树分数} – \underbrace{\frac{(G_L+G_R)^2}{H_L+H_R+\lambda}}_{分裂前分数}] – \underbrace{\gamma}_{新叶节点复杂度}\tag{2-9}
式2-9即2-8红色部分的分裂后 – 分裂前的分数。Gain值越大,说明分裂后能使目标函数减少越多,就越好

该式子可以作为一个打分函数衡量树结构 q q 的质量,也就是说可以作为是否可以进行分裂的判断标准,即分裂之后两边的目标函数之和是否能够小于不分裂的目标函数值
L s p l i t = 1 2 [ ( i I L g i ) 2 i I L h i + λ + ( i I R g i ) 2 i I R h i + λ ( i I g i ) 2 i I h i + λ ] γ \mathcal{L}_{s p l i t}=\frac{1}{2}\left[\frac{\left(\sum_{i \in I_{L}} g_{i}\right)^{2}}{\sum_{i \in I_{L}} h_{i}+\lambda}+\frac{\left(\sum_{i \in I_{R}} g_{i}\right)^{2}}{\sum_{i \in I_{R}} h_{i}+\lambda}-\frac{\left(\sum_{i \in I} g_{i}\right)^{2}}{\sum_{i \in I} h_{i}+\lambda}\right]-\gamma
其中 I L I_L I R I_R 是分裂后属于左右节点的实例集合, I = I L I R I=I_{L} \cup I_{R} ;上式大于0选择分裂。注意分裂不一定会使情况变好,因为有一个引入新叶子的惩罚项 γ \gamma ,优化这个目标相当于进行树的剪枝。当引入的分裂带来的增益小于一个阀值的时候,不进行分裂操作。

一个树结构分数计算例子如下图:

分裂算法

Basic Exact Greedy Algorithm

因此,每次分裂,枚举所有可能的分裂方案,就和CART中回归树进行划分一样,要枚举所有特征和特征的取值。该算法称为Exact Greedy Algorithm,如下图所示:遍历所有特征及分割点,选择增益最多的特征和分割点的组合

假设现在枚举的是年龄特征 x j x_j 。现在要考虑划分点a,因此要计算枚举 x j < a x_j < a a x j a\leq x_j 的导数和:

可以看出,对于一个特征,对特征取值排完序后,枚举所有的分裂点a,只要从左到右扫描一遍就可以枚举出所有分割的梯度 G L G_L G R G_R ,然后用式2-9计算即可。这样假设树的高度为 H H ,特征数 d d ,则复杂度为 O ( H d n l o g n ) O(Hdnlogn) 。 其中,排序为 O ( n l o g n ) O(nlogn) ,每个特征都要排序所以乘以 d d ,每一层都要这样一遍,所以乘以高度H。这个仍可以继续优化(之后再讲)。

树节点划分算法-Approximate Algorithm

精确地枚举所有可能的划分,十分耗时;当数据量大的时候,几乎不可能将数据全部加载进内存;精确划分在分布式中也会有问题。因此文章提出了近似的策略。

简单的说,就是根据特征 k k 的分布来确定 l l 个候选切分点 S k = { s k 1 , s k 2 , s k l } S_{k}=\left\{s_{k 1}, s_{k 2}, \cdots s_{k l}\right\} ,然后根据这些候选切分点把相应的样本放入对应的中,对每个桶的 G , H G,H 进行累加,最后通过遍历所有的候选分裂点来找到最佳分裂点。方法过程如下图:

给定了候选切分点后,一个例子为:

那么,现在有两个问题:

  1. 如何选取候选切分点 S k = { s k 1 , s k 2 , s k l } S_k = \{s_{k1},s_{k2},\cdots s_{kl}\} 呢?
  2. 什么时候进行候选切分点的选取?

分界点选择时机

近似方法分为全局和局部两种。**全局策略(Global)**是在建立第 k k 棵树时(初始化阶段)利用样本的梯度对样本进行离散化,每一维的特征都建立buckets。在建树过程中,重复利用这些buckets进行分裂判断。**局部策略(Local)**是在每次进行分裂时,都重新计算每个样本的梯度并重新构建buckets,再进行分裂判断。局部选择的复杂度更高,实验中效果更好。

桶的个数等于 1 / eps, 可以看出:

  • 全局切分点的个数够多的时候,和Exact greedy算法性能相当。
  • 局部切分点个数不需要那么多,因为每一次分裂都重新进行了选择。

切分点的选取-Weighted Quantile Sketch

quantile是分位数,先回答一下什么是分位数,WIKI百科上是这么说的

quantiles are cut points dividing the range of a probability distribution into contiguous intervals with equal probabilities, or dividing the observations in a sample in the same way.

即把概率分布划分为连续的区间,每个区间的概率相同。

以统计学常见的四分位数为例,就是:

四分位数(Quartile)把所有数值由小到大排列并分成四等份,处于三个分割点位置的数值就是四分位数。

1)第一四分位数(Q1),又称“较小四分位数”,等于该样本中所有数值由小到大排列后第25%的数字;

2)第二四分位数(Q2),又称“中位数”,等于该样本中所有数值由小到大排列后第50%的数字;

3)第三四分位数(Q3),又称“较大四分位数”,等于该样本中所有数值由小到大排列后第75%的数字。

可以看出,简单的分位数就是先把数值进行排序,然后根据你采用的几分位数把数据分为几份即可。

上文近似算法的关键在于怎么选择 S k = { s k 1 , s k 2 s k l } S_k = \left\{s_{k 1}, s_{k 2} \dots s_{k l}\right\} ,即怎么限定近似算法buckets的边界。Quantile Sketch的思想是用k分位点来选取。但是实际中,我们要均分的是loss,而不是样本的数量,而每个样本对loss的贡献可能是不一样的,按样本均分会导致loss分布不均匀,取到的分位点会有偏差。怎么衡量每个样本都loss的贡献呢?

我们定义集合 D k = { ( x 1 k , h 1 ) , ( x 2 k , h 2 ) ( x n k , h n ) \mathcal{D}_{k}=\left\{\left(x_{1 k}, h_{1}\right),\left(x_{2 k}, h_{2}\right) \cdots\left(x_{n k}, h_{n}\right)\right. 代表每个训练实例的k-th特征值以及二阶梯度。定义rank函数 r k : R [ 0 , + ) r_{k} : \mathbb{R} \rightarrow[0,+\infty) 如下:
r k ( z ) = 1 ( x , h ) D k h ( x , h ) D k , , x < z h r_{k}(z)=\frac{1}{\sum_{(x, h) \in \mathcal{D}_{k}} h} \sum_{(x, h) \in \mathcal{D}_{k,}, x<z} h
rank函数计算的是对某一个特征上,样本特征值小于 z z 的二阶梯度除以所有二阶梯度的总和。式3-2表达了第k个特征小于z的样本比例,和之前的分位数挺相似。

而候选切分点 S k = { s k 1 , s k 2 s k l } S_k = \left\{s_{k 1}, s_{k 2} \dots s_{k l}\right\} 要求:
r k ( s k , j ) r k ( s k , j + 1 ) < ϵ , s k 1 = min i x i k , s k l = max i x i k \left|r_{k}\left(s_{k, j}\right)-r_{k}\left(s_{k, j+1}\right)\right|<\epsilon, \quad s_{k 1}=\min _{i} \mathbf{x}_{i k}, s_{k l}=\max _{i} \mathbf{x}_{i k}
直观来看就是让相邻两个候选分裂点相差不超过某个值 ϵ \epsilon 。因此总共会得到 1 / ϵ / \epsilon 个切分点(桶)。我们对这么多个桶进行分支判断,显然比起对n个样本找分裂节点更快捷。 ϵ \epsilon 越大桶数量越少,粒度越粗。

一个例子如下:

要切分为3个,总和为1.8,因此第1个在0.6处,第2个在1.2处。

那么,为什么要用二阶梯度加权?将前面我们泰勒二阶展开后的目标函数2-4进行配方:
(3-3) i = 1 N ( g i f t ( x i ) + 1 2 h i f t 2 ( x i ) ) + Ω ( f t ) = i = 1 N 1 2 h i ( 2 g i h i f t ( x i ) + f t 2 ( x i ) ) + Ω ( f t ) = i = 1 N 1 2 h i ( g i 2 h i 2 + 2 g i h i f t ( x i ) + f t 2 ( x i ) ) + Ω ( f t ) = i = 1 N 1 2 h i ( f t ( x i ) ( g i h i ) ) 2 + Ω ( f t ) \begin{aligned} &\sum_{i=1}^N\left(g_if_t({\bf x_i}) + \frac{1}{2}h_if_t^2({\bf x_i})\right) + \Omega(f_t)\\ = &\sum_{i=1}^N\frac{1}{2}h_i\left(2\frac{g_i}{h_i}f_t({\bf x_i}) + f_t^2({\bf x_i})\right) + \Omega(f_t) \\ =&\sum_{i=1}^N \frac{1}{2}h_i\left(\frac{g_i^2}{h_i^2} +2\frac{g_i}{h_i}f_t({\bf x_i}) + f_t^2({\bf x_i})\right) + \Omega(f_t) \\ =&\sum_{i=1}^N \frac{1}{2}{\color{red}h_i}\left( f_t({\bf x_i}) – ({\color{red}- \frac{g_i}{h_i}})\right)^2 + \Omega(f_t) \tag{3-3} \end{aligned}
推导第三行可以加入 g i 2 h i 2 \frac{g_{i}^{2}}{h_{i}^{2}} 是因为 g i g_i h i h_i 是上一轮的损失函数求导,是常量。

从式3-3可以看出,目标函数就像是标签为 g i / h i −g_i/h_i ,权重为 h i h_i 的平方损失,因此用 h i h_i 加权。

一些Trick

Shrinkage和采样

除了正则项以外,还有shrinkage与采样技术来避免过拟合

Shrinkage

shrinkage(收缩率)就是在每步迭代添加树的过程中,对叶子节点乘以一个缩减权重 η \eta ,类似于随机梯度下降中的学习率。该操作的作用是减少每棵树的影响力,留更多的空间给后来的树提升。
(3-1) y ^ i t = y ^ i ( t 1 ) + η f t ( x i ) \hat y_i^t = \hat y_i^{(t-1)} + {\color{red} \eta} f_t(x_i)\tag{3-1}
通常步长 η \eta 取值为0.1。

采样

采样的技术有两种,一种是行采样(样本采样),是bagging的思想,每次只抽取部分样本进行训练,不使用全部样本。行采样增加了不同样本集合的差异性,从而不同基学习器之间的差异性也增大,避免过拟合。另一种是列采样(特征采样),相当于做随机特征筛选,进入模型的特征个数越少(即模型变量越少),模型越简单,根据机器学习理论(方差偏差理论),模型越简单,模型泛化性越好。

列采样的实现方式有两种,一种是按层随机(一般效果好一点),另一种是建树前就随机选择特征。按层随机:上文提到每次分裂一个节点时,我们都要遍历所有的特征和分割点,从而确定最优分割点。如果加入列采样,在对同一层的每个节点分裂之前,先随机选择一部分特征,于是只需要遍历这部分特征,来确定最优的分割点。建树前随机选择特征:在建树前就随机选择一部分特征,之后所有叶子结点的分裂都使用这部分特征。

稀疏值处理 – Sparsity-aware Split Finding

在真实世界中,我们的特征往往是稀疏的,可能的原因有:

  1. 数据缺失值
  2. 大量的0值(比如统计出现的)
  3. 进行了One-hot 编码

XGBoost能对缺失值自动进行处理,其思想是对于缺失值自动学习出它该被划分的方向(左子树or右子树):

注意,上述的算法只遍历非缺失值。划分的方向怎么学呢?很naive但是很有效的方法:

  1. 让特征k的所有缺失值的都到右子树,然后和之前的一样,枚举划分点,计算最大的gain
  2. 让特征k的所有缺失值的都到左子树,然后和之前的一样,枚举划分点,计算最大的gain

这样最后求出最大增益的同时,也知道了缺失值的样本应该往左边还是往右边。使用了该方法,相当于比传统方法多遍历了一次,但是它只在非缺失值的样本上进行迭代,因此其复杂度与非缺失值的样本成线性关系。在Allstate-10k数据集上,比传统方法快了50倍:

分块并行 – Column Block for Parallel Learning

在建树的过程中,最耗时是找最优的切分点,而这个过程中,最耗时的部分是将数据排序。为了减少排序的时间,提出Block结构存储数据。

  • Block中的数据以稀疏格式CSC进行存储

  • Block中的特征进行排序(不对缺失值排序)

  • Block 中特征还需存储指向样本的索引,这样才能根据特征的值来取梯度。

  • 一个Block中存储一个或多个特征的值

可以看出,只需在建树前排序一次,后面节点分裂时可以直接根据索引得到梯度信息。

  • 在Exact greedy算法中,将整个数据集存放在一个Block中。这样,复杂度从原来的 O ( H d x 0 l o g n ) O(Hd||x||_0logn) 降为 O ( H d x 0 + x 0 l o g n ) O(Hd||x||_0+||x||_0logn) ,其中 x 0 ||x||_0 为训练集中非缺失值的个数。这样,Exact greedy算法就省去了每一步中的排序开销。
  • 在近似算法中,使用多个Block,每个Block对应原来数据的子集。不同的Block可以在不同的机器上计算。该方法对Local策略尤其有效,因为Local策略每次分支都重新生成候选切分点。

Block结构还有其它好处,数据按列存储,可以同时访问所有的列,很容易实现并行的寻找分裂点算法。此外也可以方便实现之后要讲的out-of score计算。

缺点是空间消耗大了一倍。

总结

XGBoost为什么快

  1. 当数据集大的时候使用近似算法

    在特征分裂时,根据特征k的分布确定 l l 个候选切分点。根据这些切分点把相应的样本放入对应的桶中,对每个桶的 G , H G,H 进行累加,最后通过遍历所有的候选分裂点来找到最佳分裂点。我们对这么多个桶进行分支判断,显然比起对n个样本找分裂节点更快捷。

  2. Block与并行

    1. XGBoost工具支持并行

      当然这个并行是在特征的粒度上,而非tree粒度,因为本质还是boosting算法。我们知道,决策树的学习最耗时的一个步骤是对特征的值进行排序(因为要确定最佳分割点)。xgboost在训练之前,预先对数据进行了排序,然后保存为block结构,后面的迭代中重复地使用这个结构,大大减小计算量。这个block结构也使得并行成为可能。在进行节点分裂时,需要计算每个特征的增益,最终选增益最大的那个特征去做分裂,那么各个特征的增益计算就可以开多线程进行。

  3. CPU cache 命中优化

  4. Block预取、Block压缩、Block Sharding等

XGBoost与GBDT的异同

  1. GBDT是机器学习算法,XGBoost是该算法的工程实现
  2. 传统GBDT以CART作为基分类器,XGBoost还支持线性分类器,这个时候XGBoost相当于带L1和L2正则化项的Logistic回归(分类问题)或者线性回归(回归问题)。
  3. 传统的GBDT只用了一阶导数信息(使用牛顿法的除外),而XGBoost对损失函数做了二阶泰勒展开。并且XGBoost支持自定义损失函数,只要损失函数一阶、二阶可导。
  4. 在使用CART作为基分类器时,XGBoost的目标函数多了正则项控制模型复杂度, 相当于预剪枝,使得学习出来的模型更加不容易过拟合。
  5. 传统的GBDT在每轮迭代时使用全部数据,XGBoost则采用了与随机森林相似的策略,支持对数据进行采样(行采样和列采样)。
  6. 对缺失值的处理。传统的GBDT没有涉及对缺失值进行处理,XGBoost能够自动学习出缺失值的处理策略。
  7. XGBoost工具支持并行。当然这个并行是在特征的粒度上,而非tree粒度,因为本质还是boosting算法。我们知道,决策树的学习最耗时的一个步骤是对特征的值进行排序(因为要确定最佳分割点)。xgboost在训练之前,预先对数据进行了排序,然后保存为block结构,后面的迭代中重复地使用这个结构,大大减小计算量。这个block结构也使得并行成为可能。在进行节点分裂时,需要计算每个特征的增益,最终选增益最大的那个特征去做分裂,那么各个特征的增益计算就可以开多线程进行。
  8. 可并行的近似直方图计算。

XGBoost Scalable的体现

XGBoost的paper在KKD上发表,名为:《Xgboost: A scalable tree boosting system》,那么scalable体现在哪?

参考知乎上王浩的回答,修改如下:

  • 模型的scalability:弱分类器可以支持cart也可以支持lr和linear, 但其实这是Boosting算法做的事情,XGBoost只是实现了而已。
  • 目标函数的scalability: 支持不同的loss function, 支持自定义loss function,只要一、二阶可导。有这个特性是因为泰勒二阶展开,得到通用的目标函数形式。
  • 学习方法的scalability:Block结构支持并行化,支持 Out-of-core计算(这点和王浩的看法不一样,他写的是优化的trick)

XGBoost 防止过拟合的方法

  • 目标函数的正则项, 叶子节点数+叶子节点数输出分数的平方和。相当于预剪枝。
    Ω ( f t ) = γ T + 1 2 λ j = 1 T w j 2 \Omega(f_t) =\gamma T +\frac{1}{2}\lambda\sum_{j=1}^{T} w_j^2

  • 行抽样和列抽样:训练的时候只用一部分样本和一部分特征

  • 可以设置树的最大深度

  • η \eta : 可以叫学习率、步长或者shrinkage

  • Early stopping:使用的模型不一定是最终的ensemble,可以根据测试集的测试情况,选择使用前若干棵树

XGBoost的缺点

  1. 空间开销大。需要保存数据的特征值。XGBoost采用Block结构,存储指向样本的索引,需要消耗两倍的内存
  2. 时间开销大(相对于lightGBM而言)。在寻找最优切分点时,要对每个特征都进行排序,还要对每个特征的每个值都进行了遍历,并计算增益。
  3. 对Cache不友好(相对于lightGBM而言)。使用Block块预排序后,特征对梯度的访问是按照索引来获取的,是一种随机访问,而不同特征访问顺序也不一样,容易照成命中率低的问题。同时,在每一层长树的时候,需要随机访问一个行索引到叶子索引的数组,并且不同特征访问的顺序也不一样,也会造成较大的Cachemiss。

参考资料

『我爱机器学习』集成学习(三)XGBoost

猜你喜欢

转载自blog.csdn.net/zjwreal/article/details/92105075
今日推荐