梯度提升:把错误一层层补上
约 14 分钟
随机森林是「很多棵树各自独立、最后投票」。梯度提升走的是完全相反的思路:树一棵接一棵地长,每一棵专门修正前面所有树的错误。
这个思路在表格数据上长期占据统治地位——Kaggle 上的结构化数据竞赛,冠军方案几乎清一色是它。
核心思想:拟合残差
用一个极简的例子。预测房价,真实值 100 万:
| 轮次 | 这棵树预测 | 累计预测 | 还差多少(残差) |
|---|---|---|---|
| 第 1 棵 | 80 | 80 | 20 |
| 第 2 棵 | 15 | 95 | 5 |
| 第 3 棵 | 4 | 99 | 1 |
第 2 棵树的训练目标不是 100,而是第 1 棵留下的残差 20。 第 3 棵拟合第 2 棵留下的残差 5。
最终预测 = 所有树的预测相加。 每棵树都很弱(通常只有 3–6 层深),但一层层叠加后极其强大。
为什么叫「梯度」提升
上面用「残差」是为了直观。更一般的表述是:每棵新树拟合的是损失函数关于当前预测的负梯度。
当损失函数是平方误差 时:
负梯度恰好就是残差。 所以「拟合残差」是平方损失下的特例。
换成别的损失函数(分类用的对数损失、回归用的绝对误差),负梯度就不是残差了,但框架完全一样。这就是「梯度」二字的来历——它是在函数空间里做梯度下降。
三个关键超参数
| 参数 | 作用 | 典型值 |
|---|---|---|
learning_rate |
每棵树的贡献打多少折 | 0.01–0.1 |
n_estimators |
树的数量 | 100–2000 |
max_depth |
单棵树深度 | 3–6 |
学习率和树数量是一对:学习率调小,就要用更多树来补偿。小学习率 + 多树,通常比大学习率 + 少树效果好,代价是训练更慢。
注意 max_depth 只有 3–6——和随机森林用深树完全相反。因为提升法要的是「弱学习器」,每棵树只需要捕捉一点点信号,深了反而容易过拟合。
和随机森林的根本区别
| 随机森林 | 梯度提升 | |
|---|---|---|
| 树的关系 | 独立并行 | 串行依赖 |
| 单棵树 | 深、强 | 浅、弱 |
| 主要降低 | 方差 | 偏差 |
| 能否并行 | 树之间可以 | 树之间不能(但单树内部可以) |
| 树多了会过拟合吗 | 不会 | 会,需要早停 |
| 调参难度 | 低 | 高 |
最后两行是实践中最重要的差别。 梯度提升必须用早停(early_stopping_rounds)来决定树的数量,否则一定会过拟合。
三个主流实现
| 库 | 特点 |
|---|---|
| XGBoost | 最早流行,工程优化扎实,生态成熟 |
| LightGBM | 速度快、内存省,用直方图算法和 leaf-wise 生长,大数据首选 |
| CatBoost | 对类别特征处理最好,默认参数就很能打 |
import lightgbm as lgb
model = lgb.LGBMClassifier(learning_rate=0.05, n_estimators=1000, max_depth=5)
model.fit(X_tr, y_tr,
eval_set=[(X_val, y_val)],
callbacks=[lgb.early_stopping(50)]) # 50 轮不提升就停
练习:解释一下——为什么梯度提升的单棵树要浅(3–6 层),而随机森林的单棵树可以很深?(提示:想想各自主要在对抗偏差还是方差)
梯度提升和随机森林在「树的关系」和「主要降低什么」上有什么根本区别?为什么梯度提升的单棵树要浅?
登录 后可看答案