跳转至

12 - 集成学习进阶

集成学习进阶图

🎯 集成学习概述

核心思想

"三个臭皮匠,顶个诸葛亮"

Text Only
单个模型:可能有偏差或方差问题
多个模型:综合多个模型的预测,提高稳定性和准确性

集成学习 = 基学习器 + 集成策略

为什么集成有效?

Text Only
假设条件:
- 每个基学习器准确率 > 50%
- 基学习器之间相互独立

10个独立分类器,每个准确率70%:
多数投票准确率 = Σ C(10,k) × 0.7^k × 0.3^(10-k)  (k=6,7,8,9,10)
              ≈ 85.0%

结论:集成可以显著提升性能!

集成学习分类

Text Only
┌─────────────────────────────────────────┐
│           集成学习方法                   │
├─────────────────┬───────────────────────┤
│    同质集成      │       异质集成        │
│  (同类型基学习器) │   (不同类型基学习器)   │
├─────────────────┼───────────────────────┤
│ • Bagging       │ • Stacking            │
│ • Boosting      │ • Voting              │
│ • Random Forest │ • Blending            │
│ • XGBoost       │                       │
└─────────────────┴───────────────────────┘

🎲 Bagging (Bootstrap Aggregating)

核心思想

并行训练多个模型,通过自助采样构建不同训练集

Text Only
步骤:
1. 从原始数据集D中有放回地抽样n次,得到D₁
2. 重复B次,得到B个数据集:D₁, D₂, ..., D_B
3. 在每个数据集上训练一个基学习器
4. 预测时:分类用投票,回归用平均

自助采样 (Bootstrap)

Text Only
原始数据集:[A, B, C, D, E] (N=5)

采样1:[A, A, B, C, D]  (E未被抽中)
采样2:[B, C, C, D, E]  (A未被抽中)
采样3:[A, B, D, E, E]  (C未被抽中)

每个样本被抽中的概率:1 - (1 - 1/N)^N ≈ 63.2%
未被抽中的概率:≈ 36.8% (用于OOB估计)

Bagging 降低方差的数学原理

\(B\) 个基学习器的预测分别为 \(f_1(x), f_2(x), \ldots, f_B(x)\),每个方差为 \(\sigma^2\),任意两个之间的相关系数为 \(\rho\)

Bagging 的预测为 \(\hat{f}(x) = \frac{1}{B}\sum_{b=1}^{B} f_b(x)\),其方差为:

\[\text{Var}\left(\hat{f}\right) = \rho \sigma^2 + \frac{1-\rho}{B} \sigma^2\]
  • \(B \to \infty\) 时,方差趋向 \(\rho\sigma^2\)
  • 关键:相关性 \(\rho\) 越低,方差降低越多。随机森林通过随机特征选择进一步降低 \(\rho\),这就是它优于普通 Bagging 的原因。

OOB (Out-of-Bag) 估计

Text Only
每个基学习器有约36.8%的数据未被使用
→ 可以用作验证集,无需额外交叉验证

OOB误差估计:
对每个样本x,用所有未使用x的基学习器预测
然后投票/平均得到最终预测

代码实现

Python
from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split

# 创建数据
X, y = make_classification(n_samples=1000, n_features=20, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Bagging
bagging = BaggingClassifier(
    estimator=DecisionTreeClassifier(),  # 基学习器
    n_estimators=100,                     # 基学习器数量
    max_samples=0.8,                      # 每次采样比例
    max_features=0.8,                     # 特征采样比例
    bootstrap=True,                       # 有放回采样
    bootstrap_features=False,             # 特征是否有放回
    oob_score=True,                       # 启用OOB估计
    n_jobs=-1,                            # 并行
    random_state=42
)

bagging.fit(X_train, y_train)
print(f"训练准确率: {bagging.score(X_train, y_train):.4f}")
print(f"测试准确率: {bagging.score(X_test, y_test):.4f}")
print(f"OOB分数: {bagging.oob_score_:.4f}")

🌲 随机森林 (Random Forest)

核心思想

Bagging + 随机特征选择 = 随机森林

Text Only
随机性来源:
1. 样本随机:Bootstrap采样
2. 特征随机:每个节点分裂时随机选择m个特征

优点:
- 进一步降低树之间的相关性
- 提高泛化能力
- 减少过拟合

算法流程

Text Only
训练阶段:
对于 b = 1, 2, ..., B:
    1. 从训练集D中Bootstrap采样得到D_b
    2. 在D_b上训练决策树T_b:
       对于每个节点:
           a. 随机选择m个特征 (m << p)
           b. 在这m个特征中选择最优分裂
    3. 不剪枝,完全生长

预测阶段:
分类:ŷ = mode{T₁(x), T₂(x), ..., T_B(x)}
回归:ŷ = (1/B) Σ T_b(x)

特征重要性

Python
import matplotlib.pyplot as plt
import numpy as np

# 训练随机森林
from sklearn.ensemble import RandomForestClassifier

rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X_train, y_train)

# 获取特征重要性
importances = rf.feature_importances_
indices = np.argsort(importances)[::-1]

# 可视化
plt.figure(figsize=(10, 6))
plt.bar(range(X.shape[1]), importances[indices])
plt.title("特征重要性")
plt.xlabel("特征")
plt.ylabel("重要性")
plt.show()

# 打印前10重要特征
print("Top 10 重要特征:")
for i in range(10):
    print(f"特征 {indices[i]}: {importances[indices[i]]:.4f}")

特征重要性计算原理

Text Only
方法1:基于不纯度减少 (Gini Importance)
- 计算每个特征在所有树中带来的不纯度减少总和
- 归一化得到重要性分数

方法2:基于排列 (Permutation Importance)
- 随机打乱某个特征的值
- 观察模型性能下降程度
- 下降越多,特征越重要

超参数调优

Python
from sklearn.model_selection import GridSearchCV

param_grid = {
    'n_estimators': [100, 200, 500],
    'max_depth': [None, 10, 20, 30],
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4],
    'max_features': ['sqrt', 'log2', None]
}

grid_search = GridSearchCV(
    RandomForestClassifier(random_state=42),
    param_grid,
    cv=5,
    n_jobs=-1,
    scoring='accuracy'
)

grid_search.fit(X_train, y_train)
print(f"最佳参数: {grid_search.best_params_}")
print(f"最佳分数: {grid_search.best_score_:.4f}")

🚀 Boosting

核心思想

串行训练,每个新模型纠正前一个模型的错误

Text Only
Bagging vs Boosting:

Bagging (并行):
模型1 ─┐
模型2 ─┤→ 平均/投票
模型3 ─┘
各模型独立,减少方差

Boosting (串行):
模型1 → 残差 → 模型2 → 残差 → 模型3 ...
后一个模型关注前一个模型的错误
减少偏差和方差

AdaBoost (Adaptive Boosting)

核心思想

调整样本权重,让后续模型关注难分类样本

Text Only
步骤:
1. 初始化样本权重:w_i = 1/N
2. 对于 m = 1, 2, ..., M:
   a. 根据权重训练基学习器 G_m
   b. 计算加权错误率:e_m = Σ w_i × I(y_i ≠ G_m(x_i))
   c. 计算模型权重:α_m = 0.5 × ln((1-e_m)/e_m)
   d. 更新样本权重:w_i ← w_i × exp(-α_m × y_i × G_m(x_i))  (y_i, G_m ∈ {-1, +1})
   e. 归一化权重
3. 最终模型:G(x) = sign(Σ α_m × G_m(x))

代码实现

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

# AdaBoost
ada = AdaBoostClassifier(
    estimator=DecisionTreeClassifier(max_depth=1),  # 决策树桩
    n_estimators=100,
    learning_rate=0.1,
    algorithm='SAMME.R',  # 或 'SAMME'
    random_state=42
)

ada.fit(X_train, y_train)
print(f"训练准确率: {ada.score(X_train, y_train):.4f}")
print(f"测试准确率: {ada.score(X_test, y_test):.4f}")

# 查看每个基学习器的权重
print(f"\n前10个基学习器权重: {ada.estimator_weights_[:10]}")

AdaBoost 与指数损失的等价性

AdaBoost 可以从"前向分步算法 + 指数损失"的统一框架推导而来。

指数损失函数\(L(y, f(x)) = \exp(-y \cdot f(x))\),其中 \(y \in \{-1, +1\}\)\(f(x) = \sum_{m=1}^{M} \alpha_m G_m(x)\)

前向分步算法:每轮固定前 \(m-1\) 个基学习器,只优化第 \(m\) 个:

\[(\alpha_m, G_m) = \arg\min_{\alpha, G} \sum_{i=1}^{N} \exp\left[-y_i \left(f_{m-1}(x_i) + \alpha G(x_i)\right)\right]\]

\(\bar{w}_i^{(m)} = \exp(-y_i f_{m-1}(x_i))\)(只依赖前 \(m-1\) 轮,视为常数),则:

\[= \arg\min_{\alpha, G} \sum_i \bar{w}_i^{(m)} \exp(-\alpha y_i G(x_i))\]

分两步求解

  1. 固定 \(\alpha > 0\),求 \(G_m\):将求和拆为 \(y_i = G(x_i)\)(正确)和 \(y_i \neq G(x_i)\)(错误)两部分:
\[G_m^* = \arg\min_G \sum_i \bar{w}_i^{(m)} \mathbb{1}[y_i \neq G(x_i)]\]

即加权错误率最小的分类器。

  1. \(\alpha\) 求导令其为零(利用 \(G_m^*\)):
\[\frac{\partial}{\partial \alpha} \left[e^{-\alpha}\sum_{y_i=G_m} \bar{w}_i + e^{\alpha}\sum_{y_i \neq G_m} \bar{w}_i \right] = 0\]
\[\Rightarrow \alpha_m = \frac{1}{2}\ln\frac{1 - e_m}{e_m}\]

其中 \(e_m = \frac{\sum_i \bar{w}_i \mathbb{1}[y_i \neq G_m(x_i)]}{\sum_i \bar{w}_i}\) 是加权错误率。

样本权重更新也自然导出:\(\bar{w}_i^{(m+1)} = \bar{w}_i^{(m)} \exp(-\alpha_m y_i G_m(x_i))\)

结论:AdaBoost 的 \(\alpha_m\) 公式、样本权重更新规则都是指数损失下前向分步算法的精确解。这揭示了 AdaBoost 的本质是在函数空间中做指数损失的逐步优化。


🌟 Gradient Boosting (梯度提升)

核心思想

用梯度下降的思想在函数空间优化

Text Only
类比:
参数空间的梯度下降:θ ← θ - α × ∇L
函数空间的梯度下降:f(x) ← f(x) - α × g(x)

其中 g(x) 是损失函数对当前模型的负梯度(伪残差)

GBDT (Gradient Boosting Decision Tree)

Text Only
算法流程:
1. 初始化:f₀(x) = argmin_c Σ L(y_i, c)
2. 对于 m = 1, 2, ..., M:
   a. 计算伪残差:r_im = -[∂L(y_i, f(x_i))/∂f(x_i)]_{f=f_{m-1}}
   b. 用 {(x_i, r_im)} 训练回归树,得到 h_m(x)
   c. 计算步长 ρ_m (线搜索)
   d. 更新:f_m(x) = f_{m-1}(x) + ρ_m × h_m(x)
3. 最终模型:f_M(x)

常见损失函数

Text Only
回归问题:
- 平方损失:L(y, f) = (y - f)²/2
- 绝对损失:L(y, f) = |y - f|
- Huber损失:结合两者优点

分类问题:
- 对数损失(二分类):L(y, f) = log(1 + exp(-yf))
- 多分类对数损失

代码实现

Python
from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor

# 分类
gb_clf = GradientBoostingClassifier(
    n_estimators=100,        # 树的数量
    learning_rate=0.1,       # 学习率(收缩率)
    max_depth=3,             # 树深度
    min_samples_split=2,     # 节点分裂最小样本
    subsample=0.8,           # 子采样比例(随机梯度提升)
    random_state=42
)

gb_clf.fit(X_train, y_train)
print(f"GBDT分类准确率: {gb_clf.score(X_test, y_test):.4f}")

# 回归示例
from sklearn.datasets import make_regression
X_reg, y_reg = make_regression(n_samples=1000, n_features=10, noise=0.1)
X_train_r, X_test_r, y_train_r, y_test_r = train_test_split(X_reg, y_reg, test_size=0.2, random_state=42)

gb_reg = GradientBoostingRegressor(n_estimators=100, max_depth=3, random_state=42)
gb_reg.fit(X_train_r, y_train_r)
print(f"GBDT回归R²: {gb_reg.score(X_test_r, y_test_r):.4f}")

⚡ XGBoost

核心创新

eXtreme Gradient Boosting - 高效、灵活、可移植

Text Only
相比传统GBDT的改进:
1. 正则化:防止过拟合
   Obj = Σ L(y_i, ŷ_i) + Σ Ω(f_k)
   其中 Ω(f) = γT + ½λ||w||²

2. 二阶泰勒展开:更精确的优化
   L(y, ŷ+δ) ≈ L(y, ŷ) + gδ + ½hδ²

3. 分裂点查找算法:贪心 + 近似

4. 并行处理:特征级别的并行

5. 缺失值处理:自动学习分裂方向

6. 树剪枝:后剪枝(GBDT是预剪枝)

安装与使用

Bash
pip install xgboost
Python
import xgboost as xgb
from sklearn.datasets import load_breast_cancer

# 加载数据
data = load_breast_cancer()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# XGBoost分类
xgb_clf = xgb.XGBClassifier(
    n_estimators=100,
    max_depth=5,
    learning_rate=0.1,
    subsample=0.8,
    colsample_bytree=0.8,
    reg_alpha=0.1,        # L1正则
    reg_lambda=1.0,       # L2正则
    random_state=42,
    eval_metric='logloss'
)

xgb_clf.fit(X_train, y_train)
print(f"XGBoost准确率: {xgb_clf.score(X_test, y_test):.4f}")

# 特征重要性
xgb.plot_importance(xgb_clf, max_num_features=10)
plt.show()

XGBoost原生接口

Python
# 转换为DMatrix(XGBoost高效数据结构)
dtrain = xgb.DMatrix(X_train, label=y_train)
dtest = xgb.DMatrix(X_test, label=y_test)

# 参数
params = {
    'objective': 'binary:logistic',
    'max_depth': 5,
    'learning_rate': 0.1,
    'subsample': 0.8,
    'colsample_bytree': 0.8,
    'eval_metric': 'auc'
}

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

# 预测
preds = model.predict(dtest)

超参数调优

Python
from sklearn.model_selection import RandomizedSearchCV

param_dist = {
    'n_estimators': [100, 200, 500],
    'max_depth': [3, 5, 7, 10],
    'learning_rate': [0.01, 0.05, 0.1, 0.3],
    'subsample': [0.6, 0.8, 1.0],
    'colsample_bytree': [0.6, 0.8, 1.0],
    'reg_alpha': [0, 0.1, 1],
    'reg_lambda': [1, 10, 100]
}

random_search = RandomizedSearchCV(
    xgb.XGBClassifier(random_state=42, eval_metric='logloss'),
    param_distributions=param_dist,
    n_iter=50,
    cv=5,
    n_jobs=-1,
    random_state=42
)

random_search.fit(X_train, y_train)
print(f"最佳参数: {random_search.best_params_}")

💡 LightGBM

核心创新

Light Gradient Boosting Machine - 更快、更低内存

Text Only
相比XGBoost的改进:

1. 基于直方图的决策树算法
   - 连续特征离散化为k个bin
   - 时间复杂度从 O(#data × #features) 降到 O(#bin × #features)

2. 带深度限制的 Leaf-wise 生长策略
   - XGBoost: Level-wise(按层生长)
   - LightGBM: Leaf-wise(按叶子生长)
   - 相同分裂次数下,Leaf-wise损失更低

3. 直接支持类别特征

4. 并行学习优化
   - 特征并行
   - 数据并行
   - 投票并行

安装与使用

Bash
pip install lightgbm
Python
import lightgbm as lgb

# LightGBM分类
lgb_clf = lgb.LGBMClassifier(
    n_estimators=100,
    max_depth=-1,           # -1表示无限制
    learning_rate=0.1,
    num_leaves=31,          # 最大叶子数
    subsample=0.8,
    colsample_bytree=0.8,
    reg_alpha=0.1,
    reg_lambda=1.0,
    random_state=42
)

lgb_clf.fit(X_train, y_train)
print(f"LightGBM准确率: {lgb_clf.score(X_test, y_test):.4f}")

# 原生接口
train_data = lgb.Dataset(X_train, label=y_train)
valid_data = lgb.Dataset(X_test, label=y_test, reference=train_data)

params = {
    'objective': 'binary',
    'metric': 'auc',
    'boosting_type': 'gbdt',
    'num_leaves': 31,
    'learning_rate': 0.05,
    'feature_fraction': 0.9,
    'bagging_fraction': 0.8,
    'bagging_freq': 5,
    'verbose': 0
}

model = lgb.train(
    params,
    train_data,
    num_boost_round=1000,
    valid_sets=[train_data, valid_data],
    callbacks=[lgb.early_stopping(50), lgb.log_evaluation(100)]
)

🐱 CatBoost

核心特点

Categorical Boosting - 专为类别特征优化

Text Only
主要特点:
1. 自动处理类别特征
   - 不需要one-hot编码
   - 使用Ordered Target Statistics

2. 减少过拟合
   - Ordered boosting
   - 避免目标泄露

3. 更快的训练速度
   - 对称树结构
   - 高效实现

使用示例

Python
from catboost import CatBoostClassifier

# 假设有类别特征
cat_features = [0, 2, 5]  # 类别特征的列索引

cat_clf = CatBoostClassifier(
    iterations=100,
    learning_rate=0.1,
    depth=6,
    cat_features=cat_features,
    random_seed=42,
    verbose=False
)

cat_clf.fit(X_train, y_train)
print(f"CatBoost准确率: {cat_clf.score(X_test, y_test):.4f}")

🥞 Stacking (堆叠)

核心思想

用另一个模型(元学习器)组合多个基学习器的预测

Text Only
两层结构:
Layer 0(基学习器):
  - 模型1: 随机森林
  - 模型2: XGBoost
  - 模型3: SVM
  - 模型4: 逻辑回归

Layer 1(元学习器):
  - 用Layer 0的预测作为特征,训练最终模型

防止信息泄露

Text Only
问题:如果用训练好的基学习器预测训练集,元学习器会过拟合

解决:使用交叉验证生成元特征

步骤:
1. 将训练集分成K折
2. 对于每个基学习器:
   - 用K-1折训练,预测剩余1折
   - 循环K次,得到完整的元特征
3. 用所有训练数据重新训练基学习器(用于测试集预测)

代码实现

Python
from sklearn.ensemble import StackingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
import xgboost as xgb

# 定义基学习器
estimators = [
    ('rf', RandomForestClassifier(n_estimators=100, random_state=42)),
    ('xgb', xgb.XGBClassifier(n_estimators=100, random_state=42, eval_metric='logloss')),
    ('svc', SVC(probability=True, random_state=42))
]

# Stacking
stacking = StackingClassifier(
    estimators=estimators,
    final_estimator=LogisticRegression(),  # 元学习器
    cv=5,                                   # 交叉验证折数
    stack_method='predict_proba',          # 使用概率作为元特征
    n_jobs=-1
)

stacking.fit(X_train, y_train)
print(f"Stacking准确率: {stacking.score(X_test, y_test):.4f}")

# 查看各基学习器性能
for name, estimator in estimators:
    estimator.fit(X_train, y_train)
    score = estimator.score(X_test, y_test)
    print(f"{name}: {score:.4f}")

🗳️ Voting (投票)

硬投票 vs 软投票

Text Only
硬投票 (Hard Voting):
- 每个模型预测一个类别
- 多数表决
- 适用于:分类器性能相近

软投票 (Soft Voting):
- 每个模型预测类别概率
- 概率平均后取最大
- 适用于:分类器性能差异大,且能输出概率

代码实现

Python
from sklearn.ensemble import VotingClassifier

# 硬投票
voting_hard = VotingClassifier(
    estimators=estimators,
    voting='hard',
    n_jobs=-1
)

voting_hard.fit(X_train, y_train)
print(f"硬投票准确率: {voting_hard.score(X_test, y_test):.4f}")

# 软投票(需要基学习器支持predict_proba)
voting_soft = VotingClassifier(
    estimators=estimators,
    voting='soft',
    weights=[1, 2, 1],  # 可以给不同模型不同权重
    n_jobs=-1
)

voting_soft.fit(X_train, y_train)
print(f"软投票准确率: {voting_soft.score(X_test, y_test):.4f}")

📊 集成学习方法对比

方法 基学习器 训练方式 主要特点 适用场景
Bagging 同类型 并行 降低方差 高方差模型(如决策树)
Random Forest 决策树 并行 特征随机性 通用,特征重要性分析
AdaBoost 弱学习器 串行 调整样本权重 简单分类问题
GBDT 决策树 串行 梯度下降优化 表格数据,竞赛首选
XGBoost 决策树 串行+并行 正则化,高效 大规模数据
LightGBM 决策树 串行+并行 直方图算法,快速 大规模数据,类别特征
CatBoost 决策树 串行+并行 处理类别特征 大量类别特征
Stacking 不同类型 两阶段 元学习器组合 多样化模型组合
Voting 不同类型 并行 简单投票 快速集成

🎯 实践建议

选择指南

Text Only
数据规模小 (<10K):
→ Random Forest, AdaBoost

数据规模中 (10K-100K):
→ XGBoost, LightGBM, CatBoost

数据规模大 (>100K):
→ LightGBM, CatBoost

类别特征多:
→ CatBoost (无需预处理)

需要解释性:
→ Random Forest (特征重要性)

竞赛场景:
→ XGBoost + LightGBM + Stacking

调参策略

Python
# XGBoost调参顺序
# 1. 确定学习率和树数量
# 2. 调树深度和叶子数
# 3. 调正则化参数
# 4. 调采样参数

# LightGBM关键参数
params = {
    'learning_rate': 0.05,      # 先调
    'num_leaves': 31,           # 控制复杂度
    'max_depth': -1,            # 与num_leaves配合
    'min_child_samples': 20,    # 防止过拟合
    'subsample': 0.8,           # 行采样
    'colsample_bytree': 0.8,    # 列采样
    'reg_alpha': 0.1,           # L1
    'reg_lambda': 1.0           # L2
}

常见陷阱

Text Only
❌ 基学习器太强
→ 集成效果不明显,需要弱学习器

❌ 基学习器相关性太高
→ 多样性不足,无法降低方差

❌ 过拟合元学习器
→ 使用交叉验证生成元特征

❌ 忽略数据泄露
→ 确保测试集不参与任何训练过程

💡 总结

集成学习的核心:多样性 + 准确性

Text Only
多样性来源:
- 数据多样性:Bagging的Bootstrap采样
- 特征多样性:Random Forest的特征随机选择
- 算法多样性:Stacking的不同类型模型
- 序列多样性:Boosting的样本权重调整

集成策略:
- 平均:回归问题,降低方差
- 投票:分类问题,提高稳定性
- 学习:Stacking,自动学习组合权重

下一步:学习 13-降维与流形学习.md,掌握高维数据处理方法!