躺懂XGBoost,步步推导

  • 网上资料实在良莠不齐,这边贴个图,那边贴个图,就完成了XGBoost的详细说明
  • 好的资料又往往需要付费

因此,本文将假设你只具备了普通的大学数学水平,步步推导,完成XGBoost的详细说明

(方便无数据结构基础的同学学习,在一些基础名词后也会进行简单说明,对计算机同学可能显得有些多此一举,见谅!)

目录

回归树及其数学表达

XGBoost 目标函数(最终需要优化的函数)

前向分步算法

正则项处理

损失函数处理

确定树的结构

穷举法

精确贪心算法

思想

具体算法流程

优化:近似算法

压缩特征数:

压缩候选切分点数量

加权分位法

全局策略

局部策略

缺失值处理

学习率shrinkage

系统设计

核外块运算

分块并行

 缓存优化

缓存优化

Done


回归树及其数学表达

假如说我们有n个样本,每个样本具备m个特征,如下图所示:

 现将第一个样本丢入我们的回归树里:

 

  •  回归树的每个节点都包含一组判断条件,比如,在此我们假设根节点(最上面的那个点)的判断条件是第一个特征小于10
  • 假如满足,则往左走,反之,往右走。后续节点会对其他特征值进行判断。
  • 当走到叶子节点(就是最下面的那些点)时,就代表该样本落入该节点对应的分类值,即w2
  • 这棵树的高度和节点数量只是一个例子说明,如果只是这么一棵树,最多在第二层再进行个粗略判断(如x2>100),显然无法有效分类特征稍多的样本。

将所有的样本都丢入这颗回归树里面,他们最终都落入属于自己的叶子节点。我们假设节点值w1, w2, w3, w4的值设为10, 20, 30, 40。则现在每个样本都有属于自己的节点值。

  • 记函数  g(X_i) = j     意为 i 号样本在分类后所属第 j 号叶子节点 
  • 记每个叶子节点的样本集合为I1, I2, I3, I4
  • 则存在节点集合与样本之间的映射关系:I_j = {\{i | g(X_i) = j}\}
  • 这个集合的意思是:分类为第 j 个 叶子节点的所有样本集合

我们再以样本1为例,则

g(X_1) = 2

w_{g(X_1))} = w_2 = 20

因此我们也有了回归树的目标函数 (这里的θ是回归树的参数,可以先不管,就先简单理解为第i号样本丢进去会被分类到一个节点值 wj 就好了)

T(\theta ;X_{i}) = w_{g(x_{i})}


XGBoost 目标函数(最终需要优化的函数)

值得一提的是,我们上述的回归树模型(通俗易懂版)只是简单的告诉你,把第i个样本Xi丢进来,会得到某个节点值,而没有说明这棵树是怎么建起来的。换言之,我们的分类点是啥,它到底靠不靠谱我们也不清楚(根节点的x1 < 10只是我随便假设的)。

(还要注意区分以下第i号样本: Xi (X是大写) 与第i个特征 xi (x是小写)

XGBoost的重点就在于,咱是咋建树的。

现将我们的回归树模型视作一个基础单元——基学习器。

那一堆学习器学习出来的结果加在一起是不是就可以视作最后的结果啦(?????这里我不确定,投票吗还是?)

前向分步算法

前向分步算法,是我们XGBoost所采用的优化算法。它是一种贪心算法,就是把手头的这个部分弄到最优,把它固定后再去研究后面的部分。

数学背景是局部最优解是全局最优解?????????????????这个我也不知道,再说

那在我们以回归树为基学习器的XGBoost中,前向分步算法将以每棵回归树为单位来进行优化。

假设我们现在在优化第 t 课决策树(前t-1棵已经优化结束):

\hat{y}^{T}_{i}=\sum_{j = 1}^{T}f_{j}(X_{i}) =\hat{y}^{T-1}_{j}+f_{T}(Xi)

  •  其中\hat{y}^{T}_{i} 是第i个样本在第T棵回归树的累计预测值
  • f_{j}(X_{i})是第j棵回归树的预测值,其余式同理

又有以下这个式子:

T(\theta ;X_{i}) = w_{g(x_{i})}

 因此我们又可把预测值写为

 \hat{y}^{T}_{i}=\hat{y}^{T-1} + w_{g(X_i))}

于是我们就可以定义XGBoost的目标函数了

\sum_{i=1}^{N}{L(y_i , \hat{y}_{i}^{(t)})} + \sum_{j=1}^{t}{\Omega{(f_j)}}

  • y_i 是第i个样本的真实值
  • L(y_i , \hat{y}_{i}^{(t)}) 是第i个样本的真实值与预测值的误差函数,具体是啥误差函数后文再说
  • \Omega{(f_j)} 是第j棵回归树的复杂程度,具体是啥也后文再说
  • 实际上这个目标函数就是机器学习中常见的经验风险与结构风险之和(知道的可以跳过)
  • 经验风险指的是对训练样本的拟合情况。这个风险不简单是越小越好,因为假如我们的训练数据中有些离群点,那把他也郑重其事地考虑进去最终只会让你的模型过于复杂(也叫过拟合),没办法用来很好地进行预测,而预测才是我们的最终目的。过拟合就是下图:
  • 因此我们引入正则化(regularization)的概念。一般来说,泛化能力好的模型本身不能太复杂(奥卡姆剃刀原理,感兴趣的可以去了解下),所以我们对于模型的本身也需要有所限制。而结构风险指的就是模型的复杂情况。

看完上面那段逼逼,那我们的优化目标也就很清楚了:

(w_1^*, w_2^*,..., w_m^*) = argmin\sum_{i=1}^{N}{L(y_i , \hat{y}_{i}^{(t)})} + \sum_{j=1}^{t}{\Omega{(f_j)}}

  • 这里再提一嘴,因为对于第 t 次优化而言,前 t-1 棵树已经优化过了,所以我们只要优化第 t 棵树
  • 优化第t棵树也就是找到合适的节点值,让我们的目标函数最小

正则项处理

现在我们来定义我们的正则项,就是防止模型太复杂的那部分式子

\Omega{(ft)} = \gamma K + \frac{1}{2}\lambda\sum_{i=1}^{K}{w_j^2}

  • K是叶子节点数,叶子节点数越多,代表这棵树的深度越深,越容易过拟合
  • wj是每个节点值,也不能让一棵回归树贡献太多的复杂度。因此要让每个节点值都不那么大。
  • \gamma\lambda是超参(不能由模型自己调整,需要我们手动调整的部分)
    • 代表的是我们对这两个部分的惩罚值
    • 如果我们希望模型的叶子节点数量很少,那我们可以把\gamma设置的很大,那模型必须自己把K修改到很小才能最优化我们的目标函数
    • \lambda是同样的道理

损失函数处理

如果有机器学习基础的话,看到这里很多人想的是:梯度下降!牛顿法!

但是,高等数学告诉我们:回归树的损失函数不可导~嘿嘿

举个例子:

都不连续了,当然不可导咯,梯度下降也就自然不能用了。

那咋办?

回顾一下回归树的过程

假如,我们现在又五个样本,每个样本有两个特征与其相应的值。

经过回归树后,每个样本都落入了叶子节点中:

 1,4 落入节点1,2落入节点2,3落入节点3,5落入节点4

方便起见,我们取均方误差MSE,即 L(y, \hat{y}) = (y - \hat{y})^2

则有:

 现在让我们转变一下视角。原来我们的想法是进行

\sum_{i=1}^{N}{L(y_i , \hat{y}_{i}^{(t)})}

的优化。现在我们发现,y1, y2, y3, y4, y5其实都是固定值,只需找到w1使(y1-w1)^2+(y4-w1)^2最小(这个很简单了,展开后w1 = -a/2b就行了,初中数学)。而实现目标函数最小只要实现每个节点样本集合与wj的损失函数最小即可。因此累加样本成为了累加叶子节点

所以,我们的目标函数转化为

\sum_{i=1}^{N}{L(y_i , \hat{y}_{i}^{(t)})} + \gamma K + \frac{1}{2}\lambda\sum_{i=1}^{m}{w_j^2} = \sum_{j = 1}^{k}{[\sum_{i\in I_{j}} L(y_i , \hat{y}_{i}^{(t)})}+\frac{1}{2}\lambda w_{j}^{2}] + \gamma K

=\sum_{j = 1}^{k}{[\sum_{i\in I_{j} }{L(yi, \hat{y}^{(t-1)} + w_{j} ) } + \frac{1}{2} \lambda w_{j}^{2} ]} + \gamma K

但是,我们的损失函数其实并不一定是均方误差的,先前所假设的均方误差只是为了方便理解,实际情况会有许多的不同选择。在均方误差的情况下,我们是很容易求出使目标函数最小的节点值wj的,但在损失函数一般化的情况下,该怎么求呢?

这里就可以引出我们高等数学中学过的——泰勒展开啦。不要怕,不会涉及到很复杂的高阶泰勒展开,我们只要用二阶泰勒展开就行了。(这里是对目标函数中的L损失函数求\hat{y_{i}}^{t}的导数,和前面在回归树中说的无法直接对特征xi求导不是一回事哦!gi,hi是定点的导数,为常数)

 于是我们的目标函数可以近似写为:(wj* 表示使目标函数最小的wj取值)

C与γK都是常量,不影响我们对wj*的讨论。此时,目标函数成为了我们最熟悉的二次函数形式~

那么很简单,由于λ>0(超参数必须设置为>0),损失函数都是凸函数,二阶导≥0,因此λ+Hj > 0 ,我们由x_{min} = -\frac{b}{2a}即可求得wj*。

w_{j}* = -\frac{Gj}{\lambda+Hj}

 将wj*代回优化后的目标函数,我们的目标函数就有了最终形式

Obj^{(t)^{*}} = Obj^{min} = C - \frac{1}{2}\sum_{j = 1}^{K}{ \frac{G_{j}^{2}}{\lambda + H_{j}} } + \gamma K

至此,我们完成了针对第 t 棵回归树节点值以及该树对应的目标函数的求解,看的这的可以再总结消化消化,我们将进入第二部分了。


确定树的结构

先来理一遍哪些值会根据不同的决策树变化:

不变的值:

  • xj —— 每个样本的特征值
  • yi —— 样本对应的真实值
  • \hat{ y}_{i}^{(t-1)} —— 前t-1棵已经确定的回归树的累计预测值
  • hi, gi —— 损失函数一阶、二阶导数( L''(yi, \hat{y}^{(t-1)} )显然不随现在的目标回归树结构而改变)
  • λ、γ—— 超参,不改
  • 常量C——所有样本取累计预测值的损失函数加总

改变的值:

  • K—— 不同树的叶子数不同
  • Hj, Gj —— 不同回归树的分裂节点规则不同,导致最终落入每个叶子节点的导数值之和不同
  • wj —— 根据公式,随Hj,Gj而改变
  • Obj* —— 不同树的目标函数值也随Hj,Gj而改变

在这个部分,我们的目标就是找出Obj* 最小的那颗回归树作为我们的第t棵树,换言之,就是咱的第t棵树到底长啥样?

穷举法

先介绍几个基础方法方便理解,如果不想看的话,可以直接跳过。

最最最愚蠢的方法就是,我把所有可能的树全建起来,找Obj*最小的那棵不就行了?

那我们假设,样本有m个特征,每个特征分别有si个取值(i = 1,2,...m)

我们能构建最复杂的树是啥样的捏? 应该就是每个样本落入1个叶子节点吧,此时用于判断的非叶子节点一共有\sum{Si}个(每个特征的取值多需要一个节点进行判断)。那最简单的树是啥捏? 所有的样本都在一个节点。假如说我们由P个内部节点(就是非叶子节点),那选择方法就一共C_{\sum{si}}^{P}种。所有的建树方法就是个天文数字了。。。。我这里就不细细算了。

那怎么优化呢?一种初步优化方法——精确贪心算法

精确贪心算法

思想

现在我们不再把树看成一棵一棵,而是将它视为一个分裂的过程。

比如说,第一步就是根节点分裂为两个叶子节点

 第二步,第三步类似

现在我们以任意一个节点的分裂为例(他的父辈节点都被省略)。

  • Obj前 代表待分裂节点的Obj值 
  • Obj后 代表分裂节点后两个孩子的Obj值相加
  • 信息增益有非常多种,这里我们定义为Obj前-Obj后。那么显然,信息增益越大越好,也就是我们需要选择一种分裂方法,使Obj后最小。
  • 那这样每一次分裂都取最大的方法就称为贪婪

接着我们可以对其孩子节点进行分裂,就如之前提到的第二步与第三步一样,不停的分裂下去。那什么时候分裂停止呢?其实对于停止的限制条件比较多,我这里列举几个常见的。

  1. max gain ≤ 10^-5 (某个小数) 
  2. 叶子节点包含样本个数=1 (或是其他的值也行)
  3. 限制层数、叶子节点个数等方法来防止模型过于复杂

具体算法流程

 以上是论文里的代码,其中d和m我怀疑是写错了,应该都指的是一共有多少个特征。

简单说明一下变量的含义:

  • I—— 当前节点中的样本集合
  • dm—— 特征的维数,也就是一共有几个特征
  • xjk—— 以第k个特征对样本进行排序,比如排完的值为[1,1, 3, 4, 5, 6],j=0代表的就是值最小的样本的集合。(注意,这里gj与hj代表的是集合内的导数之和,如[1,1]就应当把两个都为1的样本的损失函数导数相加)
  • gainscore应当也是一个意思,都是信息增益,初值为0

我将以一个例子来解释这个算法:

五个样本,共有三个特征,每个特征的值假设如上↑

最外层循环for k = 1 to m,意味着对3个特征进行顺序遍历,即依次取x1,x2,x3作为分裂标准;现在暂且以x1为分裂标准为例:

对I中的ABCDE以x1进行排序,并将符合的x1落入叶子节点中:

然后我们就可以计算相应的GL, GR, HL, HR。注意计算GR和HR的时候,不需要累加,只需要用G和H分别减去左节点里的值就行了。

我们再依次算出另外两棵对x1分裂树的信息增益,就可以再进入外层循环,寻找以x2, x3为特征进行分裂的信息增益。外层循环结束后,我们记下使信息增益最大的分裂方法,作为我们树的结构。然后再对其他节点进行分裂,直到遇上所设的前提限制(如层数限制)

但是精确贪婪算法依然存在巨大的耗时问题,这部分的开销主要来源于它的排序。假如样本很多,特征维度也很多,排序的时间将花费巨大,那如何对它进行优化呢?

回顾精确贪心算法的流程图,我们发现最为耗时的来源于两个部分:1. 特征数量 2. 特征取值数 这两部分直接影响迭代次数与树的节点数量。因此,优化方向即为两个:1.压缩特征数 2. 压缩候选切分点数量。

优化:近似算法

当我们压缩了特征数和候选切分点数量时,其实就无法保证算法求得最优解,实质上是牺牲精确度换回了更少时间。

压缩特征数:

列采样

按树随机

在进行数据预处理之前,我们就先随机选出进行分类的部分特征。在该树的每个层级之内,都仅考虑所选特征的分类。

譬如一共有3个特征,第一棵树随机选取前两个x1, x2作为该树分类特征,则第一棵树的所有层级都只根据x1, x2作为特征分类进行进一步分裂。

按层随机

 每一层的叶子节点的分裂特征标准需进行重新的采样。不多解释了,字面意思理解即可。相对来说,按树随机更加brute,可能会丢失更多的信息。

压缩候选切分点数量

前面已经提到,精确贪心算法会对每一个特征的所有特征值进行分裂,并计算增益。那压缩候选切分点数量即要求仅考虑部分的特征值进行分裂,那如何选取这部分特征值呢?

假设当前节点需要根据特征x1的值进行分裂,一共有八个特征值,如下图:

我们倾向让每个节点内的样本数差不多,在本例当中,一共有12个样本,假定我们希望将这12个样本分入3个节点,那每个节点中应该具有4个样本。

因此有以下查找:

当第一个分裂标准顺移到2(2的下部,因为是≤)时,满足x1≤2的样本数量恰好为4,则第一个分裂点应该在2。第二个分裂标准继续向下顺移,移动到5时,恰好满足第二个节点的样本数量为4。本例情况如下图所示:

但是这种分类方法实质上是有问题的!目前所考虑的都是理想情况,实际上样本在某个取值的出现概率并不一定均匀,而且每个样本的权重也应当是不一样的。因此现在引入一种新的采样方式——加权分位法。

加权分位法

不过在具体解释加权分位法之前,我们需要重新处理一下目标函数。

\sum_{i=1}^{N}{ (g_{i}f_{i}(x_i) + \frac{1}{2}h_{i}f_{t}^{2}(xi)) } + \Omega(f_t)

=\sum_{i=1}^{N}{\frac{1}{2}h_{i} (2\frac{g_{i}}{h_{i}} f_{t}(xi) + f_{t}^2(xi))} + \Omega(f_{t})

因为加入常数项不影响寻找使得目标函数最小的xi,为凑出平方公式,我们加入一个常数项

=\sum_{i=1}^{N}{​{}\frac{1}{2}h_{i}(\frac{g_i^2}{h_i^2} + 2\frac{g_{i}}{h_{i}} f_{t}(xi) + f_{t}^2(xi) )} + \Omega(f_{t})

=\sum_{i=1}^{N}{ \frac{1}{2} h_{i} (f_{t}(xi)-(-\frac{g_i}{h_i}))^2 } + \Omega(f_t)

那么我们每个样本进来时,都可以计算出它的平方损失。那他对于整体目标函数的影响有多大呢?就由其权重hi决定。

现在将所有样本的hi先填入表格之中:

因此,权重加总为27,可以理解总共有27个样本,以样本B为例,它的权重是3。可以视为有3份,根据这样的情况,我们再次将样本均分入不同的组之中。

理想状况是,每个组均分到9个样本,但是实际情况中无法保证可以均分。像下图HLI求和是8,而G的权重是5,权重仅辅助我们理解,实际操作中一个节点依然只是能包含1个样本,并不可能把G拆成5分均匀填充。因此,此处我们简单定义为,将加总未到平均值的下一个样本直接加入本组中,得到了下图:

 当然,在实际操作的过程中,我们会把权重进行归一化,而且实际上我们也并不清楚样本应该被分到几组中。所以此时会有一个超参ε来规定每个组的宽度。在本例中,我们取ε=10/27,而一切处于ε宽度之内的分法都是可以的。比如,我们可以将1也就是BD单独分为1组,因为4/27<10/27。不过,如果我们希望让组数尽可能小,就会得到以下分法:

全局策略

类似按树随机的概念。在我们进行分裂时,可以将该特征的分类情况在全局中保有。但是也有问题,比如上述例子中,我们选择3,5,7来进行分裂,于根节点处采用3进行分裂,而左孩子中分裂标准依然为3,5,7,因此无法进行分裂。所以一般也不推荐使用全局策略

局部策略

每次节点划分的标准都会再次改变。相对全局策略耗时更多,不过胜在精细化程度更高。

效果:

  • 蓝色的曲线被遮住了,代表的是精确贪心算法。
  • eps表示ε
  • 在全局策略,分组较少的情况下,精确度较低(橙色线)
  • 在全局策略,分组非常多的情况下,耗时近似为精确贪心算法,效果也差不多
  • 在局部策略,分组较少的情况下,精确度近乎相同,且耗时缩短

至此,我们大致的算法讲解已经完成了。读到这里的可以再消化一下~

缺失值处理

有些特征由于业务逻辑的问题,并不是所有样本都有该值。因此,在以一个特征为标准进行分列时,会有一部分该特征为缺失值的样本不知道自己的归属。

从全局最优角度来看,应该把每个种缺失值的特征组合分别放到左右两个分裂节点中,找出它的最大gain的放法;从精确贪心角度来看,每次选一个样本,分别放入左右节点,选择gain较大的方法。

但是,上述两种的耗时过大了,这里采取一种比较简单的方法,即 把所有缺失值样本视为一个整体,分别放入左右两个节点,选择gain较大的方法。

值得一提的是,在加权分位法排序特征值以及选择划分依据时,缺失值样本都不参与。

学习率shrinkage

在xgboost中也加入了步长η,也叫做收缩率(shrinkage)

\hat{y}^{t}_{i} = \hat{y}^{t-1}_i + \eta f_{t}(xi)

为了防止过拟合,不能使最终学习器对于训练数据过于贴合,在一般的情况下表现欠佳,一般会加入<1的学习率来放宽模型的拟合程度。

到现在为止,xgboost的算法就结束了。后面讲的系统设计,非计算机学生可以略过。

系统设计

这部分网上的资料千篇一律,没有深度。在没有源码的基础上,我也仅仅能谈谈自己的理解。

核外块运算

因为实际上的样本量可能非常多,内存无法加载所有的样本数据,因此必须有部分数据被存储于磁盘之中。

从磁盘读取角度来看,为增加磁盘吞吐量,xgboost将应存储于磁盘的样本分为不同组,每组存储到不同的磁盘分区。

从并行的角度来看,倘若快速的内存运算每次都需要等待慢速的磁盘IO,将会耗费非常多的额外时间。因此,xgboost创建了一个进程,使在内存处理的同时就进行磁盘数据读取。

此外,在样本存储于磁盘的过程中,会采用一种压缩算法来减小空间,被称为块压缩。而分磁盘存储被称为块拆分。在每次读取数据前需要先进行解压缩。

分块并行

在求取第t课树时,最耗时的算法即为排序与遍历特征。(回忆一下精确贪心算法,我们必须以特征的顺序去进行遍历,找到最大gain的那种分法)

因此,产生了两个对应于不同问题的解决方案:

1. 遍历特征采取并行,用不同的进程同时进行特征的gain计算,计算结束后即可直接返回结果。

2. 排序不需要在每个特征都进行一次。可以先统一地进行一次预处理,针对不同的特征值进行排序,并指向对应的样本,存储在一个数据结构Block中。示意图如下:

 缓存优化

先给出问题:Block结构会导致缓存命中率低。

我们先来解释一下什么是缓存命中率:

我们或多或少应该都听过这几个概念:cpu,缓存和内存。我们的电脑在把数据从最慢的磁盘读到内存里来提高整体运算效率。但是,内存的读写速度与CPU的计算速度相比依然非常慢。因此我们又引入了一个缓存结构,它的读写速度与CPU的计算速度相比差不多。

现在假如有12345678910号文件已经被取到内存之中,为了更高的效率,在CPU计算时会将一部分文件一起读入缓存来降低读写的时间消耗。比如,读了123进入缓存。而缓存命中率指的是,CPU需要的文件是否在缓存里面。假如cpu需要处理的文件编号是1 6 10,则每次处理新文件的时候都需要把一批文件重新读入缓存。缓存命中率低带来的是更多的时间消耗。

而我们的Block结构由于进行了排序,所以它对于样本的索引是不连续的。但在内存中样本的存储是顺序结构,因此会带来缓存命中率低的问题。

缓存优化

我们回顾一下算法,其实我们并不需要每次都读取样本,我们需要的仅仅是gi和hi。这俩兄弟在求取第t棵树时都是不变的,那这就给了我们思路:没必要通过索引访问文件,只要在Block中再开辟一个缓存区,存储这个样本的gi和hi就行了。存储如下图:

至此,缓存命中率低的问题也被解决了。因为根据索引的访问不再需要找到内存中的文件,直接访问结构体的buffer就行。非顺序访问变成了顺序访问。 

Done

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值