📑 本页目录(点开跳转)
04A / 先看直觉,再看细节
一棵树不够,
可以一起投票,也可以接着纠错。
随机森林让多棵树并行投票;GBDT 按顺序添加新树,改进当前预测。
先抓住一个具体结果
新树只补一点
某个样本原预测为 3,新树建议加 1,学习率为 0.1,那么更新后是 3.1。新树的输出不是最终答案;一般损失下它学习的是负梯度方向。
GBDT 的加法更新
接下来,按需要选一项
你现在想看什么?
这些入口是选择,不是必须按顺序完成的任务。
可以停在这里
随机森林让多棵树并行投票;GBDT 按顺序添加新树,改进当前预测。
需要更多细节时,继续看完整正文 →原有正文、图解和例子都在下方。按需跳转,不必一次读完。
04a · 集成学习与 GBDT
⏱ 34 分钟 | ⭐ 集成的收益全部来自成员之间的「差异」
🎯 一句话
一棵树很弱(上一章那个 91.8%), 但把几百棵弱树按正确方式组合起来,就是表格数据上至今难以撼动的最强模型 —— XGBoost / LightGBM 的全部秘密就在这。
📍 组合方式分三条完全不同的路:Bagging(并行,降方差)、Boosting(串行,降偏差)、 Stacking(分层)。⭐ 而它们组合的每一棵树,都是上一章那个 CART。
🌳 三、集成学习:三种组合方式
单棵树不稳定又易过拟合。把很多棵组合起来——但怎么组合,分成三条完全不同的路线:
- 怎么组合:同一份数据 → 有放回抽样 3 次 → 分别训出树1 / 树2 / 树3 → 投票 / 平均
- 代表:随机森林
- 怎么组合:树1 → 找出树1错在哪 → 树2 专门补这些错 → 找出还错在哪 → 树3 再补 → …
- 代表:GBDT / XGBoost / LightGBM / CatBoost
- 怎么组合:树 + 线性 + 神经网络 → 各自的预测 → 再训一个模型学「怎么组合它们」
🔗 Kaggle 第 3 章模型融合 讲的就是 ③ 的各种实战技巧。
🌲 四、随机森林:Bagging + 双重随机
💡 核心直觉:集成的收益来自成员之间的「差异」。全都一样的树,平均一百次还是那棵树。
优点:几乎不用调参、不易过拟合、能给特征重要性、可并行 缺点:精度通常略输 Boosting、模型体积大
🚀 五、GBDT:Boosting 的核心思想
这是本章最重要的部分——Kaggle 的主力算法。
它在做什么
💡 人话:
每棵新树不预测答案本身,而是预测「前面所有树加起来还差多少」。 像考试订正错题:第一遍做完,专门针对错的部分再学一遍,反复迭代。
数学上:每棵新树拟合的是损失函数的负梯度(所以叫「梯度」提升)。 用 MSE 损失时,负梯度恰好就是残差——上面那个直觉是严格成立的。
三个关键超参(调参时主要动这三个)
| 参数 | 作用 | 经验值 |
|---|---|---|
n_estimators 树的数量 |
越多拟合越强 | 100–2000,配合早停 |
learning_rate 学习率 ⭐ |
每棵树的贡献打几折 | 0.01–0.1。小 lr + 多树 = 更好但更慢 |
max_depth 树深 |
单棵树复杂度 | 3–8(比随机森林浅得多!) |
🔑 最重要的一条调参直觉:
learning_rate和n_estimators是一对——学习率减半,树的数量大致要翻倍。 先固定 lr=0.1 调其他参数,最后再降 lr、加树数换取最后那点精度。
为什么 GBDT 的树要浅?
信息关系
🚪 这里有一句话故意没讲完
「每棵新树拟合负梯度」——⭐ 负梯度只告诉你往哪个方向走,没告诉你该走多远。 GBDT 用 learning_rate 把步子统一压小,是绕开这个问题而不是解决它。
👉 04b · XGBoost 的推导:从负梯度到闭式解 该去的时候:要面试(「XGBoost 为什么用二阶泰勒展开」是必考)、要自定义损失函数、或者想知道 LightGBM 凭什么快、CatBoost 在防什么。 可以先不去的时候:只想会调参把分刷上去 —— 直接跳 Kaggle 第 1 章,那里有现成模板。 ⚠️ 但三巨头对比表在 04b,而且在那里它是推出来的不是背的。
🔨 动手:亲眼看集成的威力
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import (RandomForestClassifier,
GradientBoostingClassifier, BaggingClassifier)
import numpy as np
X, y = load_breast_cancer(return_X_y=True)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3,
random_state=42, stratify=y)
print(f"{'模型':<22}{'训练集':>9}{'测试集':>9}")
for name, m in [
("单棵决策树", DecisionTreeClassifier(random_state=0)),
("Bagging(100棵)", BaggingClassifier(n_estimators=100, random_state=0)),
("随机森林(100棵)", RandomForestClassifier(n_estimators=100, random_state=0)),
("GBDT(100棵,深度3)", GradientBoostingClassifier(n_estimators=100,
max_depth=3, random_state=0)),
]:
m.fit(X_tr, y_tr)
print(f"{name:<22}{m.score(X_tr,y_tr):>8.1%}{m.score(X_te,y_te):>9.1%}")
print("\nGBDT:学习率与树数量的配合")
print(f"{'树数':<8}{'学习率':<10}{'测试集':>9}")
for n, lr in [(10,0.1),(50,0.1),(200,0.1),(500,0.1),(500,0.01)]:
m = GradientBoostingClassifier(n_estimators=n, learning_rate=lr,
max_depth=3, random_state=0).fit(X_tr, y_tr)
print(f"{n:<8}{lr:<10}{m.score(X_te,y_te):>8.1%}")
print("\n特征重要性 Top5:")
rf = RandomForestClassifier(n_estimators=200, random_state=0).fit(X_tr, y_tr)
names = load_breast_cancer().feature_names
for i in np.argsort(-rf.feature_importances_)[:5]:
print(f" {names[i]:<25} {rf.feature_importances_[i]:.3f}")
实测结果:
① 多棵树,测试集更稳
| 模型 | 训练集 | 测试集 | 读法 |
|---|---|---|---|
| 单棵决策树 | 100.0% | 91.8% | 第 1 章那棵树:把训练集背下来了 |
| Bagging(100 棵) | 100.0% | 94.2% | 比单棵树高 2.4 个百分点 |
| 随机森林(100 棵) | 100.0% | 94.2% | 随机化让多棵树不再长得一模一样 |
| GBDT(100 棵、深度 3) | 100.0% | 94.2% | 后面的树专门修正前面的残差 |
② GBDT:学习率和树数要配套
| 树数 | 学习率 | 测试集 | 读法 |
|---|---|---|---|
| 10 | 0.1 | 93.0% | 树还不够,来不及修完错误 |
| 200 | 0.1 | 94.7% | 继续提升 |
| 500 | 0.1 | 95.3% | 更多树仍然有收益 |
| 500 | 0.01 | 94.2% | 步子太小,500 棵仍然不够 |
③ 这次随机森林最看重的特征
| 特征 | 重要性 |
|---|---|
| worst perimeter | 0.140 |
| worst concave points | 0.132 |
| worst radius | 0.115 |
💡 注意:所有集成模型训练集都是 100%,但测试集比单棵树高 2–3.5%。 训练集 100% 不等于过拟合——关键看测试集有没有跟着掉。
⚠️ 特征重要性的三个坑
树模型能给特征重要性,但别太当真:
| 坑 | 说明 |
|---|---|
| 偏向高基数特征 | 取值越多的特征(如 ID)越容易被选中分裂,重要性虚高 |
| 相关特征分摊 | 两个高度相关的特征会平分重要性,看起来都不重要 |
| 只反映训练集 | 不代表因果,也不代表在新数据上有用 |
⭐ 第一个坑的根就是第二节那件事:信息增益、增益率、基尼全都偏爱取值多的特征(用户 ID 的信息增益能到满分 1.000)。 CART 靠「只能二叉」把病压住了,但取值多 = 候选切分点多 = 被选中的机会多,这个偏向从来没被真正消除。
✅ 更可靠的做法:Permutation Importance(打乱某列看性能掉多少)或 SHAP。
🔗 和站内其他章的关系
| 相关的地方 | 根因在这 |
|---|---|
| Kaggle 第 1、2 章大量用 LightGBM | 就是本章的 GBDT |
| Kaggle 第 3 章模型融合 | Stacking / Blending 的实战版 |
| 推荐算法第 7 章「用 GBDT 看特征重要性」 | 本章的特征重要性(连同它的坑) |
| 推荐算法第 18 章的蒸馏 | Boosting 的"用弱模型逼近强模型"思想有相通处 |
看完这一章,接着往哪走:
| 去哪 | 为什么要去那里 |
|---|---|
| ⭐ 04b · XGBoost 的推导 | 这一章故意没讲完的那半句在那里:负梯度只给方向不给步长,XGBoost 怎么用二阶泰勒展开把「下一棵树该怎么长」变成有闭式解的目标函数;三巨头对比表也在那里,而且是推出来的 |
| 04 · 决策树:怎么切一刀 | ⚠️ 「特征重要性偏向高基数特征」这个坑的根在那一章第二节 —— 三代判据全都偏爱取值多的特征,CART 只是靠「只能二叉」压住 |
| 数学原理 · 08 偏差方差分解 | 本章说 Bagging 降方差、Boosting 降偏差,那两个词的严格定义和分解式在那里 |
| 第 16 章 · 特征工程基础 | 那里的 OOF 目标编码是「怎么用类别特征而不泄漏」的手工做法——树模型碰上高基数类别特征时的标准解 |
| 第 5 章 · 评估与过拟合 | 本章实测里「训练集 100% 但测试集只有 91.8%」到底算不算过拟合——判断标准在那一章 |
✅ 检查点
- Bagging 和 Boosting 的根本区别?各降低什么?
- 随机森林为什么要「随机选特征」?只做 Bootstrap 不够吗?
- GBDT 里每棵新树学的是什么?为什么树要比随机森林浅?
learning_rate和n_estimators是什么关系?调参该先动哪个?- 实测里所有集成模型训练集都是 100%,这算过拟合吗?该看哪个数?
- 特征重要性有哪三个坑?其中「偏向高基数特征」的根在哪一章?
👀 答案
- Bagging 并行、每棵树独立在抽样数据上训练后投票,降方差;Boosting 串行、每棵新树纠正前面所有树的错误,降偏差。
- 不够。 只做 Bootstrap 的话,每棵树都可能先选到同一个最强特征分裂、长得都一样。⭐ 集成的收益全部来自成员之间的差异 —— 全都一样的树,平均一百次还是那棵树。
- 学的是前面所有树的残差(严格说是损失的负梯度)。浅是故意的:每棵树只需纠正一点点,深了就一步跨太大而过拟合,所以 3–8 层(比随机森林浅得多)。
- 是一对:学习率减半,树的数量大致要翻倍。⭐ 先固定
lr=0.1调其他参数,最后再降 lr、加树数换取最后那点精度。 - 不算。训练集 100% 不等于过拟合,要看测试集有没有跟着掉——这里单棵树是 100.0% / 91.8%,三种集成都是 100.0% / 94.2%,测试集反而涨了 2.4 个点。
- 偏向高基数特征、相关特征分摊重要性、只反映训练集不代表因果。用 Permutation Importance 或 SHAP 更可靠。⭐ 第一个坑的根在 04 章第二节:三代判据全都偏爱取值多的特征,CART 只是靠「只能二叉」把它压住,没消除。
🛑 可以停在这里
到这里表格数据的主力你已经会用了:三条组合路线、随机森林的两次随机、GBDT 的三个旋钮, 外加特征重要性那三个坑。⭐ 只想把分刷上去的话,这些已经够了。
⚠️ 什么时候看下一页:你想知道「每棵新树拟合负梯度」后面那半句 —— 负梯度只给方向不给步长,而 XGBoost 怎么把它变成一个有闭式解的问题。
⚡ 走神救援
先记住这几件事
- Bagging 让模型并行学习后汇总;Boosting 让后面的模型继续修正前面的不足。
- GBDT 沿损失的负梯度方向拟合新树;平方损失下可理解为拟合残差。
- 学习率控制每棵新树的贡献,树的数量与复杂度需要一起验证。
- 先用验证集检查收益,再决定是否增加模型;模型更多并不自动更好。
下一节 👉 04b-XGBoost的推导.md