GUET_09组_2021

机器学习经典算法——XGBoost

一、集成思想

  在学习XGBoost之前,我们得需要先明白集成思想。集成学习方法是指将多个学习模型组合,以获得更好的效果,使组合后的模型具有更强的泛化能力。另外XGBoost是以分类回归树(CART树)进行组合。故在此之前,我们先看下CART树(CART树具体原理请自行复习)。如下,通过输入用户年龄、性别进行判断用户是否喜欢玩游戏的得分值。由此得到一颗CART树模型。
在这里插入图片描述
  我们知道对于单个的决策树模型容易出现过拟合,并且不能在实际中有效应用。所以出现了集成学习方法。如下图,通过两棵树组合进行玩游戏得分值预测。
在这里插入图片描述
  其中tree1中对小男孩的预测分值为2,tree2对小男孩的预测分值为0.9,则该小男孩的最后得分值为2.9;而tree1对老爷爷的预测分值为-1,tree2对老爷爷的预测分值为-0.9,则该小男孩最后得分分值为-1.9。因此我们可以预测小男孩会玩电脑游戏,而老爷爷不会玩电脑游戏。

下面我们来介绍一下集成学习。
  集成学习通过构建并结合多个学习器来完成学习任务,有时也被称为多分类器系统、基于委员会的学习等。集成学习通过将多个学习器进行结合,常可获得比单一学习器显著优越的泛化性能。下面从两个方面对集成学习进行简要介绍。

  • 分类
      根据个体学习器的生成方式,目前的集成学习方法大致可以分为两大类,即个体学习器间存在强依赖关系、必须串行生成的序列化方法,以及个体学习器间不存在强依赖关系、可同时生成的并行化方法;前者的代表是Boosting,后者的代表是Bagging和随机森林。
  • 结合策略
      根据个体学习器的生成方式,目前的集成学习方法大致可以分为两大类,即个体学习器间存在强依赖关系、必须串行生成的序列化方法,以及个体学习器间不存在强依赖关系、可同时生成的并行化方法;前者的代表是Boosting,后者的代表是Bagging和随机森林。
    [1] 平均法
    对于数值形输出,最常见的结合策略即为平均法:
    H ( x ) = 1 T ∑ m = 1 T h i ( x ) H(x) = \frac{1}{T} \sum_{m=1}^Th_i(x) H(x)=T1m=1Thi(x)
    其中 h i ( x ) h_i(x) hi(x)为基学习器的输出结果, H ( x ) H(x) H(x)为最终学习器的结果, T T T为基学习器的个数。
    [2] 加权平均法
    H ( x ) = ∑ m = 1 T w i h i ( x ) H(x) = \sum_{m=1}^Tw_ih_i(x) H(x)=m=1Twihi(x)
    其中 w i w_i wi是个体学习器 h i h_i hi的权重,通常要求 w i ⩾ 0 , ∑ i = 1 T = 1 , w i = 1 w_i⩾0,\sum_{i=1}^T=1,w_i=1 wi0,i=1T=1,wi=1。显然,简单平均法是加权平均法令 w i = 1 / T w_i=1/T wi=1/T的特例。
    [3] 投票法
    预测结果为得票最多的标记,若同时有多个标记获得相同的票数,则从中随机选取一个。
    [4] 学习法
    当训练数据很多时,可以通过另一个学习器来对所有基学习器产生结果的结合方法进行学习,这时候个体学习器称为初级学习器,用于结合的学习器成为次级学习器或元学习器。

二、基本原理及目标函数推导

  XGBoost和GBDT两者都是Boosting方法,除了工程实现、解决问题上的一些差异外,最大的不同就是目标函数的定义。因此,本文我们从目标函数开始探究XGBoost的基本原理。

2.1 第 t t t颗树的学习

XGBoost是由 T T T个基模型组成的一个加法模型,假设我们第 t t t 次迭代要训练的树模型是 f t ( x ) f_t(x) ft(x),则有:
y ^ i ( t ) = ∑ t = 1 T f k ( x i ) = y ^ i ( t − 1 ) + f t ( x i ) \hat{y}_i^{(t)} = \sum_{t=1}^Tf_k(x_i)=\hat{y}_i^{(t-1)}+f_t(x_i) y^i(t)=t=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 t t-1颗树的预测结果, f t ( x i ) f_t(x_i) ft(xi)表示第 t t t棵树的模型(function)。

2.2 定义一棵树及其复杂度

我们知道XGBoost的基模型不仅支持决策树,还支持线性模型,本文我们主要介绍基于决策树的目标函数。我们可以重新定义一棵决策树,其包括两个部分:

  • 叶子节点的权重向量 ω \omega ω
  • 实例(样本)到叶子结点的映射关系 q q q (本质是树的分支结构);
    在这里插入图片描述

决策树的复杂度 Ω \Omega Ω可由叶子数 T T T 组成,叶子节点越少模型越简单,此外叶子节点也不应该含有过高的权重 ω \omega ω (类比 LR 的每个变量的权重),所以目标函数的正则项由生成的所有决策树的叶子节点数量,和所有节点权重所组成的向量的 L 2 L_2 L2 范式共同决定。
在这里插入图片描述
  那么还剩一个问题,我们如何选择每一轮加入什么 f f f呢?答案是直接的,每一轮选取的 f f f必须要使得我们的目标函数尽量大的降低。
在这里插入图片描述

2.3 XGBoost的实现思路

  直观上看,目标要求预测误差尽量小,叶子节点尽量少,节点数值尽量不极端(这个怎么看,如果某个样本label数值为4,那么第一个回归树预测3,第二个预测为1;另外一组回归树,一个预测2,一个预测2,那么倾向后一种,为什么呢?前一种情况,第一棵树学的太多,太接近4,也就意味着有较大的过拟合的风险)
  ok,听起来很美好,可是怎么实现呢,上面这个目标函数跟实际的参数怎么联系起来,记得我们说过,回归树的参数:(1)选取哪个feature分裂节点呢;(2)节点的预测值(总不能靠取平均值这么粗暴不讲道理的方式吧,好歹高级一点)。上述形而上的公式并没有“直接”解决这两个,那么是如何间接解决的呢?
  先说答案:贪心策略+最优化(二次最优化)
  通俗解释贪心策略:就是决策时刻按照当前目标最优化决定,说白了就是眼前利益最大化决定,“目光短浅”策略,他的优缺点细节大家自己去了解,经典背包问题等等。
  这里是怎么用贪心策略的呢,刚开始你有一群样本,放在第一个节点,这时候T=1,如果这里的 l ( y i ^ , y i ) l(\hat{y_i},y_i) l(yi^,yi)误差表示用的是平方误差,那么上述函数就是一个关于 w w w的二次函数求最小值,取最小值的点就是这个节点的预测值,最小的函数值为最小损失函数。
  这里处理的就是二次函数最优化! 要是损失函数不是二次函数咋办?可以使用泰勒展开式,不是二次的想办法近似为二次。

三、目标函数推导

3.1 XGBoost的目标函数

损失函数可由预测值 y ^ i \hat{y}_i y^i与真实值 y i y_i yi进行表示:
L = ∑ y = 1 n l ( y i , y ^ i ) L=\sum_{y=1}^nl(y_i,\hat{y}_i) L=y=1nl(yi,y^i)
  其中, n n n为样本数量, l ( y i , y ^ i ) l(y_i,\hat{y}_i) l(yi,y^i)表示第 i i i个样本的预测误差,误差越小越好。

  我们知道模型的预测精度由模型的偏差和方差共同决定,损失函数代表了模型的偏差,想要方差小则需要在目标函数中添加正则项,用于防止过拟合。所以目标函数由模型的损失函数 L L L与抑制模型复杂度的正则项 Ω \Omega Ω组成,目标函数的定义如下:
O b j ( t ) = ∑ y = 1 n l ( y i , y ^ i ( t ) ) + ∑ i = 1 t Ω ( f i ) = ∑ y = 1 n l ( y i , y ^ i ( t − 1 ) + f t ( x i ) ) + ∑ i = 1 t Ω ( f i ) = ∑ y = 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} Obj^{(t)} & =\sum_{y=1}^nl(y_i,\hat{y}_i^{(t)})+\sum_{i=1}^t\Omega(f_i) \\ & =\sum_{y=1}^nl(y_i,\hat{y}_i^{(t-1)}+f_t(x_i))+\sum_{i=1}^t\Omega(f_i)\\ &= \sum_{y=1}^nl(y_i,\hat{y}_i^{(t-1)}+f_t(x_i))+\Omega(f_t)+constant \end{aligned} Obj(t)=y=1nl(yi,y^i(t))+i=1tΩ(fi)=y=1nl(yi,y^i(t1)+ft(xi))+i=1tΩ(fi)=y=1nl(yi,y^i(t1)+ft(xi))+Ω(ft)+constant
  其中, ∑ i = 1 t Ω ( f i ) \sum_{i=1}^t\Omega(f_i) i=1tΩ(fi) 是将全部 T T T棵树的复杂度进行求和,添加到目标函数中作为正则化项,用于防止模型过度拟合。在这里我们将正则项进行拆分,由于前 t t t-1 棵树的结构已经确定,因此前 t t t-1 棵树的复杂度之和可以用一个常量表示,如下所示:
∑ i = 1 t Ω ( f i ) = Ω ( f t ) + ∑ i = 1 t − 1 Ω ( f i ) = Ω ( f t ) + c o n s t a n t \begin{aligned} \sum_{i=1}^t\Omega(f_i) &=\Omega(f_t)+\sum_{i=1}^{t-1}\Omega(f_i)\\ &=\Omega(f_t)+constant \end{aligned} i=1tΩ(fi)=Ω(ft)+i=1t1Ω(fi)=Ω(ft)+constant

3.2 引入泰勒展开式及公式推导

泰勒公式是将一个在 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 ) f(x) f(x) 在包含 x 0 x_0 x0 的某个闭区间 [ a , b ] [a,b] [a,b] 上具有 n n n 阶导数,且在开区间 ( a , b ) (a,b) (a,b) 上具有 n n n 阶导数,则对闭区间 [ a , b ] [a,b] [a,b] 上任意一点 x x x 有:
f ( x ) = ∑ i = 0 n f ( i ) ( x 0 ) i ! ( x − x 0 ) i + R n ( x ) f(x)=\sum_{i=0}^n \frac{f^{(i)}(x_0)}{i!} (x-x_0)^i +R_n(x) f(x)=i=0ni!f(i)(x0)(xx0)i+Rn(x)
  其中的多项式称为函数在 x 0 x_0 x0 处的泰勒展开式, R n ( x ) R_n(x) Rn(x) 是泰勒公式的余项且是 ( x − x 0 ) n (x-x_0)^n (xx0)n 的高阶无穷小。
  根据泰勒公式,把函数 f ( x + Δ x ) f(x+\Delta x) f(x+Δx) 在点 x x x 处进行泰勒的二阶展开,可得如下等式:
f ( x + Δ x ) ≈ f ( x ) + f ′ ( x ) Δ x + 1 2 f ′ ′ ( x ) Δ x 2 f(x+\Delta x) \approx f(x) +f'(x)\Delta x + \frac 12f''(x) \Delta x^2 f(x+Δx)f(x)+f(x)Δx+21f(x)Δx2
回到XGBoost的目标函数上来, f ( x ) f(x) f(x) 对应损失函数 l ( y i , y ^ i ( t − 1 ) + f t ( x i ) ) l(y_i,\hat{y}_i^{(t-1)}+f_t(x_i)) l(yi,y^i(t1)+ft(xi)) x x x 对应前 t t t-1 棵树的预测值 y i ^ ( t − 1 ) \hat{y_i}^{(t-1)} yi^(t1) Δ x \Delta x Δx 对应于我们正在训练的第 t t t 棵树 f t ( x i ) f_t(x_i) ft(xi),则可以将损失函数写为:
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(y_i,\hat{y}_i^{(t-1)}+f_t(x_i))=l(y_i,\hat{y}_i^{(t-1)})+g_if_t(x_i)+\frac 12h_if_t^2(x_i) l(yi,y^i(t1)+ft(xi))=l(yi,y^i(t1))+gift(xi)+21hift2(xi)
  其中, g i g_i gi 为损失函数的一阶导, h i h_i hi为损失函数的二阶导,注意这里的求导是对 y ^ i ( t − 1 ) \hat{y}_i^{(t-1)} y^i(t1) 求导。

我们以平方损失函数为例:
l ( y i , y ^ i ( t − 1 ) ) = ( y i − y ^ i ( t − 1 ) ) 2 l(y_i,\hat{y}_i^{(t-1)})=(y_i-\hat{y}_i^{(t-1)})^2 l(yi,y^i(t1))=(yiy^i(t1))2
则:
g i = ∂ l ( y i , y ^ i ( t − 1 ) ) ∂ y i ^ ( t − 1 ) = − 2 ( y i − y ^ i ( t − 1 ) ) g_i=\frac{\partial l(y_i,\hat{y}_i^{(t-1)})}{\partial \hat{y_i}^{(t-1)}}=-2(y_i-\hat y_i^{(t-1)}) gi=yi^(t1)l(yi,y^i(t1))=2(yiy^i(t1))
h i = ∂ 2 l ( y i , y ^ i ( t − 1 ) ) ∂ ( y i ^ ( t − 1 ) ) 2 = 2 h_i=\frac{\partial ^2 l(y_i,\hat{y}_i^{(t-1)})}{\partial (\hat{y_i}^{(t-1)})^2}=2 hi=(yi^(t1))22l(yi,y^i(t1))=2
  将上述的二阶展开式,带入到XGBoost的目标函数中,可以得到目标函数的近似值:
O b j ( t ) ≅ ∑ i = 1 n [ l ( y i , y ^ i ( t − 1 ) ) + g i f i ( x i ) + 1 2 h i f t 2 ( x i ) ] + Ω ( f t ) + c o n s t a n t Obj^{(t)} \cong \sum_{i=1}^n[l(y_i,\hat{y}_i^{(t-1)})+g_if_i(x_i)+\frac 12h_if_t^2(x_i)]+\Omega (f_t)+constant Obj(t)i=1n[l(yi,y^i(t1))+gifi(xi)+21hift2(xi)]+Ω(ft)+constant

  由于在第 t t t 步时 y ^ i ( t − 1 ) \hat{y}_i^{(t-1)} y^i(t1) 其实是一个已知的值,所以 l ( y i , y ^ i ( t − 1 ) ) l(y_i,\hat{y}_i^{(t-1)}) l(yi,y^i(t1)) 是一个常数,其对函数的优化不会产生影响。因此,去掉全部的常数项,得到目标函数为:

O b j ( t ) ≅ ∑ i = 1 n [ g i f i ( x i ) + 1 2 h i f t 2 ( x i ) ] + Ω ( f t ) Obj^{(t)} \cong \sum_{i=1}^n[g_if_i(x_i)+\frac 12h_if_t^2(x_i)]+\Omega (f_t) Obj(t)i=1n[gifi(xi)+21hift2(xi)]+Ω(ft)
  定义 q q q 函数将输入 x x x 映射到某个叶节点上,则 f t ( x ) = w q ( x ) ) f_t(x)=w_{q(x)}) ft(x)=wq(x)) (每一个样本所在叶子索引的分数 ),此外,定义每个叶子节点 j j j 上的样本集合为 I j = { i ∣ q ( x i ) = j } I_j= \lbrace i|q(x_i)=j \rbrace Ij={iq(xi)=j},将上式进行重写,并展开 Ω \Omega Ω 项得
在这里插入图片描述
  定义 G j G_j Gj(每个叶子节点里面一阶梯度的和)和 H j H_j Hj(每个叶子节点里面二阶梯度的和):
G j = ∑ i ∈ I j g i , H i = ∑ i ∈ I j h i G_j=\sum_{i \in I_j}g_i, H_i=\sum_{i \in I_j}h_i Gj=iIjgi,Hi=iIjhi
 目标函数改写为:
在这里插入图片描述
因此,现在要做的是两件事:

  • 确定树的结构, 这样,这一轮的目标函数就确定了下来。
  • 求使得当前这一轮(第 t t t 轮)的目标函数最小的叶结点分数 w w w。( O b j Obj Obj 代表了当我们指定一个树的结构的时候,我们在目标上面最多减少多少,也称为结构分数)。
    假设已经知道了树的结构,那么第2件事情是十分简单的,对于一个确定的结构 q ( x ) q(x) q(x),我们可以计算最优的权重 w ∗ w^* w,对目标函数求偏导得到:
    w j ∗ = − G j H j + λ w^*_j=- \frac{G_j}{H_j+ \lambda} wj=Hj+λGj
    将上式回代目标函数 O b j ( t ) Obj^{(t)} Obj(t) 得到:
    O b j = O b j ( t ) ( q ) = − 1 2 ∑ j = 1 T G j 2 H j + λ + γ T Obj = Obj^{(t)}(q)=- \frac 12 \sum_{j=1}^T\frac{G_j^2}{H_j+ \lambda}+\gamma T Obj=Obj(t)(q)=21j=1THj+λGj2+γT

3.3 求解实例

   O b j Obj Obj 代表了当我们指定一个树的结构的时候,我们在目标上面最多减少多少,可叫做结构分数(structure score), O b j Obj Obj 计算示例:
在这里插入图片描述
  我们只需要在每个叶子上对梯度和二阶梯度统计求和,然后应用得分公式(scoring formula)来获得质量分(quality score)。

确定树的结构
接下来要解决的就是上面提到的第一个问题,即如何确定树的结构。
  暴力枚举所有的树结构,然后选择结构分数最小的。 树的结构太多了,这样枚举一般不可行。通常采用贪心法,每次尝试分裂一个叶节点,计算分裂后的增益,选增益最大的。这个方法在之前的决策树算法中大量被使用。而增益的计算方式比如ID3的信息增益,C4.5的信息增益率,CART的Gini系数等。
  回想目标函数 O b j Obj Obj,衡量了每个叶子节点对总体损失的贡献,我们希望目标函数越小越好,因此 ( ∑ i ∈ I j g i ) 2 ∑ i ∈ I j h i + λ \frac {(\sum_{i \in I_j}g_i)^2}{\sum_{i \in I_j}h_i+\lambda} iIjhi+λ(iIjgi)2 越大越好。从而得到以下打分公式:
在这里插入图片描述
通常,不可能枚举所有可能的树结构 q q q。而贪婪算法会从单个叶子出发,迭代添加分枝到树中。
  假设 L L L_L LL L R L_R LR 是一次划分(split)后的左节点和右节点所对应的实例集合。 I = L L ∪ L R I=L_L\cup L_R I=LLLR, 比较分割后的增益。利用这个打分函数来寻找出一个最优结构的树,加入到我们的模型中,再重复这样的操作。
在这里插入图片描述
引入新叶子得惩罚项
  优化这个目标对应了树的剪枝, 当引入的分割带来的增益小于一个阀值的时候,我们可以剪掉这个分割。;这样根据推导引入了选取分裂节点的计算分数和叶子的惩罚项,替代了回归树的基尼系数剪枝操作。
  除了采用前面提到的正则化目标函数外,还会使用两种额外的技术来进一步阻止过拟合:

  • 收缩(Shrinkage):Shrinkage会在每一步树提升时,将新加入的weights通过一个因子 η \eta η 进行缩放。与随机优化中的学习率相类似,对于用于提升模型的新增树(future trees),shrinkage可以减少每棵单独的树和叶子空间(leaves space)的影响。
  • 列特征子抽样(column feature subsampling):该技术借鉴自随机森林。根据用户的反馈,比起传统的行子抽样(row sub-sampling:同样也支持),使用列子抽样可以阻止过拟合。列子抽样的使用可以加速并行算法的计算。

3.4 确定划分属性

3.4.1 精确的贪心算法(exact greedy algorithm)

树学习算法中的一个关键问题就是找到最好的分裂点(best split)。为了达到这个目标,分裂算法会在所有特征上,枚举所有可能的划分,我们称它为“精确贪婪算法(exact greedy algorithm)”。该算法如下图所示。它会对连续型特征枚举所有可能的split,为了更高效,该算法必须首先根据特征值对数据进行排序,以有序的方式访问数据来枚举打分公式中的结构得分(structure score)的梯度统计(gradient statistics)。
在这里插入图片描述

算法原理:
对于每棵树 m m m,针对样本的每个特征 j j j,按照样本进行排序,然后计算该特征下的 score,选取最好的划分点,然后对每个节点循环执行贪婪算法的过程,得到决策树。

3.4.2 近似算法(approximate algorithm)

完全贪婪算法使得XGBoost的每一步都按照分裂后增益最大的分裂点进行分裂,而分裂点的选取是枚举所有分割点。当数据量十分庞大,以致于不能全部放入内存时,贪婪算法就会很慢,因此XGBoost引入了近似的算法,如下。
在这里插入图片描述
  该算法首先会根据特征分布的百分位数(percentiles of feature distribution),提出候选划分点(candidate splitting points)。接着,该算法将连续型特征映射到由这些候选点划分的分桶(buckets)中,聚合统计信息,基于该聚合统计找到proposal中的最优解
  简单的说,就是根据特征k kk的分布来确定l ll个候选切分点,然后根据这些候选切分点把相应的样本放入对应的桶中,对每个桶的 G G G H H H (一阶梯度,二阶梯度)进行累加。最后在候选切分点集合上采用贪心算法。

算法原理:
第一个for循环:对特征 K 按照特征分布的分位数找到切割点的候选集合 S k = { s k 1 , s k 2 , . . . , s k l } S_k=\{s_{k1},s_{k2},... ,s_{kl} \} Sk={sk1,sk2,...,skl},这样做的目的是提取出部分的切分点不用遍历所有的切分点。其中获取某个特征K的候选切割点的方式叫proposal。主要有两种proposal方式:global proposal 和 local proposal。
第二个for循环:将每个特征的取值映射到由这些特征对应的候选点集划分的分桶(buckets)区间 { s k , v ≥ x j k > s k , v − 1 {s_{k,v}} \geq x_{jk} >s_{k,v-1} sk,vxjksk,v1} 中,对每个桶(区间)内的样本统计值 G , H G , H G,H 进行累加统计,最后在这些累计的统计量上寻找最佳分裂点。这样做的主要目的是获取每个特征的候选分割点的 G , H G , H G,H 量。

给定了候选切分点后,计算打分函数的例子如下:
在这里插入图片描述
分桶当然是越多越好,但越多意味计算量也就更大,极端情况下就是一个数据一个桶,但这违背了分桶的初衷,所以分桶数量需要折中。

那么,现在有两个问题:

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

对于候选切分点的选取时机,原文中给了两种方式:global proposal和local proposal。

  • Global: 学习每棵树前, 提出候选切分点
  • Local: 每次分裂前, 重新提出候选切分点

原文对两种方法做了比较,最终得出的结论是:

  • 由于global方式后续不再改变,因此往往需要更多的候选点;local方式不用像global方式那样设定分桶分的很细,但是每次分裂前都要重新提出候选切分点,因此带来的复杂度也更大。通常情况下,都会优先选用global的形式。
  • 全局切分点的个数足够多时,和Exact greedy算法性能相当。
  • 局部切分点个数不需要那么多,因为每一次分裂都重新进行选择。

四、实战演练

下列我们做了一个算法实战,根据诊断措施预测糖尿病的发病率。

  1. 数据集中总共有768条记录,数据集特征如下:
    在这里插入图片描述

测试代码

import xgboost# First XGBoost model for Pima Indians dataset
from numpy import loadtxt
from xgboost import XGBClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# load data
dataset = loadtxt('pima-indians-diabetes.csv', delimiter=",")
# split data into X and y
X = dataset[:,0:8]
Y = dataset[:,8]
# split data into train and test sets
seed = 7
test_size = 0.33
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=test_size, random_state=seed)
# fit model no training data
model = XGBClassifier()
model.fit(X_train, y_train)
# make predictions for test data
y_pred = model.predict(X_test)
predictions = [round(value) for value in y_pred]
# evaluate predictions
accuracy = accuracy_score(y_test, predictions)
print("Accuracy: %.2f%%" % (accuracy * 100.0))
  1. 实验中将数据集分为了:训练集(67%),测试集(33%):
    在这里插入图片描述
    用 67% 的训练集集训练好模型之后,用 33% 的测试集进行测试,其测试结果为:正确性达 74.02%。
    在这里插入图片描述

  2. 不断增加树,观察效果,当达到一种饱和状态,停止增加树,训练结束:
    在这里插入图片描述
    在加入树的过程中,损失函数 L o g ( L o s s ) Log(Loss) Log(Loss) 的变化:
    在这里插入图片描述

  3. 可以分析得到八个特征分别在预测糖尿病的重要性:

from numpy import loadtxt
from xgboost import XGBClassifier
from xgboost import plot_importance
from matplotlib import pyplot
# load data
dataset = loadtxt('pima-indians-diabetes.csv', delimiter=",")
# split data into X and y
X = dataset[:,0:8]
y = dataset[:,8]
# fit model no training data
model = XGBClassifier()
model.fit(X, y)
# plot feature importance
plot_importance(model)
pyplot.show()

在这里插入图片描述

  通过计算不同属性参与节点的分类个数可以看出对糖尿病发病情况影响最重要的属性是糖尿病谱系统功能。

五、总结

5.1 重要问题总结

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

  • XGBoost为什么要引入泰勒展开?
    XGBoost使用了一阶和二阶偏导, 二阶导数有利于梯度下降的更快更准。使用泰勒展开取得函数做自变量的二阶导数形式,可以在不选定损失函数具体形式的情况下,仅仅依靠输入数据的值就可以进行叶子分裂优化计算,本质上也就把损失函数的选取和模型算法优化/参数选择分开了。这种去耦合增加了XGBoost的适用性,使得它按需选取损失函数,可以用于分类,也可以用于回归。

  • XGBoost相对于GBDT有什么优点?
    [1] 基分类器: XGBoost的基分类器不仅支持cart树,还支持线性分类器,此时的XGBoost相当于带L1和L2正则项的逻辑回归(分类问题)或者线性回归(回归问题)。
    [2] 导数信息: XGBoost对损失函数做了二阶泰勒展开,GBDT只用了一阶导数,并且XGBoost还支持自定义损失函数,只有损失函数一阶,二阶可导。
    [3] 正则项: XGBoost的目标函数中加入了正则项,这是GBDT中没有的,用于防止过拟合
    [4] 列抽样: XGBoost支持列抽样,即特征抽样,与随机森林类似,用于防止过拟合
    [5] 缺失值处理: 对树中的非叶子结点,XGBoost在训练过程中会自动学习出它的默认分裂方向,如果测试样本的该特征的特征值缺失,将会划入默认分支中去。
    [6] 并行化: 注意不是tree维度的并行(Boosting策略类的加法模型都做不到tree级别的并行),而是特征维度的并行,XGBoost预先将每个特征按特征值排好序,存储为block结构,分裂结点时,可以采用多线程并行查找每个特征的最佳分裂点,从而提升训练速度
    [7] 处理不平衡: 在计算损失函数时,考虑不平衡因素,加权损失。

  • XGBoost对缺失值怎么处理?
    在普通的GBDT策略中,对于缺失值的方法是先手动对缺失值进行填充,然后当做有值的特征进行处理,但是这样人工填充不一定准确,而且没有什么理论依据。而XGBoost采取的策略是先不处理那些值缺失的样本,采用那些有值的样本搞出分裂点,在遍历每个有值特征的时候,尝试将缺失样本划入左子树和右子树,选择使损失最优的值作为分裂点。

  • 如何理解XGBoost的并行计算?
    初始学习XGBoost,一个很容易困惑的点就是XGBoost的并行计算,毕竟其采用的加法模型架构,怎么看都是一种串行计算。事实上,XGBoost的并行不是在计算各轮添加的回归树上,而是体现在上述Split Finding中的排序上,即并行对各个样本,依据其所有m个特征值进行m轮从小到大排序,然后计算各个特征下各样本从左到右做划分对应的score,这才是并行计算的实质所在。由于XGBoost主要的计算量都击中在找Split Finding上,故而对这一步的并行计算大大加速了XGBoost的模型运算速度。

5.2 XGBoost的一些重点

  • w w w 是最优化求出来的,不是啥平均值或规则指定的,这个算是一个思路上的新颖吧;
  • 正则化防止过拟合的技术,上述看到了,直接 Loss function 里面就有;
  • 支持自定义loss function,只要能泰勒展开(能求一阶导和二阶导)就行;
  • 支持并行化,这个地方有必要说明下,因为这是xgboost的闪光点,直接的效果是训练速度快,boosting技术中下一棵树依赖上述树的训练和预测,所以树与树之间应该是只能串行!那么大家想想,哪里可以并行? 没错,在选择最佳分裂点,进行枚举的时候并行!(据说恰好这个也是树形成最耗时的阶段)
  • 特别设计了针对稀疏数据的算法:
    假设样本的第i个特征缺失时,无法利用该特征对样本进行划分,这里的做法是将该样本默认地分到指定的子节点,至于具体地分到哪个节点还需要某算法来计算,
      算法的主要思想是,分别假设特征缺失的样本属于右子树和左子树,而且只在不缺失的样本上迭代,分别计算缺失样本属于右子树和左子树的增益,选择增益最大的方向为缺失数据的默认方向(咋一看如果缺失情况为3个样本,那么划分的组合方式岂不是有8种?指数级可能性啊,仔细一看,应该是在不缺失样本情况下分裂后,把第一个缺失样本放左边计算下loss function和放右边进行比较,同样对付第二个、第三个…缺失样本,这么看来又是可以并行的??)(答:论文中“枚举”指的不是枚举每个缺失样本在左边还是在右边,而是枚举缺失样本整体在左边,还是在右边两种情况。 分裂点还是只评估特征不缺失的样本。);
  • xgboost还支持设置样本权重,这个权重体现在梯度 g i g_i gi 和二阶梯度 h i h_i hi 上,是不是有点AdaBoost的意思,重点关注某些样本。
  • 交叉验证,方便选择最好的参数,early stop,比如你发现30棵树预测已经很好了,不用进一步学习残差了,那么停止建树。

5.3 XGBoost的一些不足

  • 虽然利用预排序和近似算法可以降低寻找最佳分裂点的计算量,但在节点分裂过程中仍需要遍历数据集;
  • 预排序过程的空间复杂度过高,不仅需要存储特征值,还需要存储特征对应样本的梯度统计值的索引,相当于消耗了两倍的内存。

六、Reference

本文参考了几位大牛的博客,并附上参考链接。
[1] XGBoost:A Scalable Tree Boosting System
[2] 论文精读(一)——XGBoost:A Scalable Tree Boosting System
[3] 深入理解XGBoost
[4] 机器学习–boosting家族之XGBoost算法
[5] xgboost入门与实战(原理篇)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值