🧠 第九章 模型评估和优化
📄 一句话概括

单棵深度 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 秒实验

  1. 把树的数量从 15 改成 5 和 45,看投票准确率怎么变。
  2. 把随机特征数从 3 改成 6(每棵都用全部特征),看集成还有没有提升。
  3. 想一想:为什么 Boosting 适合「两个分数都低」的情况,而 Bagging 不适合?

常见误区

  1. 「集成一定比单模型好」——基模型已经很稳、或者基模型之间没差异时,提升是零。
  2. 「树越多越好」——通常到几十棵就饱和,再多只是浪费时间。
  3. 「Bagging 和 Boosting 可以互换」——一个降方差一个降偏差,用错方向没效果。
自测三问(点开对答案) 1. Bagging 靠什么制造模型之间的差异? —— 自助抽样(每棵吃不同的样本)和随机特征子集(每棵只看部分特征)。 2. 为什么单棵树更弱,合起来反而更强? —— 各棵树的错误不相关,投票把不相关的错误平均掉了。 3. Bagging 和 Boosting 分别在降什么? —— Bagging 降方差,Boosting 降偏差。

八种优化方法讲完了。下一节是本章最后一个实战:把多个训练好的模型融起来用。

相关笔记