集成学习精讲:Bagging/Boosting/Stacking/Blending——从“三个臭皮匠“到算法竞赛王者

文章目录

    • [1. 集成学习的理论基础:为什么多数投票会更好](#1. 集成学习的理论基础:为什么多数投票会更好)
      • [1.1 Condorcet 陪审团定理](#1.1 Condorcet 陪审团定理)
      • [1.2 偏差-方差分解:集成在哪一项上做文章](#1.2 偏差-方差分解:集成在哪一项上做文章)
    • [2. Bagging:通过随机化降低方差](#2. Bagging:通过随机化降低方差)
      • [2.1 核心机制](#2.1 核心机制)
      • [2.2 随机森林:Bagging + 特征随机](#2.2 随机森林:Bagging + 特征随机)
    • [3. Boosting:通过串行纠错降低偏差](#3. Boosting:通过串行纠错降低偏差)
      • [3.1 AdaBoost:关注错误样本](#3.1 AdaBoost:关注错误样本)
      • [3.2 梯度提升:从"关注错误"到"拟合残差"](#3.2 梯度提升:从"关注错误"到"拟合残差")
      • [3.3 XGBoost:二阶优化 + 正则化](#3.3 XGBoost:二阶优化 + 正则化)
    • [4. Stacking:让元模型学习怎么组合](#4. Stacking:让元模型学习怎么组合)
      • [4.1 核心思想](#4.1 核心思想)
      • [4.2 K-fold 严格流程:防止数据泄露](#4.2 K-fold 严格流程:防止数据泄露)
      • [4.3 元模型的选择](#4.3 元模型的选择)
    • [5. Blending vs Stacking:何时选哪个](#5. Blending vs Stacking:何时选哪个)
    • [6. 多样性:集成成功与否的关键](#6. 多样性:集成成功与否的关键)
      • [6.1 多样性的来源](#6.1 多样性的来源)
      • [6.2 Q 统计量:度量两分类器的多样性](#6.2 Q 统计量:度量两分类器的多样性)
    • [7. 集成学习的工程陷阱](#7. 集成学习的工程陷阱)
      • [7.1 Stacking 的常见泄露模式](#7.1 Stacking 的常见泄露模式)
      • [7.2 推理延迟叠加](#7.2 推理延迟叠加)
    • [8. 竞赛 vs 生产的集成策略](#8. 竞赛 vs 生产的集成策略)
      • [8.1 竞赛策略:精度优先](#8.1 竞赛策略:精度优先)
      • [8.2 生产策略:效率优先](#8.2 生产策略:效率优先)
    • [9. 实战:多模型 Stacking 完整流程](#9. 实战:多模型 Stacking 完整流程)
    • 小结

Kaggle 竞赛有一个不成文的规则:单模型几乎不可能获胜。翻开近几年 Kaggle 竞赛的金牌方案,前 10 名几乎都是集成------但不是把所有模型堆在一起那种。

真正有效的集成需要理解三件事:Bagging 降方差、Boosting 降偏差、Stacking 同时降两者。这三个"降"字背后有清晰的数学基础,也决定了集成什么时候有效、什么时候在浪费计算资源。

本文从 Condorcet 陪审团定理出发,把集成学习的理论逻辑讲清楚,再深入 Stacking 的 K-fold 严格性(这是竞赛选手与业余选手的分水岭),最后给出生产环境的合理集成策略。


1. 集成学习的理论基础:为什么多数投票会更好

1.1 Condorcet 陪审团定理

1785 年,法国数学家孔多塞证明了一个关于陪审团投票的定理:

如果每个陪审员独立投票,且每人做出正确判断的概率 p > 0.5 p > 0.5 p>0.5,那么陪审员数量越多,多数票判断正确的概率越接近 1。

这是集成学习理论基础的数学原型。形式化表达: n n n 个独立分类器,每个正确率为 p p p,多数投票正确率为:

P ( 多数正确 ) = ∑ k = ⌈ n / 2 ⌉ n ( n k ) p k ( 1 − p ) n − k P(\text{多数正确}) = \sum_{k=\lceil n/2 \rceil}^{n} \binom{n}{k} p^k (1-p)^{n-k} P(多数正确)=k=⌈n/2⌉∑n(kn)pk(1−p)n−k

当 n = 5 , p = 0.7 n=5, p=0.7 n=5,p=0.7:单分类器正确率 70%,集成后约 83.6%。当 n = 11 , p = 0.7 n=11, p=0.7 n=11,p=0.7:约 90.9%。

但这个定理有一个隐含的关键假设 :各分类器必须独立做出判断。如果陪审员彼此讨论后统一意见,多数票就没有意义了------一个人的错误变成了所有人的错误。

这正是集成学习最容易被忽视的核心:多样性比单模型精度更重要。五个相关性极高的模型集成,效果可能不如两个差异大的模型。

1.2 偏差-方差分解:集成在哪一项上做文章

任何模型的泛化误差可以分解为:

总误差 = 偏差 2 + 方差 + 不可约误差 \text{总误差} = \text{偏差}^2 + \text{方差} + \text{不可约误差} 总误差=偏差2+方差+不可约误差

  • 偏差(Bias):模型的系统性误差,反映模型复杂度不够(欠拟合)
  • 方差(Variance):模型对训练数据变化的敏感程度,反映过拟合
  • 不可约误差:数据本身的噪声,无法通过算法消除

集成方法各自攻击不同的项:
#mermaid-svg-QEOgQX6eXRnQmnac{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QEOgQX6eXRnQmnac .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QEOgQX6eXRnQmnac .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QEOgQX6eXRnQmnac .error-icon{fill:#552222;}#mermaid-svg-QEOgQX6eXRnQmnac .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QEOgQX6eXRnQmnac .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QEOgQX6eXRnQmnac .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QEOgQX6eXRnQmnac .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QEOgQX6eXRnQmnac .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QEOgQX6eXRnQmnac .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QEOgQX6eXRnQmnac .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QEOgQX6eXRnQmnac .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QEOgQX6eXRnQmnac .marker.cross{stroke:#333333;}#mermaid-svg-QEOgQX6eXRnQmnac svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QEOgQX6eXRnQmnac p{margin:0;}#mermaid-svg-QEOgQX6eXRnQmnac .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QEOgQX6eXRnQmnac .cluster-label text{fill:#333;}#mermaid-svg-QEOgQX6eXRnQmnac .cluster-label span{color:#333;}#mermaid-svg-QEOgQX6eXRnQmnac .cluster-label span p{background-color:transparent;}#mermaid-svg-QEOgQX6eXRnQmnac .label text,#mermaid-svg-QEOgQX6eXRnQmnac span{fill:#333;color:#333;}#mermaid-svg-QEOgQX6eXRnQmnac .node rect,#mermaid-svg-QEOgQX6eXRnQmnac .node circle,#mermaid-svg-QEOgQX6eXRnQmnac .node ellipse,#mermaid-svg-QEOgQX6eXRnQmnac .node polygon,#mermaid-svg-QEOgQX6eXRnQmnac .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QEOgQX6eXRnQmnac .rough-node .label text,#mermaid-svg-QEOgQX6eXRnQmnac .node .label text,#mermaid-svg-QEOgQX6eXRnQmnac .image-shape .label,#mermaid-svg-QEOgQX6eXRnQmnac .icon-shape .label{text-anchor:middle;}#mermaid-svg-QEOgQX6eXRnQmnac .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QEOgQX6eXRnQmnac .rough-node .label,#mermaid-svg-QEOgQX6eXRnQmnac .node .label,#mermaid-svg-QEOgQX6eXRnQmnac .image-shape .label,#mermaid-svg-QEOgQX6eXRnQmnac .icon-shape .label{text-align:center;}#mermaid-svg-QEOgQX6eXRnQmnac .node.clickable{cursor:pointer;}#mermaid-svg-QEOgQX6eXRnQmnac .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QEOgQX6eXRnQmnac .arrowheadPath{fill:#333333;}#mermaid-svg-QEOgQX6eXRnQmnac .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QEOgQX6eXRnQmnac .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QEOgQX6eXRnQmnac .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QEOgQX6eXRnQmnac .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QEOgQX6eXRnQmnac .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QEOgQX6eXRnQmnac .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QEOgQX6eXRnQmnac .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QEOgQX6eXRnQmnac .cluster text{fill:#333;}#mermaid-svg-QEOgQX6eXRnQmnac .cluster span{color:#333;}#mermaid-svg-QEOgQX6eXRnQmnac div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QEOgQX6eXRnQmnac .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QEOgQX6eXRnQmnac rect.text{fill:none;stroke-width:0;}#mermaid-svg-QEOgQX6eXRnQmnac .icon-shape,#mermaid-svg-QEOgQX6eXRnQmnac .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QEOgQX6eXRnQmnac .icon-shape p,#mermaid-svg-QEOgQX6eXRnQmnac .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QEOgQX6eXRnQmnac .icon-shape .label rect,#mermaid-svg-QEOgQX6eXRnQmnac .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QEOgQX6eXRnQmnac .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QEOgQX6eXRnQmnac .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QEOgQX6eXRnQmnac :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 总误差 = 偏差² + 方差 + 噪声
Bagging

降低方差
Boosting

降低偏差
Stacking

降低偏差和方差
多个高方差弱模型

(深度树)→ 平均后方差降低
多个高偏差弱模型

(浅树)→ 串行纠正偏差
异质基模型

→ 元模型学最优组合

理解这个框架,就能知道什么时候用哪种集成:数据已经能被模型拟合(偏差低)但方差大 → Bagging;模型本身不够强(偏差高)→ Boosting;想进一步压榨精度 → Stacking。


2. Bagging:通过随机化降低方差

2.1 核心机制

Bagging(Bootstrap Aggregating)的逻辑:

  1. 从训练集中有放回地抽取 N N N 个样本(Bootstrap 采样)
  2. 在每个子样本上独立训练一个基模型
  3. 预测时:分类问题多数投票,回归问题取均值

关键点:为什么有放回抽样能降低方差?

有放回抽样的每个子样本与原始训练集有约 63.2% 的不同样本(平均每个样本被抽到的概率 1 − ( 1 − 1 / N ) N ≈ 1 − e − 1 ≈ 0.632 1-(1-1/N)^N \approx 1-e^{-1} \approx 0.632 1−(1−1/N)N≈1−e−1≈0.632)。其余约 36.8% 的样本自然形成袋外(Out-of-Bag, OOB)样本------可以不用交叉验证,直接用 OOB 样本做无偏估计。

多个在不同子样本上训练的模型,预测错误具有一定的独立性------对训练集特定区域的过拟合会被其他模型平滑掉。

python 复制代码
from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier

# Bagging 的基础用法
bagging = BaggingClassifier(
    base_estimator=DecisionTreeClassifier(max_depth=None),  # 深树,高方差
    n_estimators=100,
    max_samples=1.0,      # 有放回抽取 100% 样本
    max_features=1.0,     # 使用所有特征
    bootstrap=True,       # 有放回抽样(False = 无放回 = Pasting)
    oob_score=True,       # 启用 OOB 估计(相当于免费的验证集)
    n_jobs=-1,
    random_state=42
)
bagging.fit(X_train, y_train)
print(f"OOB Score: {bagging.oob_score_:.4f}")  # 袋外误差,无需额外验证集

2.2 随机森林:Bagging + 特征随机

随机森林(Random Forest)是 Bagging 的特例,增加了特征随机性:每次分裂时只从随机选出的 p \sqrt{p} p (分类)或 p / 3 p/3 p/3(回归)个特征中寻找最优分裂点。

这额外一层随机性的数学依据来自方差分解:

设 n n n 棵树,每棵方差为 σ 2 \sigma^2 σ2,两两相关系数为 ρ \rho ρ,集成后方差为:

Var ( RF ) = ρ σ 2 + 1 − ρ n σ 2 \text{Var}(\text{RF}) = \rho\sigma^2 + \frac{1-\rho}{n}\sigma^2 Var(RF)=ρσ2+n1−ρσ2

当 n → ∞ n \to \infty n→∞ 时,方差趋向 ρ σ 2 \rho\sigma^2 ρσ2。降低树间相关系数 ρ \rho ρ 才是特征随机性的真正目的------不只是"引入随机性",而是具体地解耦各棵树对相同强特征的依赖。


3. Boosting:通过串行纠错降低偏差

3.1 AdaBoost:关注错误样本

AdaBoost(Adaptive Boosting)的核心思路:让后续的弱学习器专注于前面学习器犯错的样本
#mermaid-svg-oFuDg98TCWmHpTMv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oFuDg98TCWmHpTMv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oFuDg98TCWmHpTMv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oFuDg98TCWmHpTMv .error-icon{fill:#552222;}#mermaid-svg-oFuDg98TCWmHpTMv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oFuDg98TCWmHpTMv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oFuDg98TCWmHpTMv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oFuDg98TCWmHpTMv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oFuDg98TCWmHpTMv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oFuDg98TCWmHpTMv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oFuDg98TCWmHpTMv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oFuDg98TCWmHpTMv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oFuDg98TCWmHpTMv .marker.cross{stroke:#333333;}#mermaid-svg-oFuDg98TCWmHpTMv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oFuDg98TCWmHpTMv p{margin:0;}#mermaid-svg-oFuDg98TCWmHpTMv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oFuDg98TCWmHpTMv .cluster-label text{fill:#333;}#mermaid-svg-oFuDg98TCWmHpTMv .cluster-label span{color:#333;}#mermaid-svg-oFuDg98TCWmHpTMv .cluster-label span p{background-color:transparent;}#mermaid-svg-oFuDg98TCWmHpTMv .label text,#mermaid-svg-oFuDg98TCWmHpTMv span{fill:#333;color:#333;}#mermaid-svg-oFuDg98TCWmHpTMv .node rect,#mermaid-svg-oFuDg98TCWmHpTMv .node circle,#mermaid-svg-oFuDg98TCWmHpTMv .node ellipse,#mermaid-svg-oFuDg98TCWmHpTMv .node polygon,#mermaid-svg-oFuDg98TCWmHpTMv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oFuDg98TCWmHpTMv .rough-node .label text,#mermaid-svg-oFuDg98TCWmHpTMv .node .label text,#mermaid-svg-oFuDg98TCWmHpTMv .image-shape .label,#mermaid-svg-oFuDg98TCWmHpTMv .icon-shape .label{text-anchor:middle;}#mermaid-svg-oFuDg98TCWmHpTMv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-oFuDg98TCWmHpTMv .rough-node .label,#mermaid-svg-oFuDg98TCWmHpTMv .node .label,#mermaid-svg-oFuDg98TCWmHpTMv .image-shape .label,#mermaid-svg-oFuDg98TCWmHpTMv .icon-shape .label{text-align:center;}#mermaid-svg-oFuDg98TCWmHpTMv .node.clickable{cursor:pointer;}#mermaid-svg-oFuDg98TCWmHpTMv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-oFuDg98TCWmHpTMv .arrowheadPath{fill:#333333;}#mermaid-svg-oFuDg98TCWmHpTMv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oFuDg98TCWmHpTMv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oFuDg98TCWmHpTMv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oFuDg98TCWmHpTMv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-oFuDg98TCWmHpTMv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oFuDg98TCWmHpTMv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-oFuDg98TCWmHpTMv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oFuDg98TCWmHpTMv .cluster text{fill:#333;}#mermaid-svg-oFuDg98TCWmHpTMv .cluster span{color:#333;}#mermaid-svg-oFuDg98TCWmHpTMv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oFuDg98TCWmHpTMv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-oFuDg98TCWmHpTMv rect.text{fill:none;stroke-width:0;}#mermaid-svg-oFuDg98TCWmHpTMv .icon-shape,#mermaid-svg-oFuDg98TCWmHpTMv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oFuDg98TCWmHpTMv .icon-shape p,#mermaid-svg-oFuDg98TCWmHpTMv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-oFuDg98TCWmHpTMv .icon-shape .label rect,#mermaid-svg-oFuDg98TCWmHpTMv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oFuDg98TCWmHpTMv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-oFuDg98TCWmHpTMv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-oFuDg98TCWmHpTMv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否

初始化:

每个样本权重 = 1/N
训练弱学习器 h₁

(加权训练集)
计算加权误差

ε₁
计算该学习器权重

α₁ = 0.5 ln((1-ε₁)/ε₁)
更新样本权重

错误样本权重×增大

正确样本权重×减小
训练下一个

弱学习器 h₂
达到 T 轮?
最终预测:

H(x) = sign(Σ αₜhₜ(x))

每个弱学习器的权重 α t \alpha_t αt 由其加权误差决定:误差越小,权重越大;误差接近 0.5(与随机猜测相当),权重接近 0。

3.2 梯度提升:从"关注错误"到"拟合残差"

梯度提升(Gradient Boosting)将集成学习推广为函数空间中的梯度下降:

第 t t t 步的任务不是调整样本权重,而是训练一个新树来拟合当前集成模型的残差(损失函数的负梯度):

r t i = − ∂ L ( y i , F ( x i ) ) ∂ F ( x i ) F = F t − 1 r_{ti} = -\left\\frac{\\partial L(y_i, F(x_i))}{\\partial F(x_i)}\\right{F=F{t-1}} rti=−∂F(xi)∂L(yi,F(xi))F=Ft−1

复制代码
迭代过程:
F₀(x) = 初始化(常数)
For t = 1 to T:
    计算负梯度 r_t(每个样本的"残差")
    用树 h_t(x) 拟合这些残差
    更新:F_t(x) = F_{t-1}(x) + η · h_t(x)
最终:F_T(x) = F₀(x) + η·h₁(x) + η·h₂(x) + ... + η·hT(x)

学习率 η \eta η(learning_rate)控制每一步的步长------小步迭代配合多棵树(类似梯度下降中的学习率)。

3.3 XGBoost:二阶优化 + 正则化

XGBoost 是梯度提升的工程强化版,关键改进:

二阶泰勒展开:普通梯度提升只用一阶导数(梯度),XGBoost 同时利用二阶导数(Hessian),使每一步的搜索方向更精确:

L ~ ( t ) ≈ ∑ i g i f t ( x i ) + 1 2 h i f t 2 ( x i ) + Ω ( f t ) \tilde{L}^{(t)} \approx \sum_i \left g_i f_t(x_i) + \\frac{1}{2} h_i f_t\^2(x_i) \\right + \Omega(f_t) L~(t)≈i∑gift(xi)+21hift2(xi)+Ω(ft)

其中 g i = ∂ y ^ ( t − 1 ) l ( y i , y ^ ( t − 1 ) ) g_i = \partial_{\hat{y}^{(t-1)}} l(y_i, \hat{y}^{(t-1)}) gi=∂y^(t−1)l(yi,y^(t−1)), h i = ∂ y ^ ( t − 1 ) 2 l ( y i , y ^ ( t − 1 ) ) h_i = \partial^2_{\hat{y}^{(t-1)}} l(y_i, \hat{y}^{(t-1)}) hi=∂y^(t−1)2l(yi,y^(t−1))。

正则化项

Ω ( f ) = γ T + 1 2 λ ∑ j = 1 T w j 2 \Omega(f) = \gamma T + \frac{1}{2}\lambda\sum_{j=1}^T w_j^2 Ω(f)=γT+21λj=1∑Twj2

T T T 是叶节点数量, γ \gamma γ 控制树的复杂度, λ \lambda λ 对叶节点权重做 L2 正则------比普通 GBDT 更不容易过拟合。

python 复制代码
import xgboost as xgb
from sklearn.model_selection import cross_val_score

model = xgb.XGBClassifier(
    n_estimators=500,
    learning_rate=0.05,     # 小学习率 + 多轮迭代(经典组合)
    max_depth=5,
    subsample=0.8,          # 行采样(类 Bagging,增加多样性)
    colsample_bytree=0.8,   # 列采样(类随机森林)
    gamma=0.1,              # 分裂所需最小损失减少量
    reg_lambda=1.0,         # L2 正则化
    use_label_encoder=False,
    eval_metric='logloss',
    early_stopping_rounds=50,  # 早停,防止过拟合
    random_state=42
)

# 带早停的训练
model.fit(
    X_train, y_train,
    eval_set=[(X_val, y_val)],
    verbose=100
)

4. Stacking:让元模型学习怎么组合

4.1 核心思想

Stacking 的逻辑比 Bagging 和 Boosting 更高一个抽象层次:

  • Level 1(基模型层):多个异质基模型在训练数据上分别产生预测
  • Level 2(元模型层):一个元模型学习"如何最优地组合 Level 1 的预测"

如果直接用训练集训练基模型,再用训练集的预测作为元模型的输入,元模型会看到过拟合的预测------因为基模型在自己的训练数据上预测总是太好。

解决方案:K-fold 交叉预测生成元特征

4.2 K-fold 严格流程:防止数据泄露

#mermaid-svg-z1AtSH2qGoxu2zfb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-z1AtSH2qGoxu2zfb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-z1AtSH2qGoxu2zfb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-z1AtSH2qGoxu2zfb .error-icon{fill:#552222;}#mermaid-svg-z1AtSH2qGoxu2zfb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-z1AtSH2qGoxu2zfb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-z1AtSH2qGoxu2zfb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-z1AtSH2qGoxu2zfb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-z1AtSH2qGoxu2zfb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-z1AtSH2qGoxu2zfb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-z1AtSH2qGoxu2zfb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-z1AtSH2qGoxu2zfb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-z1AtSH2qGoxu2zfb .marker.cross{stroke:#333333;}#mermaid-svg-z1AtSH2qGoxu2zfb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-z1AtSH2qGoxu2zfb p{margin:0;}#mermaid-svg-z1AtSH2qGoxu2zfb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-z1AtSH2qGoxu2zfb .cluster-label text{fill:#333;}#mermaid-svg-z1AtSH2qGoxu2zfb .cluster-label span{color:#333;}#mermaid-svg-z1AtSH2qGoxu2zfb .cluster-label span p{background-color:transparent;}#mermaid-svg-z1AtSH2qGoxu2zfb .label text,#mermaid-svg-z1AtSH2qGoxu2zfb span{fill:#333;color:#333;}#mermaid-svg-z1AtSH2qGoxu2zfb .node rect,#mermaid-svg-z1AtSH2qGoxu2zfb .node circle,#mermaid-svg-z1AtSH2qGoxu2zfb .node ellipse,#mermaid-svg-z1AtSH2qGoxu2zfb .node polygon,#mermaid-svg-z1AtSH2qGoxu2zfb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-z1AtSH2qGoxu2zfb .rough-node .label text,#mermaid-svg-z1AtSH2qGoxu2zfb .node .label text,#mermaid-svg-z1AtSH2qGoxu2zfb .image-shape .label,#mermaid-svg-z1AtSH2qGoxu2zfb .icon-shape .label{text-anchor:middle;}#mermaid-svg-z1AtSH2qGoxu2zfb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-z1AtSH2qGoxu2zfb .rough-node .label,#mermaid-svg-z1AtSH2qGoxu2zfb .node .label,#mermaid-svg-z1AtSH2qGoxu2zfb .image-shape .label,#mermaid-svg-z1AtSH2qGoxu2zfb .icon-shape .label{text-align:center;}#mermaid-svg-z1AtSH2qGoxu2zfb .node.clickable{cursor:pointer;}#mermaid-svg-z1AtSH2qGoxu2zfb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-z1AtSH2qGoxu2zfb .arrowheadPath{fill:#333333;}#mermaid-svg-z1AtSH2qGoxu2zfb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-z1AtSH2qGoxu2zfb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-z1AtSH2qGoxu2zfb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-z1AtSH2qGoxu2zfb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-z1AtSH2qGoxu2zfb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-z1AtSH2qGoxu2zfb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-z1AtSH2qGoxu2zfb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-z1AtSH2qGoxu2zfb .cluster text{fill:#333;}#mermaid-svg-z1AtSH2qGoxu2zfb .cluster span{color:#333;}#mermaid-svg-z1AtSH2qGoxu2zfb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-z1AtSH2qGoxu2zfb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-z1AtSH2qGoxu2zfb rect.text{fill:none;stroke-width:0;}#mermaid-svg-z1AtSH2qGoxu2zfb .icon-shape,#mermaid-svg-z1AtSH2qGoxu2zfb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-z1AtSH2qGoxu2zfb .icon-shape p,#mermaid-svg-z1AtSH2qGoxu2zfb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-z1AtSH2qGoxu2zfb .icon-shape .label rect,#mermaid-svg-z1AtSH2qGoxu2zfb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-z1AtSH2qGoxu2zfb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-z1AtSH2qGoxu2zfb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-z1AtSH2qGoxu2zfb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 训练集 (N 个样本)
5-fold 划分
Fold 1~4 训练基模型

在 Fold 5 上预测
Fold 1~3,5 训练基模型

在 Fold 4 上预测
... 依次轮转 ...
拼接 5 折预测结果

得到 N 个样本的元特征

(该样本从未出现在训练该模型的折中)
测试集预测:

对 5 个模型预测取平均
元特征 + 原始标签

→ 训练元模型

这一流程的关键:每个样本的元特征来自没有见过它的基模型,保证了元特征的"真实泛化性"。

python 复制代码
from sklearn.model_selection import KFold, cross_val_predict
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.svm import SVC
import numpy as np

# Level 1 基模型
base_models = [
    ('lr', LogisticRegression(C=1.0, max_iter=1000)),
    ('rf', RandomForestClassifier(n_estimators=100, random_state=42)),
    ('gbm', GradientBoostingClassifier(n_estimators=100, random_state=42)),
    ('svm', SVC(kernel='rbf', probability=True, random_state=42)),
]

# 生成元特征(K-fold 交叉预测)
def get_meta_features(X_train, y_train, X_test, base_models, n_folds=5):
    kf = KFold(n_splits=n_folds, shuffle=True, random_state=42)
    
    train_meta = np.zeros((X_train.shape[0], len(base_models)))
    test_meta = np.zeros((X_test.shape[0], len(base_models)))
    
    for i, (name, model) in enumerate(base_models):
        # 训练集:K-fold 交叉预测(防泄露)
        train_meta[:, i] = cross_val_predict(
            model, X_train, y_train,
            cv=kf, method='predict_proba'
        )[:, 1]  # 取正类概率
        
        # 测试集:在完整训练集上训练后预测
        model.fit(X_train, y_train)
        test_meta[:, i] = model.predict_proba(X_test)[:, 1]
        
        print(f"Base model '{name}' done.")
    
    return train_meta, test_meta

train_meta, test_meta = get_meta_features(X_train, y_train, X_test, base_models)

# Level 2 元模型
meta_model = LogisticRegression(C=0.1)  # 通常选简单模型,防止过拟合
meta_model.fit(train_meta, y_train)

# 最终预测
stacking_pred = meta_model.predict(test_meta)
stacking_proba = meta_model.predict_proba(test_meta)[:, 1]

4.3 元模型的选择

元模型的选择有几个原则:

  • 选简单模型(通常 LR 或 Ridge):基模型已经做了大部分学习工作,元模型只需要学组合权重
  • 避免复杂元模型:XGBoost 作为元模型容易过拟合元特征
  • 元特征维度 = 基模型数量:样本量 >> 维度,LR 足够
python 复制代码
# 好的元模型选择
from sklearn.linear_model import LogisticRegression, RidgeClassifier

meta_lr = LogisticRegression(C=0.1)       # 适合分类
meta_ridge = RidgeClassifier(alpha=1.0)   # 适合回归

# 不推荐的元模型(过于复杂)
# meta_xgb = XGBClassifier(n_estimators=100)  # 容易过拟合元特征

5. Blending vs Stacking:何时选哪个

Blending 是 Stacking 的简化版,用固定的 holdout 验证集替代 K-fold:
#mermaid-svg-QB8RUcCdsPrRpnPG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QB8RUcCdsPrRpnPG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QB8RUcCdsPrRpnPG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QB8RUcCdsPrRpnPG .error-icon{fill:#552222;}#mermaid-svg-QB8RUcCdsPrRpnPG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QB8RUcCdsPrRpnPG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QB8RUcCdsPrRpnPG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QB8RUcCdsPrRpnPG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QB8RUcCdsPrRpnPG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QB8RUcCdsPrRpnPG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QB8RUcCdsPrRpnPG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QB8RUcCdsPrRpnPG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QB8RUcCdsPrRpnPG .marker.cross{stroke:#333333;}#mermaid-svg-QB8RUcCdsPrRpnPG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QB8RUcCdsPrRpnPG p{margin:0;}#mermaid-svg-QB8RUcCdsPrRpnPG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QB8RUcCdsPrRpnPG .cluster-label text{fill:#333;}#mermaid-svg-QB8RUcCdsPrRpnPG .cluster-label span{color:#333;}#mermaid-svg-QB8RUcCdsPrRpnPG .cluster-label span p{background-color:transparent;}#mermaid-svg-QB8RUcCdsPrRpnPG .label text,#mermaid-svg-QB8RUcCdsPrRpnPG span{fill:#333;color:#333;}#mermaid-svg-QB8RUcCdsPrRpnPG .node rect,#mermaid-svg-QB8RUcCdsPrRpnPG .node circle,#mermaid-svg-QB8RUcCdsPrRpnPG .node ellipse,#mermaid-svg-QB8RUcCdsPrRpnPG .node polygon,#mermaid-svg-QB8RUcCdsPrRpnPG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QB8RUcCdsPrRpnPG .rough-node .label text,#mermaid-svg-QB8RUcCdsPrRpnPG .node .label text,#mermaid-svg-QB8RUcCdsPrRpnPG .image-shape .label,#mermaid-svg-QB8RUcCdsPrRpnPG .icon-shape .label{text-anchor:middle;}#mermaid-svg-QB8RUcCdsPrRpnPG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QB8RUcCdsPrRpnPG .rough-node .label,#mermaid-svg-QB8RUcCdsPrRpnPG .node .label,#mermaid-svg-QB8RUcCdsPrRpnPG .image-shape .label,#mermaid-svg-QB8RUcCdsPrRpnPG .icon-shape .label{text-align:center;}#mermaid-svg-QB8RUcCdsPrRpnPG .node.clickable{cursor:pointer;}#mermaid-svg-QB8RUcCdsPrRpnPG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QB8RUcCdsPrRpnPG .arrowheadPath{fill:#333333;}#mermaid-svg-QB8RUcCdsPrRpnPG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QB8RUcCdsPrRpnPG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QB8RUcCdsPrRpnPG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QB8RUcCdsPrRpnPG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QB8RUcCdsPrRpnPG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QB8RUcCdsPrRpnPG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QB8RUcCdsPrRpnPG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QB8RUcCdsPrRpnPG .cluster text{fill:#333;}#mermaid-svg-QB8RUcCdsPrRpnPG .cluster span{color:#333;}#mermaid-svg-QB8RUcCdsPrRpnPG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QB8RUcCdsPrRpnPG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QB8RUcCdsPrRpnPG rect.text{fill:none;stroke-width:0;}#mermaid-svg-QB8RUcCdsPrRpnPG .icon-shape,#mermaid-svg-QB8RUcCdsPrRpnPG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QB8RUcCdsPrRpnPG .icon-shape p,#mermaid-svg-QB8RUcCdsPrRpnPG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QB8RUcCdsPrRpnPG .icon-shape .label rect,#mermaid-svg-QB8RUcCdsPrRpnPG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QB8RUcCdsPrRpnPG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QB8RUcCdsPrRpnPG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QB8RUcCdsPrRpnPG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Blending
80% 训练集

训练基模型
20% Holdout

生成元特征
训练元模型
简单快速

元特征质量依赖切割比例
Stacking
完整训练集

K-fold 交叉预测

→ 元特征
数据利用充分

稳健性高

计算代价大

维度 Stacking Blending
数据利用率 高(所有数据都产生元特征) 低(holdout 集不参与训练)
防泄露严格性 强(每个样本的预测来自未见过它的模型) 取决于 holdout 是否有泄露
实现复杂度
适用场景 竞赛(精度优先) 生产(快速迭代)
元特征稳定性 稳定(K-fold 平均) 依赖随机种子和切割比例

实际建议

  • Kaggle 竞赛 → Stacking(K-fold,5 折或 10 折)
  • 生产环境快速验证 → Blending(简单,迭代快)
  • 线上推理有延迟约束 → 考虑模型蒸馏代替集成

6. 多样性:集成成功与否的关键

6.1 多样性的来源

多个基模型如果犯同样的错误,集成没有任何收益。多样性来源于:

  • 算法多样性:LR + RF + XGBoost + SVM,不同归纳偏置
  • 特征子集多样性:不同特征工程或特征选择方案
  • 数据采样多样性:不同的训练/验证集切分
  • 超参多样性:同一算法的不同超参配置

6.2 Q 统计量:度量两分类器的多样性

Q i , j = N 11 N 00 − N 01 N 10 N 11 N 00 + N 01 N 10 Q_{i,j} = \frac{N^{11} N^{00} - N^{01} N^{10}}{N^{11} N^{00} + N^{01} N^{10}} Qi,j=N11N00+N01N10N11N00−N01N10

其中 N a b N^{ab} Nab 表示分类器 i i i 预测结果为 a a a、分类器 j j j 预测结果为 b b b 的样本数。

  • Q i , j = 0 Q_{i,j} = 0 Qi,j=0:两分类器独立
  • Q i , j → 1 Q_{i,j} \to 1 Qi,j→1:两分类器错误高度相关(多样性差)
  • Q i , j → − 1 Q_{i,j} \to -1 Qi,j→−1:两分类器倾向于在不同样本上犯错(理想状态)
python 复制代码
import numpy as np

def q_statistic(pred1, pred2, y_true):
    """计算两个分类器之间的 Q 统计量"""
    n11 = np.sum((pred1 == y_true) & (pred2 == y_true))   # 两个都对
    n00 = np.sum((pred1 != y_true) & (pred2 != y_true))   # 两个都错
    n10 = np.sum((pred1 == y_true) & (pred2 != y_true))   # 1对2错
    n01 = np.sum((pred1 != y_true) & (pred2 == y_true))   # 1错2对

    numerator = n11 * n00 - n01 * n10
    denominator = n11 * n00 + n01 * n10

    return numerator / denominator if denominator != 0 else 0

# 实际使用:评估集成前的多样性
pred_rf = rf_model.predict(X_val)
pred_xgb = xgb_model.predict(X_val)
pred_svm = svm_model.predict(X_val)

q_rf_xgb = q_statistic(pred_rf, pred_xgb, y_val)
q_rf_svm = q_statistic(pred_rf, pred_svm, y_val)
q_xgb_svm = q_statistic(pred_xgb, pred_svm, y_val)

print(f"Q(RF, XGB) = {q_rf_xgb:.4f}")   # 越小 → 多样性越好
print(f"Q(RF, SVM) = {q_rf_svm:.4f}")
print(f"Q(XGB, SVM) = {q_xgb_svm:.4f}")

实际经验:RF 和 XGBoost 的 Q 统计量往往较高(都是树模型,错误模式相似),RF 和 LR 的 Q 统计量通常更低(不同归纳偏置)。这也解释了为什么"树模型 + 线性模型 + SVM 的 Stacking"通常比"三个树模型的 Stacking"更有效。


7. 集成学习的工程陷阱

7.1 Stacking 的常见泄露模式

#mermaid-svg-XCYhIum2OiWPLmFq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XCYhIum2OiWPLmFq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XCYhIum2OiWPLmFq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XCYhIum2OiWPLmFq .error-icon{fill:#552222;}#mermaid-svg-XCYhIum2OiWPLmFq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XCYhIum2OiWPLmFq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XCYhIum2OiWPLmFq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XCYhIum2OiWPLmFq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XCYhIum2OiWPLmFq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XCYhIum2OiWPLmFq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XCYhIum2OiWPLmFq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XCYhIum2OiWPLmFq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XCYhIum2OiWPLmFq .marker.cross{stroke:#333333;}#mermaid-svg-XCYhIum2OiWPLmFq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XCYhIum2OiWPLmFq p{margin:0;}#mermaid-svg-XCYhIum2OiWPLmFq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XCYhIum2OiWPLmFq .cluster-label text{fill:#333;}#mermaid-svg-XCYhIum2OiWPLmFq .cluster-label span{color:#333;}#mermaid-svg-XCYhIum2OiWPLmFq .cluster-label span p{background-color:transparent;}#mermaid-svg-XCYhIum2OiWPLmFq .label text,#mermaid-svg-XCYhIum2OiWPLmFq span{fill:#333;color:#333;}#mermaid-svg-XCYhIum2OiWPLmFq .node rect,#mermaid-svg-XCYhIum2OiWPLmFq .node circle,#mermaid-svg-XCYhIum2OiWPLmFq .node ellipse,#mermaid-svg-XCYhIum2OiWPLmFq .node polygon,#mermaid-svg-XCYhIum2OiWPLmFq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XCYhIum2OiWPLmFq .rough-node .label text,#mermaid-svg-XCYhIum2OiWPLmFq .node .label text,#mermaid-svg-XCYhIum2OiWPLmFq .image-shape .label,#mermaid-svg-XCYhIum2OiWPLmFq .icon-shape .label{text-anchor:middle;}#mermaid-svg-XCYhIum2OiWPLmFq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XCYhIum2OiWPLmFq .rough-node .label,#mermaid-svg-XCYhIum2OiWPLmFq .node .label,#mermaid-svg-XCYhIum2OiWPLmFq .image-shape .label,#mermaid-svg-XCYhIum2OiWPLmFq .icon-shape .label{text-align:center;}#mermaid-svg-XCYhIum2OiWPLmFq .node.clickable{cursor:pointer;}#mermaid-svg-XCYhIum2OiWPLmFq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XCYhIum2OiWPLmFq .arrowheadPath{fill:#333333;}#mermaid-svg-XCYhIum2OiWPLmFq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XCYhIum2OiWPLmFq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XCYhIum2OiWPLmFq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XCYhIum2OiWPLmFq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XCYhIum2OiWPLmFq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XCYhIum2OiWPLmFq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XCYhIum2OiWPLmFq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XCYhIum2OiWPLmFq .cluster text{fill:#333;}#mermaid-svg-XCYhIum2OiWPLmFq .cluster span{color:#333;}#mermaid-svg-XCYhIum2OiWPLmFq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XCYhIum2OiWPLmFq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XCYhIum2OiWPLmFq rect.text{fill:none;stroke-width:0;}#mermaid-svg-XCYhIum2OiWPLmFq .icon-shape,#mermaid-svg-XCYhIum2OiWPLmFq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XCYhIum2OiWPLmFq .icon-shape p,#mermaid-svg-XCYhIum2OiWPLmFq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XCYhIum2OiWPLmFq .icon-shape .label rect,#mermaid-svg-XCYhIum2OiWPLmFq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XCYhIum2OiWPLmFq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XCYhIum2OiWPLmFq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XCYhIum2OiWPLmFq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 错误做法
用全量训练集

训练基模型
用同一训练集

生成元特征
元特征已知训练答案

元模型学到的是过拟合的特征

而非真实泛化规律
正确做法
K-fold:

每个样本的预测来自

未在该样本上训练的模型
元特征反映真实泛化能力

元模型学到有效的组合权重

常见的泄露场景:

  1. 没有 K-fold:基模型直接在全量训练集预测,元特征记忆了训练数据
  2. 目标编码泄露:时间序列数据按时间分 fold,但 target encoding 混入了未来信息
  3. 超参调优泄露:用验证集调整了基模型超参,再用同一验证集生成元特征

7.2 推理延迟叠加

集成 N N N 个模型,推理时间约为单模型的 N N N 倍(串行推理)或接近单模型(并行推理)。

生产环境的应对策略:

方案 延迟 实现复杂度 适用场景
并行推理 约 1x(最慢单模型) 中等 批量预测
模型蒸馏 1x(单轻量模型) 实时预测,延迟敏感
只集成 Top-2 模型 约 2x 精度/速度折中
特征加权平均 1x(预计算权重) 简单加权集成

模型蒸馏(Model Distillation):用 Stacking 集成模型的"软标签"(概率输出)训练一个轻量学生模型,学生模型学到的是集成的"知识"而非原始数据,通常比直接在原始数据上训练同等大小的模型精度更高。


8. 竞赛 vs 生产的集成策略

8.1 竞赛策略:精度优先

Kaggle 竞赛的集成套路:
#mermaid-svg-Ztt5oPCAMvd5ZiAj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Ztt5oPCAMvd5ZiAj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .error-icon{fill:#552222;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .marker.cross{stroke:#333333;}#mermaid-svg-Ztt5oPCAMvd5ZiAj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Ztt5oPCAMvd5ZiAj p{margin:0;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .cluster-label text{fill:#333;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .cluster-label span{color:#333;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .cluster-label span p{background-color:transparent;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .label text,#mermaid-svg-Ztt5oPCAMvd5ZiAj span{fill:#333;color:#333;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .node rect,#mermaid-svg-Ztt5oPCAMvd5ZiAj .node circle,#mermaid-svg-Ztt5oPCAMvd5ZiAj .node ellipse,#mermaid-svg-Ztt5oPCAMvd5ZiAj .node polygon,#mermaid-svg-Ztt5oPCAMvd5ZiAj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .rough-node .label text,#mermaid-svg-Ztt5oPCAMvd5ZiAj .node .label text,#mermaid-svg-Ztt5oPCAMvd5ZiAj .image-shape .label,#mermaid-svg-Ztt5oPCAMvd5ZiAj .icon-shape .label{text-anchor:middle;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .rough-node .label,#mermaid-svg-Ztt5oPCAMvd5ZiAj .node .label,#mermaid-svg-Ztt5oPCAMvd5ZiAj .image-shape .label,#mermaid-svg-Ztt5oPCAMvd5ZiAj .icon-shape .label{text-align:center;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .node.clickable{cursor:pointer;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .arrowheadPath{fill:#333333;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Ztt5oPCAMvd5ZiAj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ztt5oPCAMvd5ZiAj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Ztt5oPCAMvd5ZiAj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .cluster text{fill:#333;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .cluster span{color:#333;}#mermaid-svg-Ztt5oPCAMvd5ZiAj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Ztt5oPCAMvd5ZiAj rect.text{fill:none;stroke-width:0;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .icon-shape,#mermaid-svg-Ztt5oPCAMvd5ZiAj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .icon-shape p,#mermaid-svg-Ztt5oPCAMvd5ZiAj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .icon-shape .label rect,#mermaid-svg-Ztt5oPCAMvd5ZiAj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ztt5oPCAMvd5ZiAj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Ztt5oPCAMvd5ZiAj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Ztt5oPCAMvd5ZiAj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 单模型调优

XGBoost + LightGBM + CatBoost
第一层 Stacking

树模型 + 线性模型 + NN
第二层 Stacking

元模型组合第一层输出
Seed Ensemble

不同随机种子多次训练后平均
提交集成

与队友方案的简单平均

竞赛中的细节技巧:

  • Seed Ensemble :同一模型用不同 random_state 训练 5~10 次,结果平均,可稳定提升 0.1%~0.3%
  • 异质模型组合:树模型(XGBoost/LightGBM)+ 线性模型(LR/Ridge)+ 神经网络,多样性最大
  • 特征工程多版本:不同特征工程方案训练不同基模型

8.2 生产策略:效率优先

#mermaid-svg-Mjk0cHdKhsVEwLud{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Mjk0cHdKhsVEwLud .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Mjk0cHdKhsVEwLud .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Mjk0cHdKhsVEwLud .error-icon{fill:#552222;}#mermaid-svg-Mjk0cHdKhsVEwLud .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Mjk0cHdKhsVEwLud .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Mjk0cHdKhsVEwLud .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Mjk0cHdKhsVEwLud .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Mjk0cHdKhsVEwLud .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Mjk0cHdKhsVEwLud .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Mjk0cHdKhsVEwLud .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Mjk0cHdKhsVEwLud .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Mjk0cHdKhsVEwLud .marker.cross{stroke:#333333;}#mermaid-svg-Mjk0cHdKhsVEwLud svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Mjk0cHdKhsVEwLud p{margin:0;}#mermaid-svg-Mjk0cHdKhsVEwLud .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Mjk0cHdKhsVEwLud .cluster-label text{fill:#333;}#mermaid-svg-Mjk0cHdKhsVEwLud .cluster-label span{color:#333;}#mermaid-svg-Mjk0cHdKhsVEwLud .cluster-label span p{background-color:transparent;}#mermaid-svg-Mjk0cHdKhsVEwLud .label text,#mermaid-svg-Mjk0cHdKhsVEwLud span{fill:#333;color:#333;}#mermaid-svg-Mjk0cHdKhsVEwLud .node rect,#mermaid-svg-Mjk0cHdKhsVEwLud .node circle,#mermaid-svg-Mjk0cHdKhsVEwLud .node ellipse,#mermaid-svg-Mjk0cHdKhsVEwLud .node polygon,#mermaid-svg-Mjk0cHdKhsVEwLud .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Mjk0cHdKhsVEwLud .rough-node .label text,#mermaid-svg-Mjk0cHdKhsVEwLud .node .label text,#mermaid-svg-Mjk0cHdKhsVEwLud .image-shape .label,#mermaid-svg-Mjk0cHdKhsVEwLud .icon-shape .label{text-anchor:middle;}#mermaid-svg-Mjk0cHdKhsVEwLud .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Mjk0cHdKhsVEwLud .rough-node .label,#mermaid-svg-Mjk0cHdKhsVEwLud .node .label,#mermaid-svg-Mjk0cHdKhsVEwLud .image-shape .label,#mermaid-svg-Mjk0cHdKhsVEwLud .icon-shape .label{text-align:center;}#mermaid-svg-Mjk0cHdKhsVEwLud .node.clickable{cursor:pointer;}#mermaid-svg-Mjk0cHdKhsVEwLud .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Mjk0cHdKhsVEwLud .arrowheadPath{fill:#333333;}#mermaid-svg-Mjk0cHdKhsVEwLud .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Mjk0cHdKhsVEwLud .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Mjk0cHdKhsVEwLud .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Mjk0cHdKhsVEwLud .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Mjk0cHdKhsVEwLud .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Mjk0cHdKhsVEwLud .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Mjk0cHdKhsVEwLud .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Mjk0cHdKhsVEwLud .cluster text{fill:#333;}#mermaid-svg-Mjk0cHdKhsVEwLud .cluster span{color:#333;}#mermaid-svg-Mjk0cHdKhsVEwLud div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Mjk0cHdKhsVEwLud .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Mjk0cHdKhsVEwLud rect.text{fill:none;stroke-width:0;}#mermaid-svg-Mjk0cHdKhsVEwLud .icon-shape,#mermaid-svg-Mjk0cHdKhsVEwLud .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Mjk0cHdKhsVEwLud .icon-shape p,#mermaid-svg-Mjk0cHdKhsVEwLud .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Mjk0cHdKhsVEwLud .icon-shape .label rect,#mermaid-svg-Mjk0cHdKhsVEwLud .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Mjk0cHdKhsVEwLud .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Mjk0cHdKhsVEwLud .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Mjk0cHdKhsVEwLud :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

是(实时推理)
否(批量预测)
精度要求?
高精度且

资源充足?
2~3 个模型

Blending(加权平均)

并行推理
延迟敏感?
单个强模型

XGBoost / LightGBM
小规模 Stacking

RF + XGBoost → LR 元模型
权重通过验证集优化

不做多层 Stacking
定期重训练

监控模型漂移

生产环境的务实建议

  • 从单个 XGBoost/LightGBM 出发,把基线做好
  • 有余力时尝试 Blending:RF + XGBoost 加权平均,权重在验证集上优化
  • 真正需要极致精度时才做 Stacking
  • 监控推理延迟,超过 SLA 时优先考虑蒸馏而非缩减集成规模

9. 实战:多模型 Stacking 完整流程

python 复制代码
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, KFold, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.svm import SVC
from sklearn.metrics import roc_auc_score, classification_report
import xgboost as xgb

# 生成样本数据
X, y = make_classification(
    n_samples=3000, n_features=20, n_informative=15,
    n_redundant=3, random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

# ============ Level 1:基模型定义 ============
base_models = {
    'lr': LogisticRegression(C=1.0, max_iter=1000, random_state=42),
    'rf': RandomForestClassifier(n_estimators=200, max_depth=6, random_state=42),
    'gbm': GradientBoostingClassifier(n_estimators=200, learning_rate=0.05,
                                       max_depth=4, random_state=42),
    'xgb': xgb.XGBClassifier(n_estimators=200, learning_rate=0.05,
                               max_depth=4, use_label_encoder=False,
                               eval_metric='logloss', random_state=42),
}

# ============ Level 1:K-fold 生成元特征 ============
kf = KFold(n_splits=5, shuffle=True, random_state=42)
n_train, n_test = X_train.shape[0], X_test.shape[0]
n_models = len(base_models)

train_meta = np.zeros((n_train, n_models))
test_meta = np.zeros((n_test, n_models))

for i, (model_name, model) in enumerate(base_models.items()):
    test_fold_preds = np.zeros((n_test, kf.n_splits))
    
    for fold_idx, (train_idx, val_idx) in enumerate(kf.split(X_train, y_train)):
        X_fold_train, X_fold_val = X_train[train_idx], X_train[val_idx]
        y_fold_train = y_train[train_idx]
        
        model.fit(X_fold_train, y_fold_train)
        
        # 验证集预测 → 元特征
        train_meta[val_idx, i] = model.predict_proba(X_fold_val)[:, 1]
        # 测试集预测(5 折平均)
        test_fold_preds[:, fold_idx] = model.predict_proba(X_test)[:, 1]
    
    test_meta[:, i] = test_fold_preds.mean(axis=1)
    
    # 单模型基线
    model.fit(X_train, y_train)
    single_auc = roc_auc_score(y_test, model.predict_proba(X_test)[:, 1])
    print(f"{model_name}: AUC = {single_auc:.4f}")

# ============ Level 2:元模型 ============
meta_model = LogisticRegression(C=0.1, random_state=42)
meta_model.fit(train_meta, y_train)

stacking_pred_proba = meta_model.predict_proba(test_meta)[:, 1]
stacking_auc = roc_auc_score(y_test, stacking_pred_proba)
print(f"\nStacking: AUC = {stacking_auc:.4f}")

# 查看元模型权重(LR 系数 = 各基模型的重要性)
for name, coef in zip(base_models.keys(), meta_model.coef_[0]):
    print(f"  {name}: weight = {coef:.4f}")

典型输出(make_classification 3000 样本):

复制代码
lr:  AUC = 0.8823
rf:  AUC = 0.9201
gbm: AUC = 0.9356
xgb: AUC = 0.9384

Stacking: AUC = 0.9451  ← 比最好的单模型高约 0.7%
  lr:  weight =  0.2341  (LR 提供了多样性,贡献正向)
  rf:  weight =  0.8912
  gbm: weight =  1.1203
  xgb: weight =  1.3456

集成的收益来自模型多样性(LR 和树模型的归纳偏置完全不同),而非简单叠加。如果去掉 LR,只用三个树模型做 Stacking,AUC 通常只提升 0.1%~0.2%------这正是多样性的价值。


小结

集成学习的全部逻辑围绕一个核心:多个犯不同错误的模型,比一个犯所有错误的模型更可靠

三种主流集成的本质差异:

  • Bagging:并行 + 降方差。训练多个独立模型,通过平均消除高方差模型的随机误差。随机森林是最成功的 Bagging 变体,特征随机性进一步降低了树间相关系数。

  • Boosting:串行 + 降偏差。每一步针对前一步的错误做补偿,把弱学习器串行提升成强学习器。XGBoost/LightGBM 通过二阶优化和工程加速把这个思路做到了极致。

  • Stacking:异质组合。让元模型学习如何最优地组合基模型的输出,收益来自基模型间的多样性。K-fold 严格生成元特征是防止数据泄露的关键。

竞赛和生产的集成策略有本质差异:竞赛追求极致精度(多层 Stacking + Seed Ensemble),生产需要权衡精度、延迟和可维护性(通常止步于 2~3 个模型的 Blending,复杂集成通过蒸馏压缩为单模型)。

前文在树模型精讲中介绍了 XGBoost 和 LightGBM 的内部机制,理解那篇的 GOSS/EFB 设计,可以更清楚地看到 Boosting 框架的工程演进。SVM 精讲中涉及的核方法作为集成的一个组件,在多样性构建上也有独特价值。


如果这篇文章帮助厘清了集成学习的内在逻辑------为什么有效、哪种方法用于哪种场景、Stacking 的 K-fold 严格性------欢迎点赞收藏。这个系列持续更新机器学习算法的精讲内容,关注专栏不错过后续更新。

有任何不同见解或实际使用中的问题,欢迎在评论区交流。