摘要:集成学习是机器学习领域的核心技术之一,通过组合多个弱学习器构建强学习器。本章深入讲解Bagging、Boosting、Stacking三大集成范式,剖析随机森林、XGBoost、LightGBM、CatBoost等主流算法的原理与差异,并提供完整的实战项目代码。


环境声明

  • Python版本:Python 3.12+
  • 核心库:Scikit-learn 1.4+、XGBoost 2.0+、LightGBM 4.0+、CatBoost 1.2+
  • 开发工具:PyCharm / VS Code / Jupyter Notebook
  • 操作系统:Windows / macOS / Linux(通用)

学习目标

完成本章学习后,你将能够:

  1. 理解集成学习的核心思想与三大范式(Bagging、Boosting、Stacking)
  2. 掌握随机森林算法的Bootstrap采样与OOB评估机制
  3. 深入理解AdaBoost与GBDT的数学推导
  4. 对比分析XGBoost、LightGBM、CatBoost的算法差异与适用场景
  5. 实现Stacking集成策略与元学习器
  6. 完成一个Kaggle风格的集成学习实战项目

1. 集成学习概述

1.1 强学习器与弱学习器

集成学习的核心思想源于一个基本问题:能否将多个弱学习器组合成一个强学习器?

弱学习器(Weak Learner)

  • 定义:预测性能仅略优于随机猜测的学习器
  • 典型代表:单层决策树(决策树桩)、浅层神经网络
  • 特点:计算简单、容易训练、单独使用时精度有限

强学习器(Strong Learner)

  • 定义:能够以较高准确率进行预测的学习器
  • 典型代表:深度决策树、复杂神经网络、集成模型
  • 特点:表达能力强大、但容易过拟合

补充:集成学习的理论基础来自Valiant提出的PAC(Probably Approximately Correct)学习框架,Schapire证明了弱学习器等价于强学习器。

1.2 集成策略分类

集成范式 基学习器关系 代表算法 核心思想
Bagging 并行、独立 随机森林 Bootstrap采样降低方差
Boosting 串行、依赖 AdaBoost、GBDT、XGBoost 序列化训练降低偏差
Stacking 层级结构 堆叠泛化 元学习器组合基模型

1.3 多样性增强策略

集成学习的关键在于基学习器的多样性。常用策略包括:

数据层面

  • 数据采样扰动(Bootstrap、Bagging)
  • 输入属性扰动(随机子空间法)
  • 输出表示扰动(输出调制、ECOC编码)

算法层面

  • 基学习器类型多样化(决策树、SVM、神经网络混合)
  • 超参数随机化
  • 训练过程随机化(随机初始化、Dropout)

2. Bagging原理与随机森林

2.1 Bootstrap采样

Bagging(Bootstrap Aggregating)由Leo Breiman于1996年提出,其核心是有放回抽样

Bootstrap采样原理

给定包含m个样本的数据集D,进行m次有放回抽样,得到采样集D’。

样本在m次抽样中始终不被抽到的概率:

P(未被抽样)=(1−1m)m≈1e≈0.368P(\text{未被抽样}) = \left(1 - \frac{1}{m}\right)^m \approx \frac{1}{e} \approx 0.368P(未被抽样)=(1m1)me10.368

这意味着每个Bootstrap采样集大约包含原始数据集的63.2%的样本,剩余36.8%的样本形成袋外样本(Out-of-Bag, OOB)

2.2 Bagging算法流程

# Bagging算法伪代码示意
"""
输入:训练集 D = {(x1, y1), (x2, y2), ..., (xm, ym)}
      基学习器算法 L
      训练轮数 T

过程:
for t = 1 to T do
    # 1. Bootstrap采样
    Dt = BootstrapSample(D)
    # 2. 训练基学习器
    ht = L(Dt)
end for

输出:H(x) = argmax_y Σ_t I(ht(x) = y)  # 分类:投票
      H(x) = (1/T) Σ_t ht(x)           # 回归:平均
"""

2.3 随机森林算法

随机森林(Random Forest)是Bagging的扩展,在决策树训练时引入随机特征选择

随机森林的随机性来源

  1. 样本随机性:Bootstrap采样
  2. 特征随机性:每个节点分裂时,从d个特征中随机选择k个候选特征(通常k = log₂d)

随机森林算法步骤

步骤 操作 说明
1 Bootstrap采样 生成T个训练子集
2 构建决策树 对每个子集训练决策树
3 随机特征选择 节点分裂时随机选择k个特征
4 不剪枝生长 每棵树生长到最大深度
5 集成预测 投票(分类)或平均(回归)

2.4 OOB评估

随机森林可以利用OOB样本进行无交叉验证的性能评估

from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_classification

# 生成示例数据
X, y = make_classification(n_samples=1000, n_features=20, 
                           n_informative=10, random_state=42)

# 训练随机森林,启用OOB评估
rf = RandomForestClassifier(
    n_estimators=100,
    max_depth=10,
    oob_score=True,  # 启用OOB评估
    random_state=42,
    n_jobs=-1
)

rf.fit(X, y)
print(f"OOB Score: {rf.oob_score_:.4f}")

OOB评估的优势

  • 无需划分验证集,充分利用训练数据
  • 计算效率高,训练过程中自动完成
  • 与交叉验证结果高度一致

3. Boosting原理

3.1 Boosting核心思想

Boosting是一类将弱学习器提升为强学习器的算法家族,核心思想是:

自适应地调整样本权重,使后续学习器专注于前面学习器分错的样本。

3.2 AdaBoost算法推导

AdaBoost(Adaptive Boosting)是Boosting家族的开创性算法。

算法初始化

  • 样本权重均匀分布:D1(i)=1mD_1(i) = \frac{1}{m}D1(i)=m1

第t轮迭代

  1. 训练基学习器:在权重分布DtD_tDt下训练学习器hth_tht

  2. 计算加权错误率
    ϵt=∑i=1mDt(i)⋅I[ht(xi)≠yi]\epsilon_t = \sum_{i=1}^{m} D_t(i) \cdot \mathbb{I}[h_t(x_i) \neq y_i]ϵt=i=1mDt(i)I[ht(xi)=yi]

  3. 计算学习器权重
    αt=12ln⁡(1−ϵtϵt)\alpha_t = \frac{1}{2} \ln\left(\frac{1-\epsilon_t}{\epsilon_t}\right)αt=21ln(ϵt1ϵt)

  4. 更新样本权重
    Dt+1(i)=Dt(i)Zt⋅exp⁡(−αtyiht(xi))D_{t+1}(i) = \frac{D_t(i)}{Z_t} \cdot \exp(-\alpha_t y_i h_t(x_i))Dt+1(i)=ZtDt(i)exp(αtyiht(xi))

    其中ZtZ_tZt是归一化因子。

最终预测
H(x)=sign(∑t=1Tαtht(x))H(x) = \text{sign}\left(\sum_{t=1}^{T} \alpha_t h_t(x)\right)H(x)=sign(t=1Tαtht(x))

from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier

# 使用决策树桩作为基学习器
base_estimator = DecisionTreeClassifier(max_depth=1)

ada = AdaBoostClassifier(
    estimator=base_estimator,
    n_estimators=100,
    learning_rate=0.5,
    random_state=42
)

ada.fit(X, y)

3.3 GBDT原理

GBDT(Gradient Boosting Decision Tree)是Boosting的另一种实现,基于梯度下降思想。

核心思想

将Boosting视为函数空间中的梯度下降,每一轮训练的新树拟合的是当前模型的负梯度(伪残差)。

数学推导

假设损失函数为L(y,F(x))L(y, F(x))L(y,F(x)),当前模型为Ft−1(x)F_{t-1}(x)Ft1(x)

在第t轮,我们需要找到新树ht(x)h_t(x)ht(x)使得:

Ft(x)=Ft−1(x)+ρtht(x)F_t(x) = F_{t-1}(x) + \rho_t h_t(x)Ft(x)=Ft1(x)+ρtht(x)

其中ht(x)h_t(x)ht(x)拟合负梯度:

ht(x)≈−[∂L(y,F(x))∂F(x)]F(x)=Ft−1(x)h_t(x) \approx -\left[\frac{\partial L(y, F(x))}{\partial F(x)}\right]_{F(x)=F_{t-1}(x)}ht(x)[F(x)L(y,F(x))]F(x)=Ft1(x)

不同损失函数对应的梯度

损失函数 表达式 负梯度(伪残差)
平方损失 12(y−F)2\frac{1}{2}(y-F)^221(yF)2 y−Fy - FyF
绝对损失 ∣y−F∣|y-F|yF sign(y−F)\text{sign}(y-F)sign(yF)
对数损失 log⁡(1+e−yF)\log(1+e^{-yF})log(1+eyF) y−σ(yF)y - \sigma(yF)yσ(yF)

3.4 梯度提升框架

from sklearn.ensemble import GradientBoostingClassifier

gbdt = GradientBoostingClassifier(
    n_estimators=100,      # 树的数量
    learning_rate=0.1,     # 学习率(收缩因子)
    max_depth=3,           # 树的最大深度
    subsample=0.8,         # 子采样比例(随机梯度提升)
    random_state=42
)

gbdt.fit(X, y)

关键超参数

  • n_estimators:基学习器数量,过多会过拟合
  • learning_rate:学习率,与n_estimators trade-off
  • max_depth:树深度,控制模型复杂度
  • subsample:子采样比例,引入随机性防止过拟合

4. XGBoost/LightGBM/CatBoost深度对比

4.1 算法演进脉络

三大梯度提升框架的发布时间线:

算法 发布时间 开发团队 核心突破
XGBoost 2016年 DMLC 工程优化、正则化、并行计算
LightGBM 2017年 Microsoft 直方图算法、Leaf-wise生长、GOSS/EFB
CatBoost 2017年 Yandex 原生类别特征、Ordered TS编码、对称树

4.2 XGBoost算法详解

XGBoost(eXtreme Gradient Boosting)在GBDT基础上进行了多项改进。

目标函数

Obj=∑i=1nL(yi,y^i)+∑k=1KΩ(fk)\text{Obj} = \sum_{i=1}^{n} L(y_i, \hat{y}_i) + \sum_{k=1}^{K} \Omega(f_k)Obj=i=1nL(yi,y^i)+k=1KΩ(fk)

其中Ω(f)=γT+12λ∥w∥2\Omega(f) = \gamma T + \frac{1}{2}\lambda \|w\|^2Ω(f)=γT+21λw2是正则化项,T为叶子节点数,w为叶子权重。

二阶泰勒展开

XGBoost使用损失函数的二阶泰勒展开进行优化:

Obj(t)≈∑i=1n[gift(xi)+12hift2(xi)]+Ω(ft)\text{Obj}^{(t)} \approx \sum_{i=1}^{n} [g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i)] + \Omega(f_t)Obj(t)i=1n[gift(xi)+21hift2(xi)]+Ω(ft)

其中gi=∂y^(t−1)L(yi,y^(t−1))g_i = \partial_{\hat{y}^{(t-1)}} L(y_i, \hat{y}^{(t-1)})gi=y^(t1)L(yi,y^(t1))hi=∂y^(t−1)2L(yi,y^(t−1))h_i = \partial^2_{\hat{y}^{(t-1)}} L(y_i, \hat{y}^{(t-1)})hi=y^(t1)2L(yi,y^(t1))

最优叶子权重

wj∗=−∑i∈Ijgi∑i∈Ijhi+λw_j^* = -\frac{\sum_{i \in I_j} g_i}{\sum_{i \in I_j} h_i + \lambda}wj=iIjhi+λiIjgi

分裂增益计算

Gain=12[GL2HL+λ+GR2HR+λ−(GL+GR)2HL+HR+λ]−γ\text{Gain} = \frac{1}{2}\left[\frac{G_L^2}{H_L+\lambda} + \frac{G_R^2}{H_R+\lambda} - \frac{(G_L+G_R)^2}{H_L+H_R+\lambda}\right] - \gammaGain=21[HL+λGL2+HR+λGR2HL+HR+λ(GL+GR)2]γ

import xgboost as xgb
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split

# 生成数据
X, y = make_classification(n_samples=10000, n_features=50, 
                           n_informative=20, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# 转换为DMatrix格式(XGBoost专用数据结构)
dtrain = xgb.DMatrix(X_train, label=y_train)
dtest = xgb.DMatrix(X_test, label=y_test)

# 参数配置
params = {
    'objective': 'binary:logistic',
    'max_depth': 6,
    'learning_rate': 0.1,
    'subsample': 0.8,
    'colsample_bytree': 0.8,
    'reg_alpha': 0.1,      # L1正则化
    'reg_lambda': 1.0,     # L2正则化
    'eval_metric': 'auc'
}

# 训练模型
model = xgb.train(
    params,
    dtrain,
    num_boost_round=100,
    evals=[(dtest, 'test')],
    early_stopping_rounds=10,
    verbose_eval=False
)

4.3 LightGBM算法详解

LightGBM通过直方图算法Leaf-wise树生长策略实现高效训练。

直方图算法(Histogram-based Algorithm)

  1. 将连续特征离散化为k个bin(默认255)
  2. 构建直方图统计梯度信息
  3. 基于直方图寻找最优分裂点

复杂度对比

操作 传统GBDT LightGBM直方图
寻找分裂点 O(#data × #features) O(#bin × #features)
内存占用 存储所有数据 存储直方图(压缩)

Leaf-wise vs Level-wise

  • Level-wise(XGBoost):按层生长,每层所有节点同时分裂
  • Leaf-wise(LightGBM):选择分裂增益最大的叶子节点分裂

Leaf-wise优势:在相同节点数下,能获得更低的损失;但需要限制最大深度防止过拟合。

GOSS(Gradient-based One-Side Sampling)

保留梯度大的样本,对梯度小的样本随机采样,在保持数据分布的同时加速训练。

EFB(Exclusive Feature Bundling)

将互斥特征(不会同时取非零值)捆绑在一起,减少特征数量。

import lightgbm as lgb

# 创建Dataset
lgb_train = lgb.Dataset(X_train, y_train)
lgb_test = lgb.Dataset(X_test, y_test, reference=lgb_train)

# 参数配置
params = {
    'objective': 'binary',
    'metric': 'auc',
    'boosting_type': 'gbdt',
    'num_leaves': 31,          # 叶子节点数
    'learning_rate': 0.05,
    'feature_fraction': 0.8,   # 特征采样比例
    'bagging_fraction': 0.8,   # 数据采样比例
    'bagging_freq': 5,
    'verbose': -1
}

# 训练模型
model = lgb.train(
    params,
    lgb_train,
    num_boost_round=100,
    valid_sets=[lgb_test],
    callbacks=[lgb.early_stopping(10), lgb.log_evaluation(0)]
)

4.4 CatBoost算法详解

CatBoost(Categorical Boosting)针对类别型特征进行了深度优化。

Ordered Target Statistics(Ordered TS)

解决类别特征编码时的目标泄露问题。

传统Target Encoding:
TS=∑iyi⋅I[xi=c]+a⋅p∑iI[xi=c]+a\text{TS} = \frac{\sum_{i} y_i \cdot \mathbb{I}[x_i = c] + a \cdot p}{\sum_{i} \mathbb{I}[x_i = c] + a}TS=iI[xi=c]+aiyiI[xi=c]+ap

Ordered TS改进:

  • 引入随机排列,仅使用排列中前面的样本计算统计量
  • 类似时序数据,避免信息泄露

对称树(Symmetric Trees)

CatBoost使用相同的分裂条件对所有节点进行分裂,形成完全二叉树结构。

优势:

  • 预测速度更快(减少分支判断)
  • 减少过拟合
from catboost import CatBoostClassifier

# CatBoost支持原生类别特征
cat_features = [0, 1, 2]  # 类别特征索引

model = CatBoostClassifier(
    iterations=100,
    learning_rate=0.1,
    depth=6,
    loss_function='Logloss',
    eval_metric='AUC',
    random_seed=42,
    verbose=False
)

model.fit(X_train, y_train, eval_set=(X_test, y_test), verbose=False)

4.5 三大框架深度对比

特性 XGBoost LightGBM CatBoost
训练速度 极快 中等
内存占用 较高 中等
类别特征 需预处理(One-hot/Label) 需预处理 原生支持
缺失值处理 自动学习分裂方向 自动处理 自动处理
并行方式 特征并行 + 数据并行 特征并行 + 数据并行 特征并行
树生长策略 Level-wise Leaf-wise Symmetric
GPU支持 支持 支持 支持
过拟合倾向 较低 中等(需调参) 较低

适用场景建议

  • XGBoost:追求稳定性、需要详细调参控制、数据量中等
  • LightGBM:大规模数据、追求训练速度、特征维度高
  • CatBoost:类别特征丰富、追求开箱即用、对过拟合敏感

5. Stacking与Blending

5.1 Stacking原理

Stacking(堆叠泛化)是一种两层(或多层)的集成策略,使用**元学习器(Meta-learner)**来组合基学习器的预测结果。

Stacking架构

输入特征 X
    |
    v
+------------------+
|   第一层(基学习器) |
|  [模型A] [模型B]  |
|  [模型C] [模型D]  |
+------------------+
    |
    v
+------------------+
|   第二层(元学习器) |
|   逻辑回归/线性模型  |
+------------------+
    |
    v
  最终预测

关键问题:避免信息泄露

使用交叉验证生成元特征,确保元学习器的训练数据与基学习器的训练数据隔离。

5.2 Stacking实现

from sklearn.ensemble import StackingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier

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

# 定义元学习器
final_estimator = LogisticRegression()

# 创建Stacking分类器
stacking_clf = StackingClassifier(
    estimators=estimators,
    final_estimator=final_estimator,
    cv=5,                    # 5折交叉验证生成元特征
    stack_method='predict_proba',  # 使用概率作为元特征
    n_jobs=-1
)

stacking_clf.fit(X_train, y_train)
score = stacking_clf.score(X_test, y_test)
print(f"Stacking Accuracy: {score:.4f}")

5.3 Blending方法

Blending是Stacking的简化版本,使用**留出集(Hold-out Set)**而非交叉验证。

Blending流程

  1. 将训练集划分为两部分:训练子集和验证子集
  2. 在训练子集上训练基学习器
  3. 基学习器对验证子集进行预测,生成元特征
  4. 元学习器在元特征上训练

Stacking vs Blending

特性 Stacking Blending
数据划分 K折交叉验证 单次留出
数据利用率 高(所有数据参与) 较低(验证集固定)
计算成本 高(K倍训练)
过拟合风险 较低 较高

6. Voting与加权集成

6.1 硬投票与软投票

Voting是最简单的集成策略,直接对基学习器的预测结果进行投票。

硬投票(Hard Voting)

y^=mode(h1(x),h2(x),...,hT(x))\hat{y} = \text{mode}(h_1(x), h_2(x), ..., h_T(x))y^=mode(h1(x),h2(x),...,hT(x))

选择预测类别最多的作为最终结果。

软投票(Soft Voting)

y^=arg⁡max⁡y∑t=1Twt⋅Pt(y∣x)\hat{y} = \arg\max_y \sum_{t=1}^{T} w_t \cdot P_t(y|x)y^=argymaxt=1TwtPt(yx)

对预测概率进行加权平均,选择概率最高的类别。

from sklearn.ensemble import VotingClassifier

# 定义基学习器
clf1 = RandomForestClassifier(n_estimators=100, random_state=42)
clf2 = GradientBoostingClassifier(n_estimators=100, random_state=42)
clf3 = LogisticRegression(max_iter=1000, random_state=42)

# 硬投票
voting_hard = VotingClassifier(
    estimators=[('rf', clf1), ('gb', clf2), ('lr', clf3)],
    voting='hard'
)

# 软投票(需要基学习器支持predict_proba)
voting_soft = VotingClassifier(
    estimators=[('rf', clf1), ('gb', clf2), ('lr', clf3)],
    voting='soft',
    weights=[2, 2, 1]  # 加权投票
)

6.2 动态加权策略

基于验证集性能的加权

import numpy as np
from sklearn.metrics import accuracy_score

# 训练基学习器
models = {
    'rf': RandomForestClassifier(n_estimators=100, random_state=42),
    'gbdt': GradientBoostingClassifier(n_estimators=100, random_state=42),
    'xgb': xgb.XGBClassifier(n_estimators=100, random_state=42)
}

# 在验证集上评估性能
weights = {}
for name, model in models.items():
    model.fit(X_train, y_train)
    val_pred = model.predict(X_val)
    acc = accuracy_score(y_val, val_pred)
    weights[name] = acc

# 归一化权重
total = sum(weights.values())
weights = {k: v/total for k, v in weights.items()}
print(f"动态权重: {weights}")

7. 实战案例:Kaggle风格集成学习项目

7.1 项目背景

本案例使用经典的泰坦尼克号生存预测数据集,演示完整的集成学习流程。

7.2 完整代码实现

"""
集成学习实战:泰坦尼克号生存预测
使用Bagging、Boosting、Stacking等多种集成策略
"""

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split, cross_val_score, StratifiedKFold
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.ensemble import (
    RandomForestClassifier, 
    GradientBoostingClassifier,
    AdaBoostClassifier,
    VotingClassifier,
    StackingClassifier
)
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score, classification_report, roc_auc_score
import xgboost as xgb
import lightgbm as lgb
from catboost import CatBoostClassifier
import warnings
warnings.filterwarnings('ignore')

# 设置随机种子
RANDOM_STATE = 42
np.random.seed(RANDOM_STATE)

# ==========================================
# 1. 数据加载与预处理
# ==========================================
print("=" * 50)
print("步骤1: 数据加载与预处理")
print("=" * 50)

# 加载数据(使用sklearn内置数据集模拟)
from sklearn.datasets import make_classification

# 生成模拟数据(模拟泰坦尼克号特征分布)
X, y = make_classification(
    n_samples=891,           # 泰坦尼克号乘客数
    n_features=10,
    n_informative=6,
    n_redundant=2,
    n_classes=2,
    weights=[0.6, 0.4],      # 生存率约40%
    flip_y=0.1,              # 添加噪声
    random_state=RANDOM_STATE
)

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=RANDOM_STATE, stratify=y
)

print(f"训练集大小: {X_train.shape}")
print(f"测试集大小: {X_test.shape}")
print(f"训练集生存率: {y_train.mean():.3f}")

# 特征标准化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# ==========================================
# 2. 基学习器定义
# ==========================================
print("\n" + "=" * 50)
print("步骤2: 定义基学习器")
print("=" * 50)

# 定义各种基学习器
base_learners = {
    'RandomForest': RandomForestClassifier(
        n_estimators=200,
        max_depth=10,
        min_samples_split=5,
        random_state=RANDOM_STATE,
        n_jobs=-1
    ),
    'GradientBoosting': GradientBoostingClassifier(
        n_estimators=200,
        learning_rate=0.1,
        max_depth=4,
        random_state=RANDOM_STATE
    ),
    'AdaBoost': AdaBoostClassifier(
        n_estimators=200,
        learning_rate=0.5,
        random_state=RANDOM_STATE
    ),
    'XGBoost': xgb.XGBClassifier(
        n_estimators=200,
        max_depth=5,
        learning_rate=0.1,
        subsample=0.8,
        colsample_bytree=0.8,
        random_state=RANDOM_STATE,
        eval_metric='logloss'
    ),
    'LightGBM': lgb.LGBMClassifier(
        n_estimators=200,
        num_leaves=31,
        learning_rate=0.05,
        feature_fraction=0.8,
        bagging_fraction=0.8,
        random_state=RANDOM_STATE,
        verbose=-1
    ),
    'CatBoost': CatBoostClassifier(
        iterations=200,
        learning_rate=0.1,
        depth=6,
        random_seed=RANDOM_STATE,
        verbose=False
    ),
    'SVM': SVC(
        probability=True,
        kernel='rbf',
        C=1.0,
        random_state=RANDOM_STATE
    ),
    'LogisticRegression': LogisticRegression(
        max_iter=1000,
        C=1.0,
        random_state=RANDOM_STATE
    )
}

# ==========================================
# 3. 基学习器交叉验证评估
# ==========================================
print("\n" + "=" * 50)
print("步骤3: 基学习器交叉验证评估")
print("=" * 50)

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=RANDOM_STATE)
cv_results = {}

for name, model in base_learners.items():
    # 使用标准化数据(SVM和LR需要)
    if name in ['SVM', 'LogisticRegression']:
        scores = cross_val_score(model, X_train_scaled, y_train, cv=cv, scoring='accuracy')
    else:
        scores = cross_val_score(model, X_train, y_train, cv=cv, scoring='accuracy')
    
    cv_results[name] = {
        'mean': scores.mean(),
        'std': scores.std()
    }
    print(f"{name:20s}: {scores.mean():.4f} (+/- {scores.std()*2:.4f})")

# ==========================================
# 4. 投票集成
# ==========================================
print("\n" + "=" * 50)
print("步骤4: 投票集成")
print("=" * 50)

# 选择表现最好的几个模型进行投票
top_models = ['RandomForest', 'XGBoost', 'LightGBM', 'CatBoost']

# 硬投票
estimators_for_voting = [
    (name, base_learners[name]) for name in top_models
]

voting_hard = VotingClassifier(
    estimators=estimators_for_voting,
    voting='hard'
)

# 软投票
voting_soft = VotingClassifier(
    estimators=estimators_for_voting,
    voting='soft'
)

# 评估投票集成
for name, model in [('Voting_Hard', voting_hard), ('Voting_Soft', voting_soft)]:
    scores = cross_val_score(model, X_train, y_train, cv=cv, scoring='accuracy')
    cv_results[name] = {'mean': scores.mean(), 'std': scores.std()}
    print(f"{name:20s}: {scores.mean():.4f} (+/- {scores.std()*2:.4f})")

# ==========================================
# 5. Stacking集成
# ==========================================
print("\n" + "=" * 50)
print("步骤5: Stacking集成")
print("=" * 50)

# 第一层:基学习器
stacking_estimators = [
    ('rf', RandomForestClassifier(n_estimators=100, random_state=RANDOM_STATE)),
    ('xgb', xgb.XGBClassifier(n_estimators=100, random_state=RANDOM_STATE, eval_metric='logloss')),
    ('lgb', lgb.LGBMClassifier(n_estimators=100, random_state=RANDOM_STATE, verbose=-1)),
    ('cat', CatBoostClassifier(iterations=100, random_seed=RANDOM_STATE, verbose=False))
]

# 第二层:元学习器
meta_learner = LogisticRegression(max_iter=1000, random_state=RANDOM_STATE)

# Stacking分类器
stacking_clf = StackingClassifier(
    estimators=stacking_estimators,
    final_estimator=meta_learner,
    cv=5,
    stack_method='predict_proba',
    n_jobs=-1
)

scores = cross_val_score(stacking_clf, X_train, y_train, cv=cv, scoring='accuracy')
cv_results['Stacking'] = {'mean': scores.mean(), 'std': scores.std()}
print(f"{'Stacking':20s}: {scores.mean():.4f} (+/- {scores.std()*2:.4f})")

# ==========================================
# 6. 最终模型训练与评估
# ==========================================
print("\n" + "=" * 50)
print("步骤6: 最终模型训练与评估")
print("=" * 50)

# 选择最佳模型(这里以Stacking为例)
best_model = stacking_clf
best_model.fit(X_train, y_train)

# 测试集预测
y_pred = best_model.predict(X_test)
y_pred_proba = best_model.predict_proba(X_test)[:, 1]

# 评估指标
accuracy = accuracy_score(y_test, y_pred)
auc = roc_auc_score(y_test, y_pred_proba)

print(f"\n测试集准确率: {accuracy:.4f}")
print(f"测试集AUC: {auc:.4f}")
print("\n分类报告:")
print(classification_report(y_test, y_pred))

# ==========================================
# 7. 结果可视化
# ==========================================
print("\n" + "=" * 50)
print("步骤7: 结果可视化")
print("=" * 50)

# 绘制各模型性能对比
plt.figure(figsize=(12, 6))

models = list(cv_results.keys())
means = [cv_results[m]['mean'] for m in models]
stds = [cv_results[m]['std'] for m in models]

x_pos = np.arange(len(models))
plt.bar(x_pos, means, yerr=stds, align='center', alpha=0.7, capsize=5)
plt.xticks(x_pos, models, rotation=45, ha='right')
plt.ylabel('Accuracy')
plt.title('Model Comparison - Cross Validation Results')
plt.ylim([0.5, 1.0])
plt.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.savefig('ensemble_comparison.png', dpi=150)
print("对比图已保存为: ensemble_comparison.png")

# ==========================================
# 8. 特征重要性分析(以RandomForest为例)
# ==========================================
print("\n" + "=" * 50)
print("步骤8: 特征重要性分析")
print("=" * 50)

rf_model = RandomForestClassifier(n_estimators=200, random_state=RANDOM_STATE)
rf_model.fit(X_train, y_train)

feature_importance = pd.DataFrame({
    'feature': [f'Feature_{i}' for i in range(X.shape[1])],
    'importance': rf_model.feature_importances_
}).sort_values('importance', ascending=False)

print("\nTop 5 重要特征:")
print(feature_importance.head())

print("\n" + "=" * 50)
print("项目完成!")
print("=" * 50)

7.3 运行结果示例

==================================================
步骤3: 基学习器交叉验证评估
==================================================
RandomForest        : 0.8236 (+/- 0.0452)
GradientBoosting    : 0.8191 (+/- 0.0387)
AdaBoost            : 0.8056 (+/- 0.0423)
XGBoost             : 0.8315 (+/- 0.0398)
LightGBM            : 0.8340 (+/- 0.0412)
CatBoost            : 0.8291 (+/- 0.0376)
SVM                 : 0.7986 (+/- 0.0489)
LogisticRegression  : 0.7923 (+/- 0.0512)

==================================================
步骤4: 投票集成
==================================================
Voting_Hard         : 0.8365 (+/- 0.0389)
Voting_Soft         : 0.8410 (+/- 0.0367)

==================================================
步骤5: Stacking集成
==================================================
Stacking            : 0.8456 (+/- 0.0354)

8. 避坑小贴士

8.1 数据泄露问题

问题描述:在集成学习中,如果基学习器的预测结果与训练数据存在信息泄露,会导致Stacking等方法的评估结果虚高。

解决方案

  • 使用交叉验证生成元特征,而非直接在整个训练集上预测
  • 确保训练集和测试集的划分在时间序列数据中遵循时序

8.2 过拟合陷阱

问题描述:集成模型复杂度高,容易在训练集上过拟合。

预防措施

策略 适用场景 具体方法
早停法 Boosting类模型 监控验证集损失,连续N轮不下降则停止
正则化 XGBoost/LightGBM 设置reg_alpha、reg_lambda
子采样 Bagging/Boosting 设置subsample < 1.0
限制树深度 树模型 max_depth控制在3-10之间

8.3 类别不平衡处理

问题描述:集成学习对类别不平衡敏感,多数类样本可能主导预测结果。

解决方案

# 方法1: 类别权重
model = XGBClassifier(scale_pos_weight=len(neg)/len(pos))

# 方法2: 样本权重
sample_weight = compute_sample_weight('balanced', y_train)
model.fit(X_train, y_train, sample_weight=sample_weight)

# 方法3: SMOTE过采样
from imblearn.over_sampling import SMOTE
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)

8.4 超参数调优建议

调优优先级

  1. 学习率(learning_rate)与迭代次数(n_estimators):两者trade-off,学习率小则迭代次数多
  2. 树深度(max_depth/num_leaves):控制模型复杂度,先粗调再细调
  3. 采样比例(subsample/colsample):增加随机性,防止过拟合
  4. 正则化参数:最后微调

推荐调参工具

  • Optuna:基于贝叶斯优化的超参数搜索
  • Ray Tune:分布式超参数调优
  • Scikit-optimize:序列模型优化

9. 本章小结

本章深入讲解了集成学习的三大范式及其主流实现:

核心知识点回顾

  1. Bagging:通过Bootstrap采样和并行训练降低方差,随机森林是其典型代表
  2. Boosting:通过序列化训练和样本加权降低偏差,AdaBoost、GBDT、XGBoost、LightGBM、CatBoost各有特色
  3. Stacking:通过元学习器组合基模型,实现更高层次的集成

算法选择指南

  • 数据量小、追求稳定性:选择XGBoost
  • 数据量大、追求速度:选择LightGBM
  • 类别特征多、追求易用性:选择CatBoost
  • 需要解释性:选择随机森林

一句话总结:集成学习的本质是"三个臭皮匠,顶个诸葛亮",关键在于如何有效地组合多个学习器,使整体性能超越任何单一模型。


参考资料

  1. Breiman L. Bagging predictors[J]. Machine learning, 1996.
  2. Freund Y, Schapire R E. A decision-theoretic generalization of on-line learning[J]. 1997.
  3. Chen T, Guestrin C. XGBoost: A scalable tree boosting system[C]. KDD 2016.
  4. Ke G, et al. LightGBM: A highly efficient gradient boosting decision tree[C]. NIPS 2017.
  5. Prokhorenkova L, et al. CatBoost: unbiased boosting with categorical features[C]. NIPS 2018.

本文首发于CSDN专栏《机器学习精通》,转载请注明出处。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐