1、算法简介
Xgboost 的全称是eXtreme Gradient Boosting,它是Gradient Boosting Machine的一个c++实现,作者为正在华盛顿大学研究机器学习的大牛陈天奇 。xgboost最大的特点在于,它能够自动利用CPU的多线程进行并行,同时在算法上加以改进提高了精度。首先回顾一下上一篇中的提升树模型目标函数:
Obj(Θ)=∑i=1nL(yi,y^i)+∑k=1KΩ(fk)
其中 n 为样本个数,
(1)第一部分是训练误差,也就是大家相对比较熟悉的如平方误差,而第二部分是每棵树的复杂度的和。因为现在我们的参数可以认为是在一个函数空间里面,我们不能采用传统的如SGD之类的算法来学习我们的模型,因此我们会采用一种叫做Boosting的 方式,每一次保留原来的模型不变,加入一个新的函数
y^(0)i=0y^(1)i=f1(xi)=y^(0)i+f1(xi)y^(2)i=f1(xi)+f2(xi)=y^(1)i+f2(xi)......y^(t)i=∑k=1tfk(xi)=y^(t−1)i+ft(xi)
其中 xi 表示第 i 个样本点,
Obj(t)=∑i=1nL(yi,y^(t)i)+∑i=1tΩ(fi)=∑i=1nL(yi,y^(t−1)i+ft(xi))+Ω(ft)+const
注: ∑t−1i=1Ω(fi) 在目标函数中变成了常数项 const , n 为样本个数,
当
Obj(t)=∑i=1n(yi−y^(t−1)i−ft(xi))2+Ω(ft)+const=∑i=1n(yi−y^(t−1)i−ft(xi))2+Ω(ft)+const=∑i=1n[2(y^(t−1)i−yi)ft(xi)+ft(xi))2]+Ω(ft)+const
注:( y^(t−1)i−yi )一般叫做残差,括号的平方展开后,常数项加入了最后的 const 中, n 为样本个数。
更一般的,对于不是平方误差的情况,我们会采用如下的泰勒展开近似来定义一个近似的目标函数,方便我们进行这一步的计算。
目标:
泰勒公式:
f(x+Δx)≈f(x)+f′(x)Δx+12f′′(x)Δx2
定义:
gi=∂L(yi,y^(t−1))∂y^(t−1)hi=∂2L(yi,y^(t−1))∂y^(t−1)
展开时把 yi 看做常数, y^(t−1)i 看做 x ,
Obj(t)≈∑i=1n[L(yi,y^(t−1)i)+gift(xi)+12hif2t(xi)]+Ω(ft)+const
当我们把常数项移除之后,我们会发现如下一个比较统一的目标函数。这一个目标函数有一个非常明显的特点,它只依赖于每个数据点的在误差函数上的一阶导数和二阶导数:
Obj(t)≈∑i=1n[gift(xi)+12hif2t(xi)]+Ω(ft)+const
(2)以上是目标函数中训练误差的部分,接下来定义树的复杂度。 对于模型树 f 的定义做一下细化,把树拆分成结构函数
ft(xi)=wq(x),w∈RT,q:Rd−>{1,2,3...,T}
其中 T 为叶节点个数,
当我们给定了如上定义之后,我们可以定义一棵树的复杂度如下。这个复杂度包含了一棵树里面节点的个数,以及每个树叶子节点上面输出分数的
Ω(ft)=γT+12λ∑j=1Tw2j
其中 T 表示叶子节点的个数,
直观上看,目标要求预测误差尽量小,叶子节点尽量少,节点数值尽量不极端。这个怎么看,如果某个样本label数值为4,那么第一个回归树预测3,第二个预测为1;另外一组回归树,一个预测2,一个预测2,那么倾向后一种,为什么呢?前一种情况,第一棵树学的太多,太接近4,也就意味着有较大的过拟合的风险。
接下来是最关键的一步,在这种新的定义下,我们可以把目标函数进行如下改写,其中 Ij 被定义为每个叶子 j 上面样本集合
Obj(t)≈∑i=1n[gift(xi)+12hif2t(xi)]+Ω(ft)=∑i=1n[giwq(xi)+12hiw2q(xi)]+γT+12λ∑j=1Tw2j=∑j=1T[(∑i∈Ijgi)wj+12(∑i∈Ijhi+λ)w2j]+γT
这一个目标包含了 T 个相互独立的单变量二次函数。我们可以定义
那么这个目标函数可以进一步改写成如下的形式,假设我们已经知道树的结构 q ,我们可以通过这个目标函数来求解出最好的
这里只涉及到了如何求一个一维二次函数的最小值的问题,这两个的结果对应如下,左边是最好的w,右边是这个w对应的目标函数的值:
w∗j=−GjHj+λObj=−12∑j=1TGjHj+λ+γT
(3)预测值的计算
Obj代表了当我们指定一个树的结构的时候,我们在目标上面最多减少多少。我们可以把它叫做结构分数(structure score)。你可以认为这个就是类似吉尼系数一样更加一般的对于树结构进行打分的函数。所以我们的算法也很简单,我们不断地枚举不同树的结构,利用这个打分函数来寻找出一个最优结构的树,加入到我们的模型中,再重复这样的操作。不过枚举所有树结构这个操作不太可行,所以常用的方法是贪心法,每一次尝试去对已有的叶子加入一个分割。对于一个具体的分割方案,我们可以获得的增益可以由如下公式计算:
Gain=12[G2LHL+λ+G2RHR+λ−(GL+GR)2HL+HR+λ]−λ
其中第一项为左子树分数,第二项为右子树分数,第三项为不分割我们可以拿到的分数。
通俗解释贪心策略:
就是决策时刻按照当前目标最优化决定,说白了就是眼前利益最大化决定,“目光短浅”策略,他的优缺点细节大家自己去了解,经典背包问题等等。这里是怎么用贪心策略的呢,刚开始你有一群样本,放在第一个节点,这时候 T =1,
接着来,接下来要选个feature分裂成两个节点,变成一棵弱小的树苗,那么需要:1)确定分裂用的feature,how?最简单的是粗暴的枚举,选择loss function效果最好的那个;2)如何确立节点的 w <script type="math/tex" id="MathJax-Element-1349">w</script>以及最小的loss function,怎么做呢?对,二次函数的求最值(细节的会注意到,计算二次最值是不是有固定套路,导数=0的点)那么节奏是,选择一个feature分裂,计算loss function最小值,然后再选一个feature分裂,又得到一个loss function最小值…你枚举完,找一个效果最好的,把树给分裂,就得到了小树苗。
在分裂的时候,你可以注意到,每次节点分裂,loss function被影响的只有这个节点的样本,因而每次分裂,计算分裂的增益(loss function的降低量)只需要关注打算分裂的那个节点的样本,就得到上述的公式。
(4)Xgboost的优点:
Xgboost相对于普通GBDT的实现,可能具有以下的一些优势:
1) 显式地将树模型的复杂度作为正则项加在优化目标;
2) 公式推导里用到了二阶导数信息,而普通的GBDT只用到一阶;
3) 允许使用column(feature) sampling来防止过拟合,借鉴了Random Forest的思想,sklearn里的gbm好像也有类似实现;
4) 实现了一种分裂节点寻找的近似算法,用于加速和减小内存消耗;
5) 节点分裂算法能自动利用特征的稀疏性;
6) data事先排好序并以block的形式存储,利于并行计算;
7) cache-aware, out-of-core computation,这个我不太懂;
8) 支持分布式计算可以运行在MPI,YARN上,得益于底层支持容错的分布式通信框架rabit。
原文参考: https://blog.csdn.net/github_38414650/article/details/76061893
http://www.52cs.org/?p=429&_360safeparam=88181703
2、Xgboost的实现
(1)安装Xgboost:
https://wang-shuo.github.io/2017/02/21/%E5%9C%A8Windows%E4%B8%8B%E5%AE%89%E8%A3%85XGBoost/
(2)测试用例
#coding=utf-8
import xgboost as xgb
import numpy as np
data = np.random.rand(5,10) # 5 entities, each contains 10 features
label = np.random.randint(2, size=5) # binary target
dtrain = xgb.DMatrix( data, label=label)
dtest = dtrain
param = {'bst:max_depth':2, 'bst:eta':1, 'silent':1, 'objective':'binary:logistic' }
param['nthread'] = 4
param['eval_metric'] = 'auc'
evallist = [(dtest,'eval'), (dtrain,'train')]
num_round = 10
bst = xgb.train( param, dtrain, num_round, evallist )
print bst