12 - 集成学习进阶¶
🎯 集成学习概述¶
核心思想¶
"三个臭皮匠,顶个诸葛亮"
为什么集成有效?¶
假设条件:
- 每个基学习器准确率 > 50%
- 基学习器之间相互独立
10个独立分类器,每个准确率70%:
多数投票准确率 = Σ C(10,k) × 0.7^k × 0.3^(10-k) (k=6,7,8,9,10)
≈ 85.0%
结论:集成可以显著提升性能!
集成学习分类¶
┌─────────────────────────────────────────┐
│ 集成学习方法 │
├─────────────────┬───────────────────────┤
│ 同质集成 │ 异质集成 │
│ (同类型基学习器) │ (不同类型基学习器) │
├─────────────────┼───────────────────────┤
│ • Bagging │ • Stacking │
│ • Boosting │ • Voting │
│ • Random Forest │ • Blending │
│ • XGBoost │ │
└─────────────────┴───────────────────────┘
🎲 Bagging (Bootstrap Aggregating)¶
核心思想¶
并行训练多个模型,通过自助采样构建不同训练集
步骤:
1. 从原始数据集D中有放回地抽样n次,得到D₁
2. 重复B次,得到B个数据集:D₁, D₂, ..., D_B
3. 在每个数据集上训练一个基学习器
4. 预测时:分类用投票,回归用平均
自助采样 (Bootstrap)¶
原始数据集:[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)\),其方差为:
- 当 \(B \to \infty\) 时,方差趋向 \(\rho\sigma^2\)
- 关键:相关性 \(\rho\) 越低,方差降低越多。随机森林通过随机特征选择进一步降低 \(\rho\),这就是它优于普通 Bagging 的原因。
OOB (Out-of-Bag) 估计¶
代码实现¶
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 + 随机特征选择 = 随机森林
算法流程¶
训练阶段:
对于 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)
特征重要性¶
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}")
特征重要性计算原理¶
方法1:基于不纯度减少 (Gini Importance)
- 计算每个特征在所有树中带来的不纯度减少总和
- 归一化得到重要性分数
方法2:基于排列 (Permutation Importance)
- 随机打乱某个特征的值
- 观察模型性能下降程度
- 下降越多,特征越重要
超参数调优¶
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¶
核心思想¶
串行训练,每个新模型纠正前一个模型的错误
Bagging vs Boosting:
Bagging (并行):
模型1 ─┐
模型2 ─┤→ 平均/投票
模型3 ─┘
各模型独立,减少方差
Boosting (串行):
模型1 → 残差 → 模型2 → 残差 → 模型3 ...
后一个模型关注前一个模型的错误
减少偏差和方差
AdaBoost (Adaptive Boosting)¶
核心思想¶
调整样本权重,让后续模型关注难分类样本
步骤:
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))
代码实现¶
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\) 个:
令 \(\bar{w}_i^{(m)} = \exp(-y_i f_{m-1}(x_i))\)(只依赖前 \(m-1\) 轮,视为常数),则:
分两步求解:
- 固定 \(\alpha > 0\),求 \(G_m\):将求和拆为 \(y_i = G(x_i)\)(正确)和 \(y_i \neq G(x_i)\)(错误)两部分:
即加权错误率最小的分类器。
- 对 \(\alpha\) 求导令其为零(利用 \(G_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 (梯度提升)¶
核心思想¶
用梯度下降的思想在函数空间优化
GBDT (Gradient Boosting Decision Tree)¶
算法流程:
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)
常见损失函数¶
回归问题:
- 平方损失:L(y, f) = (y - f)²/2
- 绝对损失:L(y, f) = |y - f|
- Huber损失:结合两者优点
分类问题:
- 对数损失(二分类):L(y, f) = log(1 + exp(-yf))
- 多分类对数损失
代码实现¶
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 - 高效、灵活、可移植
相比传统GBDT的改进:
1. 正则化:防止过拟合
Obj = Σ L(y_i, ŷ_i) + Σ Ω(f_k)
其中 Ω(f) = γT + ½λ||w||²
2. 二阶泰勒展开:更精确的优化
L(y, ŷ+δ) ≈ L(y, ŷ) + gδ + ½hδ²
3. 分裂点查找算法:贪心 + 近似
4. 并行处理:特征级别的并行
5. 缺失值处理:自动学习分裂方向
6. 树剪枝:后剪枝(GBDT是预剪枝)
安装与使用¶
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原生接口¶
# 转换为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)
超参数调优¶
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 - 更快、更低内存
相比XGBoost的改进:
1. 基于直方图的决策树算法
- 连续特征离散化为k个bin
- 时间复杂度从 O(#data × #features) 降到 O(#bin × #features)
2. 带深度限制的 Leaf-wise 生长策略
- XGBoost: Level-wise(按层生长)
- LightGBM: Leaf-wise(按叶子生长)
- 相同分裂次数下,Leaf-wise损失更低
3. 直接支持类别特征
4. 并行学习优化
- 特征并行
- 数据并行
- 投票并行
安装与使用¶
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 - 专为类别特征优化
主要特点:
1. 自动处理类别特征
- 不需要one-hot编码
- 使用Ordered Target Statistics
2. 减少过拟合
- Ordered boosting
- 避免目标泄露
3. 更快的训练速度
- 对称树结构
- 高效实现
使用示例¶
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 (堆叠)¶
核心思想¶
用另一个模型(元学习器)组合多个基学习器的预测
两层结构:
Layer 0(基学习器):
- 模型1: 随机森林
- 模型2: XGBoost
- 模型3: SVM
- 模型4: 逻辑回归
Layer 1(元学习器):
- 用Layer 0的预测作为特征,训练最终模型
防止信息泄露¶
问题:如果用训练好的基学习器预测训练集,元学习器会过拟合
解决:使用交叉验证生成元特征
步骤:
1. 将训练集分成K折
2. 对于每个基学习器:
- 用K-1折训练,预测剩余1折
- 循环K次,得到完整的元特征
3. 用所有训练数据重新训练基学习器(用于测试集预测)
代码实现¶
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 软投票¶
硬投票 (Hard Voting):
- 每个模型预测一个类别
- 多数表决
- 适用于:分类器性能相近
软投票 (Soft Voting):
- 每个模型预测类别概率
- 概率平均后取最大
- 适用于:分类器性能差异大,且能输出概率
代码实现¶
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 | 不同类型 | 并行 | 简单投票 | 快速集成 |
🎯 实践建议¶
选择指南¶
数据规模小 (<10K):
→ Random Forest, AdaBoost
数据规模中 (10K-100K):
→ XGBoost, LightGBM, CatBoost
数据规模大 (>100K):
→ LightGBM, CatBoost
类别特征多:
→ CatBoost (无需预处理)
需要解释性:
→ Random Forest (特征重要性)
竞赛场景:
→ XGBoost + LightGBM + Stacking
调参策略¶
# 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
}
常见陷阱¶
❌ 基学习器太强
→ 集成效果不明显,需要弱学习器
❌ 基学习器相关性太高
→ 多样性不足,无法降低方差
❌ 过拟合元学习器
→ 使用交叉验证生成元特征
❌ 忽略数据泄露
→ 确保测试集不参与任何训练过程
💡 总结¶
集成学习的核心:多样性 + 准确性
多样性来源:
- 数据多样性:Bagging的Bootstrap采样
- 特征多样性:Random Forest的特征随机选择
- 算法多样性:Stacking的不同类型模型
- 序列多样性:Boosting的样本权重调整
集成策略:
- 平均:回归问题,降低方差
- 投票:分类问题,提高稳定性
- 学习:Stacking,自动学习组合权重
下一步:学习 13-降维与流形学习.md,掌握高维数据处理方法!