算法小白从决策树到XGboost

[目录]
0. 本文目的及行文概述
1. 决策树
  1.1 分类树
  1.2 回归树
2. 集成学习
  2.1 GBDT
  2.2 XGboost
3. 总结及本人写作动机

0.本文行文概述

本文主要为一位大二本科生的树+Boosting类算法的学习分享。决策树部分重直观理解,并附带了大量例子,确保草履虫都能知道每一步是如何计算;集成学习部分则主要重数学推导,也附上了一定数量的可视化。此外,XGboost的一大特点是工程实现,如列块并行学习等,但因为本文重点还是在于更偏算法的目标函数和学习策略云云,希望深入了解可参考深入理解XGBoost,优缺点分析,原理推导及工程实现

本文参考的视频及文章较多,更像是学习笔记分享,大部分的可视化内容也是直接搬运(非本人原创),涉及到的视频和文章链接都放在文末(CSDN读者可直接点击链接)。主要参考集成学习:XGBoost, lightGBM

本人公众号 “HORSE RUNNING WILD” 有更多经验笔记分享!

1. 决策树

决策树是一种常用的机器学习算法,用于分类回归任务,也就因此有分类树回归树。它通过学习数据特征之间的关系,构建一个树状模型来预测结果。以下是决策树的一些基本要点:

  • 树结构:决策树由节点(Node)和边(Edge)组成,节点代表决策点,边代表决策结果。

  • 根节点:树的顶部节点,代表整个数据集。

  • 内部节点:树中间的节点,代表一个特征或属性。

  • 叶节点:树的底部节点,代表最终的决策结果。

  • 分支:从每个内部节点延伸出来的路径,代表一个特征的不同值。

决策树示意图

如果说读者觉得上述图片和解释比较抽象,那么不妨设想一个场景:

某一天,你有点想去打高尔夫球,但是又有些犹豫。因为天气虽然有些热,但是湿度不高,又没有风。在你纠结的时刻,突然有人根据你过往打球的历史数据,为你建立了一颗决策树:

决定你是否应该打球的树

由图中我们可以看到,如果是Sunny又Windy的话,那就去打球。如果是Rainy但湿度正常的话,照打不误。

于是可能读者就会问了,是什么规则,使我们能够从过往的数据中,建立一棵决策树?换言之,我们应该如何划分最优属性

1.1 分类树

如上所述,最重要的是划分最优属性,对于分类数而言,主要有两种方式:通过信息增益划分和通过基尼指数划分。

为了便于理解起见,我们首先假设我们是有闲钱打高尔夫的小资人士,下面这张表格是我们在一些天气情况下去打高尔夫的记录:

Outlook Temp Humidity Wind Play Golf
Rainy Hot High FALSE No
Rainy Hot High TRUE No
Overcast Hot High FALSE Yes
Sunny Mild High FALSE Yes
Sunny Cool Normal FALSE Yes
Sunny Cool Normal TRUE No
Overcast Cool Normal TRUE Yes
Rainy Mild High FALSE No
Rainy Cool Normal FALSE Yes
Sunny Mild Normal FALSE Yes
Rainy Mild Normal TRUE Yes
Overcast Mild High TRUE Yes
Overcast Hot Normal FALSE Yes
Sunny Mild High TRUE No
1.1.1 信息增益

热学里有一个熵的概念,熵就是来形容系统混乱程度的,系统越混乱,熵就越大。**信息熵(Information Entropy)**也具有同样的意义,不过它描述的是随机变量的不确定性。越大的信息熵则说明随机变量越不确定。

信息熵的计算公式:
H ( X ) = − ∑ i = 1 n P ( X = i ) log ⁡ 2 P ( X = i ) H(X) = -\sum_{i=1}^{n} P(X=i) \log_2 P(X=i) H(X)=i=1nP(X=i)log2P(X=i)

上式中的 P ( X = i ) P(X=i) P(X=i) 就是某个随机变量取值 X = i X = i X=i的 概率,至于为什么公式长这样,得去咨询克劳德·艾尔伍德·香农大师。

下面我们将结合具体的场景来介绍条件熵
H ( X ∣ Y = v ) = − ∑ i = 1 n P ( X = i ∣ Y = v ) log ⁡ 2 P ( X = i ∣ Y = v ) H(X|Y=v) = -\sum_{i=1}^{n} P(X=i|Y=v) \log_2 P(X=i|Y=v) H(XY=v)=i=1nP(X=iY=v)log2P(X=iY=v)

条件熵的公式无非就是将信息熵公式中的概率换成了条件概率。譬如,我们可以直接计算出“打高尔夫球”这一事件的信息熵,也可以通过上述表格,计算出“在Windy情况下打高尔夫球”的条件熵。(读者不妨自行算算)

真正能够帮助我们划分决策树的重点来了:信息增益。信息增益的计算公式如下:
I ( X , Y ) = H ( X ) − H ( X ∣ Y ) I(X,Y) = H(X) - H(X|Y) I(X,Y)=H(X)H(XY)

该公式非常的简单,放在我们的场景里来说, H ( X ) H(X) H(X)则是打高尔夫球的信息熵,$ H(X|Y)$则可以是在Outlook条件下打球的条件熵,一做差便能得到信息增益。具体过程如图:

某属性条件熵计算示意

按照同样的道理,我们也能计算出诸如Temp和Wind等属性的信息增益。因此,我们直接取信息增益最大者,作为我们期待已久的最优属性

于是第一棵数就这麽被分出来了

同样的道理,我们可以继续划分下去,直到我们的决策出现。

1.1.2 基尼指数

无论是信息熵,还是基尼指数,都不过是一个寻找最优划分属性的准则。适于于不同的情景。基尼指数(Gini不纯度)表示在样本集合中一个随机选中的样本被分错的概率。

注意:Gini指数越小表示集合中被选中的样本被分错的概率越小,也就是说集合的纯度越高,反之,集合越不纯。当集合中所有样本为一个类时,基尼指数为0。

基尼指数的计算方法:

Gini ( p ) = ∑ k = 1 K p k ( 1 − p k ) = 1 − ∑ k = 1 K p k 2 \text{Gini}(p) = \sum_{k=1}^{K} p_k(1-p_k) = 1 - \sum_{k=1}^{K} p_k^2 Gini(p)=k=1Kpk(1pk)=1k=1Kpk2

其中, p k p_k pk 表示选中的样本属于第k个类别的概率。

既然有信息增益,那么也有基尼增益:
G a i n ( D , a ) = G i n i ( D ) − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ G i n i ( D i ) Gain(D, a) = Gini(D) - \sum_{i=1}^{n} \frac{|D^i|}{|D|} Gini(D^i) Gain(D,a)=Gini(D)i=1nDDiGini(Di)
其中, G i n i ( D ) Gini(D) Gini(D)为整个样本集, ∣ D i ∣ ∣ D ∣ \frac{|D^i|}{|D|} DDi则表示权重,也就是某种情况出现的频率。剩下的操作与计算信息增益类似:基尼增益最大者,取之!

1.2 回归树

回归树,即用树模型做回归问题,每一片叶子都输出一个预测值,一般为该节点内样本的均值。因此,一开始我们给出的表格因为只有Y/N的二元判断,故不再适用。但是我们可以对最后一列进行改动,改为“打球的时长”,即可开始我们的树模型回归。

Outlook Temp Humidity Wind Play Hours
Rainy Hot High FALSE 26
Rainy Hot High TRUE 30
Overcast Hot High FALSE 48
Sunny Mild High FALSE 46
Sunny Cool Normal FALSE 62
Sunny Cool Normal TRUE 23
Overcast Cool Normal TRUE 43
Rainy Mild High FALSE 35
Rainy Cool Normal FALSE 38
Sunny Mild Normal FALSE 48
Rainy Mild Normal TRUE 48
Overcast Mild High TRUE 62
Overcast Hot Normal FALSE 44
Sunny Mild High TRUE 30

对于树模型,最重要的还是划分最优属性

回归树的分支标准:标准方差 (Standard Deviation)。回归树使用某一特征将原集合分为多个子集,用标准方差衡量子集中的元素是否相近,越小表示越相近。首先计算根节点的标准方差:
S = ∑ ( x − x ˉ ) 2 n = 9.32 S = \sqrt{\frac{\sum(x - \bar{x})^2}{n}} = 9.32 S=n(xxˉ)2 =9.32
然后计算变异系数:
C V = S x ˉ × 100 % = 23 % CV = \frac{S}{\bar{x}} \times 100\% = 23\% CV=xˉS×100%=23%
变异系数用于决定是否进一步分叉。

然后我们计算不同属性的标准方差:
S ( T , X ) = ∑ c ∈ X P ( c ) S ( c ) S(T, X) = \sum_{c \in X} P(c) S(c) S(T,X)=cXP(c)S(c)
以Outlook为例:

Outlook Hours Played (StDev) Count
Overcast 3.49 4
Rainy 7.78 5
Sunny 10.87 5
Total 14

Outlook的标准方差计算方式则为:
S ( Hours, Outlook ) = P ( Sunny ) ⋅ S ( Sunny ) + P ( Overcast ) ⋅ S ( Overcast ) + P ( Rainy ) ⋅ S ( Rainy ) S(\text{Hours, Outlook}) = P(\text{Sunny}) \cdot S(\text{Sunny}) + P(\text{Overcast}) \cdot S(\text{Overcast}) + P(\text{Rainy}) \cdot S(\text{Rainy}) S(Hours, Outlook)=P(Sunny)S(Sunny)+P(Overcast)S(Overcast)+P(Rainy)S(Rainy)

标准方差的减小值则为:
SDR ⁡ ( T , X ) = S ( T ) − S ( T , X ) \operatorname{SDR}(T, X) = S(T) - S(T, X) SDR(T,X)=S(T)S(T,X)
接下来,重复这个过程,使用标准方差降低最多的特征进行分支,直到满足某个停止条件,如:

  • 当某个分支的变化系数小于某个值(例如10%)
  • 当前节点包含的元素个数小于某个值

最终,我们能够得到叶子节点输出一个预测值的回归树。

1.3 决策树小结

CART是最常出现的

图源CSDN

对决策树的原理和分支准则有了一定了解之后,我们可以参考上述图片,了解一下常见的决策树基学习器,之后的更高级的树算法都在建立在这几种基学习器上的。

例如GBDT是基于CART。CART其实就是Classification And Regression Tree,大白话就是“分类与回归树”,只是缩写看起来怪高级的而已。

2 集成学习

集成学习是通过构建并组合多个学习器来完成学习任务的算法。集成学习常用的有两类:

Bagging: 基学习器之间无强依赖关系,可同时生成的并行化方法。例如随机森林。

Boosting: 基学习器之间存在强烈的依赖关系,必须串行生成基分类器的方法。例如Adaboost、GBDT、XGboost和LightGBM。

图解两种集成学习

上面的话可能有点抽象,不过相信读者们看了下方的介绍与推导,也就能够理解何为**“串行”“并行”**了。当然,本文只介绍Boosting。

2.1 GBDT

2.1.1 先从BDT开始

所谓BDT,即Boosting Decision Tree(提升树),其架构如下:

反复训残差的BDT

该图相当直观地解释了BDT的架构:

  1. 将样本放入第一个弱学习器,得到预测值
  2. 将预测值与样本做差,得到残差,将残差放入第二个弱学习器,得到残差的预测值
  3. 将残差与残差预测值做差,得到二级的残差;将二级残差放入第三个弱学习器,再次得到残差
  4. 重复上述步骤,直到到达某个判断条件。
  5. 最后将弱学习器都加起来,得到强学习器。

这也就是所谓的串行。而整个提升树算法,本质加法模型前向分布算法的结合。

既然这是一个具体的算法,那么核心肯定是围绕着损失函数的,BDT的损失函数定义如下:

在前向分布算法第m步时,给定当前的模型$ f_{m-1}(x)$,求解:

min ⁡ ( ∑ i = 1 N L ( y i , f m − 1 ( x ) + T ( x , Θ m ) ) ) \min \left( \sum_{i=1}^{N} L(y_i, f_{m-1}(x) + T(x, \Theta_m)) \right) min(i=1NL(yi,fm1(x)+T(x,Θm)))

得到第m棵决策树 T ( x , Θ m ) T(x, \Theta_m) T(x,Θm)。不同问题的提升树的区别在于损失函数的不同,如分类用指数损失函数,回归使用平方误差损失。

针对于回归问题,第 m 次迭代时表示为:

L ( y , f m − 1 ( x ) + T ( x , Θ m ) ) = ( y − f m − 1 ( x ) − T ( x , Θ m ) ) 2 L\left(y, f_{m-1}(x) + T\left(x, \Theta_m\right)\right) = \left(y - f_{m-1}(x) - T\left(x, \Theta_m\right)\right)^2 L(y,fm1(x)+T(x,Θm))=(yfm1(x)T(x,Θm))2
而残差 r = y − f m − 1 ( x ) r = y - f_{m-1}(x) r=yfm1(x),该式即可化简为
L ( y , f m − 1 ( x ) + T ( x , Θ m ) ) = ( r − T ( x , Θ m ) ) 2 L\left(y, f_{m-1}(x) + T\left(x, \Theta_m\right)\right)= \left(r - T\left(x, \Theta_m\right)\right)^2 L(y,fm1(x)+T(x,Θm))=(rT(x,Θm))2
BDT架构伪代码

输入:训练数据集 D = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , … , ( x N , y N ) } D = \{(x_1, y_1), (x_2, y_2), \ldots, (x_N, y_N)\} D={(x1,y1),(x2,y2),,(xN,yN)}

  1. 初始化 f 0 ( x ) = 0 f_0(x) = 0 f0(x)=0

  2. For m = 1 , 2 , … , M m = 1, 2, \ldots, M m=1,2,,M

    • 针对每一个样本 ( x i , y i ) (x_i, y_i) (xi,yi),计算残差
      r m , i = y i − f m − 1 ( x i ) , i = 1 , 2 , … , N r_{m,i} = y_i - f_{m-1}(x_i), \quad i = 1, 2, \ldots, N rm,i=yifm1(xi),i=1,2,,N
    • 利用 { ( x i , r m , i ) } i = 1 , 2 , … , N \{(x_i, r_{m,i})\}_{i=1,2,\ldots,N} {(xi,rm,i)}i=1,2,,N 训练一个决策树(回归树),得到 T ( x ; Θ m ) T(x; \Theta_m) T(x;Θm)
    • 更新 f m ( x ) = f m − 1 ( x ) + T ( x ; Θ m ) f_m(x) = f_{m-1}(x) + T(x; \Theta_m) fm(x)=fm1(x)+T(x;Θm)
  3. 完成以上迭代,得到提升树 f M ( x ) = ∑ m = 1 M T ( x ; Θ m ) f_M(x) = \sum_{m=1}^{M} T(x; \Theta_m) fM(x)=m=1MT(x;Θm)

2.1.2 正式来到GBDT

GBDT,即Gradient Boosting Decision Tree(梯度提升决策树),理解为梯度提升 + 决策树。Friedman提出了利用最速下降的近似方法,利用损失函数的负梯度拟合基学习器:

− [ ∂ L ( y i , F ( x i ) ) ∂ F ( x i ) ] F ( x ) = F t − 1 ( x ) -\left[\frac{\partial L\left(y_i, F\left(x_i\right)\right)}{\partial F\left(x_i\right)}\right]_{F(x)=F_{t-1}(x)} [F(xi)L(yi,F(xi))]F(x)=Ft1(x)

下面我们通过平方损失函数来为大家证明该近似:
为了求导方便,在损失函数前面乘以 1 / 2 1/2 1/2

L ( y i , F ( x i ) ) = 1 2 ( y i − F ( x i ) ) 2 L\left(y_i, F\left(x_i\right)\right) = \frac{1}{2}\left(y_i - F\left(x_i\right)\right)^2 L(yi,F(xi))=21(yiF(xi))2

F ( x i ) F(x_i) F(xi) 求导,则有:

∂ L ( y i , F ( x i ) ) ∂ F ( x i ) = F ( x i ) − y i \frac{\partial L\left(y_i, F\left(x_i\right)\right)}{\partial F\left(x_i\right)} = F\left(x_i\right) - y_i F(xi)L(yi,F(xi))=F(xi)yi

残差是梯度的相反数,即:

r t i = y i − F t − 1 ( x ) = − [ ∂ L ( y i , F ( x i ) ) ∂ F ( x i ) ] F ( x ) = F t − 1 ( x ) r_{t i} = y_{i} - F_{t-1}(x) = -\left[\frac{\partial L\left(y_{i}, F\left(x_{i}\right)\right)}{\partial F\left(x_{i}\right)}\right]_{F(x)=F_{t-1}(x)} rti=yiFt1(x)=[F(xi)L(yi,F(xi))]F(x)=Ft1(x)

于是, GBDT与BDT的第一个不同点 是使用负梯度作为残差进行拟合。因此也立刻出现了第二个不同点:BDT是一个加法模型,默认每棵树的权重相等,但这可能并不是很灵活;而GBDT的梯度提升方法与主流机器学习的梯度下降有类似之处,需要有一个类似学习率的参数控制学习的步长——在此处,即为基学习器的权重

直观理解GBDT架构

如同,GBDT最终得到的Gradient,实际上是每个弱学习器,乘以权重并加和后得到的。

GBDT架构伪代码

输入:训练集: D a t a = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , ⋯   , ( x N , y N ) } , y i ∈ { + 1 , − 1 } Data=\left\{\left(x_1, y_1\right),\left(x_2, y_2\right),\cdots,\left(x_N, y_N\right)\right\}, y_i\in\{+1,-1\} Data={ (x1,y1),(x2,y2),,(xN,yN)},yi{ +1,1}

  1. 初始化: F 0 ( x ) = arg ⁡ min ⁡ h 0 ∑ i = 1 N L ( y i , h 0 ( x i ) ) F_0(x) = \arg\min_{h_0} \sum_{i=1}^{N} L(y_i, h_0(x_i)) F0(x)=argminh0i=1NL(yi,h0(xi))

  2. for t = 1 t=1 t=1 to T T T do

    2.1 计算负梯度: y ~ i = − [ ∂ L ( y i , F ( x i ) ) ∂ F ( x i ) ] F ( x ) = F t − 1 ( x ) , i = 1 , 2 , ⋯   , N \tilde{y}_i = -\left[\frac{\partial L\left(y_i, F\left(x_i\right)\right)}{\partial F\left(x_i\right)}\right]_{F(x)=F_{t-1}(x)}, i=1,2,\cdots, N y~i=[F(xi)L(yi,F(xi))]F(x)=Ft1(x),i=1,2,,N

    2.2 拟合残差得到基学习器:
    w t = arg ⁡ min ⁡ w t ∑ i = 1 N ( y ~ i − h t ( x ; w t ) ) 2 w_t = \arg\min_{w_t} \sum_{i=1}^N \left(\tilde{y}_i - h_t\left(x; w_t\right)\right)^2 wt=argwtmini=1N(y~iht(x;wt))2

    2.4 得到基学习器的权重:
    α t = arg ⁡ min ⁡ α t ∑ i = 1 N L ( y i , f t − 1 ( x i ) + α t h t ( x ; w t ) ) \alpha_t = \arg\min_{\alpha_t} \sum_{i=1}^N L\left(y_i, f_{t-1}\left(x_i\right) + \alpha_t h_t\left(x; w_t\right)\right) αt=argαtmini=1NL(yi,ft1(xi)+αtht(x;wt))

    2.5 更新 F t ( x ) = F t − 1 ( x i ) + α t h t ( x ; w t ) F_t(x) = F_{t-1}(x_i) + \alpha_t h_t(x; w_t) Ft(x)=Ft1(xi)+αtht(x;wt)

PS:此处关于BDT和GBDT行云流水的推导已经完成,但是如果读者觉得仍然和实际场景有些脱节的话,可以参考有具体例子讲解、一步一步带着大家算的:

图解机器学习算法(9) | GBDT模型详解(机器学习通关指南·完结)

2.2 XGboost

XGBoost,即Extreme Gradient Boosting,是GBDT(梯度提升决策树)的一种,也是加法模型和前向优化算法的结合。作为一个完整的监督学习方法,以下四个要素是不可或缺的:

  • 模型:给定输入 x x x 后预测输出 y y y 的方法,比如回归、分类、排序等。
  • 参数:模型中的参数,比如线性回归中的权重和偏置。
  • 目标函数:即损失函数,包含正则化项。
  • 学习方法:给定目标函数后求解模型和参数的方法,比如梯度下降法、数学推导等。

本文将逐步为读者解析。

2.2.1 XGboost的模型形式

假如我们要判断一个人是否喜欢电脑游戏,可以采取下列的决策树:

该树的每一个节点都有一个分数,利用该分数,我们可以进行回归,同样也可以映射为概率进行分类。

如果觉得一棵树太武断了,不妨多来几棵树。如果是并行的,则是Bagging,代表算法有随机森林;如果是串行的,则是Boosting,代表算法则是我们要讲解的XGboost

给定数据集:

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 \mathbb{R}^m, y_i \in \mathbb{R}) D=(Xi,yi)(D=n,xiRm,yiR)

XGBoost利用前向分布算法,学习到包含 K 棵树的加法模型:

y ^ i = ∑ t = 1 K f t ( x i ) , f t ∈ F \hat{y}_i = \sum_{t=1}^K f_t\left(x_i\right), \quad f_t \in \mathcal{F} y^i=t=1Kft(xi),ftF

其中有 K 棵树 $ f$ 是回归树,而 $ \mathcal{F} $ 对应回归树组成的函数空间。那么怎么得到这些树,也就是树的结构和叶子节点的预测结果?这就涉及到我们目标函数的建构了。

2.2.2 目标函数

WARNING:此处全是数学推导

定义目标函数为损失函数+正则项:

Obj ( Θ ) = ∑ i = 1 N l ( y i , y ^ i ) + ∑ j = 1 t Ω ( f j ) , f j ∈ F \text{Obj}(\Theta) = \sum_{i=1}^N l\left(y_i, \hat{y}_i\right) + \sum_{j=1}^t \Omega\left(f_j\right), \quad f_j \in \mathcal{F} Obj(Θ)=i=1Nl(yi,y^i)+j=1tΩ(fj),fjF

正则项的组成一般是对模型复杂度的惩罚,如叶节点的数量。如何优化这个目标函数呢?因为 $ f $ 是决策树,而不是数值型的向量,我们不能使用梯度下降的算法进行优化。

我们首先来关注损失函数项的推导:

XGBoost是前向分布算法,我们通过贪心算法寻找局部最优解:

y ^ i ( t ) = ∑ j = 1 t f j ( x i ) = y ^ i ( t − 1 ) + f t ( x i ) \hat{y}_i^{(t)} = \sum_{j=1}^t f_j\left(x_i\right) = \hat{y}_i^{(t-1)} + f_t\left(x_i\right) y^i(t)=j=1tfj(xi)=y^i(t1)+ft(xi)

每一次迭代我们寻找使损失函数降低最大的 ( f )(CART树),因此目标函数可改写为:
O b j ( t ) = ∑ i = 1 N l ( y i , y ^ i ( t ) ) + ∑ j = 1 t Ω ( f j ) Obj^{(t)} = \sum_{i=1}^N l\left(y_i, \hat{y}_i^{(t)}\right) + \sum_{j=1}^t \Omega\left(f_j\right) Obj(t)=i=1Nl(yi,y^i(t))+j=1tΩ(fj)
y ^ i ( t ) \hat{y}_i^{(t)} y^i(t) 使用迭代公式进行替换,并且将 j < = t − 1 j <= t -1 j<=t1时的正则项视作常数,将 Ω ( f t ) \Omega\left(f_t\right) Ω(ft)单独提出,可以得到
= ∑ i = 1 N l ( y i , y ^ i ( t − 1 ) + f t ( x i ) ) + Ω ( f t ) + constant = \sum_{i=1}^N l\left(y_i, \hat{y}_i^{(t-1)} + f_t\left(x_i\right)\right) + \Omega\left(f_t\right) + \text{constant} =i=1Nl(yi,y^i(t1)+ft(xi))+Ω(ft)+constant
常数项对我们的目标函数无用,舍之。
= ∑ i = 1 N l ( y i , y ^ i ( t − 1 ) + f t ( x i ) ) + Ω ( f t ) = \sum_{i=1}^N l\left(y_i, \hat{y}_i^{(t-1)} + f_t\left(x_i\right)\right) + \Omega\left(f_t\right) =i=1Nl(yi,y^i(t1)+ft(xi))+Ω(ft)
接下来是最关键的一个步骤,Taylor展开
O b j ( t ) = ∑ i = 1 N l ( y i , y ^ i ( t − 1 ) + f t ( x i ) ) + Ω ( f 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 ) \begin{align*} Obj^{(t)} &= \sum_{i=1}^N l\left(y_i, \hat{y}_i^{(t-1)} + f_t\left(x_i\right)\right) + \Omega\left(f_t\right) \\ &= \sum_{i=1}^N \left(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)\right) + \Omega\left(f_t\right) \end{align*} Obj(t)=i=1Nl(yi,y^i(t1)+ft(xi))+Ω(ft)=i=1N(l(yi,y^i(t1))+gift(xi)+21hift2(xi))+Ω(ft)

其中,
g i = ∂ l ( y i , y ^ i ( t − 1 ) ) ∂ y ^ i ( t − 1 ) g_i = \frac{\partial l\left(y_i, \hat{y}_i^{(t-1)}\right)}{\partial \hat{y}_i^{(t-1)}} gi=y^i(t1)l(yi,y^i(t1))
h i = ∂ 2 l ( y i , y ^ i ( t − 1 ) ) ∂ 2 y ^ i ( t − 1 ) h_i = \frac{\partial^2 l\left(y_i, \hat{y}_i^{(t-1)}\right)}{\partial^2 \hat{y}_i^{(t-1)}} hi=2y^i(t1)2l(yi,y^i(t1))

移除对第 t 轮迭代来说的常数项 l ( y i , y ^ i ( t − 1 ) ) l\left(y_i, \hat{y}_i^{(t-1)}\right) l(yi,y^i(t1)) 得到:

O b j ( t ) = ∑ i = 1 N ( g i f i ( x i ) + 1 2 h i f i 2 ( x i ) ) + Ω ( f i ) Obj^{(t)} = \sum_{i=1}^N \left(g_i f_i\left(x_i\right) + \frac{1}{2} h_i f_i^2\left(x_i\right)\right) + \Omega\left(f_i\right) Obj(t)=i=1N(gifi(xi)+21hifi2(xi))+Ω(fi)

所以目标函数只依赖于每条数据在误差函数上的一阶导数和二阶导数。

LOSS函数以及被Cover掉了,现在我们接着来看正则化项

树的复杂度可以用如树的深度、内部节点个数、叶节点个数等来衡量。

XGBoost中正则项用来衡量树的复杂度:树的叶子节点个数 ( T ) 和每棵树的叶子节点输出分数 ( W ) 的平方和(相当于L2正则化):

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

试着计算这颗简单树的正则项

该数有3个叶子节点,每个叶节点的得分平方为4,0.01和1。于是正则项计算如下:
Ω = γ ⋅ 3 + 1 2 λ ( 4 + 0.01 + 1 ) \Omega = \gamma \cdot 3 + \frac{1}{2} \lambda (4 + 0.01 + 1) Ω=γ3+21λ(4+0.01+1)

显然, γ \gamma γ λ \lambda λ 是超参数。

最后,我们来推导最终的目标函数

我们已经得到的目标函数形式为:
O b j ( t ) = ∑ i = 1 N ( g i f t ( x i ) + 1 2 h i f t 2 ( x i ) ) + γ T + 1 2 λ ∑ j = 1 T w j 2 Obj^{(t)} = \sum_{i=1}^N \left(g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)\right) + \gamma T + \frac{1}{2} \lambda \sum_{j=1}^T w_j^2 Obj(t)=i=1N(gift(xi)+21hift2(xi))+γT+21λj=1Twj2
我们其实也能看出,似乎Loss项还没和树建立连接。

定义 q q q 函数将输入 x x x 映射到某个叶子节点上,则有:

f t ( x ) = w q ( x ) , w ∈ R T , q : R d → { 1 , 2 , … , T } f_t(x) = w_{q(x)}, \quad w \in \mathbb{R}^T, q: \mathbb{R}^d \rightarrow \{1, 2, \ldots, T\} ft(x)=wq(x),wRT,q:Rd{ 1,2,,T}
其中 w w w为各个叶节点的得分, R T \mathbb{R}^T RT即为叶节点的得分集合, R d \mathbb{R}^d Rd则表明了树的结构。还是以上述我们计算正则项的简单树为例:

就是这麽简单直接

接着,我们开始一通带入和化简的操作:

定义每个叶子节点 j j j 上的样本集合为 I j = { i ∣ q ( x i ) = j } I_j = \{i | q(x_i) = j\} Ij={ iq(xi)=j},则目标函数可以改写为:

O b j ( t ) = ∑ i = 1 N ( g i f t ( x i ) + 1 2 h i f t 2 ( x i ) ) + γ T + 1 2 λ ∑ j = 1 T w j 2 = ∑ 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 + 1 2 λ ∑ j = 1 T w j 2 = ∑ j = 1 T ( G j w j + 1 2 ( H j + λ ) w j 2 ) + γ T \begin{align*} Obj^{(t)} &= \sum_{i=1}^N \left(g_{i} f_{t}(x_{i}) + \frac{1}{2} h_{i} f_{t}^2(x_{i})\right) + \gamma T + \frac{1}{2} \lambda \sum_{j=1}^T w_{j}^2 \\ &= \sum_{i=1}^N \left(g_{i} w_{q(x_{i})} + \frac{1}{2} h_{i} w_{q(x_{i})}^2\right) + \gamma T + \frac{1}{2} \lambda \sum_{j=1}^T w_{j}^2 \\ &= \sum_{j=1}^T \left(\sum_{i \in I_j} g_{i} w_{j} + \frac{1}{2} \sum_{i \in I_j} h_{i} w_{j}^2\right) + \gamma T + \frac{1}{2} \lambda \sum_{j=1}^T w_{j}^2 \\ &= \sum_{j=1}^T \left(G_{j} w_{j} + \frac{1}{2} (H_{j} + \lambda) w_{j}^2\right) + \gamma T \end{align*} Obj(t)=i=1N(gift(xi)+21hift2(xi))+γT+21λj=1Twj2=i=1N(giwq(xi)+21hiwq(xi)2)+γT+21λj=1Twj2=j=1T iIjgiwj+21iIjhiwj2 +γT+21λj=1Twj2=j=1T(Gjwj+21(Hj+λ)wj2)+γT

这就是目标函数最终的结果,其中, 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

最后一步:目标函数的优化!

我们计算第 t t t 轮时使目标函数最小的叶节点的输出分数 w w w,直接对 w w w 求导,使得导数为 0,得:

w j = − G j H j + λ w_j = -\frac{G_j}{H_j + \lambda} wj=Hj+λGj

将其带入损失函数中:

O b j ( t ) = ∑ j = 1 T ( G j w j + 1 2 ( H j + λ ) w j 2 ) + γ T = ∑ j = 1 T ( − G j 2 H j + λ + 1 2 G j 2 H j + λ ) + γ T = − 1 2 ∑ j = 1 T ( G j 2 H j + λ ) + γ T \begin{align*} Obj^{(t)} &= \sum_{j=1}^T \left(G_j w_j + \frac{1}{2} (H_j + \lambda) w_j^2\right) + \gamma T \\ &= \sum_{j=1}^T \left(-\frac{G_j^2}{H_j + \lambda} + \frac{1}{2} \frac{G_j^2}{H_j + \lambda}\right) + \gamma T \\ &= -\frac{1}{2} \sum_{j=1}^T \left(\frac{G_j^2}{H_j + \lambda}\right) + \gamma T \end{align*} Obj(t)=j=1T(Gjwj+21(Hj+λ)wj2)+γT=j=1T(Hj+λGj2+21Hj+λGj2)+γT=21j=1T(Hj+λGj2)+γT

上式越小越好。即我们要求最小化目标函数

到此,目标函数的数学推导终于完成。但是可能读者已经有些恍惚了,那么我们可以放到具体的场景中来算一算:

形象生动

2.2.3 学习策略

学习策略是用于确定树的结构的。XGboost采用贪心算法,每次尝试分裂一个叶节点,计算分裂后的增益,选择增益最大的。类似于信息增益和基尼增益,那XGBoost中怎么计算增益呢?损失函数是:

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

其中红色部分衡量了叶子节点对总体损失的贡献,目标函数越小越好,则红色部分就越大越好,在XGBoost中增益计算方法是:

Gain = 1 2 [ G L 2 H L + λ + G R 2 H R + λ − ( G L + G R ) 2 H L + H R + λ ] − y ‾ ‾ ‾ ‾ \text{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] - \underline{\underline{\underline{\underline{y}}}} Gain=21[HL+λGL2+HR+λGR2HL+HR+λ(GL+GR)2]y

第一项和第二项分别为分裂后的左右节点的分数,第三项为父节点的分数,最后减去新叶节点的复杂度。Gain值越大,说明分裂后能使目标函数减小的越多,也就是越好。

下面我们介绍第一种方法:

  • 精确贪心算法

就像CART树一样,枚举所有的特征和特征值,计算树的分裂方式。该方法叫做“精确贪心算法”,伪代码实现如下:

Input: I I I, instance set of current node
Input: d d d, feature dimension
g a i n ← 0 gain \leftarrow 0 gain0
G ← ∑ i ∈ I g i , H ← ∑ i ∈ I h i G \leftarrow \sum_{i \in I} g_i, H \leftarrow \sum_{i \in I} h_i GiIgi,HiIhi

for k = 1 k = 1 k=1 to m m m do %从1到 m m m颗树
      G L ← 0 , H L ← 0 G_L \leftarrow 0, H_L \leftarrow 0 GL0,HL0 %初始化一二阶导数
      for j j j in sorted( I I I, by x j k x_{jk} xjk) do %排序
          G L ← G L + g j , H L ← H L + h j G_L \leftarrow G_L + g_j, H_L \leftarrow H_L + h_j GLGL+gj,HLHL+hj
          G R ← G − G L , H R ← H − H L G_R \leftarrow G - G_L, H_R \leftarrow H - H_L GRGGL,HRHHL
          s c o r e ← max ⁡ ( s c o r e , G L 2 H L + λ + G R 2 H R + λ − G 2 H + λ ) score \leftarrow \max(score, \frac{G_L^2}{H_L + \lambda} + \frac{G_R^2}{H_R + \lambda} - \frac{G^2}{H + \lambda}) scoremax(score,HL+λGL2+HR+λGR2H+λG2)
       end
end

Output: 以最大分数分裂

当数据量过大时,贪心算法几乎是不可行的,于是我们介绍第二种方法:

  • 近似算法

当数据量庞大,无法全部存入内存中时,精确算法很慢,因此引入近似算法。近似算法与精确贪心算法最大的差异在于,该算法首先根据特征分布的分位数提出候选划分点,然后将连续型特征映射到由这些候选点划分的桶中,然后聚合统计信息找到所有区间的最佳分裂点。具体实现和理解起来具有一定难度,建议参考深入理解XGBoost,优缺点分析,原理推导及工程实现

在这一点上,我认为对于绝大多数使用者而言,只需要理解贪心算法后,了解近似算法有些类似于优化算法,即可能会损失一定的精度,但是效率可以大幅提升。

2.2.4 缺失值处理

XGboost还支持缺失值处理,但因为感觉并不算独属于XGboost的算法特色,故此处略作不讲。有兴趣可参考还有人不懂XGBoost的缺失值处理?(全面解析篇)

3 总结与本人写作动机

  • 总结

本文详细(又图文并茂)地介绍了决策树的基本内容,并且进一步讲解了集成学习中GBDTXGboost的算法原理。

和传统的梯度提升决策树( GBDT )不同,xgboost 给损失函数增加了正则化项,且由于有些损失函数是难以计算导数的,xgboost 使用损失函数的二阶泰勒展开作为损失函数的拟合(这一点可谓是精妙)。

  • 本人写作动机

一个大二的本科生最近喜提期末月双大创与数模加身,并且在组会上看到队友因为不懂算法原理惨遭导师拷问,遂开始扪心自问:“对于算法,难道真的知道怎么用就够了?”

经过一番思考之后,觉得对于正在进阶的科研牛马(以及大部分理科牲)而言,懂原理虽是废时间,但绝对是不可获取的一步。倘若只会套皮抄代码,上限可以预见也不过是个Ctrl C + Ctrl V的水平。我们或许可以做出一个还不错的结果,但是我们不知道自己在做什么。

例如当日的组会现场,导师质问:

“为什么你用偏最小二乘去回归液滴模型的RMSE达到了60Mev?这明显比正常值高了十几倍?你给我解释一下偏最小二乘回归的原理是什么,然后给我解释一下为什么会出现这种情况?”

我可爱的队友愣住了。因为PLSR的部分是我做的,我直接把源代码给了他。虽然我不在现场,但是我几乎可以看到他吞吞吐吐的神色:

“嗯…偏最小二乘就是…给最小二乘加了惩罚项…”

屏幕外的我脸都快要气绿了。还好我机智地准备了PLSR的数学推导,到我发言时把场给圆了回来,(破除了导师怀疑我们诈骗的可能性)。但同时,我也感觉有一丝侥幸成分,因为事实上也是在我用了PLSR跑出结果之后,对结果之差感到不可置信,才去认认真真看了一遍推导。

这一次也给了我一个教训。虽然现在各种库和接口都很方便好用,但是更重要的是,用一个方法,不能不懂原理,否则连自己在干什么也不知道。 如果这样蒙混下去,总有一天会原形毕露的。

限于作者水平,欢迎大家讨论指正!