【博客地址】:https://blog.csdn.net/sunyaowu315
【博客大纲地址】:https://blog.csdn.net/sunyaowu315/article/details/82905347

  对数据分析、机器学习、数据科学、金融风控等感兴趣的小伙伴,需要数据集、代码、行业报告等各类学习资料,可添加qq群(资料共享群):102755159,也可添加微信wu805686220,加入微信讨论群,相互学习,共同成长。


文章目录

  • 一 写在前边
  • 二 XGBoost简介
  • 三 相关知识
    • 1、机器学习 & 监督学习
    • 2、决策树
    • 3、集成学习
  • 四 XGBoost详解
    • 1、CART树
    • 2、Boosting tree
    • 3、目标函数
    • 4、贪心策略 + 加法训练 + 最优化
    • 5、正则项
    • 6、树结构
    • 7、XGBoost算法特点
    • 8、XGBoost工程优化
  • 五 调参技巧 & 代码实现
    • 1、Gradient Boosting的调参
    • 2、XGBoost的调参
  • 六 项目实战
  • 七 关键词
  • 八 结尾

一 写在前边

开始之前,先立个flag:2019年,抽出时间每月写一篇高质量博文,范围在软件技术、机器学习、风险管理、金融业务理解等,夯实自己的知识面、基础能力、行业认知等。

还是用自己一贯的风格,有了这样一个想法就开始写起来。2019年1月初开始学习,2019年1月14日提笔,随着对XGBoost版块内容理解的加深,不断增删查改,让文章变得逐渐精彩,这篇博文拟定2019年1月末前落笔,加油。


二 XGBoost简介

简单介绍:

XGBoost的全称是 Extreme Gradient Boosting。是一个专注于梯度提升算法研究的机器学习函数库,诞生于2014年2月,作者为华盛顿大学研究机器学习的大牛——陈天奇。他在研究中深深的体会到机器学习现有库计算速度慢和精度不够准的问题,为此着手搭建 XGBoost 项目。 XGBoost 学习效果很好,速度也很快。相比梯度提升算法在另一个常用机器学习库scikit-learn中的实现,它的性能经常有十倍以上的提升。问世后,因其优良的学习效果以及高效的训练速度而获得广泛的关注,在各大算法比赛上也大放异彩。

在2018年KDD会议上,作者陈天奇将这一库函数所涉及到的理论推导和加速方法整理为论文发表了出来,尽管这只是一个机器学习方面的函数库,但其中有大量通用的优化加速方法,值得我们学习。为此,我们从原论文出发,结合其他优秀博主博文中精彩的见解,开始本文对XGBoost算法原理的探索。

现在开始:

首先,我们来弄清楚XGBoost在机器学习中所处的位置:

可以看到,在机器学习中,XGBoost属于监督学习中决策树的提升树范畴内(后文有机器学习、监督学习等知识的相关介绍)。

简单说,XGBoost就是用多颗cart决策树通过集成思想搭建的复杂树模型。多颗树如何做预测呢?答案非常简单,就是将每棵树的预测值加总到一起作为最终的预测值,可谓简单粗暴。我们了解过的随机森林RandomForest就是一种依托决策树而建立起来的简单套袋决策相加模型。

为了方便理解,我们用如下一颗CART树和两堆CART树的示例,来判断一个人是否会喜欢计算机游戏:

一棵树: 根据年龄、性别来做判断

两棵树: 树1:年龄、性别;树2:平时是否使用计算机

预测值为树1和树2预测值之和。该图表明,我们就是简单将各个CART树的预测分数相加来实现对样本的预测。

!!!蒙了吧!这么多树是怎么选出来的?每棵树的分值又是怎么算出来的?为什么把多棵树分值加和就能作为预测值?效果如何呢?

数学很美,因为它能将很多抽象的事物用漂亮的公式表达出来。——孙耀武 (哈哈哈哈)

现在的树模型,干干巴巴的,麻麻赖赖的,一点儿不圆润!盘它!

接下来我们层层追溯,将XGBoost相关机器学习基础知识点剖析一遍。(方便初学者,高手可直接跳过)

盘的大致顺序为:机器学习——监督学习——决策树——集成学习——XGBoost

过程中会穿插一些像GBDT集成算法以及如今最热门的集成学习提升算法Lightgbm的比对等。


三 相关知识

1、机器学习 & 监督学习

机器学习概念很火,其实比较好理解,我们移步机器学习介绍章节:机器学习简介

2、决策树

决策树,很好理解,就是一颗可以帮助决策的分叉树模型。

我们移步决策树原理介绍章节:决策树原理(看罢如果对决策树的原理仍未熟悉的话,多看几遍!多看几遍还不懂的话, 要么是作者功力一般,要么是我们看得还不够)

回到本文,我们已经知道了决策树算法的设计原理:

  • 用一些简单的决策规则,根据IF TEHN 逻辑,选择多个特征属性进行分叉,循环多次后成为一个具有一定分类意义的树模型。

3、集成学习

集成学习(ensemble learning),多颗可以帮助决策的树模型组合在一起。

篇幅有限,我们再次移步集成学习原理介绍章节:集成学习原理(如果对集成学习的大致思路仍不了解的话 ,要么。。。。。。要么(手动狗头))

回到本文,我们已经知道了集成学习算法的设计原理:

  • 利用一些组合算法的逻辑,通过对弱决策树的组合,生成一个复杂的具有强决策能力的集成树模型。

四 XGBoost详解

XGBoost为什么使用CART树而不用其他普通的决策树呢?简单讲,对于分类问题而言,CART树的叶子节点对应的值是一个具体的分数而非类别,这样就特别有利于实现高效的优化算法。

XGBoost出名的原因一是准,二是快,之所以快,其中就有选用CART树的一份功劳。

1、CART树

CART(分类回归树, Class and Regression Tree)是XGBoost最基本的组成部分。其根据训练特征及训练数据构建分类树,判定每条数据的预测结果。其中构建树使用giniginigini指数计算增益,即进行构建树的特征选取,giniginigini指数公式如下:

Gini(D)=∑k=1Kpk(1−pk)Gini(D) = \sum_{k=1}^{K}p_{k}(1-p_{k})Gini(D)=k=1∑K​pk​(1−pk​)
其中pkp_{k}pk​表示数据集DDD中类别kkk的概率,KKK表示类别个数。此处的kkk表示分类类别。

giniginigini指数计算增益公式如下:

Gini(D,A)=∣D1∣∣D∣Gini(D1)+∣D2∣∣D∣Gini(D2)Gini(D,A) = \frac{\left |D1 \right |}{\left |D \right |}Gini(D1) + \frac{\left |D2 \right |}{\left |D \right |}Gini(D2)Gini(D,A)=∣D∣∣D1∣​Gini(D1)+∣D∣∣D2∣​Gini(D2)

DDD表示整个数据集,D1D_{1}D1​和D2D_{2}D2​分别表示数据集中特征为AAA的数据集和特征非AAA的数据集,Gini(D1)Gini(D_{1})Gini(D1​)表示特征为AAA的数据集的giniginigini指数。

我们以是否打网球为例(只是举个栗子):

Gini(D,A1=“晴”)=510×(15×45×2)∔510×(35×25×2)=0.4Gini(D,A_{1} = “晴”)= \frac{5}{10}\times (\frac{1}{5}\times\frac{4}{5}\times2)\dotplus \frac{5}{10}\times (\frac{3}{5}\times\frac{2}{5}\times2)=0.4Gini(D,A1​=“晴”)=105​×(51​×54​×2)∔105​×(53​×52​×2)=0.4

Gini(D,A2=“高”)=0.305Gini(D,A_{2} = “高”)= 0.305Gini(D,A2​=“高”)=0.305

Gini(D,A2=“适中”)=0.3Gini(D,A_{2} = “适中”)= 0.3Gini(D,A2​=“适中”)=0.3

Gini(D,A2=“低”)=0.419Gini(D,A_{2} = “低”)= 0.419Gini(D,A2​=“低”)=0.419

其中,Gini(D,A2="适中")Gini(D,A_{2} = "适中")Gini(D,A2​="适中") 最小,所以构造树首先使用温度适中(我们在决策树篇中说过,giniginigini系数和信息增益的不同之处在于:giniginigini指数的选择由小到大)。然后分别在左右子树中查找构造树的下一个条件。

本例中,使用温度适中拆分后,子树刚好类别全为是,即温度适中时去打网球的概率为1。

2、Boosting tree

一颗CART往往过于简单,并不能有效地做出预测,为此,采用更进一步的模型boosting tree,利用多棵树来进行组合预测。具体算法如下:

输入:训练集 N={(x1,y1),(x2,y2),…(xn,yn)}N=\left\{ (x_1,y_1 ),(x_2,y_2 ),…(x_n,y_n ) \right\}N={(x1​,y1​),(x2​,y2​),…(xn​,yn​)}

输出:提升树 fK(x)f_{K}(x)fK​(x)

步骤:

  • (1)初始化 f0(x)=0f_{0}(x)=0f0​(x)=0

  • (2)对 k=1,2,…,Kk=1,2,…,Kk=1,2,…,K

    • ①计算残差 rki=yi−f(k−1)(xi),i=1,2,…,nr_{ki}=y_i-f_{(k-1)} (x_i ),i=1,2,…,nrki​=yi​−f(k−1)​(xi​),i=1,2,…,n

    • ②拟合残差 rki学习一个回归树,得到N(x:θk)r_{ki} 学习一个回归树,得到 N(x:θ_k )rki​学习一个回归树,得到N(x:θk​)

    • ③更新 fk(x)=f(k−1)(x)+N(x:θk)f_k (x)=f_{(k-1)} (x)+N(x:θ_k )fk​(x)=f(k−1)​(x)+N(x:θk​)

  • (3)得到回归提升树: fK(x)=Σk=1KN(x:θk)f_K (x)=\Sigma_{k=1}^{K}N(x:θ_k )fK​(x)=Σk=1K​N(x:θk​)

不好理解?没事,下面XGBoost有详细的数学推到过程。

3、目标函数

知道了XGBoost是由多个cart组合成的模型,我们就赋予它一个数学表达式,如下所示:

yi^=∑k=1Kfk(xi),fk∈F\hat{y_{i}} = \sum_{k=1}^{K}f_{k}(x_{i}),f_{k}\in Fyi​^​=k=1∑K​fk​(xi​),fk​∈F

这里的fff表示一棵具体的cartcartcart树,FFF表示所有可能的cartcartcart树,f(xi)f(x_{i})f(xi​)就是一棵树的预测值,yi^\hat{y_{i}}yi​^​就是这K颗树的预测值加和了。

模型表示出来后,我们自然而然就想问:

  • ①这个表达式的参数是什么?
  • ②用来优化这些参数的目标函数又是什么?

首先明确下我们的目标,希望建立K个回归树,使得树群的预测值尽量接近真实值(准确率)而且有尽量大的泛化能力(机器学习更为本质的东西),从数学角度看这是一个多目标泛函最优化,看下目标函数(函数的由来可回顾决策树部分,这里不再细说):
L(ϕ)=∑i(yi^−yi)+∑kΩ(fk)L(\phi ) = \sum_{i}(\hat{y_{i} }-y_{i}) + \sum_{k}^{}\Omega (f_{k})L(ϕ)=i∑​(yi​^​−yi​)+k∑​Ω(fk​)

我们看到,这个目标函数同样包含两部分:

  • ①第一部分是损失函数,其中i表示第iii个样本,l(yi−yi)l(y^i−yi)l(yi−yi)表示第iii个样本的预测误差,误差越小越好。
  • ②第二部分是复杂项,∑kΩ(fk)∑kΩ(fk)∑kΩ(fk)表示树的正则项,也就是树的复杂度。该项越小,复杂度越低,泛化能力越强。这里的复杂度为K棵树的复杂度相加。(我们稍后推导:正则项为什么是树的复杂度?)

上面,我们获取了XGBoost模型和它的目标函数,那么训练的任务就是通过最小化目标函数来找到最佳的参数组。

这个模型的目标函数目前看来还比较抽象,接下来我们逐一对其具体化。首先,目标函数的参数在哪里?

我们很自然地想到,XGBoost模型由CART树组成,参数自然存在于每棵CART树之中。那么,就单一的 CART树而言,它的参数是什么呢?

根据上面对CART树的介绍,我们知道,确定一棵CART树需要确定两部分:

  • ①树的结构,即如何分裂节点,分裂多少节点。这个结构负责将一个样本映射到一个确定的叶子节点上,其本质上就是一个函数。
  • ②就是各个叶子节点上的分数(值)。

叶子节点的分数作为参数是没问题的,但树的结构如何作为参数呢?而且我们有K棵树!怎么确定结构呢?

假设: 如果K棵树的结构都已确定,那么整个模型要关注的就是所有K棵树的叶子节点的值,模型的正则化项就可以设为各个叶子节点值的平方和。此时,整个目标函数就是一个K棵树的所有叶子节点的值的函数,我们就可以使用梯度下降或者随机梯度下降来优化这个目标函数。

但现在树的结构不定,这个假设中用到的办法没用了,必须另寻它法。

4、贪心策略 + 加法训练 + 最优化

通俗解释贪心策略:就是决策时刻按照当前目标最优化决定,说白了就是眼前利益最大化决定,“目光短浅”策略。

所谓加法训练,本质上是一个元算法,适用于所有的加法模型,它是一种启发式算法(boosting相关算法有讲)。运用加法训练,我们的目标不再是直接优化整个目标函数,而是分步优化目标函数。首先优化第一棵树,之后优化第二棵树,依次循环,直至优化完K棵树。整个过程如下所示:

yi(0)=0^{y_{i}^{(0)}}=0yi(0)​=0

yi(1)=f1(xi)=yi(0)+f1(xi)^{y_{i}^{(1)}}=f_{1}({x_{i}})={y_{i}^{(0)}}+f_{1}({x_{i}})yi(1)​=f1​(xi​)=yi(0)​+f1​(xi​)

yi(2)=f1(xi)+f2(xi)=yi(1)+f2(xi)^{y_{i}^{(2)}}=f_{1}({x_{i}}) + f_{2}({x_{i}})={y_{i}^{(1)}}+f_{2}({x_{i}})yi(2)​=f1​(xi​)+f2​(xi​)=yi(1)​+f2​(xi​)

.........

yi(t)=∑k=1tfk(xi)=yi(t−1)+ft(xi)^{y_{i}^{(t)}}= \sum_{k=1}^{t}f_{k}({x_{i}})={y_{i}^{(t-1)}}+f_{t}({x_{i}})yi(t)​=∑k=1t​fk​(xi​)=yi(t−1)​+ft​(xi​)

在第ttt步时,我们添加了一棵最优的CART树ftf_tft​,这棵最优的CART树ftf_tft​是怎么得来的呢?非常简单,就是在现有的t−1t-1t−1棵树的基础上,使得目标函数最小的那棵CART树,如下图所示:

objt=∑i=1nl(yi,y^i(t))+∑i=1tΩ(fi)obj^{t} = \sum_{i = 1}^{n}l(y_{i},\hat{y}_{i}^{(t)})+\sum_{i = 1}^{t}\Omega (f_{i})objt=i=1∑n​l(yi​,y^​i(t)​)+i=1∑t​Ω(fi​)

=∑i=1nl(yi,y^i(t−1)+ft(xi))+Ω(ft)+constant= \sum_{i = 1}^{n}l(y_{i},\hat{y}_{i}^{(t-1)}+f_{t}({x_{i}}))+\Omega (f_{t}) + constant=i=1∑n​l(yi​,y^​i(t−1)​+ft​(xi​))+Ω(ft​)+constant

constantconstantconstant就是前t−1t-1t−1棵树的复杂度。

假如我们使用的损失函数是MSEMSEMSE,那么上述表达式会变成这个样子:

objt=∑i=1n(yi−(y^i(t−1)+ft(xi)))2+∑i=1tΩ(fi)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})objt=i=1∑n​(yi​−(y^​i(t−1)​+ft​(xi​)))2+i=1∑t​Ω(fi​)

=∑i=1n[2(y^i(t−1)−yi)ft(xi)+ft(xi)2]+Ω(ft)+constant= \sum_{i = 1}^{n}[2(\hat{y}_{i}^{(t-1)}-y_{i})f_{t}({x_{i}})+f_{t}({x_{i}})^{2}]+\Omega (f_{t}) + constant=i=1∑n​[2(y^​i(t−1)​−yi​)ft​(xi​)+ft​(xi​)2]+Ω(ft​)+constant

这个式子非常漂亮,因为它含有ft(xi)f_t(x_i)ft​(xi​)的一次式和二次式,而且一次式项的系数是残差,这会对我们后续的优化提供很多方便。

注意:ft(xi)f_t(x_i)ft​(xi​)其实就是ftf_tft​的某个叶子节点的值。之前我们提到过,CART树中,叶子节点的值可以作为模型的参数。

但是对于其他损失函数,我们未必能得出如此漂亮的式子。所以,对于一般的损失函数,我们需要将其作泰勒二阶展开,如下所示:

objt=∑i=1n[l(yi,y^i(t−1))+gift(xi)+12hift2(xi)]+Ω(ft)+constantobj^{t} = \sum_{i = 1}^{n}[l(y_{i},\hat{y}_{i}^{(t-1)})+gif_{t}({x_{i}})+\frac{1}{2}hif_{t}^{2}({x_{i}})]+\Omega (f_{t}) + constantobjt=i=1∑n​[l(yi​,y^​i(t−1)​)+gift​(xi​)+21​hift2​(xi​)]+Ω(ft​)+constant

其中:
gi=∂yt−1^l(yi,y^i(t−1))gi = \partial _{\hat{y^{t-1}}}l(y_{i},\hat{y}_{i}^{(t-1)})gi=∂yt−1^​​l(yi​,y^​i(t−1)​)
hi=∂yt−1^2l(yi,y^i(t−1))hi = \partial _{\hat{y^{t-1}}}^{2}l(y_{i},\hat{y}_{i}^{(t-1)})hi=∂yt−1^​2​l(yi​,y^​i(t−1)​)

这里明确下gigigi和hihihi的含义:

现有t−1t-1t−1棵树,这t−1t-1t−1棵树组成的模型对第iii个训练样本有一个预测值yiy^iyi,这个yiy^iyi与真实标签yiyiyi肯定有一定的差距,而这个差距可以用l(yi,yi)l(yi,y^i)l(yi,yi)这个损失函数来衡量。gi和hi的就是对这个损失函数的梯度和二阶梯度。

来看一个具体的例子,假设我们正在优化第11棵CART树,也就是说前10棵CART树已经确定了。这10棵树对样本(xi,yi=1)(x_i,y_i=1)(xi​,yi​=1)的预测值是yi=−1y^i=-1yi=−1,假设我们现在是做分类,损失函数是:

L(θ)=∑i[yiln(1+e−yi^)+(1−yi)ln(1+eyi^)]L(\theta )=\sum_{i}^{}[y_{i}ln(1+e^{-\hat{y_{i}}})+(1-y_{i})ln(1+e^{\hat{y_{i}}})]L(θ)=i∑​[yi​ln(1+e−yi​^​)+(1−yi​)ln(1+eyi​^​)]

在yi=1y_i=1yi​=1时,损失函数变为:

ln(1+e−yi^)ln(1+e^{-\hat{y_{i}}})ln(1+e−yi​^​)

我们可以求出这个损失函数对于y^i的梯度,如下所示:

11+e−yi^(e−yi^)∗(−1)=−e−yi^1+e−yi^\frac{1}{1+e^{-\hat{y_{i}}}}(e^{-\hat{y_{i}}})*(-1) = \frac{-e^{-\hat{y_{i}}}}{1+e^{-\hat{y_{i}}}}1+e−yi​^​1​(e−yi​^​)∗(−1)=1+e−yi​^​−e−yi​^​​

将yi=−1y^i =-1yi=−1代入上面的式子,计算得到-0.27。这个-0.27就是gig_igi​。该值为负,也就是说,如果我们想要减小这10棵树在该样本点上的预测损失,就应该沿着梯度的反方向走,也就是要增大yiy^iyi的值,使其趋向于正,因为yi=1y_i=1yi​=1就是正的。

若NNN是训练样本的数量,那么在优化第ttt棵树时,就各有NNN个gigigi和hihihi要计算。如果有10万样本,在优化第t棵树时,就需要计算出个10万个gigigi和hihihi。XGBoost的并行运算能力正好可以解决这种庞大复杂的计算工作。

好,现在我们来审视下这个式子,哪些是常量,哪些是变量。式子最后有一个constantconstantconstant项就是前t−1t-1t−1棵树的正则化项。l(yi,yit−1)l(yi, yi^t-1)l(yi,yit−1)也是常数项。剩下的三个变量项分别是第ttt棵CART树的一次式,二次式,和整棵树的正则化项。再次提醒,这里所谓的树的一次式,二次式,其实都是某个叶子节点的值的一次式,二次式。

我们的目标是让这个目标函数最小化,常数项显然没有什么用,把它们去掉就变成了下面这样:

∑i=1n[gift(xi)+12hift2(xi)]+Ω(ft)\sum_{i = 1}^{n}[gif_{t}({x_{i}})+\frac{1}{2}hif_{t}^{2}({x_{i}})]+\Omega (f_{t})i=1∑n​[gift​(xi​)+21​hift2​(xi​)]+Ω(ft​)

因为这些一次式和二次式的系数是gigigi和hihihi,而gigigi和hihihi可以并行地求出来。而且,gigigi和hihihi不依赖于损失函数的形式,只要这个损失函数二次可微就可以。这有什么好处呢?我们可以自定义损失函数,只需满足二次可微即可。

5、正则项

现在就剩后面的Ω(ft)Ω(ft)Ω(ft)了。我们来定义下如何衡量一棵树的正则化项。这个事儿并没有一个客观的标准,可以见仁见智。为此,我们先对CART树作另一番定义,如下所示:

那么复杂度的表达式可以表示为 :

ft(x)=ωq(x),ω∈RT,q:Rd→{1,2,3....,T}f_{t}(x)=\omega _{q(x)},\omega \in R^{T},q:R^{d}\rightarrow \left \{ 1,2,3....,T\right \}ft​(x)=ωq(x)​,ω∈RT,q:Rd→{1,2,3....,T}

需要解释下这个定义,首先,一棵树有TTT个叶子节点,这TTT个叶子节点的值组成了一个TTT维向量www,q(x)q(x)q(x)是一个映射,用来将样本映射成111到TTT的某个值,也就是把它分到某个叶子节点,q(x)q(x)q(x)其实就代表了CART树的结构。wq(x)w_q(x)wq​(x)自然就是这棵树对样本xxx的预测值了。

有了这个定义,XGBoost就使用了如下的正则化项:

Ω(f)=γT,12λ∑j=1Tωj2\Omega (f)=\gamma T,\frac{1}{2}\lambda \sum_{j=1}^{T}\omega _{j}^{2}Ω(f)=γT,21​λj=1∑T​ωj2​

注意:这里出现了γγγ和λλλ,这是XGBoost自己定义的,在使用XGBoost时,你可以设定它们的值,显然,γγγ越大,表示越希望获得结构简单的树,因为此时对较多叶子节点的树的惩罚越大。λλλ越大也是越希望获得结构简单的树。

为什么XGBoost要选择这样的正则化项?很简单,好使!效果好才是真的好。

至此,我们关于第t棵树的优化目标已然很清晰,下面我们对它做如下变形,请睁大双眼,集中精力:

objt≈∑i=1n[giωq(x)+12hiωq(x)2]+γT+12λ∑j=1Tωj2obj^{t}\approx \sum_{i = 1}^{n}[gi\omega _{q(x)}+\frac{1}{2}hi\omega ^{2}_{q(x)}]+\gamma T+\frac{1}{2}\lambda \sum_{j=1}^{T}\omega _{j}^{2}objt≈i=1∑n​[giωq(x)​+21​hiωq(x)2​]+γT+21​λj=1∑T​ωj2​

=∑i=1T[(∑i∈Ijgi)ωj+12(∑i∈Ijhi+λ)ωj2]+γT=\sum_{i = 1}^{T}[(\sum_{i\in Ij }^{}gi)\omega _{j}+\frac{1}{2}(\sum_{i\in Ij }hi+\lambda )\omega ^{2}_{j}]+\gamma T=i=1∑T​[(i∈Ij∑​gi)ωj​+21​(i∈Ij∑​hi+λ)ωj2​]+γT

这里需要停一停,认真体会下。IjIjIj代表什么?它代表一个集合,集合中每个值代表一个训练样本的序号,整个集合就是被第ttt棵CART树分到了第jjj个叶子节点上的训练样本。理解了这一点,再看这步转换,其实就是内外求和顺序的改变。

进一步,我们可以做如下简化:

objt=∑i=1T[Giωi+12(Hj+λ)ωj2]+γTobj^{t}=\sum_{i = 1}^{T}[G_{i}\omega _{i}+\frac{1}{2}(H_{j}+\lambda )\omega ^{2}_{j} ]+\gamma Tobjt=i=1∑T​[Gi​ωi​+21​(Hj​+λ)ωj2​]+γT

其中的GjGjGj和HjHjHj就是简化后损失函数的梯度和二阶梯度

对于第ttt棵CART树的某一个确定的结构(可用q(x)q(x)q(x)表示),所有的GjGjGj和HjHjHj都是确定的。而且上式中各个叶子节点的值wjwjwj之间是互相独立的。上式其实就是一个简单的二次式,我们很容易求出各个叶子节点的最佳值以及此时目标函数的值。如下所示:

ωj∗=−GIHi+λ\omega _{j}^{*}=-\frac{G_{I}}{H_{i}+\lambda }ωj∗​=−Hi​+λGI​​

obj∗=−12∑j=1TGj2Hj+λ+γTobj^{*}=-\frac{1}{2}\sum_{j=1}^{T}\frac{G_{j}^{2}}{H_{j}+\lambda }+\gamma Tobj∗=−21​j=1∑T​Hj​+λGj2​​+γT

obj∗obj*obj∗代表了什么呢?

它表示了这棵树的结构有多好,值越小,代表这样结构越好!也就是说,它是衡量第ttt棵CART树的结构好坏的标准。

注意,这个值仅仅是用来衡量结构的好坏的,与叶子节点的值可是无关的。为什么?请再仔细看一下obj∗obj^{*}obj∗的推导过程。obj∗obj^{*}obj∗只和GjGjGj和HjHjHj和TTT有关,而它们又只和树的结构(q(x))(q(x))(q(x))有关,与叶子节点的值可是半毛关系没有。如下图所示:

这里,我们对w∗jw*_jw∗j​给出一个直觉的解释,以便能获得感性的认识。我们假设分到j这个叶子节点上的样本只有一个。那么,w∗jw*_jw∗j​就变成如下这个样子:

ωj∗=(1Hi+λ)∗(−gi)\omega _{j}^{*}=(\frac{1}{H_{i}+\lambda })*(-gi)ωj∗​=(Hi​+λ1​)∗(−gi)

1Hi+λ\frac{1}{H_{i}+\lambda }Hi​+λ1​是学习率,(−gi)(-gi)(−gi)是反向的梯度

这个式子告诉我们,w∗jw*_jw∗j​的最佳值就是负的梯度乘以一个权重系数,该系数类似于随机梯度下降中的学习率。观察这个权重系数,我们发现,hjh_jhj​越大,这个系数越小,也就是学习率越小。hjh_jhj​越大代表什么意思呢?代表在该点附近梯度变化非常剧烈,可能只要一点点的改变,梯度就从10000变到了1,所以,此时,我们在使用反向梯度更新时步子就要小而又小,也就是权重系数要更小。

6、树结构

好了,有了评判树结构好坏的标准,我们就可以先求最佳的树结构,这个定出来后,最佳的叶子结点的值实际上在上面已经求出来了。

问题是: 树的结构近乎无限多,一个一个去测算它们的好坏程度,然后再取最好的显然是不现实的。所以,我们仍然需要采取贪心策略,就是逐步学习出最佳的树结构。这与我们将K棵树的模型分解成一棵一棵树来学习是一个道理,只不过从一棵一棵树变成了一层一层节点而已。看下具体学习过程:

以上文提到过的判断一个人是否喜欢计算机游戏为例子。最简单的树结构就是一个节点的树。可以算出这棵单节点的树的好坏程度obj∗obj^{*}obj∗。假设现在想按照年龄将这棵单节点树进行分叉,我们需要知道:

  • ①按照年龄分是否有效,也就是是否减少了obj∗obj^{*}obj∗的值

  • ②如果可分,那么以哪个年龄值来分。

为了回答上面两个问题,我们可以将这一家五口人按照年龄做个排序。如下图所示:

按照这个图从左至右扫描,我们就可以找出所有的切分点。对每一个确定的切分点,我们衡量切分好坏的标准如下:

Gain=12[GL2HL+λ+GR2HR+λ−(GL+GR)2HL+HR+λ]−γGain=\frac{1}{2}[ \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 } ]-\gamma Gain=21​[HL​+λGL2​​+HR​+λGR2​​−HL​+HR​+λ(GL​+GR​)2​]−γ

这个GainGainGain实际上就是单节点的obj∗obj^{*}obj∗减去切分后的两个节点的树obj∗obj^{*}obj∗,GainGainGain如果是正的,并且值越大,表示切分后obj∗obj*obj∗越小于单节点的obj∗obj^{*}obj∗,就越值得切分。同时,我们还可以观察到,GainGainGain的左半部分如果小于右侧的γγγ,则GainGainGain就是负的,表明切分后obj∗obj^{*}obj∗反而变大了。γγγ在这里实际上是一个临界值,它的值越大,表示我们对切分后objobjobj下降幅度要求越严。这个值也是可以在XGBoost中设定的。

扫描结束后,我们就可以确定是否切分,如果切分,对切分出来的两个节点,递归地调用这个切分过程,我们就能获得一个相对较好的树结构。

接下来,继续分裂,按照上述的方式,形成一棵树,再结合建树过程中的加法训练形成第二棵树,之后循环,每次在上一次的预测基础上取最优进一步分裂/建树,这就是这个XGBoost算法贪心策略的核心!

凡是这种循环迭代的方式必定有停止条件,什么时候停止呢:

  • ①当节点引入的分裂带来的增益小于一个阀值的时候,停止分裂,我们可自定义一个参数设置;
  • ②当达到最大深度时则停止建立决策树,设置一个超参数max_depth,防止过拟合;
  • ③当样本权重和小于设定阈值时则停止建树,这涉及到一个超参数-最小的样本权重和min_child_weight,就是一个叶子节点样本太少了,也终止,同样防止过拟合;

注意: XGBoost的切分操作和普通的决策树切分过程是不一样的。普通的决策树在切分的时候并不考虑树的复杂度,而依赖后续的剪枝操作来控制。XGBoost在切分的时候就已经考虑了树的复杂度,就是那个γγγ参数。所以,它不需要进行单独的剪枝操作(当然XGBoost支持后剪枝)。

7、XGBoost算法特点

看下XGBoost的一些重点:

  • ①ω\omegaω是最优化求出来的,不是啥平均值或规则指定的,这个算是一个思路上的新颖吧。传统GBDT以CART作为基分类器,XGBoost还支持线性分类器,这个时候XGBoost相当于带L1和L2正则化项的逻辑斯蒂回归(分类问题)或者线性回归(回归问题)。传统GBDT在优化时只用到一阶导数信息,XGBoost则对代价函数进行了二阶泰勒展开,同时用到了一阶和二阶导数。顺便提一下,XGBoost工具支持自定义代价函数,只要函数可一阶和二阶求导。
  • ②正则化项过拟合;
  • ③支持自定义损失函数;
  • ④支持并行化。boosting技术中下一棵树依赖上述树的训练和预测,所以树与树之间应该是只能串行!哪里可以并行?没错,同层级节点可并行。具体对于某个选择最佳分裂点,候选分裂点计算增益用多线程并行。(据说恰好这个也是树形成最耗时的阶段),训练效率高!
  • ⑤可实现后剪枝;
  • ⑥交叉验证,方便选择最好的参数,early stop,比如你发现30棵树预测已经很好了,不用进一步学习残差了,那么停止建树;
  • ⑦行采样、列采样,随机森林的套路(防止过拟合),不仅能降低过拟合,还能减少计算;
  • 对缺失值的处理。对于特征的值有缺失的样本,XGBoost可以自动学习出它的分裂方向;
  • ⑧支持设置样本权重,这个权重体现在梯度gigigi和二阶梯度hihihi上,是不是有点adaboost的意思,重点关注某些样本。XGBoost在进行完一次迭代后,会将叶子节点的权重乘上该系数,主要是为了削弱每棵树的影响,让后面有更大的学习空间。实际应用中,一般把eta设置得小一点,然后迭代次数设置得大一点。
  • ⑨对缺失值的处理。XGBoost还特别设计了针对稀疏数据的算法, 假设样本的第i个特征缺失时,无法利用该特征对样本进行划分,这里的做法是将该样本默认地分到指定的子节点,至于具体地分到哪个节点还需要某算法来计算。
    算法的主要思想是,分别假设特征缺失的样本属于右子树和左子树,而且只在不缺失的样本上迭代,分别计算缺失样本属于右子树和左子树的增益,选择增益最大的方向为缺失数据的默认方向(咋一看如果缺失情况为3个样本,那么划分的组合方式岂不是有8种?指数级可能性啊,仔细一看,应该是在不缺失样本情况下分裂后(有大神的请确认或者修正),把第一个缺失样本放左边计算下loss function和放右边进行比较,同样对付第二个、第三个…缺失样本,这么看来又是可以并行的??)。

8、XGBoost工程优化

  • ①Column Block for Parallel Learning
    总的来说:按列切开,升序存放;
    方便并行,同时解决一次性样本读入炸内存的情况

  • ②由于将数据按列存储,可以同时访问所有列,那么可以对所有属性同时执行split finding算法,从而并行化split finding(切分点寻找)-特征间并行
    可以用多个block(Multiple blocks)分别存储不同的样本集,多个block可以并行计算-特征内并行

  • ③Blocks for Out-of-core Computation
    数据大时分成多个block存在磁盘上,在计算过程中,用另外的线程读取数据,但是由于磁盘IO速度太慢,通常更不上计算的速度,
    将block按列压缩,对于行索引,只保存第一个索引值,然后只保存该数据与第一个索引值之差(offset),一共用16个bits来保存 offset,因此,一个block一般有216216个样本。

五 调参技巧 & 代码实现

1、Gradient Boosting的调参

Tree-Specific Parameters

  • min_samples_split:对一个节点劈裂的条件之一就是该节点包含样本数大于min_samples_split。该值过高则可能过拟合,过小则欠拟合。
  • min_samples_leaf:叶节点包含的最少样本数。参数目的同min_samples_split。通常训练集中类不均衡时,该值需要设置小点,以防止小类被大类覆盖。
  • min_weight_fraction_leaf:同min_samples_leaf,只不过改成叶节点包含样本数量占总样本的比例下限。两者仅设其一。
  • max_depth: 树的最大深度,不容易调整,通常要采用CV(cross validate)方式。
  • max_leaf_nodes: 叶节点最大数量。作用同最大深度。两者仅设其一。
  • max_features: 寻找最优切分点时,考虑的最大特征的个数。随机选取。经验规则是设为总特征数量的开方,但在调参过程中考虑总数的30%-40%范围。该超参设置过高可能导致过拟合。

Boosting Parameters

  • learning_rate:确定了每棵基本树的输出结果在最终结果中的影响权重。通常较小的值使得模型鲁棒性更好,但需要更多的基本树,导致计算量较大。
  • n_estimators:基本基本树的个数。
  • subsample:从总的训练数据集中挑选样本的比例,以构建某个基本树的专有的训练数据集。通常设置为0.8。

Miscellaneous Parameters

  • loss: 损失函数。通常默认的效果不错。
  • init: 将已有的决策树设为基本树。
  • warm_start:设置为True则表示可以借用前面已经训练好的基本树训练接下来添加的基本树。合理设置可以加快训练速度。

调参过程

参考资料2中,Aarshay分享了他的调参经验,核心的思想是先调节影响最大的参数,再调节次要的参数。

  • 设置较大的learning_rate以及相应的最优n_estimator
  • 调节Tree-Specific Parameters
  • 调节max_depth以及min_samples_split
  • 调节min_samples_leaf
  • 调节max_features
  • 降低learning_rate,并相应地增加n_estimator

2、XGBoost的调参

  • general parameters:使用何种基本模型。通常是决策树或线性模型。
  • booster parameters:基本树必需的超参数。
  • gamma: 正则化项T前系数。
  • reg_lambda: L2正则化项前系数。
  • reg_alpha:L1正则化项前系数。
  • min_child_weight :节点包含样本的权重之和的下限。即如果某个节点所有样本的权重之和低于该值,则不劈裂。其作用类似于GB的min_samples_leaf。
  • colsample_bytree:构建某棵基本树时,从所有特征中随机抽取特征数量比例。
  • colsample_bylevel: 选择最优切分点时,考虑的特征数量所占比例。其作用类似于GB中的max_features。
    其他参数参考GB。
  • task parameters:学习过程必须的超参数。目标函数形式、模型评价标准等。

调参过程

参考GB的调参过程。对于XGBoost, max_depth、min_child_weight以及gamma是影响基本树预测准确率最为重要的三个因素。

# 实现XGBoost回归, 以MSE损失函数为例
import numpy as npclass Node:def __init__(self, sp=None, left=None, right=None, w=None):self.sp = sp  # 非叶节点的切分,特征以及对应的特征下的值组成的元组self.left = leftself.right = rightself.w = w  # 叶节点权重,也即叶节点输出值def isLeaf(self):return self.wclass Tree:def __init__(self, _gamma, _lambda, max_depth):self._gamma = _gamma  # 正则化项中T前面的系数self._lambda = _lambda  # 正则化项w前面的系数self.max_depth = max_depthself.root = Nonedef _candSplits(self, X_data):# 计算候选切分点splits = []for fea in range(X_data.shape[1]):for val in X_data[fea]:splits.append((fea, val))return splitsdef split(self, X_data, sp):# 劈裂数据集,返回左右子数据集索引lind = np.where(X_data[:, sp[0]] <= sp[1])[0]rind = list(set(range(X_data.shape[0])) - set(lind))return lind, rinddef calWeight(self, garr, harr):# 计算叶节点权重,也即位于该节点上的样本预测值return - sum(garr) / (sum(harr) + self._lambda)def calObj(self, garr, harr):# 计算某个叶节点的目标(损失)函数值return (-1.0 / 2) * sum(garr) ** 2 / (sum(harr) + self._lambda) + self._gammadef getBestSplit(self, X_data, garr, harr, splits):# 搜索最优切分点if not splits:return Noneelse:bestSplit = NonemaxScore = -float('inf')score_pre = self.calObj(garr, harr)subinds = Nonefor sp in splits:lind, rind = self.split(X_data, sp)if len(rind) < 2 or len(lind) < 2:continuegl = garr[lind]gr = garr[rind]hl = harr[lind]hr = harr[rind]score = score_pre - self.calObj(gl, hl) - self.calObj(gr, hr)  # 切分后目标函数值下降量if score > maxScore:maxScore = scorebestSplit = spsubinds = (lind, rind)if maxScore < 0:  # pre-stoppingreturn Noneelse:return bestSplit, subindsdef buildTree(self, X_data, garr, harr, splits, depth):# 递归构建树res = self.getBestSplit(X_data, garr, harr, splits)depth += 1if not res or depth >= self.max_depth:return Node(w=self.calWeight(garr, harr))bestSplit, subinds = ressplits.remove(bestSplit)left = self.buildTree(X_data[subinds[0]], garr[subinds[0]], harr[subinds[0]], splits, depth)right = self.buildTree(X_data[subinds[1]], garr[subinds[1]], harr[subinds[1]], splits, depth)return Node(sp=bestSplit, right=right, left=left)def fit(self, X_data, garr, harr):splits = self._candSplits(X_data)self.root = self.buildTree(X_data, garr, harr, splits, 0)def predict(self, x):def helper(currentNode):if currentNode.isLeaf():return currentNode.wfea, val = currentNode.spif x[fea] <= val:return helper(currentNode.left)else:return helper(currentNode.right)return helper(self.root)def _display(self):def helper(currentNode):if currentNode.isLeaf():print(currentNode.w)else:print(currentNode.sp)if currentNode.left:helper(currentNode.left)if currentNode.right:helper(currentNode.right)helper(self.root)class Forest:def __init__(self, n_iter, _gamma, _lambda, max_depth, eta=1.0):self.n_iter = n_iter  # 迭代次数,即基本树的个数self._gamma = _gammaself._lambda = _lambdaself.max_depth = max_depth  # 单颗基本树最大深度self.eta = eta  # 收缩系数, 默认1.0,即不收缩self.trees = []def calGrad(self, y_pred, y_data):# 计算一阶导数return 2 * (y_pred - y_data)def calHess(self, y_pred, y_data):# 计算二阶导数return 2 * np.ones_like(y_data)def fit(self, X_data, y_data):step = 0while step < self.n_iter:tree = Tree(self._gamma, self._lambda, self.max_depth)y_pred = self.predict(X_data)garr, harr = self.calGrad(y_pred, y_data), self.calHess(y_pred, y_data)tree.fit(X_data, garr, harr)self.trees.append(tree)step += 1def predict(self, X_data):if self.trees:y_pred = []for x in X_data:y_pred.append(self.eta * sum([tree.predict(x) for tree in self.trees]))return np.array(y_pred)else:return np.zeros(X_data.shape[0])if __name__ == '__main__':from sklearn.datasets import load_bostonfrom sklearn.model_selection import train_test_splitfrom sklearn.metrics import mean_absolute_errorimport matplotlib.pyplot as pltboston = load_boston()y = boston['target']X = boston['data']X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)f = Forest(50, 0, 1.0, 4, eta=0.8)f.fit(X_train, y_train)y_pred = f.predict(X_test)print(mean_absolute_error(y_test, y_pred))plt.scatter(np.arange(y_pred.shape[0]), y_test - y_pred)plt.show()

六 项目实战

  • XGBoost算法构建信用评分卡模型

七 关键词

  • 决策树cart
  • 信息增益
  • 基尼系数
  • 分类误差(分类树)
  • 均方误差(回归树)
  • 贪心策略
  • 加法训练
  • 目标函数
  • 损失函数
  • 正则项
  • 泰勒展开
  • 样本节点
  • 交叉验证
  • 预剪枝
  • 并行处理
  • 高度灵活性

八 结尾

XGBoost和深度学习的关系,陈天奇在Quora上的解答如下:
不同的机器学习模型适用于不同类型的任务。深度神经网络通过对时空位置建模,能够很好地捕获图像、语音、文本等高维数据。而基于树模型的XGBoost则能很好地处理表格数据,同时还拥有一些深度神经网络所没有的特性(如:模型的可解释性、输入数据的不变性、更易于调参等)。

这两类模型都很重要,并广泛用于数据科学竞赛和工业界。举例来说,几乎所有采用机器学习技术的公司都在使用tree boosting,同时XGBoost已经给业界带来了很大的影响。

参考

  • 陈天奇原始论文
  • Opencv2.4.9源码分析——Decision Trees
  • 通俗、有逻辑的写一篇说下XGBoost的原理,供讨论参考
  • XGBoost简介
  • XGBoost原理解析
  • XGBoost的原理没你想像的那么难
  • 机器学习算法中 GBDT 和 XGBoost 的区别有哪些?
  • XGBoost算法简介及Python实现
  • XGBoost参数调优
  • [机器学习]集成学习–bagging、boosting、stacking

好了,现在是2019年1月23日,憋了将近20天,从逻辑,到算法,再到细节,终于拿下。2019年我与机器学习算法的厮杀之2月再见。

作为一个半路出家的小学生,补充一句:

数学真美。

【算法】Xgboost原理相关推荐

  1. halcon机器视觉算法原理与编程实战_快速弄懂机器学习里的集成算法:原理、框架与实战...

    作者:  博观厚积 简书专栏:https://www.jianshu.com/u/2f376f777ef1 1. 关于集成学习算法 集成学习算法,通俗地讲就是:三个臭皮匠,顶个诸葛亮,这在很多地方都有 ...

  2. xgboost原理及应用

    1.背景 关于xgboost的原理网络上的资源很少,大多数还停留在应用层面,本文通过学习陈天奇博士的PPT地址和xgboost导读和实战 地址,希望对xgboost原理进行深入理解. 2.xgboos ...

  3. matlab算法应用论文(带代码)_左手论文 右手代码 深入理解网红算法XGBoost

    这篇文章的起源是上周给同事讲解XGBoost原理+白板手推公式,结果差点翻车,发现有些地方理解还是不够深入,于是又花了一周时间把之前啃过的所有资料和笔记仔细梳理一遍,又想起自己的知乎专栏已经快一年没更 ...

  4. XGBoost原理与实例分析

    这几天一直在研究XGboost的基本原理与代码调参,其原理是在GBDT的基础上进行优化,但也有很多的不同之处:所以自己准备更新两篇博客分为XGBoost原理与实例和XGBoost实战与调参优化来巩固这 ...

  5. 集成学习——XGBoost原理理解

    XGBoost全称为Extreme Gradient Boosting,它是2016年陈天奇和Carlos Guestrin在国际AI顶会ACM SIGKDD(知识发现与数据挖掘会议)上的正式发布,论 ...

  6. GBDT 算法:原理篇

    2019独角兽企业重金招聘Python工程师标准>>> 本文由云+社区发表 GBDT 是常用的机器学习算法之一,因其出色的特征自动组合能力和高效的运算大受欢迎. 这里简单介绍一下 G ...

  7. (二)提升树模型:Xgboost原理与实践

    本篇博客是提升树模型博客的第二篇文章,第一篇介绍GBDT的博客可以参看这里.第三篇介绍Lightgbm博客可以参看这里. 本篇博客是基于kingsam_的博客整理而来,在此表示感谢.在这篇文章的基础上 ...

  8. xgboost原理分析以及实践

    摘要 本文在写完GBDT的三篇文章后本来就想写的,但一直没有时间,终于刚好碰上需要,有空来写这篇关于xgboost原理以及一些实践的东西(这里实践不是指给出代码然后跑结果,而是我们来手动算一算整个xg ...

  9. XGBoost原理及目标函数推导详解

    前言 XGBoost(eXtreme Gradient Boosting)全名叫极端梯度提升,XGBoost是集成学习方法的王牌,在Kaggle及工业界都有广泛的应用并取得了较好的成绩,本文较详细的介 ...

  10. 机器学习算法原理:详细介绍各种机器学习算法的原理、优缺点和适用场景

    目录 引言 二.线性回归 三.逻辑回归 四.支持向量机 五.决策树 六.随机森林 七.K-均值聚类 八.主成分分析(PCA) 九.K近邻算法 十.朴素贝叶斯分类器 十一.神经网络 十二.AdaBoos ...

最新文章

  1. Spring 框架的AOP之注解的方式
  2. 将h5用HBuilderX打包成安卓app后,document.documentElement.scrollTop的值始终为0或者document.body.scrollTop始终为0...
  3. micropython esp32手册_使用ESP32控制板(二):燒錄MicroPython韌體
  4. Ubuntu字符界面输入密码始终提示错误 login incorrect 解决办法
  5. window.parent ,window.top,window.self 详解
  6. 【python】有意思的python小项目GitHub地址汇总
  7. 高级短链接工具+汉化版语言包premium-url-shortener-v5.9.8完美修复开心版QW提示跳转
  8. python 读取redis数据后转为dataframe格式数据
  9. 208-实现 Trie (前缀树)
  10. default在php定义什么,default(T)的含义
  11. 忘记密码怎么卸载和删除网维大师~
  12. appium工作原理
  13. SPSS可信度数据分析
  14. 遗传算法(2):对适应度函数的改进
  15. 逻辑英语语法电子版_11920671英语逻辑语法上.pdf
  16. Javascript中new关键字和this指向
  17. Java内存模型中的happen-before是什么?
  18. 第十五周项目二-----用哈希法组织关键字之线性探测法
  19. W74 - 999、云计算工程师认证
  20. 面试时如何回答你的缺点是什么?

热门文章

  1. 锁定计算机还能远程控制,我的电脑可能被远程控制
  2. AABB包围盒和OBB包围盒区别
  3. ubuntu 16.04 配置网络代理
  4. 风尚云网学习-关于学习和前端
  5. ipad下载python_ipad python
  6. 时域技术在天线测量中的应用
  7. 关于各类图形CAD底层内核
  8. IOS 10 适配系列 _3_ Xcode 8 GM seed
  9. ExpandableListView购物车
  10. Appfuse 开发环境搭建