08 - 集成学习:Boosting 与 Bagging

3 阅读17分钟

一句话理解集成学习: "三个臭皮匠,顶个诸葛亮" —— 把多个"普通"模型组合起来,得到一个"超强"模型。


目录

  1. 为什么集成学习有效?
  2. 集成学习全景图
  3. Bagging 原理
  4. 随机森林(回顾)
  5. Boosting 原理
  6. AdaBoost 详解
  7. 梯度提升树 GBDT
  8. XGBoost / LightGBM / CatBoost 三大神器
  9. Stacking 堆叠法
  10. 投票法 Voting
  11. 实战案例
  12. 总结与选型指南

1. 为什么集成学习有效?

1.1 生活中的类比

想象你生病了,去看医生:

  • 只看一个医生 → 他可能误诊(单模型可能过拟合或欠拟合)
  • 看三个医生,取多数意见 → 误诊概率大大降低(集成学习!)

再比如:

  • 你在某综艺节目里看到"观众投票"环节,100 个观众投票猜一个问题的答案, 正确率往往比单独一个"专家"还高。这就是统计学上著名的 "群体智慧"(Wisdom of Crowds)

1.2 数学直觉

假设我们有 3 个独立的分类器,每个准确率 70%:

单个模型正确率 = 0.7

3 个模型投票(至少 2 个正确才算正确):
P(正确) = C(3,2)*0.7^2*0.3 + C(3,3)*0.7^3
        = 3*0.49*0.3 + 0.343
        = 0.441 + 0.343
        = 0.784   ← 从 70% 提升到 78.4%!

模型越多、越独立,提升越明显。这就是集成学习的核心思想。

1.3 集成学习解决的三大问题

┌─────────────────────────────────────────────────┐
│           集成学习解决的核心问题                    │
├─────────────┬─────────────┬─────────────────────┤
│  统计问题    │  计算问题    │  表示问题             │
│(数据不够时   │(局部最优时   │(单模型表达能力        │
│ 减少选错模   │ 多起点搜索   │ 不够时,组合多个      │
│ 型的风险)    │ 更好的解)    │ 模型扩展边界)         │
└─────────────┴─────────────┴─────────────────────┘

2. 集成学习全景图

                    ┌──────────────┐
                    │   集成学习    │
                    └──────┬───────┘
                           │
           ┌───────────────┼───────────────┐
           │               │               │
     ┌─────▼─────┐  ┌─────▼─────┐  ┌──────▼─────┐
     │  Bagging   │  │  Boosting  │  │  Stacking   │
     │  (并行)    │  │  (串行)    │  │  (分层)     │
     └─────┬─────┘  └─────┬─────┘  └──────┬─────┘
           │               │               │
     ┌─────▼─────┐  ┌─────▼─────────┐  多层模型
     │ 随机森林   │  │AdaBoost       │  堆叠组合
     │           │  │GBDT           │
     └───────────┘  │XGBoost        │
                    │LightGBM       │
                    │CatBoost       │
                    └───────────────┘

关键区别:

特性BaggingBoostingStacking
模型关系并行、独立串行、依赖分层组合
数据采样有放回采样调整样本权重交叉验证划分
核心目标降低方差降低偏差综合优势
代表算法随机森林AdaBoost, GBDT各种模型堆叠
过拟合风险较低较高中等

3. Bagging 原理

3.1 什么是 Bagging?

Bagging = Bootstrap Aggregating(自助聚合)

就像你做一道菜拿不准味道,于是:

  1. 分别让 10 个厨师用 随机挑选的食材(有放回抽样)做同一道菜
  2. 把 10 道菜混合在一起(或取平均味道)
  3. 最终的菜比任何单个厨师做的都稳定好吃

3.2 Bagging 流程图

原始训练集 D(N 个样本)
     │
     │  有放回随机抽样(Bootstrap)
     │
     ├──────────┬──────────┬──────────┐
     ▼          ▼          ▼          ▼
  子集 D1     子集 D2    子集 D3    子集 Dk
  (N个样本)   (N个样本)  (N个样本)  (N个样本)
     │          │          │          │
     ▼          ▼          ▼          ▼
  模型 h1     模型 h2    模型 h3    模型 hk
     │          │          │          │
     └──────────┴─────┬────┴──────────┘
                      │
                      ▼
              ┌───────────────┐
              │  聚合 (投票/   │
              │   平均)        │
              └───────┬───────┘
                      │
                      ▼
                 最终预测结果

3.3 Bootstrap 采样的特点

每次有放回地抽 N 个样本,某个样本没有被抽到的概率:

P(某样本未被抽到) = (1 - 1/N)^N ≈ 1/e ≈ 0.368

→ 大约 36.8% 的样本不会出现在某个子集中
→ 这部分叫做 OOB(Out-Of-Bag)样本,可用来做验证!

3.4 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)

# 创建 Bagging 分类器
bagging = BaggingClassifier(
    estimator=DecisionTreeClassifier(),  # 基学习器:决策树
    n_estimators=100,                     # 100 棵树
    max_samples=0.8,                      # 每次抽 80% 样本
    bootstrap=True,                       # 有放回抽样
    oob_score=True,                       # 用 OOB 评估
    random_state=42,
    n_jobs=-1                             # 并行训练
)

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

4. 随机森林(回顾)

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

它在 Bagging 的基础上更进一步:不仅对样本随机采样,还对特征随机采样。

随机森林 vs 普通 Bagging:

普通 Bagging:  随机采样 [样本]   → 训练决策树
随机森林:      随机采样 [样本]   → 在随机选的 [特征子集] 上训练决策树
                                     ↑
                                这一步让树之间更"不同"
                                降低了树之间的相关性
                                集成效果更好!
from sklearn.ensemble import RandomForestClassifier

rf = RandomForestClassifier(
    n_estimators=200,          # 200 棵树
    max_features='sqrt',       # 每次随机选 sqrt(总特征数) 个特征
    max_depth=10,              # 限制树深度
    min_samples_leaf=5,        # 叶子最少样本数
    oob_score=True,
    random_state=42,
    n_jobs=-1
)
rf.fit(X_train, y_train)
print(f"随机森林准确率: {rf.score(X_test, y_test):.4f}")

# 特征重要性
import pandas as pd
importance = pd.Series(rf.feature_importances_).sort_values(ascending=False)
print("Top 5 重要特征:", importance.head().to_dict())

5. Boosting 原理

5.1 核心思想

Bagging 是 "大家各干各的,最后投票", Boosting 是 "后一个人专门弥补前一个人的错误"

生活类比:

你考试考砸了,老师给你出了一张新卷子,但你上次做错的题出现的频率更高。 你再考一次,老师再根据你的错题出卷子……如此循环,你会越来越强!

5.2 Boosting 串行流程图

1 轮:
┌──────────┐    训练     ┌──────────┐    预测
│ 全部样本  │ ─────────→ │  模型 h1  │ ─────────→  得到错误样本
│(等权重)   │            └──────────┘              │
└──────────┘                                       │
                                                   ▼
第 2 轮:                                    调整样本权重
┌──────────┐    训练     ┌──────────┐    (错误样本权重↑)
│ 加权样本  │ ─────────→ │  模型 h2  │ ─────────→  得到错误样本
│(错的更重) │            └──────────┘              │
└──────────┘                                       │
                                                   ▼
第 3 轮:                                    调整样本权重
┌──────────┐    训练     ┌──────────┐
│ 加权样本  │ ─────────→ │  模型 h3  │ ─────→ ......
│(错的更重) │            └──────────┘
└──────────┘

                    最终模型
                       │
                       ▼
        H(x) = α1*h1(x) + α2*h2(x) + α3*h3(x) + ...
               ↑
          每个模型的权重由其准确率决定
          (越准确的模型,权重越大)

5.3 Boosting 为什么降低偏差?

                        偏差-方差 对比

    Bagging:                          Boosting:
    ┌─────────────────┐               ┌─────────────────┐
    │ 多个高方差模型   │               │ 多个高偏差模型    │
    │ (深决策树)       │               │ (浅决策树/桩)     │
    │       ↓         │               │       ↓          │
    │ 平均后方差↓     │               │ 串行组合偏差↓    │
    │ 偏差基本不变    │               │ 方差会略微上升    │
    └─────────────────┘               └─────────────────┘

6. AdaBoost 详解

6.1 AdaBoost = Adaptive Boosting(自适应提升)

核心思想:给分错的样本加权,让后面的模型重点关注难分的样本

6.2 算法步骤

初始化:每个样本权重 = 1/N

For t = 1, 2, ..., T:

    1. 用加权样本训练弱分类器 h_t

    2. 计算加权错误率:
       ε_t = Σ(分错样本的权重)

    3. 计算模型权重:
       α_t = 0.5 * ln((1 - ε_t) / ε_t)

       ε_t 越小(越准) → α_t 越大(越重要)

    4. 更新样本权重:
       分对的样本:w = w * exp(-α_t)  ← 权重降低
       分错的样本:w = w * exp(+α_t)  ← 权重升高

    5. 归一化权重,使之和为 1

最终模型:H(x) = sign(Σ α_t * h_t(x))

6.3 权重调整可视化

第 1 轮(所有样本等权重):
样本:  [●] [●] [●] [○] [○] [○] [●] [○] [●] [○]
权重:  0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1
              ↑           ↑
            分错!        分错!

第 2 轮(分错的权重增大):
样本:  [●] [●] [●] [○] [○] [○] [●] [○] [●] [○]
权重:  0.06 0.06 0.22 0.06 0.06 0.22 0.06 0.06 0.06 0.06
                  ↑               ↑
              权重变大!        权重变大!
              (上轮分错)       (上轮分错)

第 3 轮(继续调整...):
→ 模型越来越关注"难搞"的样本
→ 最终组合所有弱模型,各自贡献不同权重

6.4 Python 示例

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

# AdaBoost 默认基学习器就是深度为1的决策树桩
ada = AdaBoostClassifier(
    estimator=DecisionTreeClassifier(max_depth=1),  # 弱学习器:决策树桩
    n_estimators=200,          # 200 轮
    learning_rate=0.1,         # 学习率(缩减每个模型的贡献)
    algorithm='SAMME.R',       # 使用概率估计的版本
    random_state=42
)

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

# 查看每一轮的训练/测试误差
import matplotlib.pyplot as plt
import numpy as np

train_errors = []
test_errors = []
for y_pred_train, y_pred_test in zip(
    ada.staged_predict(X_train), ada.staged_predict(X_test)
):
    train_errors.append(1 - np.mean(y_pred_train == y_train))
    test_errors.append(1 - np.mean(y_pred_test == y_test))

plt.plot(train_errors, label='Train Error')
plt.plot(test_errors, label='Test Error')
plt.xlabel('Boosting 轮数')
plt.ylabel('Error Rate')
plt.legend()
plt.title('AdaBoost 误差随轮数变化')
plt.show()

7. 梯度提升树 GBDT

7.1 GBDT = Gradient Boosting Decision Tree

如果说 AdaBoost 是 "给分错的样本加权", 那 GBDT 就是 "每一轮拟合上一轮的残差(错误)"

生活类比:

射箭比赛。第一箭偏了 10 厘米,你就瞄准偏差的方向补一箭(-10cm), 还差 2 厘米,再补一箭(-2cm)……不断修正,越来越接近靶心。

7.2 GBDT 残差拟合过程

目标值:     y = [90, 85, 70, 65]

第 1 棵树(粗略预测):
预测值:     ŷ1 = [80, 80, 80, 80]     ← 比如用均值
残差:       r1 = [10,  5, -10, -15]    ← y - ŷ1

第 2 棵树(拟合残差 r1):
预测残差:   ŷ2 = [8,  3, -8, -12]
新残差:     r2 = [2,  2, -2, -3]       ← r1 - ŷ2

第 3 棵树(拟合残差 r2):
预测残差:   ŷ3 = [1.5, 1.5, -1.5, -2.5]
新残差:     r3 = [0.5, 0.5, -0.5, -0.5] ← 残差越来越小!

最终预测:
F(x) = ŷ1 + η*ŷ2 + η*ŷ3 + ...
       ↑       ↑
      初始    学习率(防止过拟合)

7.3 为什么叫"梯度"提升?

损失函数的负梯度方向 ≈ 残差

对于均方误差 L = (y - F(x))^2 / 2:
  -∂L/∂F(x) = y - F(x) = 残差

→ 每一步沿着损失函数的负梯度方向更新
→ 这就是梯度下降的思想!
→ 所以叫 "梯度" 提升

对于不同的损失函数(如对数损失、Huber 损失),负梯度不一定严格等于残差, 但思想是一致的:每轮拟合负梯度方向

7.4 Python 示例

from sklearn.ensemble import GradientBoostingClassifier

gbdt = GradientBoostingClassifier(
    n_estimators=200,          # 200 棵树
    learning_rate=0.1,         # 学习率
    max_depth=3,               # 每棵树深度(GBDT 通常用浅树)
    subsample=0.8,             # 行采样比例(类似 Bagging 的效果)
    min_samples_leaf=5,
    random_state=42
)

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

8. XGBoost / LightGBM / CatBoost 三大神器

这三个是 GBDT 的"加强版",是 Kaggle 竞赛中的常胜将军。

8.1 三者关系

        GBDT(基础版)
         │
    ┌────┼────────────────┐
    │    │                │
    ▼    ▼                ▼
 XGBoost  LightGBM     CatBoost
 (2014)   (2017)        (2017)
 微软陈天奇  微软           Yandex

 正则化     直方图加速      类别特征
 二阶导数   GOSS采样        有序提升
 稀疏感知   按叶子生长      减少过拟合

8.2 详细对比

特性XGBoostLightGBMCatBoost
速度中等最快较慢
内存较大最小中等
类别特征需手动编码直接支持原生最优支持
树生长策略按层生长(Level-wise)按叶子生长(Leaf-wise)对称树
过拟合控制需注意最好
小数据集容易过拟合
GPU 支持
适合场景通用大数据集有大量类别特征

8.3 树生长策略对比图

Level-wise (XGBoost 默认):
按层分裂,每层所有叶子都分裂

         [根]
        /    \
      [A]    [B]         ← 第1层全部分裂
     / \    / \
   [C] [D][E] [F]       ← 第2层全部分裂

优点:不容易过拟合
缺点:可能做了不必要的分裂


Leaf-wise (LightGBM):
每次只分裂增益最大的叶子

         [根]
        /    \
      [A]    [B]B 增益更大
              / \
            [E] [F]E 增益更大
            / \
          [G] [H]

优点:同等叶子数下损失更低
缺点:树不平衡,可能过拟合

8.4 Python 示例

# ==================== XGBoost ====================
import xgboost as xgb

xgb_clf = xgb.XGBClassifier(
    n_estimators=200,
    learning_rate=0.1,
    max_depth=5,
    subsample=0.8,
    colsample_bytree=0.8,      # 列采样
    reg_alpha=0.1,              # L1 正则化
    reg_lambda=1.0,             # L2 正则化
    use_label_encoder=False,
    eval_metric='logloss',
    random_state=42,
    n_jobs=-1
)
xgb_clf.fit(X_train, y_train,
            eval_set=[(X_test, y_test)],
            verbose=False)
print(f"XGBoost 准确率: {xgb_clf.score(X_test, y_test):.4f}")


# ==================== LightGBM ====================
import lightgbm as lgb

lgb_clf = lgb.LGBMClassifier(
    n_estimators=200,
    learning_rate=0.1,
    max_depth=-1,               # 不限深度
    num_leaves=31,              # 最大叶子数(关键参数!)
    subsample=0.8,
    colsample_bytree=0.8,
    reg_alpha=0.1,
    reg_lambda=1.0,
    random_state=42,
    n_jobs=-1,
    verbose=-1
)
lgb_clf.fit(X_train, y_train)
print(f"LightGBM 准确率: {lgb_clf.score(X_test, y_test):.4f}")


# ==================== CatBoost ====================
from catboost import CatBoostClassifier

cat_clf = CatBoostClassifier(
    iterations=200,
    learning_rate=0.1,
    depth=6,
    l2_leaf_reg=3,              # L2 正则化
    random_state=42,
    verbose=0                   # 不打印训练过程
)
cat_clf.fit(X_train, y_train)
print(f"CatBoost 准确率: {cat_clf.score(X_test, y_test):.4f}")

9. Stacking 堆叠法

9.1 核心思想

Stacking 的思路完全不同于 Bagging 和 Boosting:

就像一个公司做决策:

  • 第一层:不同部门(销售、技术、财务)各自给出意见
  • 第二层:CEO 综合所有部门的意见,做出最终决定

CEO 不需要懂每个部门的细节,只需要学会"如何综合意见"。

9.2 Stacking 架构图

                    原始训练数据
                        │
        ┌───────────────┼───────────────┐
        │               │               │
        ▼               ▼               ▼
  ┌───────────┐  ┌───────────┐  ┌───────────┐
  │ 模型 1     │  │ 模型 2     │  │ 模型 3     │    第一层
  │ (随机森林) │  │ (XGBoost)  │  │ (SVM)     │   (基学习器)
  └─────┬─────┘  └─────┬─────┘  └─────┬─────┘
        │               │               │
        ▼               ▼               ▼
     预测 P1          预测 P2         预测 P3
        │               │               │
        └───────────────┼───────────────┘
                        │
                        ▼
              ┌──────────────────┐
              │ 新特征矩阵       │
              │ [P1, P2, P3]     │         第二层
              └────────┬─────────┘        (元学习器)
                       │
                       ▼
              ┌──────────────────┐
              │ 元模型            │
              │ (逻辑回归)        │
              └────────┬─────────┘
                       │
                       ▼
                  最终预测结果

9.3 关键细节:用交叉验证防止过拟合

直接在训练集上预测再当特征,会导致严重过拟合!正确做法:

5折交叉验证生成第一层预测:

训练集分5份:[Fold1] [Fold2] [Fold3] [Fold4] [Fold5]

第1次:用 Fold2-5 训练 → 预测 Fold1 → 得到 Fold1 的预测值
第2次:用 Fold1,3-5 训练 → 预测 Fold2 → 得到 Fold2 的预测值
第3次:用 Fold1,2,4,5 训练 → 预测 Fold3 → ...
第4次:...
第5次:...

拼接所有 Fold 的预测值 → 第二层的训练特征(没有泄漏!)

9.4 Python 示例

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

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

# 定义 Stacking
stacking = StackingClassifier(
    estimators=estimators,
    final_estimator=LogisticRegression(),  # 第二层用逻辑回归
    cv=5,                                   # 5折交叉验证
    stack_method='predict_proba',           # 用概率作为特征
    n_jobs=-1
)

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

10. 投票法 Voting

10.1 硬投票 vs 软投票

假设有 3 个模型对一个样本进行分类:

硬投票(Hard Voting)—— 少数服从多数:
  模型 A 预测:类别 1
  模型 B 预测:类别 0
  模型 C 预测:类别 1
  → 最终结果:类别 12票 vs 1票)

软投票(Soft Voting)—— 根据概率平均:
  模型 AP(类别1) = 0.9,  P(类别0) = 0.1
  模型 BP(类别1) = 0.3,  P(类别0) = 0.7
  模型 C:P(类别1) = 0.8,  P(类别0) = 0.2

  平均:P(类别1) = (0.9+0.3+0.8)/3 = 0.667
        P(类别0) = (0.1+0.7+0.2)/3 = 0.333
  → 最终结果:类别 1

软投票通常效果更好,因为它利用了概率信息。

10.2 Python 示例

from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier

# 硬投票
hard_voting = VotingClassifier(
    estimators=[
        ('lr', LogisticRegression(max_iter=1000)),
        ('svc', SVC()),
        ('dt', DecisionTreeClassifier())
    ],
    voting='hard'
)

# 软投票(需要模型支持 predict_proba)
soft_voting = VotingClassifier(
    estimators=[
        ('lr', LogisticRegression(max_iter=1000)),
        ('svc', SVC(probability=True)),       # SVC 需开启 probability
        ('dt', DecisionTreeClassifier())
    ],
    voting='soft'
)

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

11. 实战案例

11.1 案例一:信用评分(Credit Scoring)

这是金融行业最常见的机器学习应用之一。

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import roc_auc_score, classification_report
import xgboost as xgb
import lightgbm as lgb

# ---------- 1. 模拟信用评分数据 ----------
np.random.seed(42)
n = 5000
data = pd.DataFrame({
    'age': np.random.randint(18, 70, n),
    'income': np.random.lognormal(10, 1, n),
    'debt_ratio': np.random.uniform(0, 1, n),
    'num_credit_lines': np.random.randint(0, 20, n),
    'late_payments_30d': np.random.poisson(1, n),
    'late_payments_90d': np.random.poisson(0.3, n),
    'credit_utilization': np.random.uniform(0, 1, n),
})
# 简化的违约标签
prob = 1 / (1 + np.exp(-(
    -3 + 0.02 * data['debt_ratio'] * 5
    + 0.5 * data['late_payments_90d']
    - 0.01 * data['income'] / 10000
)))
data['default'] = (np.random.random(n) < prob).astype(int)

X = data.drop('default', axis=1)
y = data['default']

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42
)

# ---------- 2. 训练多个模型并对比 ----------
models = {
    'XGBoost': xgb.XGBClassifier(
        n_estimators=200, learning_rate=0.05, max_depth=4,
        subsample=0.8, colsample_bytree=0.8,
        eval_metric='auc', random_state=42, n_jobs=-1
    ),
    'LightGBM': lgb.LGBMClassifier(
        n_estimators=200, learning_rate=0.05, num_leaves=31,
        subsample=0.8, colsample_bytree=0.8,
        random_state=42, verbose=-1, n_jobs=-1
    ),
}

for name, model in models.items():
    model.fit(X_train, y_train)
    y_prob = model.predict_proba(X_test)[:, 1]
    auc = roc_auc_score(y_test, y_prob)
    print(f"{name} AUC: {auc:.4f}")

# ---------- 3. 特征重要性分析 ----------
# 以 LightGBM 为例
feature_imp = pd.DataFrame({
    'feature': X.columns,
    'importance': models['LightGBM'].feature_importances_
}).sort_values('importance', ascending=False)
print("\n特征重要性排名:")
print(feature_imp.to_string(index=False))

11.2 案例二:Kaggle 竞赛中的集成策略

Kaggle 大神们的常用套路:

Kaggle 竞赛典型集成流程:

Step 1: 特征工程(占 70% 的时间!)
  
  
Step 2: 训练多个不同类型的模型
  ├── LightGBM (不同参数组合 × 3)
  ├── XGBoost (不同参数组合 × 3)
  ├── CatBoost  2)
  ├── 神经网络  2)
  └── 其他模型 ...
  
  
Step 3: 二层 Stacking  加权平均
  ├── 方法 A: Stacking(用逻辑回归做元学习器)
  └── 方法 B: 加权平均(根据验证集表现分配权重)
  
  
Step 4: 后处理 + 提交
# 简单的加权平均融合
from sklearn.metrics import roc_auc_score

# 假设有三个模型的预测概率
pred_xgb = models['XGBoost'].predict_proba(X_test)[:, 1]
pred_lgb = models['LightGBM'].predict_proba(X_test)[:, 1]

# 方法 1: 简单平均
pred_avg = (pred_xgb + pred_lgb) / 2
print(f"简单平均 AUC: {roc_auc_score(y_test, pred_avg):.4f}")

# 方法 2: 加权平均(根据各模型的验证集表现)
# 假设 XGBoost AUC=0.85, LightGBM AUC=0.87
w_xgb, w_lgb = 0.85, 0.87
w_sum = w_xgb + w_lgb
pred_weighted = (w_xgb * pred_xgb + w_lgb * pred_lgb) / w_sum
print(f"加权平均 AUC: {roc_auc_score(y_test, pred_weighted):.4f}")

# 方法 3: 用 Optuna 自动搜索最优权重
# (实际比赛中推荐这种方法)

11.3 超参数调优小贴士

# 使用 Optuna 进行 LightGBM 调参(推荐!)
# pip install optuna

import optuna
from sklearn.model_selection import cross_val_score

def objective(trial):
    params = {
        'n_estimators': trial.suggest_int('n_estimators', 100, 500),
        'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.3, log=True),
        'num_leaves': trial.suggest_int('num_leaves', 15, 127),
        'max_depth': trial.suggest_int('max_depth', 3, 12),
        'subsample': trial.suggest_float('subsample', 0.5, 1.0),
        'colsample_bytree': trial.suggest_float('colsample_bytree', 0.5, 1.0),
        'reg_alpha': trial.suggest_float('reg_alpha', 1e-8, 10.0, log=True),
        'reg_lambda': trial.suggest_float('reg_lambda', 1e-8, 10.0, log=True),
        'random_state': 42,
        'verbose': -1,
        'n_jobs': -1,
    }
    model = lgb.LGBMClassifier(**params)
    scores = cross_val_score(model, X_train, y_train,
                              cv=5, scoring='roc_auc')
    return scores.mean()

# 运行优化
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=50, show_progress_bar=True)

print(f"最优 AUC: {study.best_value:.4f}")
print(f"最优参数: {study.best_params}")

12. 总结与选型指南

12.1 一张图总结

┌────────────────────────────────────────────────────────────────┐
│                    集成学习选型指南                               │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  数据量小 (<1万)      → CatBoost / XGBoost                     │
│  数据量大 (>100万)    → LightGBM(速度最快)                     │
│  类别特征多           → CatBoost(原生支持)                     │
│  需要可解释性         → 单棵GBDT / 随机森林                     │
│  追求极致精度         → Stacking 多模型融合                      │
│  快速建立基线         → LightGBM 默认参数                       │
│  防止过拟合是首要     → 随机森林 / Bagging                      │
│  Kaggle 竞赛          → LightGBM + XGBoost + CatBoost 融合     │
│                                                                │
└────────────────────────────────────────────────────────────────┘

12.2 常见面试题速答

Q: Bagging 和 Boosting 的区别?

Bagging: 并行,降方差,对异常值不敏感,不容易过拟合
Boosting: 串行,降偏差,对异常值敏感,可能过拟合

Q: 随机森林为什么不容易过拟合?

1. Bootstrap 采样引入随机性
2. 特征随机选择降低树之间的相关性
3. 大量树的平均进一步平滑预测

Q: XGBoost 比 GBDT 好在哪?

1. 用了二阶导数(Taylor 二阶展开),更精确
2. 加入了正则化项(L1 + L2),防止过拟合
3. 支持稀疏数据的自动处理
4. 内置列采样(类似随机森林的特征采样)
5. 支持并行计算(特征维度的并行,非树的并行)
6. 支持自定义损失函数

Q: 为什么 LightGBM 比 XGBoost 快?

1. 直方图算法:将连续特征离散化为直方图,减少计算量
2. GOSS(Gradient-based One-Side Sampling):保留梯度大的样本
3. EFB(Exclusive Feature Bundling):合并互斥的稀疏特征
4. Leaf-wise 生长:比 Level-wise 更高效

12.3 学习路线建议

初学者路线:

1. 理解决策树 ✓
        ↓
2. 理解随机森林(Bagging + 特征随机)  ← 你在这里
        ↓
3. 理解 AdaBoost(权重调整)
        ↓
4. 理解 GBDT(残差拟合)
        ↓
5. 上手 XGBoost / LightGBM(调参实战)
        ↓
6. 尝试 Stacking 融合
        ↓
7. 参加 Kaggle 竞赛实战

附录:关键术语速查

术语英文含义
集成学习Ensemble Learning组合多个模型
基学习器Base Learner被组合的单个模型
弱学习器Weak Learner比随机猜测略好的模型
有放回采样Bootstrap Sampling抽完放回再抽
袋外数据Out-of-Bag (OOB)没被抽到的样本
残差Residual真实值 - 预测值
学习率Learning Rate控制每步更新幅度
正则化Regularization防止过拟合的惩罚
元学习器Meta LearnerStacking 的第二层模型
特征重要性Feature Importance各特征对模型的贡献度

下一篇预告: 09 - 模型评估与调优:交叉验证、网格搜索与贝叶斯优化

如果这篇文章对你有帮助,请给个 Star 吧!有问题欢迎提 Issue 讨论。