XGBoost(Extreme Gradient Boosting),在kaggle中非常常用,还是很有必要学习一下的。
简单地说,XGBoost就是一个朴素地靠堆叠数量取胜的学习机,依赖的是简单的决策树模型,但是通过集成的神奇方式将多个“弱学习器”组合成一个“强学习器”。
1. 目标函数 (Objective Function)
对于给定的包含 $n$ 个样本的数据集,XGBoost 的预测输出是 $K$ 棵树的累加:
\[\hat{y}_i = \sum_{k=1}^K f_k(x_i) \quad ,\quad i=1,2...n\]每棵树都是在前面所有树的优化成果上进行进一步的修正,可以说本质上下一棵树是在拟合前面结果的残差。
用于优化的目标函数由两部分组成:损失函数(预测误差)和正则化项(复杂度惩罚)。
\[\text{Obj}(\Theta) = \sum_{i=1}^n L(y_i, \hat{y}_i) + \sum_{k=1}^K \Omega(f_k)\]2. 泰勒展开:目标函数的近似
在第 $t$ 轮迭代时,我们要在保留前 $t-1$ 轮预测结果 $\hat{y}_i^{(t-1)}$ 的基础上,寻找一个新的函数 $f_t$ 来最小化:
\[\text{Obj}^{(t)} = \sum_{i=1}^n L(y_i, \hat{y}_i^{(t-1)} + f_t(x_i)) + \Omega(f_t) + \text{constant}\]为了快速求得最优解,XGBoost 对损失函数 $L$ 进行 二阶泰勒展开:
\[L(y_i, \hat{y}_i^{(t-1)} + f_t(x_i)) \approx L(y_i, \hat{y}_i^{(t-1)}) + g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)\]其中:
- $g_i = \partial_{\hat{y}^{(t-1)}} L(y_i, \hat{y}_i^{(t-1)})$ 是一阶导数(梯度)。
- $h_i = \partial^2_{\hat{y}^{(t-1)}} L(y_i, \hat{y}_i^{(t-1)})$ 是二阶导数(海森矩阵/Hessian)。
移除常数项后,第 $t$ 步的简化目标函数为:
\[\tilde{\text{Obj}}^{(t)} = \sum_{i=1}^n [g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t)\]为什么用二阶导? 相比只用一阶导的 GBDT,二阶导提供了梯度的变化信息,能更准确地逼近真实损失,从而加快收敛速度。
3. 定义树的复杂度 (Regularization)
XGBoost的正则化项定义为:
\[\Omega(f_t) = \gamma T + \frac{1}{2} \lambda \sum_{j=1}^T w_j^2\]- $T$:叶子节点的数量。
- $w_j$:第 $j$ 个叶子的权重。
- $\gamma, \lambda$:惩罚系数(超参数)。
第一项用于限制节点数产生的过拟合,第二项用于限制树之间的贡献差,防止结果主要只由少数树贡献。同时也平滑单棵树的叶子权重差异,防止对某些局部特征过于敏感。
4. 最优叶子权重与得分计算
将树的结构代入目标函数,并将求和方式从样本遍历改为叶子遍历:
\[\tilde{\text{Obj}}^{(t)} = \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\]令 $G_j = \sum_{i \in I_j} g_i$(叶子 $j$ 内样本的一阶导和),$H_j = \sum_{i \in I_j} h_i$(二阶导和)。
对 $w_j$ 求导并令其为 0,得到最优权重 $w_j^*$:
\[w_j^* = -\frac{G_j}{H_j + \lambda}\]将 $w_j^*$ 代回目标函数,得到该树结构下的最小目标值(即结构分数):
\[\text{Obj}^* = -\frac{1}{2} \sum_{j=1}^T \frac{G_j^2}{H_j + \lambda} + \gamma T\]5. 分裂准则 (Greedy Algorithm)
注意一下,会发现前面很多过程是和树本身的结构没有关系的,也就是说这个决策树究竟怎么决策的,无人在意,关注的只是叶子节点$\omega_j$的值。那么树的结构究竟在哪里体现?其实就是$G_j$和$H_j$,树的不同会影响叶子节点中最终包含的是哪个样本,从而影响到$G_j$和$H_j$。因此通过优化树结构来优化$G_j$和$H_j$是另一个优化点。
在实际建立树时,不可能遍历所有树结构。XGBoost 采用贪心算法。对于一个节点,XGBoost会遍历每一个特征,对每一个特征遍历一遍阈值,尝试将其分裂为左子树 $L$ 和右子树 $R$,计算分裂后的 增益(Gain):
\[\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] - \gamma\]- $\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}$:不分裂时的分数。
- $\gamma$:引入新叶子节点的复杂度代价。
然后选取最大的$\text{Gain}$进行分裂,把对应的特征和阈值记录到这个节点上。
以上即为XGBoost的基本思想。



