XGBoost原理分析与快速实践

XGBoost原理分析与快速实践

1. Boosting树模型

XGBoost是Boosting树模型的一种(当然XGBoost也可以使用除树之外的其他的线性模型),其他还有许多的Boosting树模型,下面首先对Boosting树模型做一些简要的介绍。


给定n个样本,m个特征的数据集 D = { ( X i , y i ) } ( ∣ D ∣ = n , X i ∈ R M , y i ∈ R ) D=\left\{ (X_i, y_i) \right\}(|D|=n,X_i \in R^M , y_i \in R) D={ (Xi,yi)}(D=n,XiRM,yiR),提升树模型采用K次迭代的结果做为输出结果。对于输入 X i X_i Xi,输出 y ^ i \hat{y}_i y^i的表达式为:

y ^ i = ϕ ( X i ) = ∑ k = 1 K f k ( X i ) , f k ∈ F \hat{y}_i = \phi (X_i) = \sum_{k=1}^{K} f_k(X_i),f_k \in F y^i=ϕ(Xi)=k=1Kfk(Xi),fkF

其中 F = f ( X ) = { w q ( X ) } ( q : R T → T , w ∈ R T ) F={f(X)=\left\{ w_{q(X)} \right\}} (q:R^T \rightarrow T, w \in R^T) F=f(X)={ wq(X)}(q:RTT,wRT),表示提升树的空间集,各个变量的含义如下:

  • q q q 表示树的结构,可以将样本映射到对应的叶子节点
  • w w w 表示树节点的权重
  • T T T 表示树叶子结点的数量
  • 每个 f k f_k fk都是一颗树,对应独立的树的结构 q q q 和权重 w w w

有了上面的数学表达,我们可以通过下面这个图直观理解一下,图中就是有两个树,然后输入数据的输出是两个树的预测结果之和
UzPSnU.png

2. 目标函数

XGBoost的目标函数如下(生成第 t 棵新的树时):

O b j ( t ) = ∑ i = 1 n l ( y i , y ^ i t ) + Ω ( f t ) Obj^{(t)} = \sum_{i=1}^{n} l( y_i, \hat{y}_i^t) +\Omega(f_t) Obj(t)=i=1nl(yi,y^it)+Ω(ft)

符号说明

  • l l l 为损失函数
  • Ω \Omega Ω 为正则项
  • y i ^ \hat{y_i} yi^ 为预测标签
  • y i y_i yi 为真实标签
  • n n n 为样本数

目标函数包含两个部分,第一部分是损失函数,第二部分是正则项,首先介绍正则项,表达式如下:

Ω ( f ) = γ T + 1 2 λ ∣ ∣ w ∣ ∣ 2 \Omega (f) = \gamma T + \cfrac{1}{2} \lambda ||w||^2 Ω(f)=γT+21λw2

符号说明

  • 其中 T T T 为叶子结点的数量(加入该项可以控制树的复杂度,让叶子结点尽可能的少)
  • w w w 为叶子结点的权重
  • γ \gamma γ λ \lambda λ 为超参数,用于控制这两部分的权重

对于损失函数,当我们每次生成第 t 棵新的树时,我们会计算下面的目标函数:

O b j ( t ) = ∑ i = 1 n l ( y i , y ^ i t − 1 + f t ( x i ) ) + Ω ( f t ) Obj^{(t)} = \sum_{i=1}^{n} l( y_i, \hat{y}_i^{t-1} + f_t(x_i)) +\Omega(f_t) Obj(t)=i=1nl(yi,y^it1+ft(xi))+Ω(ft)

然后进来泰勒展开 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 + \cfrac{1}{2}f''(x) \Delta x^2 f(x+Δx)f(x)+f(x)Δx+21f(x)Δx2):

O b j ( t ) = ∑ i = 1 n [ l ( y i , y ^ i t − 1 ) + g i f t ( x i ) + 1 2 h i f i 2 ( x i ) ] + Ω ( f t ) Obj^{(t)} = \sum_{i=1}^{n} [ l( y_i, \hat{y}_i^{t-1}) + g_i f_t(x_i) + \cfrac{1}{2} h_i f^2_i(x_i) ] +\Omega(f_t) Obj(t)=i=1n[l(yi,y^it1)+gift(xi)+21hifi2(xi)]+Ω(ft)

其中 g i = ∂ l ( y i , y ^ i t − 1 ) ∂ y ^ i t − 1 g_i = \cfrac{\partial l(y_i, \hat{y}_i^{t-1})}{\partial \hat{y}_i^{t-1}} gi=y^it1l(yi,y^it1) h i = ∂ 2 l ( y i , y ^ i t − 1 ) ∂ ( y ^ i t − 1 ) 2 h_i = \cfrac{\partial^2 l(y_i, \hat{y}_i^{t-1})}{\partial ({\hat{y}_i^{t-1}})^2} hi=(y^it1)22l(yi,y^it1)(分别为一阶导数和二阶导数)

由于在生成第 t 棵树时,前 t-1 课树已经确定,所以 l ( y i , y ^ i t − 1 ) l(y_i, \hat{y}_i^{t-1}) l(yi,y^it1)是一个常数,所以上式又可以化简为:

O b j ( t ) = ∑ i = 1 n [ g i f t ( x i ) + 1 2 h i f i 2 ( x i ) ] + Ω ( f t ) + c o n s t a n t Obj^{(t)} = \sum_{i=1}^{n} [ g_i f_t(x_i) + \cfrac{1}{2} h_i f^2_i(x_i) ] + \Omega(f_t) + constant Obj(t)=i=1n[gift(xi)+21hifi2(xi)]+Ω(ft)+constant

更加具体一点,当我们做分类时,我们使用logloss(当然也有其他的损失函数,取决于你的用途),如下:

l ( y ^ i , y i ) = y i l n ( 1 + e − y ^ i ) + ( 1 − y i ) l n ( 1 + e − y ^ i ) l(\hat{y}_i, y_i) = y_i ln(1 + e^{-\hat{y}_i})+ (1- y_i) ln(1+ e^{-\hat{y}_i}) l(y^i,yi)=yiln(1+ey^i)+(1yi)ln(1+ey^i)

其中 g i = y ^ i − y i g_i = \hat{y}_i - y_i gi=y^iyi h i = y ^ i ∗ ( 1 − y ^ i ) h_i = \hat{y}_i * (1 - \hat{y}_i) hi=y^i(1y^i)

3. 求解过程

我们得到了上述的目标函数,我们需要对目标函数进行进一步的化简(这里使用的符号含义同上,没有做任何修改):

O b j ( t ) ≈ ∑ i = 1 n [ g i f t ( x i ) + 1 2 h i f i 2 ( x i ) ] + Ω ( f t ) + c o n s t a n t      Obj^{(t)} \approx \sum_{i=1}^{n}[ g_i f_t(x_i) + \cfrac{1}{2} h_i f^2_i(x_i) ] + \Omega(f_t) + constant \,\,\,\, Obj(t)i=1n[gift(xi)+21hifi2(xi)]+Ω(ft)+constant(这里常数项没有影响就去掉了)

= ∑ 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 =\sum_{i=1}^{n} [g_i w_{q(x_i) } + \cfrac{1}{2} h_i w^2_{q(x_i)}] + \gamma T + \cfrac{1}{2} \lambda \sum_{j=1}^{T} w_j^2 =i=1n[giwq(xi)+21hiwq(xi)2]+γT+21λj=1Twj2

= ∑ j = 1 T [ ( ∑ i ∈ I j g i ) w j + 1 2 ( ∑ i ∈ I j h i + λ ) w j 2 ] + γ T = \sum_{j=1}^{T} [(\sum_{i \in I_j} g_i) w_j + \cfrac{1}{2}(\sum_{i \in I_j}h_i + \lambda) w_j^2] + \gamma T =j=1T[(iIjgi)wj+21(iIjhi+λ)wj2]+γT

然后我们可以定义:

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

所以上述公式可以进一步化简为:

O b j ( t ) = ∑ j = 1 T [ G J w j + 1 2 ( H j + λ ) w j 2 ] + γ T Obj^{(t)} = \sum_{j=1}^{T} [G_J w_j + \cfrac{1}{2}(H_j + \lambda) w_j^2] + \gamma T Obj(t)=j=1T[GJwj+21(Hj+λ)wj2]+γT

其中 w j w_j wj 为我们待求解的权重,我们只需对 w j w_j wj 求导等于0, 可以解得:

w j ∗ = − G j H j + λ w_j^* = - \cfrac{G_j}{H_j+ \lambda} wj=Hj+λGj

然后将 w j w_j wj 的最优解打入目标函数可以得到:

O b j ( t ) = − 1 2 ∑ j = 1 T G j 2 H j + λ + γ T Obj^{(t)} = - \cfrac{1}{2} \sum_{j=1}^{T} \cfrac{G_j^2}{H_j + \lambda} + \gamma T Obj(t)=21j=1THj+λGj2+γT


最后就是说,目标函数是用来衡量第 t 棵树结构的好坏的,而与叶子结点的值是没有关系的,可以从最后的公式中看出,Obj 只与 G j G_j Gj H j H_j Hj T T T 有关,而他们又只与树的结构有关,和叶子结点的值无关,可以通过下图直观的理解:

Uz3R41.png

4. 寻找最佳分裂点

我们通过定义增益值来确定分裂点(和许多其他树的模型相同),以下是增益值的定义:

G a i n = G L 2 H L + λ + G R 2 H R + λ − ( G L + G R ) 2 H L + H R + λ − γ Gain = \cfrac{G_L^2}{H_L + \lambda} + \cfrac{G_R^2}{H_R + \lambda} - \cfrac{(G_L+ G_R)^2}{H_L + H_R + \lambda} - \gamma Gain=HL+λGL2+HR+λGR2HL+HR+λ(GL+GR)2γ

符号说明:

  • G L G_L GL 表示划分点左边数据的一阶导数之和( H L   G R   H R H_L \, G_R \,H_R HLGRHR 也都是类似的)
  • γ \gamma γ 是增加一个分支造成的复杂度增加的损失值

基于上述公式我们就有很多方案来确定最佳分裂点了

  1. 贪心法:顾名思义,就是遍历所有的分割点,分别计算损失值,然后选择增益值最大的分割点(但是计算量过于巨大)
  2. 近似算法:比如说三等分法,就是将数据分为三份 ,然后就会出现两个分割点,然后在这连个分割点中选择最佳分割点
  3. XGBoost采用的分裂方式:使用样本的二阶导数值 h i h_i hi 作为每个样本的权重,然后按照样本的权重作为依据,使用上述的三等分法,然后选择最后分割点,

对于XGBoost采用的分裂方式这里多说一些,为什么采用 h i h_i hi 作为每个样本的权重?可以通过以下式子看到各个样本对目标函数的贡献:

UzJNAH.png


当然对于选取分裂点时,有的样本会出现缺失值,首先是对于训练情况下:

尝试将缺失值样本划分到左节点或者右节点两种情况,然后选择增益率更大的一种情况,可以通过下面伪代码来理解一下:

UzJxv6.png

然后是对于预测过程,如果遇到了缺失值,我们只需要预先制定好该节点的默认分裂方向即可。

5. 直观实践

可能看完理论分析还是会有点懵,不如自己算一遍!可以参考这篇博客,这篇文章有很多的推导过程也都是参考的这篇博客,写的非常详细!

6. 代码实践

现在已经有封装好的XGBoost实现了,只需安装一个xgboost库即可,使用的过程也非常简单,因为已经封装好了,所以内部结构对于程序员(调包侠)来说是完全透明的,我们只需要想使用决策树那样使用xgboost即可,即输入训练数据的特征已经对应的标签,然后就给你训练好了,非常的人性化~所以重点可能是在特征工程(提取出重要的特征)。当然xgboost也有许多可以调节的参数,具体可以查看相应文档,点我就可以了

下面是相应代码,使用的是Pima印第安人数据集,包含8个特征,让你预测患者是否有糖尿病(就是一个二分类)。

代码的思路非常简单,就是将8个特征和相应的标签丢进去,然后就跑出来训练后的模型,然后就能进行预测了,太方便了吧~

import numpy
import xgboost
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# load数据集
dataset = numpy.loadtxt('Pima.csv', delimiter=",")
# 把 X Y 分开
X = dataset[:,0:8]   # 特征部分
Y = dataset[:,8]     # 预测结果
# 现在我们分开训练集和测试集
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)
print(X.shape, Y.shape, X_train.shape, X_test.shape)
# 训练模型
model = xgboost.XGBClassifier()
# 这⾥里里参数的设置可以⻅见:https://xgboost.readthedocs.io/en/latest/python/index.html
model.fit(X_train, y_train)
# 做预测
y_pred = model.predict(X_test)
predictions = [round(value) for value in y_pred]
# 显示准确率
accuracy = accuracy_score(y_test, predictions)
print("Accuracy: %.2f%%" % (accuracy * 100.0))

输出结果:

(768, 8) (768,) (514, 8) (254, 8)
Accuracy: 74.02%

猜你喜欢

转载自blog.csdn.net/weixin_44338712/article/details/107599469