从 Bagging 到 Stacking:集成学习学习笔记
博客列表 主页

从 Bagging 到 Stacking:集成学习学习笔记

在上一篇关于表格数据与树模型的文章里,我已经提到过 Random Forest、GBDT、XGBoost、LightGBM 和 CatBoost。但如果只记住“它们都是很多棵树的组合”,其实会漏掉一个更重要的问题:为什么它们都在做集成,训练逻辑却完全不同?

Random Forest 的核心是“并行地制造多样性,然后平均”;GBDT 的核心却是“串行地把前一轮没学好的部分继续学下去”。再往后走,Stacking 甚至不要求基模型是同一种算法,它更像是在问:我能不能再训练一个模型,专门学习‘什么时候该信谁’?

这篇文章就想把这张图补完整。全文按一条很简单的路线展开:

  • Averaging / Voting:最小化的融合原型
  • Bagging:通过重采样获得稳定性
  • Boosting:通过串行纠错持续提升模型
  • Stacking:让元模型学习组合规则
  • DCS / DES:从“全局权重”走向“局部选择”
  • 拒判 / 垃圾箱标签:和集成很像,但不属于标准 Ensemble 的边界技巧

为什么集成学习值得单独讲

如果把集成学习压缩成两个关键词,我会选 多样性偏差-方差

第一,多样性(diversity)是集成有效的前提。多个模型如果犯的是同一类错误,那么把它们平均起来也不会神奇地变好;真正有价值的是:不同模型在不同样本、不同特征子空间、不同归纳偏好上各自有强弱,于是它们的错误能够相互抵消。

第二,很多集成方法都可以放回偏差-方差(bias-variance)框架来理解。直觉上看:

  • Bagging 更像是在想办法降低方差,让模型变得更稳。
  • Boosting 更像是在持续修正偏差,让模型不断逼近更好的函数。
  • Stacking 不直接对应某一个单一方向,而是在已有模型之上再学一层组合关系。

如果只想先记一句话,可以先把三者区别记成下面这样:

  • Bagging:并行训练很多模型,再做平均或投票。
  • Boosting:串行训练很多模型,每一轮都针对前一轮的不足继续学习。
  • Stacking:先训练一批基模型,再训练一个元模型学习如何组合它们。

这也是为什么“集成学习”不只是一个工具箱标签,而是一套理解模型关系的方法。你在树模型文章里看到的 Random Forest 与 GBDT,其实只是这套框架里的两个典型分支。

小结一下:理解 ensemble 的重点,不是背多少算法名字,而是先看它如何制造多样性、再看它试图解决偏差还是方差的问题。一旦这两个坐标定住,后面的方法就容易放回同一张地图里了。

从最简单的融合开始:Averaging 与 Voting

很多时候,集成学习并不是从复杂框架开始的,而是从最朴素的问题开始:如果我已经有了多个模型,能不能直接把它们合起来?

对于回归任务,最自然的做法是平均(Averaging)。如果不同模型的重要性不同,可以使用加权平均:

\[\hat{y}(x) = \sum_{m=1}^{M} w_m \hat{y}_m(x), \qquad \sum_{m=1}^{M} w_m = 1\]

这背后的直觉非常简单:单个模型会波动,但多个模型的均值往往更稳定。实际工作里,除了均值,也有人会在异常值较多时使用中位数聚合,因为它对极端预测更不敏感。

对于分类任务,对应的是投票(Voting)。最常见的有三种形式:

  • 多数投票:谁票数最多就选谁
  • 加权投票:让更可信的模型拥有更高权重
  • 概率平均:直接平均各模型输出的类别概率,再取最大者

这里有一个很容易被忽略的问题:不同分类器的输出不一定天然可比。 有的模型输出的是概率,有的输出的是 margin,有的输出只是一个分数。如果这些量不在同一尺度上,直接加权求和往往没有明确含义。更稳妥的做法,是先把它们统一为可比较的概率或统一定义的 score;在需要时,也可以借助概率校准(例如 Platt scaling、isotonic regression)来改善可比性。

Averaging 和 Voting 看起来“很基础”,但它们非常重要,因为后面很多复杂框架本质上都包含了一个融合步骤。区别只是:有的方法只做最后一步融合,有的方法则连“子模型是怎么生成的”也一并设计好了。

小结一下:Averaging / Voting 是 ensemble 的最小原型。它们告诉我们,哪怕不改变基模型本身,只要组合方式得当,预测就可能更稳定。但如果想持续拉开性能差距,就需要进一步思考:这些模型是怎么被制造出来的?

Bagging:用重采样制造稳定性

Bagging(Bootstrap Aggregating)的核心思想是:既然多样性很重要,那就主动制造多份不同的训练数据。

最经典的做法是 bootstrap 重采样:对原始训练集进行有放回抽样,反复得到多份大小相近但样本构成不同的数据集。然后在这些数据集上分别训练基学习器,最后再把它们做平均或投票。

这套机制之所以有效,是因为它很适合高方差模型。比如决策树如果不加太多限制,往往对训练样本的微小扰动很敏感:样本稍微变一变,树的分裂结构就会明显变化。Bagging 刚好利用了这一点,让每棵树都在略有差异的数据上生长,最后再把这些波动平均掉。

Bootstrap 还顺带给了 Bagging 一个非常实用的副产品:包外样本(out-of-bag, OOB)。一轮 bootstrap 抽样之后,平均来看大约会有 36.8% 的样本没有被抽中,它们就可以拿来做一个近似的验证集,用于粗略评估泛化表现,而不一定非要额外切一块 hold-out 数据。

从 Bagging 到 Random Forest

Random Forest 可以看成 Bagging 在决策树上的一个强化版本。普通 Bagging 已经通过“样本随机”制造差异了,而 Random Forest 又往前走了一步:在每次结点分裂时,不是让树在全部特征里挑最优分裂,而是先随机抽一个特征子集,再从子集中选最优划分。

于是 Random Forest 同时引入了两层多样性:

  • 样本随机:每棵树看到的数据子集不同
  • 特征随机:每个结点可选择的候选特征不同

这也是为什么 Random Forest 往往是一个非常稳的 baseline。它的训练可以并行,调参路径相对清晰,对异常值和特征缩放也通常不太敏感。在很多结构化表格任务里,如果你还没想好要不要直接上 Boosting,Random Forest 往往是很好的第一步。

当然,Bagging 也有它的边界。它更擅长“把不稳定的模型平均稳定”,但如果单个模型本身存在较强系统性偏差,仅靠并行平均通常很难不断突破这个上限。这也正是 Boosting 要接手的地方。

小结一下:Bagging 的关键词是 并行、重采样、降方差。Random Forest 则是在 Bagging 的基础上进一步增加特征层面的随机性,因此成了决策树集成里最经典、也最耐用的一条路线。

Boosting:把前一轮没学好的部分继续学下去

如果说 Bagging 的问题意识是“怎么让模型更稳”,那么 Boosting 的问题意识更像是:怎么让模型一轮一轮地变得更强。

它不再依赖“并行生成很多模型然后平均”,而是让基学习器按顺序串起来。后一轮学习器的任务,不是从零开始重新学一遍,而是重点处理前一轮还没学好的部分。

AdaBoost:从样本重赋权开始

AdaBoost 是 Boosting 家族中最经典的入门模型。它可以写成多个弱学习器的加法组合:

\[H(x) = \sum_{t=1}^{T} \alpha_t h_t(x)\]

这里最关键的不是公式本身,而是两层“加权”思想:

  • 对于被前一轮错分的样本,提高下一轮训练时的关注度
  • 对于表现更好的弱学习器,给它更高的最终组合权重

因此,AdaBoost 的重点不是简单做平均,而是让学习过程带着记忆:前面做错的地方,后面要重点补。

不过,AdaBoost 主要是通过“样本重赋权”来推进学习。这个思路非常优美,但当损失函数更一般、任务更复杂时,我们会希望有一种更统一的观点来描述“下一轮到底该学什么”。这就通向了提升树(boosting tree)和 GBDT。

提升树与 GBDT:从纠错走向函数逼近

在提升树里,我们把最终模型看成很多棵树的加法模型:

\[F_M(x) = \sum_{m=1}^{M} T_m(x)\]

这时每一棵新树的任务,都不是独立完成一次预测,而是在当前模型的基础上再补一块。真正重要的问题变成了:这一轮到底该补什么?

GBDT(Gradient Boosting Decision Tree)给出的答案是:去拟合当前损失函数的负梯度。也就是说,在第 $m$ 轮,我们关心的是

\[r_{im} = -\left[\frac{\partial L(y_i, F(x_i))}{\partial F(x_i)}\right]_{F(x)=F_{m-1}(x)}\]

当损失函数是平方误差时,这个量就退化成大家最熟悉的“残差”;而当损失函数换成对数损失、Huber 损失等更一般的形式时,“拟合负梯度”就成了一个统一而自然的描述方式。

这一步非常关键,因为它把 Boosting 从“样本权重如何变化”提升成了“函数如何一步步逼近目标”的视角。也正因为如此,GBDT 才成为后续 XGBoost、LightGBM、CatBoost 等工程实现的共同母体。上一篇树模型文章里讨论的那些框架,虽然在正则化、分裂策略、类别特征处理和工程优化上各有差异,但它们都仍然站在这条 Boosting 主线上。

所以,理解 GBDT 的价值,并不只是为了会写一个老算法名词,而是为了真正明白:为什么表格建模里那些最常用的强模型,很多都来自“逐步拟合残差/梯度”这条路线。

小结一下:Boosting 的关键词是 串行、纠错、加法模型。AdaBoost 用样本重赋权表达这种思想,GBDT 则进一步把它推广成“每一轮去拟合损失函数的负梯度”。理解到这里,XGBoost / LightGBM / CatBoost 就会清楚很多。

Stacking:让模型自己学习“该信谁”

Bagging 和 Boosting 通常都在生成大量基学习器,而且这些基学习器往往来自同一套训练机制。Stacking(堆叠)则换了一个角度:如果我已经有几类风格差异很大的强模型,能不能再训练一个模型,专门学习它们的组合规则?

这时,基模型不一定是“弱学习器”。它们完全可以是逻辑回归、随机森林、XGBoost、神经网络甚至规则系统。重点不在于基模型弱不弱,而在于它们有没有互补信息。

Stacking 的标准训练流程通常是这样的:

  1. 把训练集划分成 $K$ 折。
  2. 对每个基模型,做 $K$ 折训练,并为每个样本生成折外预测(out-of-fold, OOF)。
  3. 把这些 OOF 预测拼起来,形成新的元特征(meta-features)。
  4. 用元特征训练一个次级学习器,也就是元模型(meta-learner)。
  5. 预测时,先让所有基模型给出输出,再交给元模型产生最终预测。

这里 OOF 是整个 Stacking 最关键的细节。如果你直接用“基模型在训练集上的预测结果”去训练元模型,元模型看到的就是被严重高估的表现,很容易把数据泄漏当成真实能力,从而在验证集或测试集上明显翻车。

为什么元模型通常不宜太复杂

很多人一想到 Stacking,就会本能地觉得“既然都堆到第二层了,元模型当然越强越好”。但实战里常见的经验恰恰相反:元模型通常不需要特别复杂。

原因很简单。元模型面对的输入,不再是原始特征,而是已经被基模型压缩过的一组预测信号。这些信号维度通常不高,而且很容易共线。如果这时再上一个过于复杂的元模型,学到的往往不是稳健的组合规律,而是验证集噪声。

因此,很多工作流里会优先尝试:

  • 分类任务用逻辑回归作为元模型
  • 回归任务用线性回归、岭回归等简单模型
  • 把基模型的概率输出作为元特征,而不是只保留硬标签

Blending 可以看成 Stacking 的一个简化版本:它不做完整的 OOF 生成,而是直接留出一块验证集专门给元模型训练。这样实现更简单、训练更快,但代价是会牺牲部分训练数据,而且对切分方式更敏感。

在数据竞赛里,Stacking 经常是进一步榨取性能上限的有效手段;在生产环境里,它也可能有价值,但要额外考虑训练复杂度、推理延迟、监控难度以及排障成本。因此,Stacking 更像是一种“在已有强模型之上继续做组合学习”的策略,而不是默认第一选择。

小结一下:Stacking 的关键词是 元特征、OOF、二层学习。它和 Bagging / Boosting 的根本区别,不在于“模型数量更多”,而在于它把“如何组合”本身也变成了一个可学习的问题。

动态选择:当“全局权重”不够用

前面的 Averaging、Voting、Bagging、Boosting、Stacking,虽然机制不同,但它们大多有一个共同点:一旦模型训练完成,融合规则通常就是全局固定的。

可现实里经常会出现一种情况:某个模型在一类样本上表现很好,在另一类样本上却很一般。如果我们只给它一个全局权重,就等于默认它在所有局部区域都同样可靠,这并不总是合理。

这就引出了动态分类器选择(Dynamic Classifier Selection, DCS)和动态集成选择(Dynamic Ensemble Selection, DES)的思路。它们不再提前确定一个统一的组合规则,而是在遇到一个新样本时,先去它附近的局部区域里看一看:在这片局部空间里,哪个模型最可信,或者哪几个模型最可信?

从直觉上看,DCS 更像是在做“局部最优模型选择”,而 DES 更像是在做“局部最优小集成”。这类方法把“模型选择”本身做成了一个依赖输入位置的动态过程,因此它们比固定权重的融合更灵活,也更贴近“不同模型擅长不同区域”的原始动机。

但这类方法在实际落地中没有前面几类方法常见,原因也很现实:

  • 预测时往往还要额外做邻域搜索,计算开销更高
  • 局部邻域本身可能不稳定,尤其在高维空间里更明显
  • 训练、调参、部署和解释都更复杂

所以我更倾向于把 DCS / DES 看成一种重要的扩展视角:它提醒我们,ensemble 不一定只能是“一个全局规则”;但在多数工程场景里,固定规则的 Bagging / Boosting / Stacking 仍然更常见、更易管理。

小结一下:DCS / DES 的贡献,在于把问题从“全局怎么加权”推进到“局部应该信谁”。它们很有启发性,但通常不是表格建模工作流里的第一优先级。

边界与误区:拒判 / 垃圾箱标签不等于标准 Ensemble

有些技巧看起来和集成学习很像,但严格说并不属于标准的 ensemble 框架。一个典型例子,就是拒判(abstention)或垃圾箱标签(reject option / garbage class)

它的核心并不是“组合多个模型”,而是允许系统在不确定时先不要做高置信度判断,或者把样本放进一个“暂不决策”的类别。这样做常见于高风险分类任务,因为与其强行给出一个可能错误的标签,不如显式承认“不够确定”。

这类策略有时会和两阶段流程一起出现:第一阶段先做正常分类,第二阶段再根据置信度阈值、异常检测结果或额外规则决定是否拒判。它确实经常被拿来和模型融合一起使用,但它本身更像是一种决策策略,而不是 Bagging / Boosting / Stacking 意义上的集成方法。

顺着这个边界,也可以顺手澄清几个常见误区:

  • 模型越多不一定越好。 如果模型高度相关,只是在重复同一种错误,集成收益会很有限。
  • 多样性不是乱堆模型。 真正有价值的是归纳偏好、训练数据、特征视角或损失函数层面的差异。
  • Stacking 最怕数据泄漏。 没有 OOF 的“堆叠”,很多时候只是把训练集过拟合得更漂亮。
  • 拒判不是白送精度。 它通常是在覆盖率、召回率和误判成本之间重新做权衡。

所以,把这些边界方法放进一篇集成学习文章里是有意义的,但最好始终记住:它们是在补充“如何做最终决策”,而不是在定义 ensemble 的标准主线。

如何选择:Bagging、Boosting、Stacking 与 DCS

如果把前面的内容压缩成一张“方法选择表”,我会这样总结:

方法 训练关系 核心目标 主要优点 主要代价 典型代表
Bagging 并行 降低方差、提升稳定性 稳健、容易并行、适合做强 baseline 对系统性偏差改善有限 Bagging, Random Forest
Boosting 串行 逐轮纠错、提升拟合能力 往往更强、对表格任务非常有效 更依赖调参,也更容易过拟合噪声 AdaBoost, GBDT
Stacking 两层学习 学习模型间的组合规则 能融合异构强模型,常用于冲击更高上限 训练流程复杂,容易发生数据泄漏 Stacking, Blending
DCS / DES 动态选择 针对局部区域决定信谁 融合规则更灵活,更贴近“局部擅长” 计算和部署复杂度高,应用面较窄 DCS, DES

如果你的主要场景是结构化表格建模,我会更倾向于这样选:

  1. 先要一个稳、快、解释上手容易的 baseline:优先考虑 Random Forest 这一类 Bagging 思路。
  2. 想把单模型性能继续往上推:优先理解并使用 GBDT 路线,再根据任务规模和特征类型选择 XGBoost、LightGBM 或 CatBoost。
  3. 已经有几类风格明显不同的强模型,想继续榨取上限:再考虑 Stacking,并认真处理 OOF、延迟和维护复杂度。
  4. 任务明显存在局部异质性,而且你愿意接受更高计算代价:可以把 DCS / DES 当作研究型或特殊场景下的补充路线。

回到文章一开始的问题:为什么 Random Forest 和 GBDT 都是“树的集成”,但行为差异这么大?答案其实已经很清楚了。前者是在做并行平均,后者是在做串行纠错。 一旦你把它们放回 Bagging 与 Boosting 这两条主线,树模型文章里那些具体框架的来龙去脉也就更容易看懂了。

参考资料

  • 周志华,《机器学习》,“集成学习”章节
  • 李航,《统计学习方法》,“提升方法”相关章节
  • Leo Breiman, Bagging Predictors, 1996
  • Leo Breiman, Random Forests, 2001
  • Yoav Freund and Robert E. Schapire, A Decision-Theoretic Generalization of On-Line Learning and an Application to Boosting, 1997
  • Jerome H. Friedman, Greedy Function Approximation: A Gradient Boosting Machine, 2001
  • David H. Wolpert, Stacked Generalization, 1992