跳转至

XGBoost

学习目标

  1. 知道XGBoost原理
  2. 了解XGBoost API的常用参数

1. XGBoost 原理

XGBoost 是(eXtreme Gradient Boosting)的简称,是优化的分布式梯度提升库:

  • 基本原理与GBDT相同,属于Gradient Boosting 类型的机器学习算法,是对GBDT的优化
  • 在训练每一棵树的时候GBDT采用了并行的方式进行训练,提高了模型训练速度
  • 对比GBDT,XGBoost无论实在训练速度上还是预测精度上都有显著提升

XGBoost属于参数学习算法,最终我们要找到一个函数来表示XGBoost模型,按照下面的思路我们来分析一下XGBoost的原理:

构造目标函数 → 目标函数的优化方法 → 用函数来表示一棵树 → 如何构建树模型

1.1 构造目标函数

通过前面章节的学习, 我们介绍了参数学习算法的基本套路:通过优化目标函数来确定参数,比如

  • 线性回归、逻辑回归, 我们可以通过梯度下降法来优化目标函数从而得到模型参数

但XGBoost 属于Boosting类集成学习模型, 要串行的训练多个模型逐步逼近降低损失,我们很难找到一个函数通过梯度下降的套路来解决这个问题。

XGBoost 使用多棵树来预测最终的结果,假设已经训练了t棵树,那么对于第i个样本的最终预测值为:

\(\large\hat{y}_i = \sum_{t=1}^{t}f_t(t_i) , f_t\in F\)

上面式子中, \(f_t(x_i)\) 为 第i 个样本 \(x_i\) 在第t棵树上的预测结果

如: \(f_1(x_1)\) 为 第1 个样本 \(x_1\) 在第1棵树上的预测结果

\(f_2(x_1)\) 为 第1 个样本 \(x_1\) 在第2棵树上的预测结果

​ ……

​ 第1个样本的最终预测结果 \(\hat{y_1} = f_1(x_1)+f_2(x_1)+……+f_t(x_1)\)

目标函数可以由下面式子表示:

\(\large Obj=\sum_{i=1}^n l(y_i,\hat{y}_i) + \sum_{t=1}^{t}\Omega (f_t)\)

上式中,左边求和项为损失函数(loss),其中 \(y_i\) 为真实值, \(\hat{y}_i\)为预测值

右边求和项用于控制模型复杂度(penlty 比如线性回归中的L1、L2正则)

XGBoost 的训练过程是一棵树接一棵树的串行训练,在训练第t棵树时,前t-1棵树已经训练结束,可视为已知,在处理目标函数时,重点关注第t棵树的情况:

\(\large \hat{y}_i = \hat{y}_i^{t-1}+f_t(x_i)\)

上式中,\(\hat{y}_i^{t-1}\) 为前t-1棵树的预测值

所以上面的目标函数可以改写成:

\(\large Obj=\sum_{i=1}^n l(y_i,\hat{y}_i^{(t-1)}+f_t(x_i)) + \sum_{t=1}^{t-1}\Omega (f_t)+\Omega(f_t)\)

上式中,\(\sum_{t=1}^{t-1}\Omega (f_t)\) 可以看做是常数(训练第t棵树时,前面t-1棵树都训练完毕),可以忽略,所以目标函数简化为:

\(\large Obj=\sum_{i=1}^n l(y_i,\hat{y}_i^{(t-1)}+f_t(x_i))+\Omega(f_t)\)

1.2 使用泰勒级数展开目标函数

直接对目标函数求解比较困难,通过泰勒展开将目标函数换一种近似的表示方式

泰勒级数,是把一个函数展开来显示的无穷级数,可以用泰勒级数的头几项来估计函数的近似值

如果 \(f(x)\) 在点 \(x=x_0\) 具有任意阶导数,则幂级数

\(\sum_{n=0}^∞\frac{f^{(n)}(x_0)}{n!}(x-x_0)^n=f(x_0)+f'(x_0)(x-x_0)+\frac{f''(x_0)}{2!}(x-x_0)^2+...+\frac{f^{(n)}(x_0)}{n!}(x-x_0)^n+...\)

称为 \(f(x)\) 在点 \(x_0\) 处的泰勒级数

这里我们把目标函数进行二阶泰勒级数展开:

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

我们把目标函数带入上面的泰勒级数:

\(f(x)→l(y_i,\hat{y}_i^{(t-1)}) ,f(x+\Delta x)→l(y_i,\hat{y}_i^{t-1}+f_t(x_i))\)

得到用二阶泰勒级数展开的目标函数

\(Obj=\sum_{i=1}^n [l(y_i,\hat{y}_i^{(t-1)})+∂_{\hat{y}_i^{(t-1)}}l(y_i,\hat{y}_i^{(t-1)})f_t(x_i)+\frac{1}{2}∂^2_{\hat{y}^{(t-1)}_i}l(y_i,\hat{y}_i^{(t-1)})f_t^2(x_i)]+\Omega(f_t)\)

\(g_i\)\(h_i\) 来表示上面式子中一阶导数 和 二阶导数的部分, 目标函数整理为:

\(\large Obj\approx\sum_{i=1}^n [l(y_i,\hat{y}_i^{(t-1)})+g_if_t(x_i)+\frac{1}{2}h_if_t^2(x_i)]+\Omega(f_t)\)

\(g_i=∂_{\hat{y}_i^{(t-1)}}l(y_i,\hat{y}_i^{(t-1)})\)

\(h_i = ∂^2_{\hat{y}^{(t-1)}_i}l(y_i,\hat{y}_i^{(t-1)})\)

上面的目标函数中,\(l(y_i,\hat{y}_i^{(t-1)})\) 为前t棵树的损失, 可以视为常数项将其忽略, 我们的目标函数可以近似的表示为:

\(\large Obj\approx\sum_{i=1}^n [g_if_t(x_i)+\frac{1}{2}h_if_t^2(x_i)]+\Omega(f_t)\)

上面的式子中,\(g_i\)\(h_i\) 是已知的

1.3 把树结构引入到目标函数

通过二阶泰勒级数展开, 我们得到了近似的目标函数:

\(\large Obj\approx\sum_{i=1}^n [g_if_t(x_i)+\frac{1}{2}h_if_t^2(x_i)]+\Omega(f_t)\)

接下来我们需要用具体的函数来替换上面公式中的 \(f_t(x_i)\)\(\Omega(f_t)\),首先看第t棵树的函数表示\(f_t(x_i)\) :

\(\large f_t(x_i) = w_{q(x_i)}\)

上面式子表示的是第i个样本在第t棵树上的取值, \(q(x_i)\) 表示第 \(x_i\) 个样本在第t棵树中落到的叶子节点编号,如下图中:

一共有三个叶子节点, 所以 \(q(x_i)\) 的取值可能为:1,2,3 ,那么如果第 \(x_i\) 个样本在第t棵树上落到了第3个叶子节点上, \(q(x_i)\) = 3 此时 \(f_t(x_i) = w_3 = 20\)

后面一项,模型复杂度\(\Omega(f_t)\)

\(\large \Omega(f_t) = \gamma T + \frac{1}{2}\lambda \sum_{j=1}^{T}w_j^2\)

上式中 T 代表叶子节点的个数, γ为一个超参数

对于上面图中的树结构 \(\Omega = \gamma3+\frac{1}{2}(15^2+10^2+20^2)\)

我们把书结构用函数表示后,目标函数为:

\(\large Obj\approx\sum_{i=1}^n [g_if_t(x_i)+\frac{1}{2}h_if_t^2(x_i)]+\Omega(f_t)\)

\(\large =\sum_{i=1}^n [g_iw_{q(x_i)}+\frac{1}{2}h_iw_{q(x_i)}^2]+\gamma T+\frac{1}{2}\lambda \sum_{j=1}^{T}w_j^2\)

\(\large =\sum_{j=1}^T [(\sum_{i\in I_j}g_i)w_j+\frac{1}{2}(\sum_{i\in I_j}h_i+\lambda)w_{j}^2]+\gamma T\)

上面式子中 \(I\) 被定义为每个叶子上面样本集合 \(I_j=i|q(x_i)=j\)

如 上图所示的树形结果, 假设:

  • 样本1,4,5落到第一个叶子节点
  • 样本3落到第二个叶子节点
  • 样本2,6落在第三个叶子节点

\(\sum_{i=1}^n g_iw_{q(x_i)}=(g_1w_1+g_4w_1+g_5w_1) +g_3w_2 +(g_2w_3+g_6w_3)=(g_1+g_4+g5)w_1+g_3w_2+(g_2+g_6)w_3\)

\(=(\sum_{i\in I_j}g_i)w_j\)

此时我们进一步令 \(G_j = \sum_{i\in I_j}g_i\)\(H_j = \sum_{i\in I_j}h_i\) 目标函数可以改写为:

\(\large obj \approx \sum_{j=1}^T [(\sum_{i\in I_j}g_i)w_j+\frac{1}{2}(\sum_{i\in I_j}h_i+\lambda)w_{j}^2]+\gamma T\)

\(\large = \sum_{j=1}^T [G_jw_j+\frac{1}{2}(H_j+\lambda)w_{j}^2]+\gamma T\)

假设我们已经知道树的结构 \(q\),我们可以通过这个目标函数来求解出最好的 \(w\) ,以及最好的 \(w\) 对应的目标函数最大的增益。上面的式子其实是关于 \(w\) 的一个一元二次函数的最小值的问题 ,求解既得到

\(\large w_j^* = - \frac{G_j}{H_j+\lambda}\)

\(\large Obj = -\frac{1}{2}\sum_{j=1}^T \frac{G_j^2}{H_j+\lambda} + \gamma T\)

Obj表示在某个确定的树结构下的结构分数(structure score),这个分数可以被看做决策树中的基尼系数、信息增益(一般决策树评分函数)

img

1.4 如何构建树

由于最终的目标函数:

\(\large Obj = -\frac{1}{2}\sum_{j=1}^T \frac{G_j^2}{H_j+\lambda} + \gamma T\)

是基于我们已经知道了所有树结构的前提,所以暴力的解法是枚举出所有可能的树结构,用上面的函数来进行打分,找到分数最小的,把对应的那棵树加入到模型中, 再不断重复, 但这种暴力解法的时间复杂度过高。

我们可以选择使用一般决策树的构建思路, 采用贪心算法的思路, 对当前树中的每一个叶子节点,用不同特征尝试进行分割,并用下面的函数计算分裂前和分裂后的增益分数,找到最优的分割方案:

其过程如下:

  1. 对树中的每个叶子结点尝试进行分裂
  2. 计算分裂前 - 分裂后的分数:
  3. 如果分数 > 0,则分裂之后分树的结构损失更小,我们会考虑此次分裂
  4. 如果分数 < 0,说明分裂后的分数比分裂前的分数大,此时不建议分裂
  5. 当触发以下条件时停止分裂:
  6. 达到最大深度
  7. 叶子结点样本数量低于某个阈值
  8. 等等...

1.5 XGBoost缺失值处理

实际的项目中的数据一般会存在缺失数据,因此在寻找最佳分割点时需要考虑如何处理缺失的特征,作者在论文中提出下面的算法

img

对于特征k,在寻找split point时只对特征值为非缺失(non-missing)的样本上对应的特征值进行遍历,统计量G和H则是全部数据上的统计量。

在遍历非缺失(non-missing)的样本 \(I_k\)时会进行两次:

  • 按照特征值升序排列遍历时实际是将missing value划分到右叶子节点

  • 按照特征值降序遍历则是划分到左叶子节点

最后选择score最大的split point以及对应的方向作为缺失值的划分方向.

在预测阶段,如果特征\(f_0\)出现了缺失值,则可以分为以下两种情况:

如果训练过程中,\(f_0\)出现过缺失值,则按照训练过程中缺失值划分的方向(左或右),进行划分;

如果训练过程中,\(f_0\)没有出现过缺失值,将缺失值的划分到默认方向(左子树)。

1.6 XGBoost原理小结

  • 构造目标函数 ,XGBoost是一棵树一棵树训练模型, 所以目标函数可以表示为前t-1棵树的损失+第t棵树的损失+控制模型复杂度的惩罚项 \(Obj=\sum_{i=1}^n l(y_i,\hat{y}_i^{(t-1)}+f_t(x_i))+\Omega(f_t)\)

  • 使用二阶泰勒泰勒级数展开目标函数: 直接求解目标函数比较困难,使用二阶泰勒级数展开目标函数。将结果整理后, 目标函数可以表示成 第t颗树的函数 \(Obj\approx\sum_{i=1}^n [g_if_t(x_i)+\frac{1}{2}h_if_t^2(x_i)]+\Omega(f_t)\)\(g_i 为一阶导数 h_i 为二阶导数)\)

  • 用函数来描述树结构: 用叶子节点的值以及叶子节点的编号来表示第t棵树的预测值,模型复杂度用节点数量以及叶子节点值的平方来描述 最终的目标函数为 \(Obj = -\frac{1}{2}\sum_{j=1}^T \frac{G_j^2}{H_j+\lambda} + \gamma T\)

  • 贪心算法建树: 前面的步骤是基于树结构为已知的前提推导得出, 所以贪心的尝试对不同特征进行分割, 用上面的函数对每一种分割打分,(类似于计算gini系数构建决策树的方式)找到增益分数最大的分割方式

  • 缺失值处理: 在训练过程中,如果特征\(f_0\)出现了缺失值,会为该特征计算一个缺失值划分方向(左子树,右子树)

2. XGBoost API

2.1 通用参数

booster [缺省值=gbtree]

  1. gbtree:使用树模型
  2. gblinear:使用线性模型
  3. dart:使用树模型,主要多了 Dropout

silent [缺省值=0]

  • 设置为 0 打印运行信息
  • 设置为 1不打印运行信息

nthread [缺省值=设置为最大可能的线程数]

  • 并行运行的线程数,输入的参数应该 <= 系统的CPU核心数
  • 若是没有设置算法会检测将其设置为 CPU 的全部核心数

下面的两个参数不需要设置,使用默认的就好了

num_pbuffer [xgboost自动设置,不需要用户设置]

  • 预测结果缓存大小,通常设置为训练实例的个数。该缓存用于保存最后 boosting 操作的预测结果

num_feature [xgboost自动设置,不需要用户设置]

  • 在boosting中使用特征的维度,设置为特征的最大维度

2.2 Booster 参数

2.2.1 Parameters for Tree Booster

eta [缺省值=0.3,别名:learning_rate]

  • 更新中减少的步长来防止过拟合
  • 每次 boosting 之后,可以直接获得新的特征权值,这样可以使得 boosting 更加鲁棒
  • 范围: [0,1]

gamma [缺省值=0,别名: min_split_loss](分裂最小loss)

  • gamma 指定了节点分裂所需的最小损失函数下降值
  • 这个参数的值和损失函数息息相关,所以是需要调整的
  • 范围: [0, ∞]

max_depth [缺省值=6]

  • 这个值为树的最大深度。 这个值也是用来避免过拟合的
  • max_depth越大,模型会学到更具体更局部的样本
  • 设置为 0 代表没有限制
  • 范围: [0 ,∞]

min_child_weight [缺省值=1]

  • 当它的值较大时,可以避免模型学习到局部的特殊样本
  • 如果这个值过高,会导致欠拟合
  • 范围: [0,∞]

subsample [缺省值=1]

  • 这个参数控制对于每棵树,随机采样的比例
  • 如果这个值设置得过大,它可能会导致过拟合
  • 如果这个值设置得过小,它可能会导致欠拟合
  • 典型值:0.5-1,0.5 代表平均采样,防止过拟合
  • 范围: (0,1]

colsample_bytree [缺省值=1]

  • 控制每棵随机特征采样的比例
  • 范围: (0,1],典型值:0.5-1

colsample_bylevel [缺省值=1]

  • 用来控制树每一次分裂时对特征的采样的比例
  • 范围: (0,1]

alpha [缺省值=0,别名: reg_alpha]

  • 权重的L1正则化项。(和Lasso regression类似)。 可以应用在很高维度的情况下,使得算法的速度更快

scale_pos_weight[缺省值=1]

  • 在各类别样本十分不平衡时,把这个参数设定为一个正值,可以使算法更快收敛
  • 通常可以将其设置为负样本的数目与正样本数目的比值

2.2.2 Parameters for Linear Booster

linear booster一般很少用到。

lambda [缺省值=0,别称: reg_lambda]

  • L2 正则化惩罚系数,增加该值会使得模型更加保守

alpha [缺省值=0,别称: reg_alpha]

  • L1正则化惩罚系数,增加该值会使得模型更加保守。

lambda_bias [缺省值=0,别称: reg_lambda_bias]

  • 偏置上的 L2 正则化(没有在L1上加偏置,因为并不重要)

2.3 学习目标参数

objective [缺省值=reg:linear]

  1. reg:linear:线性回归
  2. reg:logistic: 逻辑回归
  3. binary:logistic:二分类逻辑回归,输出为概率
  4. multi:softmax:使用softmax的多分类器,返回预测的类别(不是概率)。在这种情况下,你还需要多设一个参数:num_class(类别数目)
  5. multi:softprob:和multi:softmax参数一样,但是返回的是每个数据属于各个类别的概率。

eval_metric [缺省值=通过目标函数选择] 验证集

可供选择的如下所示:

  1. rmse: 均方根误差
  2. mae: 平均绝对值误差
  3. logloss: 负对数似然函数值
  4. error:其值通过错误分类数目与全部分类数目比值得到。对于预测,预测值大于0.5被认为是正类,其它归为负类。
  5. error@t: 不同的划分阈值可以通过 ‘t’进行设置
  6. merror: 多分类错误率,计算公式为(wrong cases)/(all cases)
  7. mlogloss: 多分类log损失
  8. auc: 曲线下的面积

seed [缺省值=0]

  • 随机数的种子,设置它可以复现随机数据的结果,也可以用于调整参数

3. 小结

  1. XGBoost 算法是对 GBDT 的改进,在损失函数中增加了正则化项,综合考虑了模型的结构风险
  2. XGBoost 使用自己的分裂增益计算方法来构建强学习器