单棵深度 6 的树训练 0.8625、测试 0.6950,差 0.1675。 15 棵随机树平均只有 0.6537,比单棵还差;但投票之后 0.7625,比单棵高 0.0675。
上一节做减法(筛掉坏特征)。这一节做加法:把多个模型合起来。 这是提升效果最稳、最不容易翻车的一招。
一、单棵树的问题:方差太大
决策树有个毛病:训练数据稍微变一点,长出来的树就完全不同。这叫高方差。 Demo 里单棵深度 6 的树训练 0.8625、测试 0.6950,差 0.1675。 它把训练集切成了很多纯块,换一批数据就不认了。 如果只训一棵树,你拿到的就是这一次抽样下的结果。换个数种子,测试分可能差 0.1。 集成学习的思路:既然单棵树不稳,那就种很多棵,让它们投票。 每棵树错的地方不一样,投票时多数会把对的答案救回来。
二、15 棵树各自更差,合起来更好
每棵树都做两件事:自助抽样(有放回抽 240 个训练样本)和随机选 3 个特征。 结果 15 棵树各自的测试准确率在 0.5925 到 0.7200 之间, 平均才 0.6537——每一棵都比单棵(0.6950)差。 但 15 棵投票之后是 0.7625,比单棵高 0.0675。 这个对比最有意思:单个更弱,合起来更强。 原因是每棵树的错误不相关——它们只看 3 个特征、吃不同的样本,所以错的地方不一样。 投票把不相关的错误平均掉了。 如果 15 棵树完全一样,投票等于没投。 所以「制造差异」是集成的前提,自助抽样和随机特征子集这两步都不能省。
三、Bagging 和 Boosting 是两回事
Bagging(Demo 这种):并行训很多个模型,各看一份自助样本,最后投票或平均。目的是降方差。 代表是随机森林。 Boosting:串行训很多个模型,每个都在修正前一个的错误,最后加权求和。目的是降偏差。 代表是 AdaBoost、GBDT、XGBoost、LightGBM。 两者的适用场景不同:Bagging 适合「模型太复杂、方差大」;Boosting 适合「模型太简单、偏差大」。 用错了没效果。 判断方法还是那两条曲线:训练分和测试分差距大 → 用 Bagging;两个都低 → 用 Boosting。
四、什么时候集成没用
基模型本身已经很稳时,集成提升很小。 比如一个调好的逻辑回归,你 bagging 十个,分数基本不动——因为它方差本来就小,没什么可降的。 基模型之间差异太小时也没用。 比如用同一份数据、同一组特征训十棵树,它们几乎一样,投票等于投一票。 数据量很大、模型已经收敛时,收益也有限。集成主要解决的是小数据下的不稳定。 所以「上集成」之前先问一句:我的模型是方差大还是偏差大? 如果是偏差大,该换更强的模型或者加特征,不是集成。
五、动手 Demo:随机森林雏形:单棵 0.6950,15 棵投票到 0.7625
造 240 个 6 维训练样本、400 个同分布测试样本;先训一棵用全部特征和全部数据的深度 6 决策树当基准; 再做 15 轮自助抽样、每轮随机选 3 个特征种一棵树,记录各自得分,最后按多数票合起来评估。
for _ in range(M):
idx = rg.choice(len(y), len(y), replace=True) # 自助抽样
feats = list(rg.choice(6, 3, replace=False)) # 随机特征子集
t = build_tree(X[idx], y[idx], feats, 0, 6)
votes += tree_predict(t, Xt)
ens = float(((votes / M > 0.5).astype(int) == yt).mean())
实跑输出:
--------------------------------------------------------------------
集成学习:十五棵树投票,比单棵树强多少
--------------------------------------------------------------------
训练集 240 个、6 维、两类中心差 0.75(重叠较多)
测试集 另外造 400 个
基模型 深度 6 的决策树(贪心切分,基尼不纯度)
集成方式 自助抽样 15 次,每次只用随机 3 个特征,再投票
单棵深度 6 的树:训练准确率 **0.8625**,测试 **0.6950**,差 0.1675。
只种一棵(用全部特征) 训练 0.8625,测试 0.6950
15 棵树的测试准确率:
树 训练准确率 测试准确率
---------------------------------
第 1 棵 0.8958 0.6525
第 2 棵 0.8083 0.6500
第 3 棵 0.8583 0.6450
第 4 棵 0.8208 0.6650
第 5 棵 0.8625 0.6225
第 6 棵 0.8500 0.7150
第 7 棵 0.8708 0.6600
第 8 棵 0.8708 0.6400
第 9 棵 0.8750 0.6675
第 10 棵 0.8625 0.5950
第 11 棵 0.8583 0.7200
第 12 棵 0.8750 0.5925
第 13 棵 0.8333 0.6950
第 14 棵 0.8500 0.6675
第 15 棵 0.8500 0.6175
15 棵的平均测试准确率 0.6537
单棵(全部特征、全部数据) 0.6950
投票集成 0.7625
集成比单棵提升 +0.0675
单棵树训练 0.8625、测试 0.6950,差 0.1 以上——典型的过拟合:它把训练集切成了很多纯块,换一批数据就不认了。
投票之后测试到 **0.7625**,比单棵高了 **0.0675**。
为什么有用:每棵树只看随机 3 个特征、吃一份自助样本,所以它们的错误不一样。一个样本被少数树判错时,多数票会把它拉回来。误差就这样被平均掉了。
这就是 Bagging 加随机特征子空间的原理——两件事合起来就是随机森林。
前提是树之间要有差异。如果 15 棵树完全一样,投票等于没投。所以「自助抽样」和「随机特征子集」这两步都不能省,它们是差异的唯一来源。
反过来,基模型本身已经很稳时(比如一个调好的逻辑回归),集成的提升会很小。所以「弱而不稳的模型 + 集成」收益最大,这也是随机森林用深树当基模型的原因。
单棵树训练 0.8625、测试 0.6950,差 0.1675——过拟合很明显。这就是「高方差」的具体样子。 15 棵树平均 0.6537,比单棵还低 0.04。 因为它们各自只看 3 个特征,信息不全,单看每一棵都是弱模型。 投票 0.7625,比单棵高 0.0675。这是整节的核心:弱模型 + 投票 > 强模型。 每棵树的测试分从 0.5925 到 0.7200,跨度 0.13——单棵的方差确实大。 这也解释了为什么必须集成:你没法保证自己抽到的是好的那一棵。
30 秒实验
- 把树的数量从 15 改成 5 和 45,看投票准确率怎么变。
- 把随机特征数从 3 改成 6(每棵都用全部特征),看集成还有没有提升。
- 想一想:为什么 Boosting 适合「两个分数都低」的情况,而 Bagging 不适合?
常见误区
- 「集成一定比单模型好」——基模型已经很稳、或者基模型之间没差异时,提升是零。
- 「树越多越好」——通常到几十棵就饱和,再多只是浪费时间。
- 「Bagging 和 Boosting 可以互换」——一个降方差一个降偏差,用错方向没效果。
自测三问(点开对答案)
1. Bagging 靠什么制造模型之间的差异? —— 自助抽样(每棵吃不同的样本)和随机特征子集(每棵只看部分特征)。 2. 为什么单棵树更弱,合起来反而更强? —— 各棵树的错误不相关,投票把不相关的错误平均掉了。 3. Bagging 和 Boosting 分别在降什么? —— Bagging 降方差,Boosting 降偏差。八种优化方法讲完了。下一节是本章最后一个实战:把多个训练好的模型融起来用。
- ☐ (本节完,下一节 9.3.1 融合模型:提升训练好的模型性能)