XGBoost原理理解

XGBoost = Extreme Gradient Boosting,也就是说,XGBoost 把 Gradient Boosting 做到了极致,具体来说,这种极致体现在训练精度高、所需计算资源少。正如陈天奇大神在 Quora 的相关回答中说:

The name xgboost, though, actually refers to the engineering goal to push the limit of computations resources for boosted tree algorithms. Which is the reason why many people use xgboost.

下图是基于树的算法的发展路径图,可以看到 XGBoost 是 Gradient Boosting 的(终极)强化版(图自here)。

接下来我们按照原论文的组织顺序来大概记录一下对 XGBoost 的原理理解。

1、Model

首先模型可以表示如下:

\hat{y}_{i}=\phi\left(\mathbf{x}_{i}\right)=\sum_{k=1}^{K} f_{k}\left(\mathbf{x}_{i}\right), \quad f_{k} \in \mathcal{F}

这里各个f_k表示回归树模型,\mathcal{F}=\left\{f(\mathrm{x})=w_{q(\mathrm{x})}\right\}\left(q: \mathbb{R}^{m} \rightarrow T, w \in \mathbb{R}^{T}\right)表示回归树空间。q表示一个树结构对应的映射,这个映射是将一个样本映射到所划分的叶子结点的编号。w_{q(x)}则表示样本所属结点的得分或权重。T表示树的叶子结点数。

听起来很复杂的样子,实际上就是建立K棵回归树,然后对一个输入X,在K颗树中都找到其所属叶子结点的得分,然后加起来就得到最终的预测\hat{y}。原论文中有清晰的图解:

2、Loss Function & Quality Score

有了模型,下一步就是定义损失函数

\begin{aligned} & \mathcal{L}(\phi) =\sum_{i} l\left(\hat{y}_{i}, y_{i}\right)+\sum_{k} \Omega\left(f_{k}\right) \\ &\text { where }\ \ \ \Omega(f) =\gamma T+\frac{1}{2} \lambda\|w\|^{2} \\ \end{aligned}

这里l是一个可导的凸损失函数,衡量模型预测值和真实值之间的差异,\Omega是针对模型复杂度的罚项,可以视作正则项。这里的\Omega(f)由两部分组成,第一项是限制叶子结点数,第二项则是限制各叶子结点的分数,目的都是防止过拟合。删去正则项则损失函数退化至普通 Gradient Boosting 的情形。

需要注意的是,损失函数\mathcal{L}(\phi)的输入是一个函数,因此不能用像SGD这样的方法去找到各个f_k,因为他们是树而不是数值向量。解决问题的方法就是加法训练(Additive Training)。定义\hat{y_{i}}^{(t)}为第i个样本在第t轮迭代得到的模型上做出的预测。则假设当前我们已经进行了t-1轮迭代,则我们希望得到一个f_t使得下面的损失函数最小:

\mathcal{L}^{(t)}=\sum_{i=1}^{n} l\left(y_{i}, \hat{y_{i}}^{(t-1)}+f_{t}\left(\mathbf{x}_{i}\right)\right)+\Omega\left(f_{t}\right)

我们用 Taylor 公式对上式进行二次近似,之所以用二次而不是一次,是为了优化时使得损失更快下降。这里我们回顾一下泰勒公式:

f(x+\Delta x) \approx f(x) + f^{'}(x)\Delta x + \frac{1}{2}f^{''}(x)\Delta x^2

\mathcal{L}^{(t)}中的\hat{y_{i}}^{(t-1)}当作上式中的xf_{t}\left(\mathbf{x}_{i}\right)当作上式中的\Delta x,则:

\mathcal{L}^{(t)} \simeq \sum_{i=1}^{n}\left[l\left(y_{i}, \hat{y}^{(t-1)}\right)+g_{i} f_{t}\left(\mathbf{x}_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(\mathbf{x}_{i}\right)\right]+\Omega\left(f_{t}\right)

其中g_{i}=\partial_{\hat{y}(t-1)} l\left(y_{i}, \hat{y}^{(t-1)}\right)为一阶导,h_{i}=\partial_{\hat{y}(t-1)}^2 l\left(y_{i}, \hat{y}^{(t-1)}\right)为二阶导。值得注意的是,如果这里只做一次近似,那么当f_t(X)沿-g方向下降最快,因此可以用f_t(X)拟合-g,这实际上就是 Gradient Boosting。

由于l\left(y_{i}, \hat{y}^{(t-1)}\right)和当前要优化的函数f_t无关,因此可以作为常数项去掉,从而:

\tilde{\mathcal{L}}^{(t)}=\sum_{i=1}^{n}\left[g_{i} f_{t}\left(\mathbf{x}_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(\mathbf{x}_{i}\right)\right]+\Omega\left(f_{t}\right)

定义I_j = \{i|q(x_i) = j\}为第j个叶结点包含的样本,则上式可以写作:

\begin{aligned} \tilde{\mathcal{L}}^{(t)} &=\sum_{i=1}^{n}\left[g_{i} f_{t}\left(\mathbf{x}_{i}\right)+\frac{1}{2} h_{i} f_{t}^{2}\left(\mathbf{x}_{i}\right)\right]+\gamma T+\frac{1}{2} \lambda \sum_{j=1}^{T} w_{j}^{2} \\ &=\sum_{j=1}^{T}\left[\left(\sum_{i \in I_{j}} g_{i}\right) w_{j}+\frac{1}{2}\left(\sum_{i \in I_{j}} h_{i}+\lambda\right) w_{j}^{2}\right]+\gamma T \end{aligned}

对于固定的树结构q(x),可以计算出各叶子结点的最优分数:

w_{j}^{*}=-\frac{ \sum_{i \in I_{j}} g_{i}}{\sum_{i \in I_{j}} h_{i}+\lambda}

代回原损失函数可以得到最优损失:

\tilde{\mathcal{L}}^{(t)}(q)=-\frac{1}{2} \sum_{j=1}^{T} \frac{\left(\sum_{i \in I_{j}} g_{i}\right)^{2}}{\sum_{i \in I_{j}} h_{i}+\lambda}+\gamma T

于是上式就可以作为衡量一个树结构q好坏的分数,这个分数类似于决策树中的基尼系数或信息增益,可以用于判断结点如何分裂。

假设当前树中一个叶子结点的样本集为I,分裂后得到的左孩子和右孩子的样本为I_LI_R,则结点分裂后得到的树结构的最优损失为:

\tilde{\mathcal{L}}^{(t)}(q_{split})=-\frac{1}{2} \sum_{j=1}^{T} \left[\frac{\left(\sum_{i \in I_{L}} g_{i}\right)^{2}}{\sum_{i \in I_{L}} h_{i}+\lambda} + \frac{\left(\sum_{i \in I_{R}} g_{i}\right)^{2}}{\sum_{i \in I_{R}} h_{i}+\lambda} \right]+\gamma (T+1)

上面两式相减,损失减少量为:

\mathcal{L}_{s p l i t}=\frac{1}{2}\left[\frac{\left(\sum_{i \in I_{L}} g_{i}\right)^{2}}{\sum_{i \in I_{L}} h_{i}+\lambda}+\frac{\left(\sum_{i \in I_{R}} g_{i}\right)^{2}}{\sum_{i \in I_{R}} h_{i}+\lambda}-\frac{\left(\sum_{i \in I} g_{i}\right)^{2}}{\sum_{i \in I} h_{i}+\lambda}\right]-\gamma

当然遍历所有可能的树结构是不可能的,我们只能采用贪心的算法,即从初始的根节点开始,每一步都采用当前的最优结点划分策略,直至达到终止条件。这部分的算例原论文也有给出:

而上面所述的贪心算法的正规化表达如下:

3、Additional Techniques

在防止过拟合的措施方面,除了上面 loss function 中正则项对树模型的限制,还可以使用 ShrinkageColumn Subsampling 的方法。

所谓 Shrinkage,就是在每一轮添加一棵树的时候加入一个收缩系数\epsilon,类似于梯度下降算法中的学习率:

\hat{y}_{i}^{(t)}=\hat{y}_{i}^{(t-1)}+\epsilon f_{t}\left(x_{i}\right)

收缩系数\epsilon通常设置为大约 0.1。这样做的原因很简单,就是减小单个树模型对结果的影响,并为未来的树对模型的改进留足空间,从而有效缓解过拟合。

所谓 Column Subsampling ,其实就是 Feature Subsampling,特征抽样。这个防止过拟合的方法就是 Random Forest 中所采用的,论文中提到,Column Subsampling 在防止过拟合方面的效果通常优于 Row Subsampling (Sample Subsampling)

4、Approximate Algorithm

上面提到的贪心算法是确定性的,因为每次结点划分前,我们都要遍历各个特征以及各特征可能的划分点,所以计算消耗较大,尤其是数据分布很集中的情形会造成很多不必要的计算,且对于数据量大到无法全部存在内存中的情形,计算效率很低(因为每个结点判断如何划分的过程中都要进行内存的存取操作)

为了加速计算,论文中提出了相应的近似算法:

概括一下就是,枚举所有特征,根据特征,比如第k个特征的分位数来决定出l个候选切分点S_k=\{s_{k1},s_{k2},⋯s_{kl}\} ,然后根据这些候选切分点把相应的样本映射到对应的桶中,对每个桶的G,H进行累加。最后在候选切分点集合上贪心查找。

下面是一个三分位数的例子,图自知乎wepon。

论文中提到了该近似算法的两种形式,一种是 global,另一种是 local

所谓 global,就是在建树之前预先将数据进行全局(global)分桶,所谓 local,就是在各个结点分裂时重新局部(local)分桶。

直觉上讲,global 需要设置更小的分位数间隔(这里用ϵ表示,3分位的分位数间隔就是\frac{1}{3}),产生更多的桶。而 local 可以设置较大的ϵ,产生更少的桶。

论文给出了两种近似算法效果的比较:

可以看到,两种近似算法在设置合适的分位数间隔的情况下都可以达到与确定性算法相当的效果,这证明了近似算法的有效性。此外,论文中还提到,local 算法更适合树较深的场景

5、Weighted Quantile Sketch

上面我们提到的近似算法中很重要的步骤就是对各个特征找到合适的分位数作为候选划分点,从所举的3分位数的例子中我们也可以看到,对于各个样本我们是一视同仁的,也就是说,我们在计算分位数的时候把各个样本的权重都视为1。论文中提出了一种更加合理的 Weighted Quantile Sketch,即带权的分位方案

D_k = {(x_{1k}, h_1),(x_{2k}, h_2)· · ·(x_{nk}, h_n)}为各样本的第k个特征的值以及其对应的二阶梯度构成的点对集合。

在此基础上,我们可以定义一个排序函数r_k:R→[0,1]

r_{k}(z)=\frac{1}{\sum_{(x, h) \in \mathcal{D}_{k}} h} \sum_{(x, h) \in \mathcal{D}_{k}, x<z} h

上式表示的就是该特征的值小于z的样本权重和占总样本权重和的比例。我们发现,这里我们把二阶梯度的值h作为样本的权重,为什么这样做接下来会提到。

于是我们就能用下面这个不等式来寻找分裂候选点\{s_{k1},s_{k2},s_{k3},⋯,s_{kl}\}

\left|r_{k}\left(s_{k, j}\right)-r_{k}\left(s_{k, j+1}\right)\right|<\epsilon, \quad s_{k 1}=\min _{i} \mathbf{x}_{i k}, s_{k l}=\max _{i} \mathbf{x}_{i k}

设定合适的ϵ,并控制相邻两个候选分裂点相差不超过某个值ϵ,则\frac{1}{ϵ}的整数值就代表几分位,比如ϵ=\frac{1}{3}就是三分位,即有 2 个候选分裂点。

下面我们来看一下为什么用二阶导h作为样本的权重是合理的,回到之前我们定义的 loss function 并做一下变形:

\begin{aligned} O b j^{(t)} & \approx \sum_{i=1}^{n}\left[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) \\ &=\sum_{i=1}^{N} \frac{1}{2} h_{i}\left(2 \frac{g_{i}}{h_{i}} f_{t}\left(\mathbf{x}_{i}\right)+f_{t}^{2}\left(\mathbf{x}_{i}\right)\right)+\Omega\left(f_{t}\right) \\ &=\sum_{i=1}^{N} \frac{1}{2} h_{i}\left(\frac{g_{i}^{2}}{h_{i}^{2}}+2 \frac{g_{i}}{h_{i}} f_{t}\left(\mathbf{x}_{i}\right)+f_{t}^{2}\left(\mathbf{x}_{i}\right)\right)+\Omega\left(f_{t}\right)-\frac{g_{i}^{2}}{2 h_{i}} \\ &=\sum_{i=1}^{N} \frac{1}{2} h_{i}\left(f_{t}\left(\mathbf{x}_{i}\right)-\left(-\frac{g_{i}}{h_{i}}\right)\right)^{2}+\Omega\left(f_{t}\right)-\frac{g_{i}^{2}}{2 h_{i}} \\ &=\sum_{i=1}^{N} \frac{1}{2} h_{i}\left(f_{t}\left(\mathbf{x}_{i}\right)-\left(-\frac{g_{i}}{h_{i}}\right)\right)^{2}+\Omega\left(f_{t}\right)-\text {constant } \end{aligned}

注意,这里我们说\frac{g_i^2}{2h_i^2}是常数是因为g_ih_i都是由上一轮迭代的结果得到的,与当前的f_t无关。此外,这里原论文中把-\frac{g_i}{h_i}写成了\frac{ g_i}{h_i},少了一个负号,这里应该是一个小小的错误,stackexchange上的相关回答也指出了这一点。

总之,变形后的 loss function 可以视作标签为-\frac{g_i}{h_i},权重为h_i的平方损失

那么为什么我们在选取候选分裂点时要考虑平方损失的系数呢?一种直观的理解是,越是权重大的样本我们越要谨慎对待,因为同样的预测偏差,权重大的样本会带来更大的损失,因此对权重大的样本我们应该细粒度划分,即尽可能考虑每个大权重样本应该划入左孩子还是右孩子结点,而对小权重的样本则不必这么谨慎,可以采取粗粒度划分

一个实际的例子如下(图自知乎wepon):

不难看出,随着样本权重的增加,我们划分的粒度越来越细。

6、 Sparsity-aware Split Finding

在实际问题中,数据稀疏性是十分常见的,可能的原因有:1)数据缺失;2)数据零值过多;3)特征工程的结果,比如One-hot编码

在训练集出现大量数据稀疏的情况下,XGBoost 在建树时可能由于当前样例数据值缺失无法判断被分配到哪个分支,最直接的想法就是给定一个默认方向,当数据值缺失时直接将样例分配给默认方向的分支。至于默认方向是左还是右则需从数据集中学习得到

论文中给出的算法如下:

上述算法的主要特点在于只需访问数据值无缺失的样例,算法的计算复杂度与无缺失数据集的大小呈线性关系,在数据稀疏的情况下能大大减少计算量

可以看到,我们对每个特征k都做两次遍历,分别按照无缺失数据集的升序和降序来遍历无缺失的值,从中寻找最优的分裂点。因为算法在计算GH时既包含了缺失值的样例,又包括了无缺失值的样例,而遍历的时候只包含了无缺失值的样例,所以两次遍历的结果是不一样的。

我们把当前结点的数据集I划分成在特征k上无缺失值的I_k和有缺失值的I_{k_{NA}},则上述算法考虑两种情形,第一种是将I_k按升序排列,并遍历候选分裂点,得到I_L(I_{k_{NA}},I_R)两个分支,然后计算这样分裂结点的得分,找到其中的最大得分作为默认方向为右的得分;第二种是将I_k按降序排列,并遍历候选分裂点,得到(I_{k_{NA}},I_L)I_R两个分支,然后计算这样分裂结点的得分,找到其中的最大得分作为默认方向为左的得分。最后比较这两个得分,选择得分大的方向作为默认方向。

举例来说,当前节点有\{0,1,2\}三个样例,其中\{2\}是缺失值样例,\{0,1\}是无缺失值的样例。则默认方向为右需要考虑的两种情形如下:

默认方向为左需要考虑的两种情形如下:

看起来是个很简单的做法对不对,但算法的性能非常不错,论文中给出了具体的量化:比基础算法快50倍。是非常大的提升了。

7、Summary

以上就是根据原论文前 3 章及文中提到的资料整理的对 XGBoost 的粗浅理解,第 4 章及之后章节的关于系统设计的部分不是我关注的重点,所以跳过了,希望理解到这个程度可以应付大部分面试,日后若发现有需要进一步理解的地方再来补充。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,928评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,748评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,282评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,065评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,101评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,855评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,521评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,414评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,931评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,053评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,191评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,873评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,529评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,074评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,188评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,491评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,173评论 2 357

推荐阅读更多精彩内容