XGBoost 知识点总结

目录

    • 一、提升树介绍
      • 1.1 监督学习元素
        • 1.1.1 模型和参数
        • 1.1.2 目标函数:训练损失+正则化
        • 1.1.3 为什么需要一般性原则
      • 1.2 决策树集成
      • 1.3 树提升
        • 1.3.1 Additive Training
        • 1.3.2 模型复杂度
        • 1.3.3 结构分
        • 1.3.4 学习树结构
      • 1.4 小结
    • 二、公式推导解释
      • 2.1 XGBoost 的目标函数
      • 2.2 学习第 t t t 棵树
      • 2.3 泰勒展开
      • 2.4 定义一棵树
      • 2.5 定义树的复杂度
      • 2.6 叶子结点归组
      • 2.7 树结构打分
      • 2.8 树的生长细节
        • 2.8.1 分裂一个结点
        • 2.8.2 寻找最佳分裂点
        • 2.8.3 停止生长
      • 2.9 XGBoost 推导图
    • 三、XGBoost 超参调优
      • 3.1 什么是超参
      • 3.2 XGBoost 超参
        • 3.2.1 通用参数
        • 3.2.2 Booster 参数
        • 3.2.3 学习任务参数
      • 3.3 示例
        • 3.3.1 导包
        • 3.3.2 读取数据集
        • 3.3.3 EDA(探索性数据分析)
        • 3.3.4 声明特征向量和目标变量
        • 3.3.5 切分数据集
      • 3.4 贝叶斯优化
        • 3.4.1 什么是 HYPEROPT
        • 3.4.2 贝叶斯优化
          • 3.4.2.1 初始化域空间
          • 3.4.2.2 定义目标函数
          • 3.4.2.3 优化算法
          • 3.2.3.4 打印结果
      • 3.5 参考资料
    • 四、XGBoost 面试题

一、提升树介绍

source:xgboost documents

XGBoost 是 ”Extreme Gradient Boosting“ 的简写,这里的术语 “Gradient Boosting” 源自 Friedman 的论文 Greedy Function Approximation: A Gradient Boosting Machine

梯度提升树已经出现了一段时间,并且有很多关于该主题的材料。本教程将使用监督学习的元素以一种独立且有原则的方式解释提升树。我们认为这种解释更清晰、更正式,并激发了 XGBoost 中使用的模型公式。

1.1 监督学习元素

XGBoost 用于监督学习问题,我们使用训练数据(具有多个特征) x i x_i xi 来预测目标变量 y i y_i yi。在我们具体学习树之前,让我们先回顾一下监督学习的基本元素。

1.1.1 模型和参数

监督学习中的模型通常是指根据输入 x i x_i xi 进行预测 y i y_i yi 的数学结构。一个常见的例子是 线性模型,其中预测为 y ^ i = ∑ j θ j x i j \hat{y}_i = \sum_j \theta_j x_{ij} y^i=jθjxij,加权输入特征的线性组合。预测值可以有不同的解释,具体取决于任务,即回归或分类。例如,它可以在逻辑回归中进行逻辑变换以获得正类的概率,也可以在我们想要对输出进行排序时用作排名分数。

参数是我们需要从数据中学习的未确定部分。在线性回归问题中,参数是系数 θ \theta θ。通常我们会用 θ \theta θ 来表示参数(一个模型中有很多参数,我们这里的定义比较草率)。

1.1.2 目标函数:训练损失+正则化

通过对 y i y_i yi 的明智选择,我们可以表达各种任务,例如回归、分类和排名。训练模型的任务相当于找到最适合训练数据 x i x_i xi 和标签 y i y_i yi 的最佳参数 θ \theta θ。为了训练模型,我们需要定义目标函数来衡量模型与训练数据的拟合程度。

目标函数的一个显着特点是它们由两部分组成:训练损失正则化项
obj ( θ ) = L ( θ ) + Ω ( θ ) \text{obj}(\theta) = L(\theta) + \Omega(\theta) obj(θ)=L(θ)+Ω(θ)
其中 L L L 是训练损失函数, Ω \Omega Ω 是正则化项。训练损失衡量我们的模型对训练数据的 预测 能力。 L L L 的一个常见选择是 均方误差,它由下式给出
L ( θ ) = ∑ i ( y i − y ^ i ) 2 L(\theta) = \sum_i (y_i-\hat{y}_i)^2 L(θ)=i(yiy^i)2
另一个常用的损失函数是逻辑损失,用于逻辑回归:
L ( θ ) = ∑ i [ y i ln ⁡ ( 1 + e − y ^ i ) + ( 1 − y i ) ln ⁡ ( 1 + e y ^ i ) ] L(\theta) = \sum_i[ y_i\ln (1+e^{-\hat{y}_i}) + (1-y_i)\ln (1+e^{\hat{y}_i})] L(θ)=i[yiln(1+ey^i)+(1yi)ln(1+ey^i)]

对于逻辑回归我们有:
f ( x ) = β T x L ( y , f ( x ) ) = log ⁡ ( 1 + exp ⁡ ( − y f ( x ) ) \begin{aligned} f(\mathbf{x}) &=\boldsymbol{\beta}^{T} \mathbf{x} \\ L(y, f(\mathbf{x})) &=\log (1+\exp (-y f(\mathbf{x})) \end{aligned} f(x)L(y,f(x))=βTx=log(1+exp(yf(x))
这里的 f ( x ) f(x) f(x) 实际就是上式中的 y ^ i \hat y_i y^i,公式部分参考 Notes on Logistic Loss Function

正则化项是人们通常忘记添加的。正则化项控制着模型的复杂度,这有助于我们避免过拟合。这听起来有点抽象,所以让我们考虑下图中的以下问题。给定图像左上角的输入数据点,要求你在视觉上拟合阶跃函数。你认为这三个解决方案中哪个最合适?

XGBoost 知识点总结_第1张图片

正确答案用红色标出。请考虑这在视觉上是否适合你。一般原则是我们想要一个 简单的预测 模型。两者之间的权衡在机器学习中也称为偏差-方差权衡

1.1.3 为什么需要一般性原则

上面介绍的元素构成了监督学习的基本元素,它们是机器学习工具包的天然构建块。例如,你应该能够描述梯度提升树和随机森林之间的差异和共性。以形式化的方式理解该过程还有助于我们理解我们正在学习的目标以及启发式算法(例如修剪和平滑)背后的原因。

1.2 决策树集成

现在我们已经介绍了监督学习的元素,让我们从真正的树开始。首先,让我们先了解一下 XGBoost 的模型选择:决策树集成。树集成模型由一组分类和回归树 (CART) 组成。这是一个简单的 CART 示例,用于对某人是否会喜欢假设的电脑游戏 X 进行分类。

XGBoost 知识点总结_第2张图片

我们将家庭成员分类为不同的叶子,并在相应的叶子上为他们分配分数。 CART 与决策树有点不同,决策树的叶子只包含决策值。在 CART 中,真实分数与每个叶子相关联,这为我们提供了超越分类的更丰富的解释。这也允许有原则的、统一的优化方法,我们将在本教程的后面部分看到。

通常,单棵树的强度不足以在实践中使用。实际使用的是集成模型,将多棵树的预测汇总在一起。

这是一个由两棵树组成的树集合的例子。每棵树的预测分数相加得到最终分数。如果你看这个例子,一个重要的事实是两棵树试图 相互补充。在数学上,我们可以将我们的模型写成以下形式:
y ^ i = ∑ k = 1 K f k ( x i ) , f k ∈ F \hat{y}_i = \sum_{k=1}^K f_k(x_i), f_k \in \mathcal{F} y^i=k=1Kfk(xi),fkF
这里的 K K K 是树的数量, f f f 是函数空间 F \mathcal{F} F 下的一个函数, F \mathcal{F} F 是所有可能的 CARTs,要优化的目标函数由下式给出:
obj ( θ ) = ∑ i n l ( y i , y ^ i ) + ∑ k = 1 K Ω ( f k ) \text{obj}(\theta) = \sum_i^n l(y_i, \hat{y}_i) + \sum_{k=1}^K \Omega(f_k) obj(θ)=inl(yi,y^i)+k=1KΩ(fk)
现在来一个棘手的问题:随机森林中使用的 模型 是什么?树集成!所以随机森林和提升树实际上是相同的模型;区别在于我们如何训练他们。这意味着,如果你为树集成编写预测服务,你只需要编写一个,它应该适用于随机森林和梯度提升树。 (有关实际示例,请参阅 Treelite。)

1.3 树提升

现在我们介绍了模型,让我们转向训练:我们应该如何学习树?答案是,就像所有监督学习模型一样:定义一个目标函数并优化它!

让以下成为目标函数(记住它总是需要包含训练损失和正则化):
obj = ∑ i = 1 n l ( y i , y ^ i ( t ) ) + ∑ i = 1 t Ω ( f i ) \text{obj} = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t)}) + \sum_{i=1}^t\Omega(f_i) obj=i=1nl(yi,y^i(t))+i=1tΩ(fi)

1.3.1 Additive Training

我们要问的第一个问题是:树的参数是什么?可以发现,我们需要学习的是那些函数 f i f_i fi,每个函数都包含树的结构和叶子分数。学习树结构比传统的优化问题要困难得多,传统的优化问题可以简单地取梯度。一次学习所有的树是很困难的。相反,我们使用了一种加法策略:修复我们学到的东西,一次添加一棵新树。我们将步骤 t t t 的预测值写为 y ^ i ( t ) \hat{y}_i^{(t)} y^i(t)。然后我们有:

y ^ i ( 0 ) = 0 y ^ i ( 1 ) = f 1 ( x i ) = y ^ i ( 0 ) + f 1 ( x i ) y ^ i ( 2 ) = f 1 ( x i ) + f 2 ( x i ) = y ^ i ( 1 ) + f 2 ( x i ) … y ^ i ( t ) = ∑ k = 1 t f k ( x i ) = y ^ i ( t − 1 ) + f t ( x i ) \begin{aligned}\hat{y}_i^{(0)} &= 0\\ \hat{y}_i^{(1)} &= f_1(x_i) = \hat{y}_i^{(0)} + f_1(x_i)\\ \hat{y}_i^{(2)} &= f_1(x_i) + f_2(x_i)= \hat{y}_i^{(1)} + f_2(x_i)\\ &\dots\\ \hat{y}_i^{(t)} &= \sum_{k=1}^t f_k(x_i)= \hat{y}_i^{(t-1)} + f_t(x_i)\end{aligned} y^i(0)y^i(1)y^i(2)y^i(t)=0=f1(xi)=y^i(0)+f1(xi)=f1(xi)+f2(xi)=y^i(1)+f2(xi)=k=1tfk(xi)=y^i(t1)+ft(xi)

剩下的问题是:我们在每一步都想要哪棵树?很自然的事情是添加优化我们的目标的那个。
obj ( t ) = ∑ i = 1 n l ( y i , y ^ i ( t ) ) + ∑ i = 1 t Ω ( f i ) = ∑ i = 1 n l ( y i , y ^ i ( t − 1 ) + f t ( x i ) ) + Ω ( f t ) + c o n s t a n t \begin{aligned}\text{obj}^{(t)} & = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t)}) + \sum_{i=1}^t\Omega(f_i) \\ & = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t-1)} + f_t(x_i)) + \Omega(f_t) + \mathrm{constant}\end{aligned} obj(t)=i=1nl(yi,y^i(t))+i=1tΩ(fi)=i=1nl(yi,y^i(t1)+ft(xi))+Ω(ft)+constant
如果我们考虑使用均方误差 (MSE) 作为我们的损失函数,则目标变为:
obj ( t ) = ∑ i = 1 n ( y i − ( y ^ i ( t − 1 ) + f t ( x i ) ) ) 2 + ∑ i = 1 t Ω ( f i ) = ∑ i = 1 n [ 2 ( y ^ i ( t − 1 ) − y i ) f t ( x i ) + f t ( x i ) 2 ] + Ω ( f t ) + c o n s t a n t \begin{aligned}\text{obj}^{(t)} & = \sum_{i=1}^n (y_i - (\hat{y}_i^{(t-1)} + f_t(x_i)))^2 + \sum_{i=1}^t\Omega(f_i) \\ & = \sum_{i=1}^n [2(\hat{y}_i^{(t-1)} - y_i)f_t(x_i) + f_t(x_i)^2] + \Omega(f_t) + \mathrm{constant}\end{aligned} obj(t)=i=1n(yi(y^i(t1)+ft(xi)))2+i=1tΩ(fi)=i=1n[2(y^i(t1)yi)ft(xi)+ft(xi)2]+Ω(ft)+constant

注:

令: a = y i , b = y ^ i ( t − 1 ) , c = f t ( x i ) a=y_i, b=\hat{y}_i^{(t-1)}, c=f_t(x_i) a=yi,b=y^i(t1),c=ft(xi)

则上式简化为 [ a − ( b + c ) ] 2 = a 2 + ( b + c ) 2 − 2 a ( b + c ) = a 2 + b 2 + c 2 + 2 b c − 2 a b − 2 a c [a-(b+c)]^2=a^2+(b+c)^2-2a(b+c)=a^2+b^2+c^2+2bc-2ab-2ac [a(b+c)]2=a2+(b+c)22a(b+c)=a2+b2+c2+2bc2ab2ac

其中 a 2 , b 2 , 2 a b a^2,b^2,2ab a2,b2,2ab 都为常数,剩下的项化简得 2 c ( b − a ) + c 2 2c(b-a)+c^2 2c(ba)+c2

MSE 的形式是友好的,有一个一阶项(通常称为残差)和一个二次项。对于其他的兴趣损失(例如,logistic loss),要得到这么好的形式并不是那么容易。所以在一般情况下,我们 将损失函数的泰勒展开式提升到二阶
obj ( t ) = ∑ i = 1 n [ l ( y i , y ^ i ( t − 1 ) ) + g i f t ( x i ) + 1 2 h i f t 2 ( x i ) ] + Ω ( f t ) + c o n s t a n t \text{obj}^{(t)} = \sum_{i=1}^n [l(y_i, \hat{y}_i^{(t-1)}) + g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t) + \mathrm{constant} obj(t)=i=1n[l(yi,y^i(t1))+gift(xi)+21hift2(xi)]+Ω(ft)+constant
这里的 g i g_i gi h i h_i hi 被定义为:
g i = ∂ y ^ i ( t − 1 ) l ( y i , y ^ i ( t − 1 ) ) h i = ∂ y ^ i ( t − 1 ) 2 l ( y i , y ^ i ( t − 1 ) ) \begin{aligned}g_i &= \partial_{\hat{y}_i^{(t-1)}} l(y_i, \hat{y}_i^{(t-1)})\\ h_i &= \partial_{\hat{y}_i^{(t-1)}}^2 l(y_i, \hat{y}_i^{(t-1)})\end{aligned} gihi=y^i(t1)l(yi,y^i(t1))=y^i(t1)2l(yi,y^i(t1))
去掉所有的常数后,步骤的具体目标变为:
∑ i = 1 n [ g i f t ( x i ) + 1 2 h i f t 2 ( x i ) ] + Ω ( f t ) \sum_{i=1}^n [g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t) i=1n[gift(xi)+21hift2(xi)]+Ω(ft)
这成为我们对新树的优化目标。该定义的一个重要优点是目标函数的值仅取决于 g i g_i gi h i h_i hi。这就是 XGBoost 支持自定义损失函数的方式。我们可以使用以 g i g_i gi h i h_i hi 作为输入的完全相同的求解器来优化每个损失函数,包括逻辑回归和成对排名!

1.3.2 模型复杂度

我们已经介绍了训练步骤,但是等等,还有一件重要的事情,正则化项!我们需要定义树的复杂度 Ω ( f ) \Omega(f) Ω(f)。为此,让我们首先将树 f ( x ) f(x) f(x) 的定义细化为:
f t ( x ) = w q ( x ) , w ∈ R T , q : R d → { 1 , 2 , ⋯   , T } . f_t(x) = w_{q(x)}, w \in R^T, q:R^d\rightarrow \{1,2,\cdots,T\} . ft(x)=wq(x),wRT,q:Rd{1,2,,T}.
这里 w w w 是叶子上的分数向量, q q q 是将每个数据点分配给相应叶子的函数, T T T 是叶子的数量。在 XGBoost 中,我们将复杂度定义为:
Ω ( f ) = γ T + 1 2 λ ∑ j = 1 T w j 2 \Omega(f) = \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2 Ω(f)=γT+21λj=1Twj2
当然,定义复杂度的方法不止一种,但这种方法在实践中效果很好。正则化是大多数树包不太仔细或简单地忽略的一部分。这是因为传统的树学习处理只强调改进杂质,而复杂性控制则留给启发式。通过正式定义它,我们可以更好地了解我们正在学习的内容并获得在野外表现良好的模型。

1.3.3 结构分

这里是导数神奇的部分。重写树模型后,我们可以将第 t t t 个树的目标值写为:
obj ( t ) ≈ ∑ i = 1 n [ g i w q ( x i ) + 1 2 h i w q ( x i ) 2 ] + γ 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 \begin{aligned}\text{obj}^{(t)} &\approx \sum_{i=1}^n [g_i w_{q(x_i)} + \frac{1}{2} h_i w_{q(x_i)}^2] + \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2\\ &= \sum^T_{j=1} [(\sum_{i\in I_j} g_i) w_j + \frac{1}{2} (\sum_{i\in I_j} h_i + \lambda) w_j^2 ] + \gamma T\end{aligned} obj(t)i=1n[giwq(xi)+21hiwq(xi)2]+γT+21λj=1Twj2=j=1T[(iIjgi)wj+21(iIjhi+λ)wj2]+γT
这里的 I j = { i ∣ q ( x i ) = j } I_j = \{i|q(x_i)=j\} Ij={iq(xi)=j} 是分配给第 j j j 个叶子的数据点指数集合。请注意,在第二行中,我们更改了总和索引,因为同一叶子上的所有数据点都获得相同的分数。我们进一步通过定义 G j = ∑ i ∈ I j g i G_j = \sum_{i\in I_j} g_i Gj=iIjgi H j = ∑ i ∈ I j h i H_j = \sum_{i\in I_j} h_i Hj=iIjhi 简化上式:
obj ( t ) = ∑ j = 1 T [ G j w j + 1 2 ( H j + λ ) w j 2 ] + γ T \text{obj}^{(t)} = \sum^T_{j=1} [G_jw_j + \frac{1}{2} (H_j+\lambda) w_j^2] +\gamma T obj(t)=j=1T[Gjwj+21(Hj+λ)wj2]+γT
在这个等式中, w j w_j wj 彼此独立, G j w j + 1 2 ( H j + λ ) w j 2 G_jw_j+\frac{1}{2}(H_j+\lambda)w_j^2 Gjwj+21(Hj+λ)wj2 的形式是二次的,对于给定的结构 q ( x ) q(x) q(x) 来说是最好的是 w j w_j wj,我们可以得到的最佳目标减少是:
w j ∗ = − G j H j + λ obj ∗ = − 1 2 ∑ j = 1 T G j 2 H j + λ + γ T \begin{aligned}w_j^\ast &= -\frac{G_j}{H_j+\lambda}\\ \text{obj}^\ast &= -\frac{1}{2} \sum_{j=1}^T \frac{G_j^2}{H_j+\lambda} + \gamma T\end{aligned} wjobj=Hj+λGj=21j=1THj+λGj2+γT
最后一个方程测量树结构 q ( x ) q(x) q(x)多好

如果这一切听起来有点复杂,让我们来看看图片,看看如何计算分数。基本上,对于给定的树结构,我们推的统计 g i g_i gi h i h_i hi 到他们所属的叶子,总结统计在一起,并使用公式来计算树有多好。此分数就像决策树中的杂质度,只是它也考虑到了模型的复杂性。

1.3.4 学习树结构

现在,我们有一种方法来衡量一棵树有多好,理想情况下,我们会列举所有可能的树木,并挑选最好的一棵。在实践中,这是棘手的,所以我们将尝试一次优化一个级别的树。具体来说,我们试图把一片叶子分成两片叶子,它获得的分数是:
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} \left[\frac{G_L^2}{H_L+\lambda}+\frac{G_R^2}{H_R+\lambda}-\frac{(G_L+G_R)^2}{H_L+H_R+\lambda}\right] - \gamma Gain=21[HL+λGL2+HR+λGR2HL+HR+λ(GL+GR)2]γ
此公式可分解为:

  1. 新左叶上的分数
  2. 新右叶上的分数
  3. 原叶上的分数
  4. 附加叶子上的正则化项

我们在这里可以看到一个重要的事实:如果收益小于 γ \gamma γ,我们最好不要添加该分支。这正是树模型中的剪枝技术!通过使用监督学习的原则,我们自然可以想出这些技术工作的原因:)

对于真正有价值的数据,我们通常希望搜索最佳拆分。为了有效地做到这一点,我们按排序顺序排列所有实例,如以下图片。

XGBoost 知识点总结_第3张图片

从左到右扫描足以计算所有可能的拆分解决方案的结构分数,并且我们可以有效地找到最佳的拆分。

注意:

additive tree 学习方法的局限性

由于列举所有可能的树结构是难以确定的,因此我们一次添加一个拆分。这种方法在大多数情况下都效果很好,但有些边缘案例由于这种方法而失败。对于这些边缘案例,训练会导致模型退化,因为我们一次只考虑一个功能维度。例如,可以查看梯度提升可以学习简单的算术吗?。

1.4 小结

既然你明白什么是提升树,你可能会问,XGBoost 的介绍在哪里?XGBoost 正是一个工具,灵感来自本教程中引入的正式原则!更重要的是,它的发展既在系统优化方面,又在机器学习的原则方面。该库的目标是突破机器计算极限,提供可扩展、便携且准确的库。确保尝试一下,最重要的是,贡献你的智慧(代码,例子,教程)到社区!


二、公式推导解释

对上文中涉及到的公式推导过程进行详细展开解释。

source: XGBoost超详细推导,终于有人讲明白了!

另附作者陈天奇 PPT:https://web.njit.edu/~usman/courses/cs675_summer20/BoostedTree.pdf

PPT 的中文逐页详解:https://blog.csdn.net/weixin_45551676/article/details/106049819

2.1 XGBoost 的目标函数

XGBoost 的目标函数由训练损失正则化项两部分组成,目标函数定义如下:
obj ( θ ) = ∑ i n l ( y i , y ^ i ) + ∑ k = 1 K Ω ( f k ) \text{obj}(\theta) = \sum_i^n l(y_i, \hat{y}_i) + \sum_{k=1}^K \Omega(f_k) obj(θ)=inl(yi,y^i)+k=1KΩ(fk)
右侧第一项为训练损失,第二项为正则化,定义为树的复杂度。

  • x i x_i xi:第 i i i 个样本
  • y i y_i yi:第 i i i 个样本 x i x_i xi 的标签值
  • y ^ i \hat y_i y^i:第 i i i 个样本 x i x_i xi 的预测值,是所有树的分值累加和, y ^ i = ∑ k = 1 K f k ( x i ) , f k ∈ F \hat{y}_i = \sum_{k=1}^K f_k(x_i), f_k \in \mathcal{F} y^i=k=1Kfk(xi),fkF
  • F \mathcal{F} F 是所有可能的 CARTs
  • n n n:一共有 n n n 个样本
  • K K K:一共有 K K K 个树
  • f k f_k fk:第 k k k 个树的 function
  • l ( y i , y ^ i ) l(y_i, \hat y_i) l(yi,y^i) :第 i i i 个样本点真实标签值与预测值之间的误差,如均方误差, l ( y i , y ^ i ) = ( y i − y ^ i ) 2 l(y_i, \hat y_i)=(y_i-\hat y_i)^2 l(yi,y^i)=(yiy^i)2
  • Ω ( f k ) \Omega(f_k) Ω(fk):第 k k k 个树的复杂度,这个后面会展开介绍
  • ∑ k = 1 K Ω ( f k ) \sum_{k=1}^K \Omega(f_k) k=1KΩ(fk) :对全部的 K K K 个树复杂度求和,添加到目标函数中作为正则化项,用于防止模型过度拟合

2.2 学习第 t t t 棵树

由于 XGBoost 是一个 boosting 加法模型,因此假设我们第 t t t 次迭代要训练的树模型是 f t ( x ) f_t(x) ft(x) ,则有:
y ^ i ( t ) = ∑ k = 1 t f k ( x i ) = y ^ i ( t − 1 ) + f t ( x i ) \hat{y}_i^{(t)} = \sum_{k=1}^t f_k(x_i)= \hat{y}_i^{(t-1)} + f_t(x_i) y^i(t)=k=1tfk(xi)=y^i(t1)+ft(xi)

  • y ^ i ( t ) \hat{y}_i^{(t)} y^i(t):第 t t t 次迭代后第 i i i 个样本的预测值
  • y ^ i ( t − 1 ) \hat{y}_i^{(t-1)} y^i(t1):前 t − 1 t-1 t1 棵树对第 i i i 个样本的预测结果
  • f t ( x i ) f_t(x_i) ft(xi):第 t t t 棵树对 x i x_i xi 这个样本的预测结果

将上式带入目标函数中 ,可以得到:
obj ( t ) = ∑ i = 1 n l ( y i , y ^ i ( t ) ) + ∑ i = 1 t Ω ( f i ) = ∑ i = 1 n l ( y i , y ^ i ( t − 1 ) + f t ( x i ) ) + Ω ( f t ) + c o n s t a n t \begin{aligned}\text{obj}^{(t)} & = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t)}) + \sum_{i=1}^t\Omega(f_i) \\ & = \sum_{i=1}^n l(y_i, \hat{y}_i^{(t-1)} + f_t(x_i)) + \Omega(f_t) + \mathrm{constant}\end{aligned} obj(t)=i=1nl(yi,y^i(t))+i=1tΩ(fi)=i=1nl(yi,y^i(t1)+ft(xi))+Ω(ft)+constant
注意上式中,只有一个变量,那就是第 t t t 棵树 , f t ,f_t ft其余的都是已知量或可通过已知量可以计算出来的。这里我们将正则化项进行了拆分,由于前 t − 1 t-1 t1 棵树的结构已经确定,因此,前 t − 1 t-1 t1 棵树的复杂度之和可以用一个常量表示:
∑ k = 1 t Ω ( f k ) = Ω ( f t ) + ∑ k = 1 t − 1 Ω ( f k ) = Ω ( f t ) + constant \sum^t_{k=1}\Omega(f_k)=\Omega(f_t)+\sum^{t-1}_{k=1}\Omega(f_k)=\Omega(f_t)+\text{constant} k=1tΩ(fk)=Ω(ft)+k=1t1Ω(fk)=Ω(ft)+constant

2.3 泰勒展开

泰勒展开是将一个在 x = x 0 x = x_0 x=x0 处具有 n n n 阶导数的函数 f ( x ) f(x) f(x) 利用关于 ( x − x 0 ) (x-x_0) (xx0) n n n 次多项式来逼近函数的方法。泰勒公式的二阶展开形式如下:
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} f(x+Δx)f(x)+f(x)Δx+21f(x)Δx2
回到我们的问题上来, f ( x ) f(x) f(x) 对应于我们的损失函数 l l l x x x 对应于前 t − 1 t-1 t1 棵树的预测值, Δ x \Delta x Δx 对应于我们正在训练的第 t t t 棵树的预测值。

首先定义损失函数 l l l 关于 y ^ ( t − 1 ) \hat y^{(t-1)} y^(t1) 的一阶偏导数和二阶偏导数:
g i = ∂ y ^ i ( t − 1 ) l ( y i , y ^ i ( t − 1 ) ) h i = ∂ y ^ i ( t − 1 ) 2 l ( y i , y ^ i ( t − 1 ) ) \begin{aligned}g_i &= \partial_{\hat{y}_i^{(t-1)}} l(y_i, \hat{y}_i^{(t-1)})\\ h_i &= \partial_{\hat{y}_i^{(t-1)}}^2 l(y_i, \hat{y}_i^{(t-1)})\end{aligned} gihi=y^i(t1)l(yi,y^i(t1))=y^i(t1)2l(yi,y^i(t1))
那么,我们的损失函数就可以转化为下式:
l ( y i , y ^ i ( t − 1 ) + f t ( x i ) ) ≃ l ( y i , y ^ i ( t − 1 ) ) + g i f t ( x i ) + 1 2 h i f t 2 ( x i ) l\left(y_{i}, \hat{y}_{i}^{(t-1)}+f_{t}\left(x_{i}\right)\right)\simeq l\left(y_{i}, \hat{y}_{i}^{(t-1)}\right)+g_{i} f_{t}\left(x_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(x_{i}\right) l(yi,y^i(t1)+ft(xi))l(yi,y^i(t1))+gift(xi)+21hift2(xi)
再将上式带入到目标函数中,可以得到目标函数的近似值:
obj ( t ) = ∑ i = 1 n [ l ( y i , y ^ i ( t − 1 ) ) + g i f t ( x i ) + 1 2 h i f t 2 ( x i ) ] + Ω ( f t ) + c o n s t a n t \text{obj}^{(t)} = \sum_{i=1}^n [l(y_i, \hat{y}_i^{(t-1)}) + g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t) + \mathrm{constant} obj(t)=i=1n[l(yi,y^i(t1))+gift(xi)+21hift2(xi)]+Ω(ft)+constant
去掉全部常数项,得到目标函数:
∑ i = 1 n [ g i f t ( x i ) + 1 2 h i f t 2 ( x i ) ] + Ω ( f t ) \sum_{i=1}^n [g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t) i=1n[gift(xi)+21hift2(xi)]+Ω(ft)

2.4 定义一棵树

我们重新定义一颗树,包括两个部分:

  • 叶子结点的权重向量 w w w
  • 实例 -> 叶子结点的映射关系 q q q(本质是树的分支结构)

一棵树的表达形式定义如下:
f t ( x ) = w q ( x ) , w ∈ R T , q : R d → { 1 , 2 , ⋯   , T } f_t(x) = w_{q(x)}, w \in R^T, q:R^d\rightarrow \{1,2,\cdots,T\} ft(x)=wq(x),wRT,q:Rd{1,2,,T}

  • w w w:是长度为 T T T 的一维向量,代表树 q q q 各叶子节点的权重
  • q q q:代表一棵树的结构,作用是,将输入 x i ∈ R d x_i \in R^d xiRd 映射到某个叶子节点,假设这棵树有 T T T 个叶子节点

2.5 定义树的复杂度

我们定义一棵树的复杂度 Ω \Omega Ω ,它由两部分组成:

  • 叶子结点的数量;
  • 叶子结点权重向量的 L 2 L_2 L2 范数;

Ω ( f ) = γ T + 1 2 λ ∑ j = 1 T w j 2 \Omega(f) = \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2 Ω(f)=γT+21λj=1Twj2

2.6 叶子结点归组

我们将属于第 j j j 个叶子结点的所有样本 x i x_i xi , 划入到一个叶子结点样本集中,数学表示如下:
I j = { i ∣ q ( x i ) = j } I_j = \{i|q(x_i)=j\} Ij={iq(xi)=j}
然后,将 2.4 和 2.5 中一棵树及其复杂度的定义,带入到 2.3 中泰勒展开后的目标函数中,具体推导如下:
obj ( t ) ≈ ∑ i = 1 n [ g i w q ( x i ) + 1 2 h i w q ( x i ) 2 ] + γ 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 \begin{aligned}\text{obj}^{(t)} &\approx \sum_{i=1}^n [g_i w_{q(x_i)} + \frac{1}{2} h_i w_{q(x_i)}^2] + \gamma T + \frac{1}{2}\lambda \sum_{j=1}^T w_j^2\\ &= \sum^T_{j=1} [(\sum_{i\in I_j} g_i) w_j + \frac{1}{2} (\sum_{i\in I_j} h_i + \lambda) w_j^2 ] + \gamma T\end{aligned} obj(t)i=1n[giwq(xi)+21hiwq(xi)2]+γT+21λj=1Twj2=j=1T[(iIjgi)wj+21(iIjhi+λ)wj2]+γT
这里的 ∑ j = 1 T \sum^T_{j=1} j=1T 是将所有训练样本,按叶子结点进行了分组。为进一步简化该式,我们进行如下定义:
G j = ∑ i ∈ I j g i H j = ∑ i ∈ I j h i G_j = \sum_{i\in I_j} g_i \\ H_j = \sum_{i\in I_j} h_i Gj=iIjgiHj=iIjhi

  • G j G_j Gj叶子结点 j j j 所包含样本的一阶偏导数累加之和,是一个常量;

  • H j H_j Hj叶子结点 j j j 所包含样本的二阶偏导数累加之和,是一个常量;

G j G_j Gj H j H_j Hj 带入目标函数中,得到最终的目标函数(注意,此时式中的变量只剩下第 t t t 棵树的权重向量 w w w):
obj ( t ) = ∑ j = 1 T [ G j w j + 1 2 ( H j + λ ) w j 2 ] + γ T \text{obj}^{(t)} = \sum^T_{j=1} [G_jw_j + \frac{1}{2} (H_j+\lambda) w_j^2] +\gamma T obj(t)=j=1T[Gjwj+21(Hj+λ)wj2]+γT

2.7 树结构打分

回忆一下高中数学知识。假设有一个一元二次函数,形式如下:
y = G x + 1 2 H x 2 , H > 0 y = Gx + \frac{1}{2}H x^2, H > 0 y=Gx+21Hx2,H>0
我们可以套用一元二次函数的最值公式轻易地求出最值点:
x ∗ = − b 2 a = − G H x^* = -\frac{b}{2a}=-\frac{G}{H} x=2ab=HG
那回到我们的目标函数 (公式 24),该如何求出它的最值呢?

先简单分析一下目标函数,对于每个叶子结点 j j j , 可以将其从目标式中拆解出:
G j w j + 1 2 ( H j + λ ) w j 2 G_jw_j + \frac{1}{2} (H_j+\lambda) w_j^2 Gjwj+21(Hj+λ)wj2
上面提到过, G j G_j Gj H j H_j Hj 相对于第 t t t 棵树来说是可以计算出来的。那么,这个式子就是一个只包含一个变量叶子结点权重 w j w_j wj 的一元二次函数,我们就可以通过最值公式求出它的最值点。

再次分析一下目标函数可以发现,各个叶子结点的目标子式是相互独立的,也就是说,当每个叶子结点的子式都达到最值点时,整个目标函数式才达到最值点。

那么,假设目前树的结构已经固定,套用一元二次函数的最值公式,我们可以轻易求出,每个叶子结点的权重 w j ∗ w_j^* wj 及其此时达到最优的目标值:

w j ∗ = − G j H j + λ obj = − 1 2 ∑ j = 1 T G j 2 h j + λ + γ T w_j^* = -\frac{G_j}{H_j+\lambda} \\ \text{obj}=-\frac{1}{2}\sum^T_{j=1}\frac{G_j^2}{h_j+\lambda}+\gamma T wj=Hj+λGjobj=21j=1Thj+λGj2+γT

2.8 树的生长细节

2.8.1 分裂一个结点

在实际训练过程中,当建立第 t t t 棵树时,XGBoost 采用贪心法进行树结点的分裂,

从树深为 0 时开始:

  • 对树中的每个叶子结点尝试进行分裂;
  • 每次分裂后,原来的一个叶子结点继续分裂为左右两个子叶子结点,原叶子结点中的样本集将根据该结点的判断规则分散到左右两个叶子结点中;
  • 新分裂一个结点后,我们需要检测这次分裂是否会给损失函数带来增益,增益的定义如下:

 Gain  = Obj L + R − ( Obj L + Obj R ) = [ − 1 2 ( G L + G R ) 2 H L + H R + λ + γ ] − [ − 1 2 ( G L 2 H L + λ + G R 2 H R + λ ) + 2 γ ] = 1 2 [ G L 2 H L + λ + G R 2 H R + λ − ( G L + G R ) 2 H L + H R + λ ] − γ \begin{aligned} \text { Gain } &= \text{Obj}_{L+R}-\left(\text{Obj}_{L}+\text{Obj}_{R}\right) \\ &=\left[-\frac{1}{2} \frac{\left(G_{L}+G_{R}\right)^{2}}{H_{L}+H_{R}+\lambda}+\gamma\right]-\left[-\frac{1}{2}\left(\frac{G_{L}^{2}}{H_{L}+\lambda}+\frac{G_{R}^{2}}{H_{R}+\lambda}\right)+2 \gamma\right] \\ &=\frac{1}{2}\left[\frac{G_{L}^{2}}{H_{L}+\lambda}+\frac{G_{R}^{2}}{H_{R}+\lambda}-\frac{\left(G_{L}+G_{R}\right)^{2}}{H_{L}+H_{R}+\lambda}\right]-\gamma \end{aligned}  Gain =ObjL+R(ObjL+ObjR)=[21HL+HR+λ(GL+GR)2+γ][21(HL+λGL2+HR+λGR2)+2γ]=21[HL+λGL2+HR+λGR2HL+HR+λ(GL+GR)2]γ

如果增益 Gain > 0 \text{Gain}>0 Gain>0,即分裂为两个叶子节点后,目标函数下降了,那么我们会考虑此次分裂的结果。但是,在一个结点分裂时,可能有很多个分裂点,每个分裂点都会产生一个增益,如何才能寻找到最优的分裂点呢?

2.8.2 寻找最佳分裂点

在分裂一个结点时,我们会有很多个候选分割点,寻找最佳分割点的大致步骤如下:

  • 遍历每个结点的每个特征;
  • 对每个特征,按特征值大小将特征值排序;
  • 线性扫描,找出每个特征的最佳分裂特征值;
  • 在所有特征中找出最好的分裂点(分裂后增益最大的特征及特征值)

上面是一种贪心的方法,每次进行分裂尝试都要遍历一遍全部候选分割点,也叫做全局扫描法。

但当数据量过大导致内存无法一次载入或者在分布式情况下,贪心算法的效率就会变得很低,全局扫描法不再适用。基于此,XGBoost 提出了一系列加快寻找最佳分裂点的方案:

  • 特征预排序+缓存:XGBoost 在训练之前,预先对每个特征按照特征值大小进行排序,然后保存为 block 结构,后面的迭代中会重复地使用这个结构,使计算量大大减小。
  • 分位点近似法:对每个特征按照特征值排序后,采用类似分位点选取的方式,仅仅选出常数个特征值作为该特征的候选分割点,在寻找该特征的最佳分割点时,从候选分割点中选出最优的一个。
  • 并行查找:由于各个特性已预先存储为 block 结构,XGBoost 支持利用多个线程并行地计算每个特征的最佳分割点,这不仅大大提升了结点的分裂速度,也极利于大规模训练集的适应性扩展。

2.8.3 停止生长

一棵树不会一直生长下去,下面是一些常见的限制条件。

  1. 当新引入的一次分裂所带来的增益 Gain < 0 \text{Gain}<0 Gain<0 时,放弃当前的分裂。这是训练损失和模型结构复杂度的博弈过程。
  2. 当树达到最大深度时,停止建树,因为树的深度太深容易出现过拟合,这里需要设置一个超参 max_depth
  3. 当引入一次分裂后,重新计算新生成的左、右两个叶子结点的样本权重和。如果任一个叶子结点的样本权重低于某一个阈值,也会放弃此次分裂。这涉及到一个超参数:最小样本权重和,是指如果一个叶子节点包含的样本数量太少也会放弃分裂,防止树分的太细,这也是过拟合的一种措施。

2.9 XGBoost 推导图

XGBoost 知识点总结_第4张图片


三、XGBoost 超参调优

source: https://www.kaggle.com/prashant111/a-guide-on-xgboost-hyperparameters-tuning

XGBoost 算法提供了大范围的超参数。我们应该知道如何调整这些超参数以改进和充分利用 XGBoost 模型。

3.1 什么是超参

XGBoost 是一种非常强大的算法。因此,它将有更多的设计决策,会有大量的超参数,这些是由算法手动指定并在整个训练阶段固定的参数。在基于树的模型中,超参数包括诸如树的最大深度、要生长的树的数量、构建每棵树时要考虑的变量数量、叶子上的最小样本数量以及用于建一棵树的观测值分数等。尽管我们专注于优化 XGBoost 超参数,但下面讨论的概念也适用于任何其他高级 ML 算法。

3.2 XGBoost 超参

通常,XGBoost 超参数分为以下 4 类:

  • 通用参数
  • Booster 参数
  • 学习任务参数
  • 命令行参数

在运行一个 XGBoost 模型之前,我们必须设置三类参数——通用参数、Booster 参数和任务参数。第四类参数是命令行参数。它们仅用于 XGBoost 的控制台版本。因此,我们将跳过这些参数并将我们的讨论限制在前三种类型的参数上。

3.2.1 通用参数

这些参数指导 XGBoost 模型的整体运行,在本节中,我们将讨论三个超参数:boosterverbositynthread。关于通用参数更多内容查看 XGBoost General Parameters。

  • booster:要选择的 booster 是哪个,有 gbtreegblineardart 三种选择,其中 gbtreedart 是基于树的模型,gblinear 是线性模型,默认是 gbtree。通常基于树的要比线性模型要好,默认值就是一个较好的选择。
  • verbosity:是否开启静默(不打印消息),0 (silent), 1 (warning), 2 (info), 3 (debug),默认值是 1,打印的消息可能有助于理解模型,建议选择 1.
  • nthread:用于运行 XGBoost 的并行线程数,应输入系统中的内核数。如果你希望在所有内核上运行,则无需设置该参数,算法将自动检测。因此不设置该参数,默认值就是最大可用内核数。这个根据实际机器负载情况进行设置即可。

还有一些参数,如 disable_default_eval_metric,如果不使用默认的评估指标则设置为 true,否则保持默认参数 false 不变。另外还有两个参数 num_pbuffer 以及 num_feature 都是自动检测的,无需用户设置。

3.2.2 Booster 参数

我们有两种类型的 booster,tree booster 和 linear booster。由于基于树的 booster 几乎总是优于线性模型,因此我们只讨论基于树的 booster,关于 booster 参数更多细节查看 Parameters for Tree Booster。

  • eta:别名 learning_rate,更新中使用步长收缩以防止过度拟合。在每一步 boosting 之后,我们可以直接得到新特征的权重,eta 缩小特征权重,使 boosting 过程更加保守。取值范围是 [0, 1],典型值为:0.01-0.2

  • gamma:别名 min_split_loss,只有当结果分裂使损失函数正减少时,节点才会分裂。Gamma 指定进行拆分所需的损失最小减少多少。它使算法保守。这些值可以根据损失函数而变化,并且应该进行调整。gamma 越大,算法就越保守。取值范围为 [0, ∞ \infty ],默认值是 0.

  • max_depth:树的最大深度,它用于控制过拟合,因为更大的深度将允许模型学习非常特定于特定样本的关系。增加这个值会使模型更复杂,更容易过拟合。在设置较大的 max_depth 值时我们应该小心,因为 XGBoost 在训练深度树时会积极消耗内存。默认值是 6,应该通过 CV 进行调整,典型值是 3-10

  • min_child_weight:它定义了孩子所需的所有观察的最小权重总和,这类似于 GBM 中的 min_child_leaf,但不完全相同。这是指观察的最小“权重总和”,而 GBM 具有最小的“观察数”。也用于控制过拟合,较高的值会阻止模型学习可能高度特定于为树选择的特定样本的关系。但太高会导致欠拟合,应使用 CV 调整,取值范围 [0, ∞ \infty ],默认值是 1

  • max_delta_step:我们允许每个叶子输出的最大增量步长。如果该值设置为 0,则表示没有约束。如果将其设置为正值,则有助于使更新步骤更加保守。通常不需要这个参数,但当类极度不平衡时它可能有助于逻辑回归。将其设置为 1-10 的值可能有助于控制更新。默认值是 0

  • subsample:训练实例的子样本比率。将其设置为 0.5 意味着 XGBoost 将在建树之前随机采样一半的训练数据。这将防止过度拟合。子采样将在每次提升迭代中发生一次。较低的值使算法更加保守并防止过拟合,但过小的值可能会导致欠拟合。默认值是 1,典型值是 0.5-1

  • sampling_method:采样方法,uniform,均匀采样,每个样本被选择的概率相同,典型设置 subsample >=0.5 较好;另一种方式是 gradient_based:每个训练实例的选择概率与梯度的正则化绝对值成正比(更特别的, g 2 + λ h 2 \sqrt{g^2+\lambda h^2} g2+λh2 ),subsample 可以设置为低至 0.1,而不会损失模型精度。注意这种采样方式只有在tree_method 设置为 gpu_hist 时才支持;其他树方法只支持 uniform 采样,默认值 uniform,因此该参数基本不用调。

  • colsample_bytree, colsample_bylevel, colsample_bynode 这是用于对列进行二次采样的一系列参数。所有的 colsample_by_* 参数的范围为 (0, 1],默认值是 1,指定被采样的列的比例;colsample_bytree 是构建每棵树时列的子样本比率。对构建的每棵树进行一次子采样。colsample_bylevel 是每个级别的列子样本比率。对于在树中达到的每个新深度级别,子采样发生一次。列是从为当前树选择的列集中子采样的。colsample_bynode 是每个节点(拆分)的列子样本比率。每次评估新的分割时都会进行一次子采样。列是从为当前级别选择的列集中子采样的。它们是累积的,这意味着 {'colsample_bytree':0.5, 'colsample_bylevel':0.5, 'colsample_bynode':0.5} 组合在 64 个特征下,每个叶子有 8 个特征。

  • lambda:别名 reg_lambda,权重的 L2 正则化项,也叫岭回归,用于处理 XGBoost 中的正则化,增加该值可以使模型更保守,默认值是 1

  • alpha:别名 reg_alpha,L1 正则化项,也叫 Lasso 回归,默认值 0

  • tree_method:构建树的算法,支持 approxhist、以及分布式训练的 gpu_hist。可供选择有:auto, exact, approx, hist, gpu_hist

    • auto:启发式选择最快的方法,小到中等数据集,使用精确贪婪(exact);大数据集,选择近似(approx),推荐使用 histgpu_hist 在大数据集上获取更好性能;由于旧行为始终在单机中使用精确贪婪,因此在选择近似算法以通知此选项时,用户将获得一条消息。
    • exact:精确贪婪算法
    • approx:使用分位数草图和梯度直方图的近似贪婪算法。
    • hist:快速直方图优化近似贪婪算法。它使用了一些性能改进,例如 bins 缓存。
    • gpu_hist:GPU 实现的 hist 算法

    默认值是 auto

  • scale_pos_weight:它控制正负权重的平衡,主要用于非平衡的类,在高类别不平衡的情况下应使用大于 0 的值,因为它有助于更快的收敛。典型的值: sum(negative instances) / sum(positive instances).
  • max_leaves:要添加的最大节点数,仅当设置了 grow_policy=lossguide 时才相关。
  • 还有一些其他参数,如 sketch_eps,updater, refresh_leaf, process_type, grow_policy, max_bin, predictornum_parallel_tree.

3.2.3 学习任务参数

这些参数用于定义优化目标,即在每一步计算的度量。它们用于指定学习任务和相应的学习目标。

  • objective:它定义了要最小化的损失函数,默认是 reg:squarederror,最常用的损失函数有:
    • reg:squarederror:回归平方损失
    • reg:squaredlogerror:回归平方对数损失, 1 2 [ l o g ( p r e d + 1 ) − l o g ( l a b e l + 1 ) ] 2 \frac{1}{2}[log(pred + 1) - log(label + 1)]^2 21[log(pred+1)log(label+1)]2,所有的输入标签要求比 -1 大。
    • reg:logistic:逻辑回归
    • binary:logistic:用于二分类的逻辑回归,输出概率
    • binary:logitraw:用于二分类的逻辑回归,在逻辑转换前输出分值
    • binary:hinge:用于二分类的 hinge 损失,它的预测是 0 或 1,而不是概率
    • multi:softmax:用于多分类的 softmax,也需要设置num_class
    • multi:softprob:与 softmax 相同,但输出 ndata * nclass 的向量,该向量可以进一步重塑为 ndata * nclass 矩阵。结果包含属于每个类的每个数据点的预测概率。
    • 更多的损失函数查看 Learning Task Parameters
  • eval_metric,在验证集上使用的评估指标,默认值是根据所选用的目标函数而定,对于回归是 rmse,对于分类是 logloss,对于 ranking 是 mean average precision。我们可以加入多个评估指标,对于 Python,传递的是一个参数对列表,而不是 map。最常用的评估指标有:
    • rmse:均方根误差
    • mae:平均绝对值误差
    • logloss:负对数似然
    • error:二分类误差率 (阈值 0.5)。计算公式: #(wrong cases)/#(all cases),对于预测,评估会将预测值大于 0.5 的实例视为正实例,将其他实例视为负实例。
    • merror:多分类误差率,计算公式: #(wrong cases)/#(all cases).
    • mlogloss:多分类负对数似然
    • auc: Area under the curve
    • aucpr: Area under the PR curve
  • seed:随机种子值,默认是 0,主要用于生成可复现结果

3.3 示例

3.3.1 导包

import pandas as pd
import numpy as np
import xgboost as xgb
from sklearn.metrics import accuracy_score
from hyperopt import STATUS_OK, Trials, fmin, hp, tpe   # 超参调整

3.3.2 读取数据集

数据集的描述:https://www.kaggle.com/binovi/wholesale-customers-data-set

data = "/content/drive/MyDrive/Wholesale customers data.csv"
df = pd.read_csv(data)

3.3.3 EDA(探索性数据分析)

  • 数据集维度:440 个实例,8 个特征维度

    df.shape
    # (440, 8)
    
  • preview 数据集

    df.head()
    
    	Channel	Region	Fresh	Milk	Grocery	Frozen	Detergents_Paper	Delicassen
    0	2		3		12669	9656	7561	214		2674				1338
    1	2		3		7057	9810	9568	1762	3293				1776
    2	2		3		6353	8808	7684	2405	3516				7844
    3	1		3		13265	1196	4221	6404	507					1788
    4	2		3		22615	5410	7198	3915	1777				5185
    

    Channel变量包含 1 和 2,这两个值将来自两个不同渠道的客户分类为:1 适用于 Horeca(酒店/零售/咖啡馆)客户和 2 零售渠道(名义)客户。

  • 数据集摘要

    df.info()
    
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 440 entries, 0 to 439
    Data columns (total 8 columns):
     #   Column            Non-Null Count  Dtype
    ---  ------            --------------  -----
     0   Channel           440 non-null    int64
     1   Region            440 non-null    int64
     2   Fresh             440 non-null    int64
     3   Milk              440 non-null    int64
     4   Grocery           440 non-null    int64
     5   Frozen            440 non-null    int64
     6   Detergents_Paper  440 non-null    int64
     7   Delicassen        440 non-null    int64
    dtypes: int64(8)
    memory usage: 27.6 KB
    

    这里只有数值类变量,且均无缺失值。

  • 数据集的统计信息,各个变量的总计,均值,方差,最值,四等分点值等信息

    df.describe()
    

3.3.4 声明特征向量和目标变量

X = df.drop('Channel', axis=1)
y = df['Channel']

把标签值,1 和 2 转换成 0 和 1,方便进一步分析:

y[y == 2] = 0
y[y == 1] = 1

3.3.5 切分数据集

将数据集划分为训练集和测试集

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 0)

3.4 贝叶斯优化

贝叶斯优化是优化或寻找机器学习或深度学习算法的最佳参数。优化是寻找成本函数最小值的过程,它决定了模型在训练集和测试集上的整体更好性能。在这个过程中,我们用各种可能的参数范围训练模型,直到获得最佳拟合模型。超参数调整有助于确定最佳调整参数并返回最佳拟合模型,这是构建 ML 或 DL 模型时要遵循的最佳实践。在本节中,我们将讨论最准确和最成功的超参数调整方法之一,即使用 HYPEROPT 的贝叶斯优化。让我们开始 HYPEROPT。

3.4.1 什么是 HYPEROPT

HYPEROPT 是一个强大的 Python 库,它搜索值的超参数空间并找到产生最小损失函数的最佳值。贝叶斯优化技术使用 Hyperopt 来调整模型超参数。 Hyperopt 是一个 Python 库,用于调整模型超参数。更多信息查看:https://hyperopt.github.io/hyperopt/?source=post_page

3.4.2 贝叶斯优化

3.4.2.1 初始化域空间
space={'max_depth': hp.quniform("max_depth", 3, 18, 1),
        'gamma': hp.uniform ('gamma', 1,9),
        'reg_alpha' : hp.quniform('reg_alpha', 40,180,1),
        'reg_lambda' : hp.uniform('reg_lambda', 0,1),
        'colsample_bytree' : hp.uniform('colsample_bytree', 0.5,1),
        'min_child_weight' : hp.quniform('min_child_weight', 0, 10, 1),
        'n_estimators': 180,
        'seed': 0
    }

可用的 hyperopt 优化算法有:

  • hp.choice(label, options) — 返回选项之一,它应该是一个列表或元组。
  • hp.randint(label, upper) — 返回范围 [0, upper) 之间的随机整数。
  • hp.uniform(label, low, high) — 返回一个介于 low 和 high 之间的值。
  • hp.quniform(label, low, high, q)round(uniform(low, high) / q) * q,四舍五入
  • hp.normal(label, mean, std) — 返回一个正态分布的真实值,均值和标准差 sigma。
3.4.2.2 定义目标函数
def objective(space):
    clf=xgb.XGBClassifier(
        n_estimators =space['n_estimators'], 
        max_depth = int(space['max_depth']), 
        gamma = space['gamma'],
        reg_alpha = int(space['reg_alpha']),
        min_child_weight=int(space['min_child_weight']),
        colsample_bytree=int(space['colsample_bytree'])
    )
    
    evaluation = [( X_train, y_train), ( X_test, y_test)]
    
    clf.fit(
        X_train, 
        y_train,
        eval_set=evaluation, 
        eval_metric="auc",
        early_stopping_rounds=10,
        verbose=False
    )
    

    pred = clf.predict(X_test)
    accuracy = accuracy_score(y_test, pred>0.5)
    print ("SCORE:", accuracy)
    return {'loss': -accuracy, 'status': STATUS_OK }
3.4.2.3 优化算法
trials = Trials()

best_hyperparams = fmin(fn = objective,
                        space = space,
                        algo = tpe.suggest,
                        max_evals = 100,
                        trials = trials)
                                                                                         
# 100%|██████████| 100/100 [00:06<00:00, 15.09it/s, best loss: -0.8712121212121212]
  • best_hyperparams 为我们提供了最适合模型和更好的损失函数值的最优参数。
  • trials 是一个包含或存储所有相关信息的对象,例如超参数、模型已训练的每组参数的损失函数。
  • ‘fmin’ 是一个优化函数,它最小化损失函数并接受 4 个输入 - 目标函数、参数空间、算法和 max_evals。
  • tpe.suggest 使用的算法.
3.2.3.4 打印结果
print("The best hyperparameters are : ","\n")
print(best_hyperparams)

The best hyperparameters are :  

{'colsample_bytree': 0.6655392754230048, 'gamma': 4.198875359789924, 'max_depth': 17.0, 'min_child_weight': 1.0, 'reg_alpha': 57.0, 'reg_lambda': 0.896332305739873}

3.5 参考资料

  • https://xgboost.readthedocs.io/en/latest/tutorials/param_tuning.html

  • https://xgboost.readthedocs.io/en/latest/parameter.html#general-parameters

  • https://medium.com/analytics-vidhya/hyperparameter-tuning-hyperopt-bayesian-optimization-for-xgboost-and-neural-network-8aedf278a1c9

  • https://www.analyticsvidhya.com/blog/2016/03/complete-guide-parameter-tuning-xgboost-with-codes-python/

  • https://www.kaggle.com/yassinealouini/hyperopt-the-xgboost-model

四、XGBoost 面试题

source: https://mp.weixin.qq.com/s?__biz=MzI1MzY0MzE4Mg==&mid=2247485159&idx=1&sn=d429aac8370ca5127e1e786995d4e8ec&chksm=e9d01626dea79f30043ab80652c4a859760c1ebc0d602e58e13490bf525ad7608a9610495b3d&scene=21#wechat_redirect

1. 简单介绍一下 XGBoost

首先需要说一说 GBDT,它是一种基于 boosting 增强策略的加法模型,训练的时候采用前向分布算法进行贪婪的学习,每次迭代都学习一棵 CART 树来拟合之前 t − 1 t-1 t1 棵树的预测结果与训练样本真实值的残差。

XGBoost 对 GBDT 进行了一系列优化,比如损失函数进行了二阶泰勒展开、目标函数加入正则项、支持并行和默认缺失值处理等,在可扩展性和训练速度上有了巨大的提升,但其核心思想没有大的变化。

2. XGBoost 与 GBDT 有什么不同

  • 基分类器:XGBoost 的基分类器不仅支持 CART 决策树,还支持线性分类器,此时 XGBoost 相当于带 L1 和L2 正则化项的 Logistic 回归(分类问题)或者线性回归(回归问题)。
  • 导数信息:XGBoost 对损失函数做了二阶泰勒展开,GBDT 只用了一阶导数信息,并且 XGBoost 还支持自定义损失函数,只要损失函数一阶、二阶可导。
  • 正则项:XGBoost 的目标函数加了正则项, 相当于预剪枝,使得学习出来的模型更加不容易过拟合。
  • 列抽样:XGBoost 支持列采样,与随机森林类似,用于防止过拟合。
  • 缺失值处理:对树中的每个非叶子结点,XGBoost 可以自动学习出它的默认分裂方向。如果某个样本该特征值缺失,会将其划入默认分支。
  • 并行化:注意不是tree维度的并行,而是特征维度的并行。XGBoost预先将每个特征按特征值排好序,存储为块结构,分裂结点时可以采用多线程并行查找每个特征的最佳分割点,极大提升训练速度。

3. XGBoost 为什么使用泰勒二阶展开

  • 精准性:相对于 GBDT 的一阶泰勒展开,XGBoost 采用二阶泰勒展开,可以更为精准的逼近真实的损失函数
  • 可扩展性:损失函数支持自定义,只需要新的损失函数二阶可导。

4. XGBoost 为什么可以并行训练

  • XGBoost 的并行,并不是说每棵树可以并行训练,XGB 本质上仍然采用 boosting 思想,每棵树训练前需要等前面的树训练完成才能开始训练。
  • XGBoost 的并行,指的是特征维度的并行:在训练之前,每个特征按特征值对样本进行预排序,并存储为Block 结构,在后面查找特征分割点时可以重复使用,而且特征已经被存储为一个个 block 结构,那么在寻找每个特征的最佳分割点时,可以利用多线程对每个 block 并行计算。

5. XGBoost 为什么快

  • 分块并行:训练前每个特征按特征值进行排序并存储为 Block 结构,后面查找特征分割点时重复使用,并且支持并行查找每个特征的分割点
  • 候选分位点:每个特征采用常数个分位点作为候选分割点
  • CPU cache 命中优化: 使用缓存预取的方法,对每个线程分配一个连续的 buffer,读取每个 block 中样本的梯度信息并存入连续的 Buffer 中。
  • Block 处理优化:Block 预先放入内存;Block 按列进行解压缩;将 Bloc k划分到不同硬盘来提高吞吐

6. XGBoost 防止过拟合的方法

XGBoost在设计时,为了防止过拟合做了很多优化,具体如下:

  • 目标函数添加正则项:叶子节点个数+叶子节点权重的 L2 正则化
  • 列抽样:训练的时候只用一部分特征(不考虑剩余的 block 块即可)
  • 子采样:每轮计算可以不使用全部样本,使算法更加保守
  • shrinkage: 可以叫学习率或步长,为了给后面的训练留出更多的学习空间

7. XGBoost如何处理缺失值

XGBoost 模型的一个优点就是允许特征存在缺失值。对缺失值的处理方式如下:

  • 在特征 k 上寻找最佳 split point 时,不会对该列特征 missing 的样本进行遍历,而只对该列特征值为 non-missing 的样本上对应的特征值进行遍历,通过这个技巧来减少了为稀疏离散特征寻找 split point 的时间开销。
  • 在逻辑实现上,为了保证完备性,会将该特征值missing的样本分别分配到左叶子结点和右叶子结点,两种情形都计算一遍后,选择分裂后增益最大的那个方向(左分支或是右分支),作为预测时特征值缺失样本的默认分支方向。
  • 如果在训练中没有缺失值而在预测中出现缺失,那么会自动将缺失值的划分方向放到右子结点。

XGBoost 知识点总结_第5张图片

find_split 时,缺失值处理的伪代码

8. XGBoost 中叶子结点的权重如何计算出来

XGBoost目标函数最终推导形式如下:
Obj ( t ) = ∑ j = 1 T [ G j w j + 1 2 ( H j + λ ) w j 2 ] + γ T \text{Obj}^{(t)}=\sum_{j=1}^{T}\left[G_{j} w_{j}+\frac{1}{2}\left(H_{j}+\lambda\right) w_{j}^{2}\right]+\gamma T Obj(t)=j=1T[Gjwj+21(Hj+λ)wj2]+γT
利用一元二次函数求最值的知识,当目标函数达到最小值时,每个叶子结点的权重为:
w j ∗ = − G j H j + λ , Obj = − 1 2 ∑ j = 1 T G j 2 H j + λ + γ T w_{j}^{*}=-\frac{G_{j}}{H_{j}+\lambda}, \quad \text{Obj}=-\frac{1}{2} \sum_{j=1}^{T} \frac{G_{j}^{2}}{H_{j}+\lambda}+\gamma T wj=Hj+λGj,Obj=21j=1THj+λGj2+γT

9. XGBoost中的一棵树的停止生长条件

  • 当新引入的一次分裂所带来的增益 Gain<0 时,放弃当前的分裂。这是训练损失和模型结构复杂度的博弈过程。
  • 当树达到最大深度时,停止建树,因为树的深度太深容易出现过拟合,这里需要设置一个超参数max_depth
  • 当引入一次分裂后,重新计算新生成的左、右两个叶子结点的样本权重和。如果任一个叶子结点的样本权重低于某一个阈值,也会放弃此次分裂。这涉及到一个超参数:最小样本权重和,是指如果一个叶子节点包含的样本数量太少也会放弃分裂,防止树分的太细。

10. RF 和 GBDT 的区别

相同点:

  • 都是由多棵树组成,最终的结果都是由多棵树一起决定。

不同点:

  • 集成学习:RF 属于 bagging 思想,而 GBDT 是 boosting 思想
  • 偏差-方差权衡:RF 不断的降低模型的方差,而 GBDT 不断的降低模型的偏差
  • 训练样本:RF 每次迭代的样本是从全部训练集中有放回抽样形成的,而 GBDT 每次使用全部样本
  • 并行性:RF 的树可以并行生成,而 GBDT 只能顺序生成(需要等上一棵树完全生成)
  • 最终结果:RF 最终是多棵树进行多数表决(回归问题是取平均),而 GBDT 是加权融合
  • 数据敏感性:RF 对异常值不敏感,而 GBDT 对异常值比较敏感
  • 泛化能力:RF 不易过拟合,而 GBDT 容易过拟合

11. XGBoost 如何处理不平衡数据

对于不平衡的数据集,例如用户的购买行为,肯定是极其不平衡的,这对 XGBoost 的训练有很大的影响,XGBoost 有两种自带的方法来解决:

第一种,如果你在意 AUC,采用 AUC 来评估模型的性能,那你可以通过设置 scale_pos_weight 来平衡正样本和负样本的权重。例如,当正负样本比例为 1:10 时,scale_pos_weight 可以取 10;

第二种,如果你在意概率(预测得分的合理性),你不能重新平衡数据集(会破坏数据的真实分布),应该设置max_delta_step 为一个有限数字来帮助收敛(基模型为 LR 时有效)。

原话是这么说的:

For common cases such as ads clickthrough log, the dataset is extremely imbalanced. This can affect the training of xgboost model, 
and there are two ways to improve it.
  If you care only about the ranking order (AUC) of your prediction
      Balance the positive and negative weights, via scale_pos_weight
      Use AUC for evaluation
  If you care about predicting the right probability
      In such a case, you cannot re-balance the dataset
      In such a case, set parameter max_delta_step to a finite number (say 1) will help convergence

那么,源码到底是怎么利用 scale_pos_weight 来平衡样本的呢,是调节权重还是过采样呢?请看源码:

if (info.labels[i] == 1.0f)  w *= param_.scale_pos_weight

可以看出,应该是增大了少数样本的权重。

除此之外,还可以通过上采样、下采样、SMOTE 算法或者自定义代价函数的方式解决正负样本不平衡的问题。

12. 比较 LR 和 GBDT,说说什么情景下 GBDT 不如 LR

先说说 LR 和 GBDT 的区别:

  • LR 是线性模型,可解释性强,很容易并行化,但学习能力有限,需要大量的人工特征工程
  • GBDT 是非线性模型,具有天然的特征组合优势,特征表达能力强,但是树与树之间无法并行训练,而且树模型很容易过拟合;

当在高维稀疏特征的场景下,LR 的效果一般会比 GBDT 好。原因如下:

先看一个例子:

假设一个二分类问题,label 为 0 和 1,特征有 100 维,如果有 1w个 样本,但其中只有 10 个正样本 1,而这些样本的特征 f1 的值为全为 1,而其余 9990 条样本的 f1 特征都为 0 (在高维稀疏的情况下这种情况很常见)。

我们都知道在这种情况下,树模型很容易优化出一个使用 f1 特征作为重要分裂节点的树,因为这个结点直接能够将训练数据划分的很好,但是当测试的时候,却会发现效果很差,因为这个特征 f1 只是刚好偶然间跟 y 拟合到了这个规律,这也是我们常说的过拟合。

那么这种情况下,如果采用 LR 的话,应该也会出现类似过拟合的情况呀: y = W 1 f 1 + W i f i + ⋯ y = W_1f_1 + W_if_i + \cdots y=W1f1+Wifi+,其中 W 1 W_1 W1 特别大以拟合这 10 个样本。为什么此时树模型就过拟合的更严重呢?

仔细想想发现,因为现在的模型普遍都会带着正则项,而 LR 等线性模型的正则项是对权重的惩罚,也就是 W 1 W_1 W1 一旦过大,惩罚就会很大,进一步压缩 W 1 W_1 W1的值,使他不至于过大。但是,树模型则不一样,树模型的惩罚项通常为叶子节点数和深度等,而我们都知道,对于上面这种 case,树只需要一个节点就可以完美分割 9990 和 10 个样本,一个结点,最终产生的惩罚项极其之小。

这也就是为什么在高维稀疏特征的时候,线性模型会比非线性模型好的原因了:带正则化的线性模型比较不容易对稀疏特征过拟合。

13. XGBoost 中如何对树进行剪枝

  • 在目标函数中增加了正则项:使用叶子结点的数目和叶子结点权重的 L2 模的平方,控制树的复杂度。
  • 在结点分裂时,定义了一个阈值,如果分裂后目标函数的增益小于该阈值,则不分裂。
  • 当引入一次分裂后,重新计算新生成的左、右两个叶子结点的样本权重和。如果任一个叶子结点的样本权重低于某一个阈值(最小样本权重和),也会放弃此次分裂。
  • XGBoost 先从顶到底建立树直到最大深度,再从底到顶反向检查是否有不满足分裂条件的结点,进行剪枝。

14. XGBoost 如何选择最佳分裂点?

XGBoost 在训练前预先将特征按照特征值进行了排序,并存储为 block 结构,以后在结点分裂时可以重复使用该结构。

因此,可以采用特征并行的方法利用多个线程分别计算每个特征的最佳分割点,根据每次分裂后产生的增益,最终选择增益最大的那个特征的特征值作为最佳分裂点。

如果在计算每个特征的最佳分割点时,对每个样本都进行遍历,计算复杂度会很大,这种全局扫描的方法并不适用大数据的场景。XGBoost 还提供了一种直方图近似算法,对特征排序后仅选择常数个候选分裂位置作为候选分裂点,极大提升了结点分裂时的计算效率。

15. XGBoost 的 Scalable 性如何体现

  • 基分类器的scalability:弱分类器可以支持 CART 决策树,也可以支持 LR 和 Linear。
  • 目标函数的scalability:支持自定义 loss function,只需要其一阶、二阶可导。有这个特性是因为泰勒二阶展开,得到通用的目标函数形式。
  • 学习方法的 scalability:Block 结构支持并行化,支持 Out-of-core计算。

16. XGBoost 如何评价特征的重要性

我们采用三种方法来评判XGBoost模型中特征的重要程度:

 官方文档:(1)weight - the number of times a feature is used to split the data across all trees. (2)gain - the average gain of the feature when it is used in trees. (3)cover - the average coverage of the feature when it is used in trees.
  • weight :该特征在所有树中被用作分割样本的特征的总次数。
  • gain :该特征在其出现过的所有树中产生的平均增益。
  • cover :该特征在其出现过的所有树中的平均覆盖范围。

注意:覆盖范围这里指的是一个特征用作分割点后,其影响的样本数量,即有多少样本经过该特征分割到两个子节点。

17. XGBooost参数调优的一般步骤

首先需要初始化一些基本变量,例如:

  • max_depth = 5
  • min_child_weight = 1
  • gamma = 0
  • subsample, colsample_bytree = 0.8
  • scale_pos_weight = 1

(1) 确定 learning rate 和 estimator 的数量

learning rate 可以先用 0.1,用 cv 来寻找最优的 estimators

(2) max_depth 和 min_child_weight

我们调整这两个参数是因为,这两个参数对输出结果的影响很大。我们首先将这两个参数设置为较大的数,然后通过迭代的方式不断修正,缩小范围。

max_depth,每棵子树的最大深度,check from range (3,10,2)。

min_child_weight,子节点的权重阈值,check from range (1,6,2)。

如果一个结点分裂后,它的所有子节点的权重之和都大于该阈值,该叶子节点才可以划分。

(3) gamma

也称作最小划分损失 min_split_loss,check from 0.1 to 0.5,指的是,对于一个叶子节点,当对它采取划分之后,损失函数的降低值的阈值。

  • 如果大于该阈值,则该叶子节点值得继续划分
  • 如果小于该阈值,则该叶子节点不值得继续划分

(4) subsample, colsample_bytree

subsample 是对训练的采样比例

colsample_bytree 是对特征的采样比例

both check from 0.6 to 0.9

(5) 正则化参数

alpha 是 L1 正则化系数,try 1e-5, 1e-2, 0.1, 1, 100

lambda 是 L2 正则化系数

(6) 降低学习率

降低学习率的同时增加树的数量,通常最后设置学习率为 0.01~0.1

18. XGBoost 模型如果过拟合了怎么解决

当出现过拟合时,有两类参数可以缓解:

第一类参数:用于直接控制模型的复杂度。包括 max_depth,min_child_weight,gamma 等参数

第二类参数:用于增加随机性,从而使得模型在训练时对于噪音不敏感。包括 subsample,colsample_bytree

还有就是直接减小 learning rate,但需要同时增加 estimator 参数。

19.为什么 XGBoost 相比某些模型对缺失值不敏感

对存在缺失值的特征,一般的解决方法是:

  • 离散型变量:用出现次数最多的特征值填充;
  • 连续型变量:用中位数或均值填充;

一些模型如 SVM 和 KNN,其模型原理中涉及到了对样本距离的度量,如果缺失值处理不当,最终会导致模型预测效果很差。

而树模型对缺失值的敏感度低,大部分时候可以在数据缺失时时使用。原因就是,一棵树中每个结点在分裂时,寻找的是某个特征的最佳分裂点(特征值),完全可以不考虑存在特征值缺失的样本,也就是说,如果某些样本缺失的特征值缺失,对寻找最佳分割点的影响不是很大。

XGBoost 对缺失数据有特定的处理方法,详情参考文章第7题

因此,对于有缺失值的数据在经过缺失处理后:

  • 当数据量很小时,优先用朴素贝叶斯
  • 数据量适中或者较大,用树模型,优先 XGBoost
  • 数据量较大,也可以用神经网络
  • 避免使用距离度量相关的模型,如 KNN 和 SVM

20. XGBoost 和 LightGBM 的区别

XGBoost 知识点总结_第6张图片

(1)树生长策略:XGB 采用 level-wise 的分裂策略,LGB 采用 leaf-wise 的分裂策略。XGB对 每一层所有节点做无差别分裂,但是可能有些节点增益非常小,对结果影响不大,带来不必要的开销。Leaf-wise 是在所有叶子节点中选取分裂收益最大的节点进行的,但是很容易出现过拟合问题,所以需要对最大深度做限制 。

(2)分割点查找算法:XGB 使用特征预排序算法,LGB 使用基于直方图的切分点算法,其优势如下:

  • 减少内存占用,比如离散为 256 个 bin 时,只需要用8 位整形就可以保存一个样本被映射为哪个 bin(这个 bin可以说就是转换后的特征),对比预排序的 exact greedy 算法来说(用 int_32 来存储索引+ 用 float_32 保存特征值),可以节省 7/8 的空间。
  • 计算效率提高,预排序的 Exact greedy 对每个特征都需要遍历一遍数据,并计算增益,复杂度为(#×#)。而直方图算法在建立完直方图后,只需要对每个特征遍历直方图即可,复杂度为(#×#)。
  • LGB还可以使用直方图做差加速,一个节点的直方图可以通过父节点的直方图减去兄弟节点的直方图得到,从而加速计算

但实际上 xgboost 的近似直方图算法也类似于 lightgbm 这里的直方图算法,为什么 xgboost 的近似算法比lightgbm 还是慢很多呢?

xgboost 在每一层都动态构建直方图, 因为 xgboost 的直方图算法不是针对某个特定的 feature,而是所有feature 共享一个直方图(每个样本的权重是二阶导),所以每一层都要重新构建直方图,而 lightgbm 中对每个特征都有一个直方图,所以构建一次直方图就够了。

(3)支持离散变量:无法直接输入类别型变量,因此需要事先对类别型变量进行编码(例如独热编码),而LightGBM 可以直接处理类别型变量。

(4)缓存命中率:XGB 使用 Block 结构的一个缺点是取梯度的时候,是通过索引来获取的,而这些梯度的获取顺序是按照特征的大小顺序的,这将导致非连续的内存访问,可能使得 CPU cache 缓存命中率低,从而影响算法效率。而 LGB 是基于直方图分裂特征的,梯度信息都存储在一个个 bin 中,所以访问梯度是连续的,缓存命中率高。

(5)LightGBM 与 XGboost 的并行策略不同:

  • 特征并行 :LGB 特征并行的前提是每个 worker 留有一份完整的数据集,但是每个 worker 仅在特征子集上进行最佳切分点的寻找;worker 之间需要相互通信,通过比对损失来确定最佳切分点;然后将这个最佳切分点的位置进行全局广播,每个 worker 进行切分即可。XGB 的特征并行与 LGB 的最大不同在于 XGB 每个worker 节点中仅有部分的列数据,也就是垂直切分,每个 worker 寻找局部最佳切分点,worker 之间相互通信,然后在具有最佳切分点的 worker 上进行节点分裂,再由这个节点广播一下被切分到左右节点的样本索引号,其他 worker 才能开始分裂。二者的区别就导致了 LGB 中 worker 间通信成本明显降低,只需通信一个特征分裂点即可,而 XGB 中要广播样本索引。
  • 数据并行 :当数据量很大,特征相对较少时,可采用数据并行策略。LGB 中先对数据水平切分,每个 worker上的数据先建立起局部的直方图,然后合并成全局的直方图,采用直方图相减的方式,先计算样本量少的节点的样本索引,然后直接相减得到另一子节点的样本索引,这个直方图算法使得 worker 间的通信成本降低一倍,因为只用通信以此样本量少的节点。XGB 中的数据并行也是水平切分,然后单个 worker 建立局部直方图,再合并为全局,不同在于根据全局直方图进行各个 worker 上的节点分裂时会单独计算子节点的样本索引,因此效率贼慢,每个 worker 间的通信量也就变得很大。
  • 投票并行(LGB):当数据量和维度都很大时,选用投票并行,该方法是数据并行的一个改进。数据并行中的合并直方图的代价相对较大,尤其是当特征维度很大时。大致思想是:每个 worker 首先会找到本地的一些优秀的特征,然后进行全局投票,根据投票结果,选择 top 的特征进行直方图的合并,再寻求全局的最优分割点。

参考:

1.https://blog.csdn.net/u010665216/article/details/78532619

2.https://blog.csdn.net/jamexfx/article/details/93780308

你可能感兴趣的:(机器学习)