ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

从零开始学AI22——集成学习

从零开始学AI22——集成学习

集成学习核心思想是“三个臭皮匠,顶个诸葛亮”,通过组合多个弱模型提升整体性能。三大流派:Bagging(如随机森林)并行训练、投票降方差,适合抗噪稳定场景;Boosting(如XGBoost)串行纠错、逐次优化,精准度高;Stacking多层融合,专家会诊,追求极致精度。在结构化数据领域,集成学习(尤其XGBoost、随机森林)是工业界与竞赛首选,广泛应用于风控、推荐、广告点击率预测等任务。非结构化数据(图像、语音)则由深度学习主导。数学本质:Bagging降低方差,Boosting通过梯度下降减偏差。代码实证显示,GBDT通过不断拟合残差逼近最优解,效果显著。

简单理解

集成学习 (Ensemble Learning)的核心思想就一句话:“三个臭皮匠,顶个诸葛亮”。

在机器学习里,我们不仅可以训练一个模型(比如一棵决策树),我们还可以训练一大堆模型,然后让它们组团来做决定。

通常,这一大堆模型(臭皮匠)合起来的效果,会比单独任何一个模型(哪怕是诸葛亮)都要好。


集成学习的“三大门派”

根据这堆模型怎么组团,集成学习分成了三个主要流派:

1. Bagging (装袋法) —— “民主投票”
  • 代表作:随机森林 (Random Forest)
  • 口号:大家各学各的,最后投票。
  • 怎么做:
    1. 找 100 个学生(模型)。
    2. 给每个学生发不一样的复习资料(随机采样训练数据)。
    3. 大家同时开始独立学习(并行训练),互不干扰。
    4. 考试时,大家一起做同一道题。
    5. 结果:少数服从多数。如果 80 人选 A,20 人选 B,那就选 A。
  • 优点:极其稳定!就算有个别学生学傻了(过拟合),大部分人还是正常的,整体结果不会跑偏。
2. Boosting (提升法) —— “接力补课”
  • 代表作:AdaBoost, GBDT, XGBoost, LightGBM
  • 口号:专门盯着错题学。
  • 怎么做:
    1. 先让第 1 个学生做题。
    2. 第 1 个学生做错的题目,会被挑出来,加倍重视。
    3. 第 2 个学生专门去学这部分做错的题。
    4. 第 2 个学生又做错的题,交给第 3 个学生去攻克……
    5. 结果:是一步步串行训练出来的。
  • 优点:极其精准!后面的学生都是为了弥补前面学生的不足而存在的,所以准确率通常比 Bagging 更高(打比赛的神器)。
3. Stacking (堆叠法) —— “专家会诊”
  • 代表作:各类比赛的冠军方案常用。
  • 口号:用一个老板来管这帮打工的。
  • 怎么做:
    1. 第一层:训练一堆不同的模型(SVM、决策树、神经网络...),让它们分别预测结果。
    2. 第二层:训练一个**“元模型” (Meta-Model)**(通常是逻辑回归)。
    3. 结果:第二层的模型把第一层所有模型的预测结果当作输入特征,学习如何组合它们才能得到最佳答案。
    4. 比如:老板发现 A 模型擅长做数学,B 模型擅长做语文,老板就会根据题目类型,动态分配权重。

一句话总结

  • Bagging (随机森林):并行训练,少数服从多数,为了防过拟合(降方差)。
  • Boosting (XGBoost):串行训练,死磕错题,为了提准确率(降偏差)。
  • Stacking:多层结构,集百家之长,为了刷榜拿冠军。

应用场景

集成学习(尤其是Random Forest和XGBoost/LightGBM)是目前工业界和竞赛圈的绝对主力。

在深度学习(Deep Learning)统治图像和语音领域的同时,集成学习统治了几乎所有的表格数据(Tabular Data)领域。

只要你的数据是存放在 Excel 或 SQL 数据库里的结构化数据,首选一定是集成学习。


1. 随机森林 (Random Forest) 的应用场景

关键词:稳定、抗噪、无脑好用(不用调参效果也不错)。

  • 金融风控(银行/互金):
    • 场景:信用卡反欺诈、贷款违约预测。
    • 优势:银行数据通常噪音很大(脏数据多),且对稳定性要求极高。随机森林不容易受异常值影响,哪怕有个别数据填错了,几十棵树投票也能把错误抵消掉。
  • 医疗病理分析:
    • 场景:基因测序分类、疾病预测。
    • 优势:医疗数据通常特征维度极高(几万个基因),样本数却很少(几百个病人)。随机森林处理高维数据能力强,且能给出特征重要性 (Feature Importance),告诉医生哪个基因最关键。
  • 电商推荐系统的粗排(Recall):
    • 场景:从百万商品里快速筛选出几千个候选集。
    • 优势:可以并行计算,速度快,适合处理大规模数据。

2. XGBoost / LightGBM 的应用场景

关键词:精准、快、打比赛神器、工业界核心。
(这是目前数据挖掘岗位的必备技能)

  • 搜索与广告点击率预估 (CTR Prediction):
    • 场景:决定给用户展示哪个广告,或者淘宝搜出来什么商品排在前面。
    • 优势:这种场景对准确率要求极高(提升 0.1% 的点击率就是几亿的收入)。Boosting 算法通过死磕残差(Residual),能把预测误差压到极致。
  • 销售预测 / 库存管理:
    • 场景:预测下个月这件衣服能卖多少件?
    • 优势:这类问题通常包含时间序列特征和各种复杂的非线性关系。GBDT 类算法能很好地拟合这些非线性规律。
  • 数据竞赛 (Kaggle/天池):
    • 场景:几乎所有非图像/视频类的比赛。
    • 地位:如果你打比赛不用 XGBoost/LightGBM/CatBoost,你基本进不了前 10%。它们就是当今表格数据领域的“屠龙刀”。

3. Stacking 的应用场景

关键词:刷分、极致精度、复杂。

  • 高精尖的预测任务:
    • 场景:顶级对冲基金的股价预测、地震波预测、精密仪器故障预测。
    • 优势:当单一模型(无论是 XGBoost 还是 神经网络)都无法再提升哪怕 0.001 的精度时,就把它们堆叠起来(Stacking)。虽然训练很慢、模型很重、部署很麻烦,但在赢者通吃的领域,这点提升值得付出代价。

集成学习什么时候不好用?

  1. 非结构化数据:
    • 图像、视频、音频:这些领域是CNN/Transformer(深度学习)的天下。集成学习处理像素级别的特征效果很差。
  2. 自然语言处理 (NLP):
    • 虽然以前有用,但现在基本也被BERT/GPT(Transformer)取代了。
  3. 对实时性要求极高且资源受限(如嵌入式设备):
    • 虽然 LightGBM 已经很快了,但如果要在一块小芯片上跑几千棵树,内存和耗电量可能还是扛不住。这时候简单的逻辑回归或单棵决策树可能更合适。

总结

  • 处理 Excel 表格数据?→→XGBoost / LightGBM(首选)。
  • 数据太脏、怕过拟合?→→随机森林。
  • 处理图片/声音?→→ 左转去找深度学习。

数学推导

集成学习的数学推导主要分为两派:

  1. Bagging (随机森林)的推导核心是统计学中的偏差-方差分解 (Bias-Variance Decomposition)。
  2. Boosting (GBDT/XGBoost)的推导核心是加法模型 (Additive Model)和前向分步算法 (Forward Stagewise Algorithm)。

我们重点讲这两个核心逻辑。


一、 Bagging 的数学原理:降低方差 (Variance Reduction)

核心问题:为什么把一堆“差不多”的模型平均一下,效果就会变好?

假设我们有 nn 个独立的随机变量(模型预测值) X1,X2,...,XnX1​,X2​,...,Xn​。
假设它们都服从正态分布,方差都是 σ2σ2。

  1. 单个模型的方差:
    Var(Xi)=σ2Var(Xi​)=σ2
    (这意味着单个模型很容易受数据波动影响,发挥不稳定)

  2. 集成模型的方差(取平均):
    令 Xˉ=1n∑XiXˉ=n1​∑Xi​。
    根据方差的性质(独立变量和的方差等于方差的和):
    Var(Xˉ)=Var(1n∑Xi)=1n2∑Var(Xi)=1n2⋅nσ2=σ2nVar(Xˉ)=Var(n1​∑Xi​)=n21​∑Var(Xi​)=n21​⋅nσ2=nσ2​

推导结论:
通过对 nn 个独立模型取平均,整体的方差变成了原来的1nn1​!
这就是随机森林为什么特别稳的数学原因。它通过“民主投票”极大地降低了模型的**过拟合(高方差)**风险。

(注:实际中模型很难完全独立,所以方差降低幅度没有 1/n 这么完美,但依然效果显著)


二、 Boosting 的数学原理:拟合残差 (Residual Fitting)

核心问题:为什么一个个接力学,就能越来越准?

Boosting 的本质是一个加法模型:
Fm(x)=∑t=1mαtht(x)Fm​(x)=∑t=1m​αt​ht​(x)

  • Fm(x)Fm​(x):最终的强学习器。
  • ht(x)ht​(x):第 tt 步训练出来的弱学习器(小树)。
  • αtαt​:第 tt 棵树的权重。

我们以GBDT (Gradient Boosting Decision Tree)为例,它的推导最直观。

推导过程:

  1. 第一步:训练第一棵树 h1(x)h1​(x) 去拟合真实值 yy。

    • 预测值:F1(x)=h1(x)F1​(x)=h1​(x)
    • 残差 (Residual):r1=y−F1(x)r1​=y−F1​(x)
  2. 第二步:训练第二棵树 h2(x)h2​(x)。

    • 关键点:这棵树的目标不再是拟合 yy,而是拟合残差 r1r1​。
    • 它想让 h2(x)≈y−F1(x)h2​(x)≈y−F1​(x)。
    • 更新强模型:F2(x)=F1(x)+h2(x)F2​(x)=F1​(x)+h2​(x)。
  3. 第 m 步(数学泛化):
    我们定义一个损失函数 L(y,F(x))L(y,F(x))(比如均方误差)。
    我们要找到第 mm 棵树 hmhm​,使得加入它之后,整体损失 LL 最小。

    利用泰勒展开(一阶展开):
    L(y,Fm−1(x)+hm(x))≈L(y,Fm−1(x))+∂L∂F⋅hm(x)L(y,Fm−1​(x)+hm​(x))≈L(y,Fm−1​(x))+∂F∂L​⋅hm​(x)

    为了让损失变小(小于上一轮的损失),我们需要让这一项变负:
    ∂L∂F⋅hm(x)<0∂F∂L​⋅hm​(x)<0

    最快让它变负的方法,就是让 hm(x)hm​(x) 沿着负梯度方向走:
    hm(x)≈−∂L(y,Fm−1(x))∂Fm−1(x)hm​(x)≈−∂Fm−1​(x)∂L(y,Fm−1​(x))​

推导结论:
GBDT 的每一棵新树,都在拟合上一轮模型的负梯度(对于均方误差来说,负梯度正好就是残差 y−y^y−y^​)。
通过不断地把“负梯度”加进去,总误差就会一步步趋近于 0。

  • XGBoost 的改进:它在泰勒展开时,保留了二阶导数(Hessian矩阵),不仅看梯度下降的方向,还看曲率,所以走得更准、更快。

三、 总结对比

维度Bagging (随机森林)Boosting (GBDT/XGBoost)
数学目标降低方差 (Variance)降低偏差 (Bias)
模型关系并行独立 (iid)串行依赖 (Sequential)
核心操作平均 / 投票梯度下降 (在函数空间)
推导逻辑统计学大数定律优化理论 (泰勒展开)

一句话总结数学本质:

  • Bagging是通过平摊风险来求稳。
  • Boosting是通过梯度下降来求准。

代码实例

我们将通过一个**波士顿房价预测(回归任务)**的案例,手写实现Boosting (GBDT)的核心逻辑,并与 Sklearn 的标准库进行对比。

我们将深入到“残差”层面,让你亲眼看到每一棵树是如何去拟合上一棵树剩下的“烂摊子”的。

Python 代码实现

请确保安装了sklearn,numpy,matplotlib。

import numpy as np import matplotlib.pyplot as plt from sklearn.tree import DecisionTreeRegressor from sklearn.ensemble import GradientBoostingRegressor, RandomForestRegressor from sklearn.metrics import mean_squared_error from sklearn.datasets import fetch_california_housing from sklearn.model_selection import train_test_split # ========================================== # 1. 准备数据 (加州房价数据集) # ========================================== # 为了方便演示和画图,我们只取前 500 个样本,且只用 1 个特征 (MedInc: 收入中位数) housing = fetch_california_housing() X = housing.data[:500, [0]] # 只取第0列特征 y = housing.target[:500] # 房价 # 排序 X 以便画图好看 (不影响训练) sort_idx = X.flatten().argsort() X = X[sort_idx] y = y[sort_idx] # 划分训练测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # ========================================== # 2. 手写 GBDT (Gradient Boosting) 核心逻辑 # ========================================== print(">>> 手写 GBDT 训练过程展示") # 超参数 n_estimators = 3 # 我们只训练 3 棵树,方便一步步看 learning_rate = 1.0 # 学习率设为 1.0 (为了让效果更明显) # 存储每一棵小树 trees = [] # 初始预测值 (通常设为 y 的平均值,这里简化为 0) f_m = np.zeros(len(y_train)) plt.figure(figsize=(15, 5)) # --- 开始串行训练 --- for i in range(n_estimators): # 1. 计算残差 (Residual) # 核心公式: r = y_true - y_current_pred # 对于均方误差(MSE),负梯度正好就是残差 residuals = y_train - f_m # 2. 训练一棵小树去拟合这个残差 (不是拟合 y !) # 这里的树深度通常很浅 (weak learner) tree = DecisionTreeRegressor(max_depth=2, random_state=42) tree.fit(X_train, residuals) trees.append(tree) # 3. 更新强学习器的预测值 # F_new(x) = F_old(x) + lr * tree.predict(x) r_pred = tree.predict(X_train) f_m += learning_rate * r_pred # --- 可视化这一步 --- plt.subplot(1, 3, i+1) # 画出残差数据点 plt.scatter(X_train, residuals, color='r', marker='+', label='Residuals (Errors)') # 画出这棵树拟合的曲线 # 为了画平滑曲线,生成一点测试数据 X_plot = np.linspace(X.min(), X.max(), 100).reshape(-1, 1) y_plot = tree.predict(X_plot) plt.plot(X_plot, y_plot, 'g-', linewidth=3, label=f'Tree {i+1} Fitting') plt.title(f"Step {i+1}: Fitting Residuals\nResidual Range: [{residuals.min():.1f}, {residuals.max():.1f}]") plt.xlabel("Feature (Income)") plt.legend() plt.grid(True, alpha=0.3) plt.tight_layout() plt.show() # --- 手写模型的最终预测 --- y_pred_manual = np.zeros(len(y_test)) for tree in trees: y_pred_manual += learning_rate * tree.predict(X_test) mse_manual = mean_squared_error(y_test, y_pred_manual) print(f"手写 GBDT (3棵树) MSE: {mse_manual:.4f}") # ========================================== # 3. 对比 Sklearn 官方实现 # ========================================== print("\n>>> Sklearn 官方模型对比") # GBDT gbdt = GradientBoostingRegressor(n_estimators=3, learning_rate=1.0, max_depth=2, random_state=42) gbdt.fit(X_train, y_train) y_pred_gbdt = gbdt.predict(X_test) mse_gbdt = mean_squared_error(y_test, y_pred_gbdt) print(f"Sklearn GBDT (3棵树) MSE: {mse_gbdt:.4f}") # 随机森林 (Bagging 对比) rf = RandomForestRegressor(n_estimators=3, max_depth=2, random_state=42) rf.fit(X_train, y_train) y_pred_rf = rf.predict(X_test) mse_rf = mean_squared_error(y_test, y_pred_rf) print(f"Sklearn RF (3棵树) MSE: {mse_rf:.4f}") # ========================================== # 4. 最终效果可视化 (整体拟合曲线) # ========================================== plt.figure(figsize=(10, 6)) plt.scatter(X_test, y_test, color='black', alpha=0.5, label='True Data') # 为了画线 X_grid = np.linspace(X.min(), X.max(), 500).reshape(-1, 1) # GBDT 预测线 y_grid_gbdt = gbdt.predict(X_grid) plt.plot(X_grid, y_grid_gbdt, 'r-', linewidth=2, label='GBDT Prediction') # RF 预测线 y_grid_rf = rf.predict(X_grid) plt.plot(X_grid, y_grid_rf, 'b--', linewidth=2, label='Random Forest Prediction') plt.title("GBDT vs Random Forest Fitting Curve") plt.xlabel("MedInc") plt.ylabel("House Price") plt.legend() plt.show()

代码深度解析

1. 手写 GBDT 的三张子图

运行代码后,你会看到三张连着的图,这就是 Boosting 的灵魂:

  • Step 1:第一棵树在拟合原始数据的误差。此时残差很大(比如在 -2 到 2 之间)。
  • Step 2:第二棵树不看原始房价了,它只看 Step 1 剩下的残差。你会发现 Y 轴的范围变小了(误差变小了)。
  • Step 3:第三棵树继续拟合 Step 2 剩下的残差。
  • 结论:每一棵树都在做一个“修补匠”,把上一棵树没预测准的地方(残差)给补上。最后把这三棵树的预测值加起来,就是一个很准的预测。
2. GBDT vs RF 的对比

在最后的对比图中:

  • Random Forest (蓝虚线):线条通常比较平滑(或阶梯状更明显),因为它是在取平均,倾向于“中庸”,方差小。
  • GBDT (红实线):线条会试图去捕捉数据的每一个拐点(拟合得更紧密),因为它在不断修正误差,偏差小。
3. 为什么 Sklearn 和手写结果略有不同?

你会发现mse_manual和mse_gbdt非常接近但不是完全一样(比如 0.98 vs 0.96)。
这是因为 Sklearn 的GradientBoostingRegressor在内部做了一些初始化优化(比如初始预测值不是 0,而是训练集的均值或中位数),而且分裂节点的算法细节可能有微小差异。但核心的“拟合残差”逻辑是完全一致的。

返回列表