摘要:集成学习是机器学习领域的核心技术之一,通过组合多个弱学习器构建强学习器。本章深入讲解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(未被抽样)=(1−m1​)m≈e1​≈0.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)

随机森林算法步骤:

步骤操作说明
1Bootstrap采样生成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=1∑m​Dt​(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​=21​ln(ϵt​1−ϵ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)=Zt​Dt​(i)​⋅exp(−αt​yi​ht​(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=1∑T​αt​ht​(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)Ft−1​(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)=Ft−1​(x)+ρt​ht​(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)=Ft−1​(x)​

不同损失函数对应的梯度:

损失函数表达式负梯度(伪残差)
平方损失12(y−F)2\frac{1}{2}(y-F)^221​(y−F)2y−Fy - Fy−F
绝对损失∣y−F∣|y-F|∣y−F∣sign(y−F)\text{sign}(y-F)sign(y−F)
对数损失log⁡(1+e−yF)\log(1+e^{-yF})log(1+e−yF)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 算法演进脉络

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

算法发布时间开发团队核心突破
XGBoost2016年DMLC工程优化、正则化、并行计算
LightGBM2017年Microsoft直方图算法、Leaf-wise生长、GOSS/EFB
CatBoost2017年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=1∑n​L(yi​,y^​i​)+k=1∑K​Ω(fk​)

其中Ω(f)=γT+12λ∥w∥2\Omega(f) = \gamma T + \frac{1}{2}\lambda \|w\|^2Ω(f)=γT+21​λ∥w∥2是正则化项,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=1∑n​[gi​ft​(xi​)+21​hi​ft2​(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^​(t−1)​L(yi​,y^​(t−1)),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^​(t−1)2​L(yi​,y^​(t−1))

最优叶子权重:

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

分裂增益计算:

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​+λGR2​​−HL​+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. 基于直方图寻找最优分裂点

复杂度对比:

操作传统GBDTLightGBM直方图
寻找分裂点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=∑i​I[xi​=c]+a∑i​yi​⋅I[xi​=c]+a⋅p​

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 三大框架深度对比

特性XGBoostLightGBMCatBoost
训练速度快极快中等
内存占用较高低中等
类别特征需预处理(One-hot/Label)需预处理原生支持
缺失值处理自动学习分裂方向自动处理自动处理
并行方式特征并行 + 数据并行特征并行 + 数据并行特征并行
树生长策略Level-wiseLeaf-wiseSymmetric
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:

特性StackingBlending
数据划分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^​=argymax​t=1∑T​wt​⋅Pt​(y∣x)

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

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

更多推荐