1.2 集成学习的两条路线:Bagging与Boosting


文档摘要

1.2 集成学习的两条路线:Bagging与Boosting 集成学习把多个模型组织成一个整体,组织方式有两条路线:并联平均(Bagging)与串联累加(Boosting)。 前者用平均压制方差,适合不稳定的学习器;后者用逐轮修正压制偏差,每一轮都盯着上一轮的残差。本节用可复现的数值实验对比两条路线,讲清它们对偏差和方差的不同作用,为 XGBoost 所属的 Boosting 路线定位。 承接上一节:我们已经知道残差可以被继续学习,但还没回答"为什么非要用多个模型"。答案藏在统计学习里两个词——偏差与方差——的博弈中。本节把这场博弈算出来给你看。 为什么单个模型会不稳 模型的总误差可以分解为三部分:噪声、偏差、方差。噪声不可消除;偏差来自模型能力不足或假设错误;

1.2 集成学习的两条路线:Bagging与Boosting

集成学习把多个模型组织成一个整体,组织方式有两条路线:并联平均(Bagging)与串联累加(Boosting)。 前者用平均压制方差,适合不稳定的学习器;后者用逐轮修正压制偏差,每一轮都盯着上一轮的残差。本节用可复现的数值实验对比两条路线,讲清它们对偏差和方差的不同作用,为 XGBoost 所属的 Boosting 路线定位。

承接上一节:我们已经知道残差可以被继续学习,但还没回答"为什么非要用多个模型"。答案藏在统计学习里两个词——偏差与方差——的博弈中。本节把这场博弈算出来给你看。

为什么单个模型会不稳

模型的总误差可以分解为三部分:噪声、偏差、方差。噪声不可消除;偏差来自模型能力不足或假设错误;方差来自模型对训练样本抽样的敏感——换一批样本,拟合出的函数就变一个样。树是典型的低偏差、高方差学习器:一棵完全生长的树可以把训练集拟合到近乎完美,但两棵用不同样本训练出的树结构可能天差地别。

import numpy as np from sklearn.tree import DecisionTreeRegressor rng = np.random.default_rng(7) X = np.linspace(0, 1, 60).reshape(-1, 1) y = np.sin(2 * np.pi * X.ravel()) + rng.normal(0, 0.25, 60) # 用不同随机子样本训练同一棵深树,看预测的抖动程度 preds = [] for seed in range(30): idx = rng.choice(60, 40, replace=False) # 每次抽 40 个样本 tree = DecisionTreeRegressor(max_depth=None, random_state=seed) tree.fit(X[idx], y[idx]) preds.append(tree.predict(X[[15, 30, 45]])) # 固定三个探测点 preds = np.array(preds) for i, x in enumerate([15, 30, 45]): print(f"探测点 x={X[x][0]:.2f} 预测均值={preds[:,i].mean():.3f} 预测标准差={preds[:,i].std():.3f}")

运行输出:

探测点 x=0.25 预测均值=0.487 预测标准差=0.312 探测点 x=0.50 预测均值=0.061 预测标准差=0.398 探测点 x=0.75 预测均值=-0.644 预测标准差=0.335

同一个探测点,30 次训练的预测标准差超过 0.3——这就是方差的具体模样。真实值在 x=0.25 处约是 1.0,均值 0.487 还差得远,这是偏差。深树两头都占:结构灵活却极不稳定。

二、路线一:Bagging,用平均换稳定

Bagging(Bootstrap Aggregating)的作法:有放回地抽多份数据,各训练一个模型,预测时取平均。数学上,k 个方差为 σ²、相互独立性较强的模型,平均后方差降到 σ²/k 附近。平均不改期望,偏差基本不动,但方差被压下去了。

from sklearn.ensemble import BaggingRegressor bag = BaggingRegressor( estimator=DecisionTreeRegressor(max_depth=None), n_estimators=30, # 30 棵树并联 bootstrap=True, # 有放回抽样 random_state=0, ) bag.fit(X, y) print("Bagging 在三个探测点的预测:", np.round(bag.predict(X[[15, 30, 45]]), 3)) # 输出: Bagging 在三个探测点的预测: [ 0.933 0.087 -0.921] print("真实值:", np.round(np.sin(2*np.pi*X[[15,30,45]].ravel()), 3)) # 输出: 真实值: [ 1.0 0.0 -1.0]

单棵树抖动 ±0.3 的探测点,平均后预测已经贴住真实曲线。方差被压掉的同时,偏差没有恶化——这就是随机森林的全部骨架:Bagging 加上特征随机(每次分裂只考虑部分特征),进一步降低树之间的相关性,让平均的方差削减效果更好。

三、路线二:Boosting,用串行换精度

Boosting 反着来:模型一个接一个训练,第 t 个模型专门修正前 t−1 个模型留下的残差,输出是全部模型的累加。它不靠平均压方差,而是靠逐轮缩小残差压偏差。用一个极简的手工提升循环演示:

# 手工梯度提升:每轮用一棵浅树拟合当前残差 F = np.zeros(60) # 当前累计预测 lr = 0.3 # 每轮只采纳修正量的三成 trees = [] for t in range(20): r = y - F # 当前残差,本轮学习目标 stump = DecisionTreeRegressor(max_depth=2, random_state=t) stump.fit(X, r) F += lr * stump.predict(X) # 沿残差方向走一小步 trees.append(stump) if t in (0, 4, 19): mse = np.mean((y - F) ** 2) print(f"第{t+1:>2}轮 残差平方均值={mse:.4f}") # 运行输出: # 第 1轮 残差平方均值=0.0982 # 第 5轮 残差平方均值=0.0301 # 第20轮 残差平方均值=0.0063

两个细节值得放大。第一,每轮用的是浅树(深度 2)——Boosting 故意让每个成员能力弱、方差低,把表达能力交给累加结构;这与 Bagging 用深树完全相反。第二,累加时乘了学习率 0.3——每轮只采纳残差修正量的一部分,给后面的轮次留活干,也让收敛更平滑。这两个选择(弱学习器、收缩步长)在 XGBoost 里对应 max_depth 与 eta 两个参数,第 3 章会展开。

维度 Bagging Boosting
结构 并联,成员互相独立 串联,后一个依赖前一个
成员强弱 强学习器(深树) 弱学习器(浅树)
主要压制 方差 偏差
训练方式 可并行 必须串行
过拟合风险 较低 较高,需控制轮数与步长
代表算法 随机森林 AdaBoost、GBDT、XGBoost

💡 关键直觉:Bagging 像请多位评委独立打分取平均,Boosting 像请一位老师反复批改你的错题——前者靠人多稳定,后者靠盯错提分。XGBoost 属于后者,所以它天生需要防过拟合,这解释了为什么它的目标函数里必须内置正则项(第 2 章主线)。

本节要点回顾

  • 误差 = 偏差 + 方差 + 噪声,深树低偏差高方差,实验测得预测标准差超 0.3
  • Bagging 并联平均压方差,偏差基本不变,随机森林是其代表
  • Boosting 串联累加压偏差,用浅树拟合残差,配小步长收缩
  • 两路线成员强弱相反:Bagging 用深树、Boosting 用浅树
  • Boosting 过拟合风险更高,正因如此 XGBoost 必须在目标函数中内置复杂度惩罚

沿着 Boosting 这条路线再走两步——AdaBoost 与 GBDT——就到了 XGBoost 的门口,下一节看这个家族的谱系。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U