0. 写在前面

看到 XGBoost 目标函数推导里凭空冒出的 $g_i$ 和 $h_i$, 很多教程只说”这是一阶导数和二阶导数”, 却不讲它们是怎么通过泰勒展开变出来的。本文把这个推导从头推一遍, 前提只有一个: 会加法和乘法。

参考来源: 一文入门: XGBoost与手推二阶导 - 博客园。工程建模与调参的内容不在本文范围内, 本站已有一篇《XGBoost梯度提升树建模教程》可以配合阅读。

1. XGBoost 是什么: 一群小朋友接力猜数字

想象我们要猜小明书包里有几颗糖:

  • 一棵决策树 = 一个小朋友。他根据线索 (书包大小, 形状, 被压瘪的程度…) 问一连串”如果…那么…“的问题, 最后给出一个猜测。
  • Boosting (提升) = 一群小朋友接力。第 1 个先粗略猜一个数; 第 2 个不重新猜, 专门纠正第 1 个的误差; 第 3 个再纠正前两个加起来之后剩下的误差……
  • 最终答案 = 所有人猜测的总和。 每个人只求”把剩下的误差补掉一点”, 这种模型叫加法模型:
\[\hat{y}_i = \sum_{t=1}^{T} f_t(x_i)\]

其中 $f_t$ 是第 $t$ 棵树, $T$ 是树的总数。XGBoost (eXtreme Gradient Boosting, 极限梯度提升) 就是这套接力玩法里策略最讲究的一版。

这里的树是 CART (Classification And Regression Tree, 分类与回归树): 样本沿着树的分支一路走到叶子, 树的输出就是那个叶子上的分数 $w$。

2. 好队伍的军规: Obj = Loss + Ω

XGBoost 给每棵树立了一条军规:

\[Obj = \underbrace{\sum_{i=1}^{n} Loss(y_i, \hat{y}_i)}_{损失: 答得准不准} + \underbrace{\sum_{j=1}^{t} \Omega(cart_j)}_{复杂度: 队伍臃不臃肿}\]

翻译成人话: 答案要准, 话还不能多。

损失 Loss 衡量预测值和真实值的差距。回归问题常用平方误差:

\[Loss(y_i, \hat{y}_i) = \frac{1}{2}(y_i - \hat{y}_i)^2\]

分类问题常用交叉熵:

\[Loss(y_i, \hat{y}_i) = -\left[y_i \log(\hat{y}_i) + (1-y_i)\log(1-\hat{y}_i)\right]\]

复杂度 Ω 是 XGBoost 的独门发明:

\[\Omega = \gamma T + \frac{1}{2}\lambda \sum_{j=1}^{T} w_j^2\]
符号 含义 人话
$T$ 叶子节点个数 提问结束后的”结论格子”数量, 每多一个格子罚 $\gamma$ 分, 防止树疯长
$w_j$ 第 $j$ 个叶子上的分数 每个格子的”自信程度”, 分数越大罚得越狠, 防止某个格子过于笃定
$\gamma, \lambda$ 罚分力度旋钮 人工调节的”严格程度”

这条军规回答了 GBDT 的一个天生难题: “到底加到第几棵树才算好?” GBDT 不知道; XGBoost 每加一棵树都要交”复杂度税”, 当损失的下降抵不过税收时, 它就自动停了。

3. 先修课: 泰勒展开, 从零学起

手推二阶导只靠一个工具: 泰勒展开 (Taylor Expansion)。我们把它当成新课重新学一遍。

3.1 它解决什么问题

已知函数 $f$ 在 $x$ 点的值, 想快速知道它在”旁边一点点” $x + \Delta x$ 处的值, 但函数太复杂算不动, 怎么办?

生活例子: 你站在山腰, 想知道往东迈一小步 $\Delta x$ 之后海拔变成多少。

  • 0 阶 (什么都不看): “海拔不会变” —— $f(x)$
  • 1 阶 (看坡度): 脚下坡度是 $f’(x)$ (上坡为正), 那就是现在的海拔加上坡度乘以步长 —— $f(x) + f’(x)\Delta x$
  • 2 阶 (发现坡度本身也在变): 越走越陡或者越走越缓, 再修一笔, 加上 $\frac{1}{2}f’‘(x)(\Delta x)^2$

写成公式:

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

一句话: 站在原地, 看坡度, 再看坡度怎么变, 就能推知迈出一小步后的世界。 步长 $\Delta x$ 越小, 这个近似越准, 因为我们没有让函数跑远。

3.2 用一个数字例子验证

取 $f(x) = x^2$, 已知 $x = 10$, 估计 $f(10.1)$, 也就是 $\Delta x = 0.1$:

  • 真实值: $f(10.1) = 10.1^2 = 102.01$
  • 0 阶: $f(10) = 100$, 误差 2.01, 很差
  • 1 阶: $f’(x) = 2x$, $f’(10) = 20$, 所以 $100 + 20 \times 0.1 = 102$, 误差 0.01, 好多了
  • 2 阶: $f’‘(x) = 2$, 所以 $102 + \frac{1}{2} \times 2 \times 0.1^2 = 102.01$, 分毫不差

2 阶展开能完全精确不是巧合: $x^2$ 本身就是二次函数, 泰勒展开到 2 阶就把它”写尽”了。记住这一点, 下一节会看到平方损失恰好也是二次的。

3.3 两个记忆要点

  1. 泰勒展开 = 用多项式临摹复杂曲线: 常数项定高度, 一次项定斜度, 二次项定弯曲度, 阶数越高临摹越像。
  2. 为什么有 $\frac{1}{2}$ 和 $(\Delta x)^2$: 二阶影响是”加速度式”的, 坡度的偏差会随步长累积, 累积量天然带平方; 而 $\frac{1}{2}$ 来自对 $x^2$ 求两次导产生的 2 被抵消掉一半, 参考上面 $f’‘(x) = 2$ 的例子。

4. 手推: 二阶导是怎么被”用进来”的

现在把泰勒这台机器接到 XGBoost 上, 一共五步。

4.1 写出总目标

第 $t$ 轮时, 模型的总目标是 (对所有 $n$ 个样本求和):

\[Obj = \sum_{i=1}^{n} Loss(y_i, \hat{y}_i^t) + \sum_{j=1}^{t} \Omega(cart_j)\]

其中 $\hat{y}_i^t$ 是前 $t$ 棵树对样本 $i$ 的总预测。

4.2 新树只负责补差价

前 $t-1$ 棵树已经训练完, 全部视为定死的常数。第 $t$ 棵树 $f_t$ 的任务不是重新预测, 而是在旧预测上加一个修正量:

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

4.3 识别出”这是泰勒场景”

关键观察: 损失函数里的真实值 $y_i$ 是固定常数 (答案都印在卷子上, 不随模型变化), 所以损失其实只随预测值变化, 可以看成一元函数 $L(\cdot)$。代入补差价公式:

\[Loss(y_i, \hat{y}_i^t) = L\left(\hat{y}_i^{t-1} + f_t(x_i)\right)\]

对照泰勒模板 $f(x + \Delta x) \approx f(x) + f’(x)\Delta x + \frac{1}{2}f’‘(x)(\Delta x)^2$:

  • 这里的 $x$ 就是旧预测 $\hat{y}_i^{t-1}$
  • 这里的 $\Delta x$ 就是新树的输出 $f_t(x_i)$

完全同构。

4.4 套用泰勒展开

\[L(\hat{y}_i^{t-1} + f_t(x_i)) \approx L(\hat{y}_i^{t-1}) + L'(\hat{y}_i^{t-1}) \cdot f_t(x_i) + \frac{1}{2} L''(\hat{y}_i^{t-1}) \cdot f_t^2(x_i)\]

习惯记号: $g_i = L’(\hat{y}_i^{t-1})$, $h_i = L’’(\hat{y}_i^{t-1})$, 分别是损失对预测值的一阶导数和二阶导数。

注意一个容易忽略的点: 求导是对预测值做的, 而且在旧预测 $\hat{y}_i^{t-1}$ 这一点上”一次性算完”, 所以对第 $t$ 棵树来说, $g_i$ 和 $h_i$ 只是两个已知的数字, 每个样本各一对。

4.5 扔掉常数, 得到第 t 棵树的优化目标

在第 4.4 节的结果里, $L(\hat{y}_i^{t-1})$ 是旧预测的损失, 前 $t-1$ 棵树的复杂度也都和第 $t$ 棵树无关, 是常数, 直接删掉。剩下:

\[Obj^t \approx \sum_{i=1}^{n}\left[g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)\right] + \Omega(f_t)\]

这就是 XGBoost 每一轮真正求解的目标。看到泰勒展开带来的差别了吗? 如果只展开到 1 阶, 上式就没有 $\frac{1}{2}h_i f_t^2(x_i)$ 那一项 —— GBDT 正是这么干的 (只拟合负梯度 $-g_i$)。多出来的二阶项让目标函数变成关于新树输出的标准二次函数: 形状像一个碗, 有唯一最低点, 可以直接求导解析求解, 而不是像 GBDT 那样只能沿着梯度方向”盲走一小步”。

4.6 再往前一步: 叶子权重与分裂增益

(参考文章没有展开这一段, 这里补齐闭环。) 把样本按”落在哪个叶子”分组, 记第 $j$ 个叶子里样本的 $g$ 之和为 $G_j$, $h$ 之和为 $H_j$, 则目标可以改写成关于叶子分数的二次函数, 对每个叶子独立求导并令其为零, 解出最优叶子分数:

\[w_j^* = -\frac{G_j}{H_j + \lambda}\]

把它代回去, 还能得到衡量”一次分裂赚了多少”的分裂增益, XGBoost 就是用它来决定树怎么长、什么时候停:

\[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] - \gamma\]

现在回头看第 2 节的复杂度公式就完整了: $\lambda$ 出现在分母上抑制叶子分数, $\gamma$ 作为增益的门槛惩罚叶子个数 —— 正则项不是拍脑袋加的, 而是从这个推导里自然长出来的。

5. XGBoost vs GBDT: 四大区别

# 区别 人话
1 自带正则项 $\Omega$ GBDT 不知道加几棵树该停; XGBoost 每棵树都交”复杂度税”, 损失降不过税就自动停
2 用到二阶导数 $h_i$ GBDT 只看一阶梯度 (斜率); XGBoost 连坡度的变化 (弯曲度) 都用上, 近似更准, 步子迈得更聪明
3 列抽样 借鉴随机森林: 不仅对样本抽样 (行), 还对特征抽样 (列), 既防过拟合又省算力
4 自动处理缺失值 缺失样本会被学习到”该往哪边走”, 不需要提前填空

其中第 2 条正是第 3 节和第 4 节全部推导的意义所在。

6. 小结

XGBoost 是一群树接力补误差; 它给队伍立了”又准又简洁”的军规 $Obj = Loss + \Omega$; 而它比 GBDT 高明的核心, 是用泰勒展开在旧预测这个小点上把未来的损失算清楚:

\[\underbrace{f(x+\Delta x)}_{未来} \approx \underbrace{f(x)}_{现在} + \underbrace{f'(x)\Delta x}_{坡度} + \frac{1}{2}\underbrace{f''(x)(\Delta x)^2}_{坡度的变化}\]

从而让每棵新树面对的是一个可以直接求解的二次优化问题, $g_i$ 和 $h_i$ 就是这台泰勒机器吐出来的两个数字。

勘误: 原参考文章有两处笔误。其一, 二分类交叉熵第二项应为 $\log(1-\hat{y}_i)$ 而非 $\log(\hat{y}_i)$, 否则 $y=0$ 时公式不成立; 其二, “叶子权重与叶子样本数量成正比”的说法不准确, 叶子权重实际由叶内样本的一阶/二阶导数之和解析求出 (见 4.6 节)。