Stacked Generalization 堆叠泛化

目录

  1. [引言:为什么需要 Stacking?](#引言:为什么需要 Stacking?)
  2. [Stacking 的核心概念](#Stacking 的核心概念)
  3. 算法原理与数学形式化
  4. [完整的 Stacking 流程](#完整的 Stacking 流程)
  5. 关键设计决策
  6. 变体与演进
  7. 常见问题
  8. 代码示例

引言:为什么需要 Stacking?

在机器学习竞赛(如 Kaggle)和工业实践中,集成学习(Ensemble Learning) 已成为提升模型性能的标准手段。传统的集成方法如 Bagging (随机森林)和 Boosting(XGBoost、LightGBM)通过组合同质基学习器来降低方差或偏差。

然而,当面对异质模型(如神经网络、梯度提升树、支持向量机的组合)时,简单的投票(Voting)或平均(Averaging)往往无法充分挖掘各模型的互补性。Stacking(Stacked Generalization,堆叠泛化) 应运而生,它通过引入元学习器(Meta-Learner) 来学习如何最优地组合基学习器的预测,实现了从"简单平均"到"智能加权"的跃迁。

核心洞见 : Stacking 的本质是将模型预测作为特征,构建更高层次的特征表示,再由元学习器进行最终决策。


Stacking 的核心概念

2.1 定义

Stacking 是一种两层(或多层)的集成学习架构,由 Wolpert 于 1992 年在论文 "Stacked Generalization" 中首次提出。其核心思想是:

  • 第一层(Level-0): 训练多个 diverse 的基学习器(Base Learners)
  • 第二层(Level-1): 使用基学习器的输出作为输入特征,训练一个元学习器(Meta-Learner)进行最终预测

2.2 与相关概念的区分

方法 组合方式 学习过程 异质性支持
Voting/Averaging 固定权重(等权或手工设定) 无学习 支持
Weighted Averaging 优化权重(如通过验证集性能) 浅层优化 支持
Stacking 元学习器动态学习组合策略 深层学习 支持
Blending 使用 hold-out 验证集训练元学习器 单层学习 支持
Boosting 序列训练,关注错误样本 自适应重加权 通常同质

关键区别 : Stacking 通过学习来发现基学习器之间的非线性交互,而非预设组合规则。


算法原理与数学形式化

3.1 符号定义

设训练集为 D = { ( x i , y i ) } i = 1 N \mathcal{D} = \{(\mathbf{x}i, y_i)\}{i=1}^N D={(xi,yi)}i=1N,其中 x i ∈ R d \mathbf{x}_i \in \mathbb{R}^d xi∈Rd, y i ∈ Y y_i \in \mathcal{Y} yi∈Y。

  • 基学习器集合: M = { M 1 , M 2 , ... , M K } \mathcal{M} = \{M_1, M_2, \dots, M_K\} M={M1,M2,...,MK}
  • 基学习器 M k M_k Mk 的预测: f k ( x ) ∈ R C f_k(\mathbf{x}) \in \mathbb{R}^{C} fk(x)∈RC( C C C 为类别数,回归时 C = 1 C=1 C=1)
  • 元学习器: M m e t a M_{meta} Mmeta

3.2 训练阶段

Step 1: 生成元特征(Meta-Features)

为避免信息泄露(Data Leakage),必须使用交叉验证生成元特征:

对于每个基学习器 M k M_k Mk,进行 T T T-折交叉验证:

  • 将训练集划分为 T T T 折: D = ⋃ t = 1 T D t v a l \mathcal{D} = \bigcup_{t=1}^T \mathcal{D}_t^{val} D=⋃t=1TDtval
  • 对第 t t t 折,使用 D ∖ D t v a l \mathcal{D} \setminus \mathcal{D}_t^{val} D∖Dtval 训练 M k ( t ) M_k^{(t)} Mk(t)
  • 对 D t v a l \mathcal{D}_t^{val} Dtval 中的样本 x i \mathbf{x}_i xi,生成预测 f k ( t ) ( x i ) f_k^{(t)}(\mathbf{x}_i) fk(t)(xi)

最终,样本 x i \mathbf{x}_i xi 的元特征向量为:
z i = [ f 1 ( x i ) , f 2 ( x i ) , ... , f K ( x i ) ] ⊤ ∈ R K ⋅ C \mathbf{z}_i = [f_1(\mathbf{x}_i), f_2(\mathbf{x}_i), \dots, f_K(\mathbf{x}_i)]^\top \in \mathbb{R}^{K \cdot C} zi=[f1(xi),f2(xi),...,fK(xi)]⊤∈RK⋅C

Step 2: 训练元学习器

构建元训练集 D m e t a = { ( z i , y i ) } i = 1 N \mathcal{D}{meta} = \{(\mathbf{z}i, y_i)\}{i=1}^N Dmeta={(zi,yi)}i=1N,训练:
M m e t a : R K ⋅ C → Y M
{meta}: \mathbb{R}^{K \cdot C} \to \mathcal{Y} Mmeta:RK⋅C→Y

3.3 预测阶段

对于新样本 x \mathbf{x} x:

  1. 所有基学习器在完整训练集 上重新训练,得到 { M k ∗ } k = 1 K \{M_k^*\}_{k=1}^K {Mk∗}k=1K
  2. 生成元特征: z = [ M 1 ∗ ( x ) , ... , M K ∗ ( x ) ] ⊤ \mathbf{z} = [M_1^*(\mathbf{x}), \dots, M_K^*(\mathbf{x})]^\top z=[M1∗(x),...,MK∗(x)]⊤
  3. 元学习器预测: y ^ = M m e t a ∗ ( z ) \hat{y} = M_{meta}^*(\mathbf{z}) y^=Mmeta∗(z)

3.4 数学优化视角

Stacking 可视为最小化泛化误差的优化问题:

min ⁡ M m e t a E ( x , y ) ∼ P [ L ( y , M m e t a ( f 1 ( x ) , ... , f K ( x ) ) ) ] \min_{M_{meta}} \mathbb{E}{(\mathbf{x},y) \sim \mathcal{P}} \left[ \mathcal{L}\left(y, M{meta}(f_1(\mathbf{x}), \dots, f_K(\mathbf{x}))\right) \right] MmetaminE(x,y)∼P[L(y,Mmeta(f1(x),...,fK(x)))]

其中 L \mathcal{L} L 为损失函数。元学习器学习的是条件期望 的估计:
M m e t a ∗ ( z ) ≈ E [ Y ∣ Z = z ] M_{meta}^*(\mathbf{z}) \approx \mathbb{E}[Y | Z=\mathbf{z}] Mmeta∗(z)≈E[Y∣Z=z]


完整的 Stacking 流程

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    Stacking 架构示意图                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Level-0 (Base Learners)          Level-1 (Meta-Learner)   │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐                      │
│  │ Model 1 │  │ Model 2 │  │ Model K │        ┌─────────┐   │
│  │ (RF)    │  │ (LGB)   │  │ (NN)    │───────▶│  LR/    │   │
│  └────┬────┘  └────┬────┘  └────┬────┘        │  Ridge  │   │
│       │            │            │             └────┬────┘   │
│       └────────────┴────────────┘                  │        │
│                    │                               │        │
│                    ▼                               ▼        │
│            ┌─────────────┐                  ┌─────────┐     │
│            │  Meta-Features │               │  Final  │     │
│            │   (Z-matrix)   │──────────────▶│  Output │     │
│            └─────────────┘                  └─────────┘     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

4.1 算法流程

训练阶段:

python 复制代码
# 伪代码表示
def train_stacking(X_train, y_train, base_models, meta_model, n_folds=5):
    # 1. 初始化元特征矩阵
    n_samples = len(X_train)
    n_models = len(base_models)
    Z = np.zeros((n_samples, n_models))  # 元特征矩阵
    
    # 2. 交叉验证生成元特征
    kfold = KFold(n_splits=n_folds, shuffle=True)
    
    for i, model in enumerate(base_models):
        for train_idx, val_idx in kfold.split(X_train):
            # 划分数据
            X_tr, X_val = X_train[train_idx], X_train[val_idx]
            y_tr = y_train[train_idx]
            
            # 训练基学习器并预测验证集
            model.fit(X_tr, y_tr)
            Z[val_idx, i] = model.predict(X_val)
    
    # 3. 在完整数据上重新训练基学习器(用于后续预测)
    fitted_base_models = []
    for model in base_models:
        model_copy = clone(model)
        model_copy.fit(X_train, y_train)
        fitted_base_models.append(model_copy)
    
    # 4. 训练元学习器
    meta_model.fit(Z, y_train)
    
    return fitted_base_models, meta_model

预测阶段:

python 复制代码
def predict_stacking(X_new, fitted_base_models, meta_model):
    # 1. 生成元特征
    Z_new = np.column_stack([
        model.predict(X_new) for model in fitted_base_models
    ])
    
    # 2. 元学习器预测
    return meta_model.predict(Z_new)

关键设计决策

5.1 基学习器的选择:多样性与准确性的权衡

多样性(Diversity) 是 Stacking 成功的关键。理想情况下,基学习器应:

  • 算法不同: 树模型、线性模型、神经网络、KNN 等
  • 数据视角不同: 不同特征子集、不同样本权重
  • 超参数不同: 同一算法的不同配置

常用组合:

  • 经典组合: Random Forest + Gradient Boosting + SVM + Neural Network
  • 竞赛组合: XGBoost + LightGBM + CatBoost + Neural Network
  • 深度学习组合: ResNet + EfficientNet + Vision Transformer

5.2 元学习器的选择

元学习器 适用场景 优点 缺点
逻辑回归/线性回归 分类/回归基准 简单、可解释、不易过拟合 只能学习线性组合
Ridge/Lasso 多重共线性严重 正则化防止过拟合 仍是线性模型
梯度提升树 需要非线性交互 学习复杂模式 容易过拟合
神经网络 大规模数据 表达能力强 需要大量数据、调参复杂
随机森林 鲁棒性要求高 抗噪声 可解释性较差

推荐实践 : 从简单的 Ridge 回归 (回归任务)或 逻辑回归(分类任务)开始,逐步尝试更复杂的元学习器。

5.3 交叉验证策略

必须避免: 使用训练集预测训练集自身(In-sample prediction),这会导致严重的信息泄露和过拟合。

推荐策略:

  • K-Fold CV : 标准选择, K = 5 K=5 K=5 或 10 10 10
  • Stratified K-Fold: 分类任务中保持类别比例
  • Time Series Split: 时序数据避免未来信息泄露

5.4 特征增强策略

除了基学习器的预测概率/值,元特征还可包括:

  • 原始特征 : 将 x \mathbf{x} x 与 z \mathbf{z} z 拼接,形成 ( x , z ) (\mathbf{x}, \mathbf{z}) (x,z)
  • 统计特征: 基学习器预测的标准差、最大值、最小值
  • 高阶交互: 基学习器预测的乘积、比值

变体与演进

6.1 Blending

Blending 是 Stacking 的简化版本,使用固定的 hold-out 验证集生成元特征,而非交叉验证。

优点 : 实现简单、计算成本低
缺点: 元学习器训练数据较少、验证集划分敏感、信息利用不充分

适用: 快速原型验证或数据量极大的场景。

6.2 Multi-Level Stacking

将 Stacking 扩展到多层:

复制代码
Level-0: 基学习器 (RF, GBDT, NN)
    ↓
Level-1: 元学习器1 (RF, GBDT)  ←  可多个
    ↓
Level-2: 元学习器2 (LR)  ←  最终输出

风险: 随着层数增加,过拟合风险急剧上升,可解释性下降。

6.3 Feature-Weighted Linear Stacking (FWLS)

Netflix 竞赛中提出的变体,元学习器为:
y ^ = ∑ k = 1 K w k ( x ) ⋅ f k ( x ) \hat{y} = \sum_{k=1}^K w_k(\mathbf{x}) \cdot f_k(\mathbf{x}) y^=k=1∑Kwk(x)⋅fk(x)

其中权重 w k ( x ) w_k(\mathbf{x}) wk(x) 依赖于原始特征 x \mathbf{x} x,通过另一组模型学习。

6.4 深度学习中的 Stacking

在现代深度学习中,Stacking 思想演变为:

  • Mixture of Experts (MoE): 门控网络动态选择专家
  • Ensemble Distillation: 将 Stacking 集成知识蒸馏到单一模型
  • Stacked Encoders: 自编码器的逐层堆叠预训练

常见问题

陷阱 表现 解决方案
信息泄露 训练集准确率极高,测试集极差 严格使用交叉验证生成元特征
同质化基学习器 Stacking 效果不如单一最佳模型 确保基学习器多样性(相关性 < 0.9)
元学习器过拟合 元特征维度高、样本少 使用简单元学习器、增加正则化
数据划分不一致 时序数据泄露、类别不平衡 使用 StratifiedKFold、TimeSeriesSplit
计算资源爆炸 多层 Stacking 训练时间过长 限制层数、使用并行训练

代码示例

8.1 使用 Scikit-learn 实现

python 复制代码
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import numpy as np

# 加载数据
data = load_breast_cancer()
X, y = data.data, data.target

# 定义基学习器
base_learners = [
    ('rf', RandomForestClassifier(n_estimators=100, random_state=42)),
    ('gbdt', GradientBoostingClassifier(n_estimators=100, random_state=42)),
    ('svc', Pipeline([
        ('scaler', StandardScaler()),
        ('svc', SVC(probability=True, random_state=42))
    ]))
]

# 元学习器
meta_learner = LogisticRegression(C=1.0, solver='lbfgs', max_iter=1000)

# Stacking 实现
class StackingClassifier:
    def __init__(self, base_learners, meta_learner, n_folds=5):
        self.base_learners = base_learners
        self.meta_learner = meta_learner
        self.n_folds = n_folds
        self.base_models_ = []
        
    def fit(self, X, y):
        n_samples = X.shape[0]
        n_base = len(self.base_learners)
        
        # 初始化元特征矩阵 (使用概率预测)
        self.n_classes_ = len(np.unique(y))
        Z = np.zeros((n_samples, n_base * self.n_classes_))
        
        # 交叉验证生成元特征
        kfold = StratifiedKFold(n_splits=self.n_folds, shuffle=True, random_state=42)
        
        for i, (name, model) in enumerate(self.base_learners):
            print(f"Training base learner: {name}")
            
            # 存储每折训练的模型
            fold_models = []
            
            for train_idx, val_idx in kfold.split(X, y):
                X_train, X_val = X[train_idx], X[val_idx]
                y_train = y[train_idx]
                
                model_copy = clone(model)
                model_copy.fit(X_train, y_train)
                
                # 预测概率作为元特征
                probs = model_copy.predict_proba(X_val)
                Z[val_idx, i*self.n_classes_:(i+1)*self.n_classes_] = probs
                
                fold_models.append(model_copy)
            
            # 在完整数据上重新训练,用于后续预测
            full_model = clone(model)
            full_model.fit(X, y)
            self.base_models_.append(full_model)
        
        # 训练元学习器
        print("Training meta-learner...")
        self.meta_learner.fit(Z, y)
        
        return self
    
    def predict(self, X):
        # 生成元特征
        Z = self._generate_meta_features(X)
        return self.meta_learner.predict(Z)
    
    def predict_proba(self, X):
        Z = self._generate_meta_features(X)
        return self.meta_learner.predict_proba(Z)
    
    def _generate_meta_features(self, X):
        n_samples = X.shape[0]
        n_base = len(self.base_learners)
        Z = np.zeros((n_samples, n_base * self.n_classes_))
        
        for i, model in enumerate(self.base_models_):
            probs = model.predict_proba(X)
            Z[:, i*self.n_classes_:(i+1)*self.n_classes_] = probs
        
        return Z

# 使用示例
from sklearn.base import clone
from sklearn.metrics import accuracy_score

stacking_clf = StackingClassifier(base_learners, meta_learner, n_folds=5)
stacking_clf.fit(X, y)

# 交叉验证评估
scores = cross_val_score(stacking_clf, X, y, cv=5, scoring='accuracy')
print(f"Stacking CV Accuracy: {scores.mean():.4f} (+/- {scores.std():.4f})")

# 对比单个模型
for name, model in base_learners:
    scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')
    print(f"{name}: {scores.mean():.4f}")

8.2 使用 ML-Ensemble 库(生产环境)

python 复制代码
# pip install mlens
from mlens.ensemble import SuperLearner
from sklearn.linear_model import Ridge
from sklearn.ensemble import RandomForestRegressor
from xgboost import XGBRegressor
import numpy as np

# 构建 Super Learner (Stacking 的一种实现)
ensemble = SuperLearner(
    folds=5,                    # 5折交叉验证
    random_state=42,
    verbose=2,
    backend="multiprocessing"   # 并行训练
)

# 添加第一层(基学习器)
ensemble.add([
    RandomForestRegressor(n_estimators=100),
    XGBRegressor(n_estimators=100),
    Ridge()
])

# 添加第二层(元学习器)
ensemble.add_meta(Ridge())

# 训练与预测
ensemble.fit(X_train, y_train)
predictions = ensemble.predict(X_test)
相关推荐
焦耳热科技前沿1 小时前
复旦大学Nat. Commun.:等离子体辅助碳热闪烧合成突破Hume-Rothery极限的亚5纳米高熵合金
人工智能·科技·自动化·能源·材料工程
未来之窗软件服务2 小时前
vosk-ASR freeswitch调用[AI人工智能(五十六)]—东方仙盟
人工智能·仙盟创梦ide·东方仙盟
踏浪无痕2 小时前
聊聊最近很火的"小龙虾":AI 应用的下一步是什么?
人工智能
chaofan9802 小时前
2026 轻量模型三国杀:Flash-Lite vs GPT-4.1 Nano vs Haiku,技术选型到底该站谁?
前端·人工智能·microsoft
BB学长2 小时前
LBM vs FVM:谁才是 CFD 的未来?
人工智能·算法·机器学习
AIDF20262 小时前
AI 芯片推理适配踩坑记:从 GPU 到国产算力的迁移思路
人工智能
Zzj_tju2 小时前
AI+医疗实战:影像+文本报告怎么结合?从单模态分类到多模态医疗 AI 系统设计
人工智能·分类·数据挖掘
智能交通技术2 小时前
iTSTech:自动驾驶、无人机与机器人在物流中的协同应用场景分析 2026
人工智能·机器学习·机器人·自动驾驶·无人机
Learn Beyond Limits2 小时前
循环神经网络的问题:梯度消失与梯度爆炸|Problems with RNNs: Vanishing and Exploding Gradients
人工智能·rnn·深度学习·神经网络·机器学习·自然语言处理·nlp