文章目录
-
- [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)的逻辑:
- 从训练集中有放回地抽取 N N N 个样本(Bootstrap 采样)
- 在每个子样本上独立训练一个基模型
- 预测时:分类问题多数投票,回归问题取均值
关键点:为什么有放回抽样能降低方差?
有放回抽样的每个子样本与原始训练集有约 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:
每个样本的预测来自
未在该样本上训练的模型
元特征反映真实泛化能力
元模型学到有效的组合权重
常见的泄露场景:
- 没有 K-fold:基模型直接在全量训练集预测,元特征记忆了训练数据
- 目标编码泄露:时间序列数据按时间分 fold,但 target encoding 混入了未来信息
- 超参调优泄露:用验证集调整了基模型超参,再用同一验证集生成元特征
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 严格性------欢迎点赞收藏。这个系列持续更新机器学习算法的精讲内容,关注专栏不错过后续更新。
有任何不同见解或实际使用中的问题,欢迎在评论区交流。