课程承诺:1 个核心概念(Boosting + 梯度提升树)+1 个核心思想(串行纠错)+1 段冠军级实战代码。学完你将掌握表格数据的终极算法,它是几乎所有结构化数据竞赛的冠军方案,也是工业界高精度场景的首选。

本节课目标:彻底搞懂 Boosting 和 Bagging 的本质区别,理解梯度提升树如何通过 "一步步纠错" 达到极致精度,实战对比 XGBoost/LightGBM 与随机森林的性能,掌握工业界最常用的调参技巧。


🧩 先回答上一课的思考题

问题:有没有串行训练的集成学习算法?答案:有,就是Boosting(提升法),也是本节课的主角。

  • Bagging(随机森林):并行训练,所有树独立生长,最后投票
  • Boosting(梯度提升树):串行训练,每一棵树都专门纠正前一棵树的错误,最后加权求和

这是两种完全不同的集成思路,而 Boosting 的效果通常比 Bagging 更好。


🧠 第一个核心概念:梯度提升树(GBDT)

最通俗的 Boosting 类比:学生备考

把模型想象成一个正在备考的学生:

  1. 第一次做模拟题,考了 60 分,错了很多题
  2. 他把所有错题整理成一个错题本,专门做这些错题
  3. 第二次做错题本,又错了一些题,再整理成新的错题本
  4. 重复这个过程,直到所有错题都做对
  5. 最终考试,他能考 95 分以上

梯度提升树就是这个过程的完美复刻:

  • 第一棵树:拟合原始数据,得到一个初步的预测
  • 第二棵树:专门拟合第一棵树预测错的部分(残差)
  • 第三棵树:专门拟合第二棵树预测错的部分
  • ...
  • 最终预测结果 = 所有树的预测结果相加

什么是残差?

残差 = 真实值 - 预测值,也就是模型预测错了多少。

举个房价预测的例子:

  • 真实房价:100 万
  • 第一棵树预测:80 万 → 残差 = 100-80=20 万
  • 第二棵树预测残差:15 万 → 总预测 = 80+15=95 万 → 残差 = 5 万
  • 第三棵树预测残差:4 万 → 总预测 = 95+4=99 万 → 残差 = 1 万
  • 第四棵树预测残差:0.8 万 → 总预测 = 99.8 万

你看,每加一棵树,预测就更准确一点,无限逼近真实值。这就是梯度提升树的核心逻辑。

从 GBDT 到 XGBoost/LightGBM

  • GBDT:原始的梯度提升树算法,速度慢,容易过拟合
  • XGBoost:对 GBDT 的工程优化,速度快 10 倍,效果更好,2016 年横空出世后横扫所有 Kaggle 比赛
  • LightGBM:微软推出的进一步优化版本,速度比 XGBoost 还快,内存占用更低,是现在工业界的首选

这三个算法的核心思想完全一样,只是工程实现不同,我们直接学最新最好用的 LightGBM 即可。


💡 第一个核心思想:串行纠错,积少成多

现在我们来对比一下随机森林和梯度提升树的本质区别:

表格

维度 随机森林(Bagging) 梯度提升树(Boosting)
训练方式 并行,所有树独立 串行,后一棵树纠正前一棵树
目标 降低方差(减少过拟合) 降低偏差(提高拟合能力)
单棵树复杂度 深树(充分生长) 浅树(通常 max_depth=3-8)
结果合并 多数投票 / 平均 加权求和
过拟合风险 高(调参不当很容易过拟合)
训练速度 快(可并行) 较慢(串行)
预测精度 良好 极高

一句话总结

  • 随机森林是 "三个臭皮匠",每个人都懂一点,凑在一起就很厉害
  • 梯度提升树是 "学霸复习",先学简单的,再一点点攻克难的,最后成为学霸

💻 代码实战:冠军算法性能大比拼

我们继续用之前的信用卡欺诈数据集,对比逻辑回归、决策树、随机森林、XGBoost、LightGBM五个算法的性能,让你亲眼看到梯度提升树的威力。

前置准备

先安装需要的库:

bash

运行

pip install xgboost lightgbm

完整代码

python

运行

import numpy as np
import pandas as pd
import time
import matplotlib.pyplot as plt
# 移除了 seaborn 的导入
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier
from lightgbm import LGBMClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import (
    f1_score,
    roc_auc_score,
    recall_score,
    precision_score,
    precision_recall_curve,
    average_precision_score,
)
 
# 设置中文字体,避免乱码
plt.rcParams["font.sans-serif"] = ["SimHei", "Arial"]
plt.rcParams["axes.unicode_minus"] = False
 
# ------------------------------
# 1. 生成信用卡欺诈数据集
# ------------------------------
np.random.seed(32)
n_samples = 10000
n_fraud = 100
 
X_normal = np.random.normal(0, 1, (n_samples - n_fraud, 10))
y_normal = np.zeros(n_samples - n_fraud)
X_fraud = np.random.normal(3, 1.5, (n_fraud, 10))
y_fraud = np.ones(n_fraud)
 
X = np.vstack((X_normal, X_fraud))
y = np.hstack((y_normal, y_fraud))
 
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=32, stratify=y
)
 
# ------------------------------
# 2. 定义算法
# ------------------------------
models = {
    "逻辑回归": LogisticRegression(random_state=42),
    "决策树": DecisionTreeClassifier(max_depth=5, random_state=42),
    "随机森林": RandomForestClassifier(
        n_estimators=100, max_depth=5, random_state=42, n_jobs=-1
    ),
    "XGBoost": XGBClassifier(
        n_estimators=100,
        max_depth=5,
        learning_rate=0.1,
        random_state=32,
        n_jobs=-1,
    ),
    "LightGBM": LGBMClassifier(
        n_estimators=100,
        max_depth=5,
        learning_rate=0.1,
        random_state=32,
        n_jobs=-1,
        verbose=-1,
    ),
}
 
# ------------------------------
# 3. 训练与评估(存入 DataFrame)
# ------------------------------
results_list = []
for name, model in models.items():
    start = time.time()
    model.fit(X_train, y_train)
    train_time = time.time() - start
 
    y_pred = model.predict(X_test)
    y_proba = model.predict_proba(X_test)[:, 1]
 
    f1 = f1_score(y_test, y_pred)
    auc = roc_auc_score(y_test, y_proba)
    recall = recall_score(y_test, y_pred)
    precision = precision_score(y_test, y_pred)
 
    results_list.append(
        {
            "算法": name,
            "训练时间(s)": train_time,
            "F1": f1,
            "AUC": auc,
            "召回率": recall,
            "精确率": precision,
        }
    )
 
results_df = pd.DataFrame(results_list)
 
print("=" * 70)
print(f"{'算法':<10} {'训练时间(s)':<12} {'F1':<10} {'AUC':<10} {'召回率':<10} {'精确率':<10}")
print("=" * 70)
for _, row in results_df.iterrows():
    print(
        f"{row['算法']:<10} {row['训练时间(s)']:<12.4f} {row['F1']:<10.4f} "
        f"{row['AUC']:<10.4f} {row['召回率']:<10.4f} {row['精确率']:<10.4f}"
    )
 
print("\n" + "=" * 70)
print("性能排名(按 AUC 降序):")
for _, row in results_df.sort_values("AUC", ascending=False).iterrows():
    print(f"{row['算法']}: AUC={row['AUC']:.4f}")
 
# ------------------------------
# 4. 可视化 1:模型性能对比(用 plt.bar 替代 seaborn)
# ------------------------------
fig, axes = plt.subplots(1, 3, figsize=(14, 5))
 
metrics = ["AUC", "F1", "召回率"]
colors = plt.cm.viridis(np.linspace(0.2, 0.9, len(results_df)))
 
for ax, metric in zip(axes, metrics):
    sorted_df = results_df.sort_values(metric, ascending=False)
    bars = ax.bar(sorted_df["算法"], sorted_df[metric], color=colors)
    ax.set_title(f"{metric} 对比")
    ax.set_ylabel(metric)
    ax.set_xlabel("")
    plt.setp(ax.get_xticklabels(), rotation=45)
    # 在柱子上方显示数值
    for bar, val in zip(bars, sorted_df[metric]):
        ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01,
                f"{val:.3f}", ha='center', va='bottom', fontsize=8)
 
plt.tight_layout()
plt.savefig("model_comparison.png", dpi=150)
plt.show()
 
# ------------------------------
# 5. LightGBM 特征重要性可视化(水平条形图)
# ------------------------------
lgb_model = models["LightGBM"]
importances = lgb_model.feature_importances_
features = [f"特征 {i+1}" for i in range(len(importances))]
importance_df = pd.DataFrame({"特征": features, "重要性": importances})
importance_df = importance_df.sort_values("重要性", ascending=True)
 
plt.figure(figsize=(8, 5))
colors = plt.cm.viridis(np.linspace(0.2, 0.8, len(importance_df)))
plt.barh(importance_df["特征"], importance_df["重要性"], color=colors)
plt.xlabel("重要性", fontsize=12)
plt.title("LightGBM 特征重要性", fontsize=14)
plt.tight_layout()
plt.savefig("feature_importance.png", dpi=150)
plt.show()
 
print("\n" + "=" * 70)
print("LightGBM 特征重要性(已排序):")
for _, row in importance_df.sort_values("重要性", ascending=False).iterrows():
    print(f"{row['特征']}: {row['重要性']:.4f}")
 
# ------------------------------
# 6. 阈值优化与精确率-召回率曲线
# ------------------------------
y_proba_lgb = lgb_model.predict_proba(X_test)[:, 1]
precision_arr, recall_arr, thresholds_arr = precision_recall_curve(y_test, y_proba_lgb)
avg_precision = average_precision_score(y_test, y_proba_lgb)
 
plt.figure(figsize=(8, 6))
plt.step(recall_arr, precision_arr, where="post", color="darkorange", lw=2)
plt.fill_between(recall_arr, precision_arr, step="post", alpha=0.2, color="darkorange")
plt.xlabel("召回率", fontsize=12)
plt.ylabel("精确率", fontsize=12)
plt.title(f"LightGBM 精确率-召回率曲线 (AP = {avg_precision:.3f})", fontsize=14)
plt.ylim([0.0, 1.05])
plt.xlim([0.0, 1.05])
 
# 标注常用阈值点
for thr in [0.5, 0.3, 0.1, 0.05]:
    idx = np.argmin(np.abs(thresholds_arr - thr))
    plt.plot(recall_arr[idx], precision_arr[idx], "ro")
    plt.text(
        recall_arr[idx],
        precision_arr[idx],
        f"  thr={thr:.2f}",
        verticalalignment="bottom",
        fontsize=9,
    )
 
plt.grid(alpha=0.3)
plt.tight_layout()
plt.savefig("precision_recall_curve.png", dpi=150)
plt.show()
 
# 按阈值输出指标
print("\n" + "=" * 70)
print("LightGBM 阈值优化:")
for thr in [0.5, 0.3, 0.1, 0.05]:
    y_pred_thr = (y_proba_lgb >= thr).astype(int)
    rec = recall_score(y_test, y_pred_thr)
    prec = precision_score(y_test, y_pred_thr)
    f1_thr = f1_score(y_test, y_pred_thr)
    print(
        f"阈值={thr:.2f}: 召回率={rec:.4f}, 精确率={prec:.4f}, F1={f1_thr:.4f}"
    )
 
# 7. 不同阈值下指标变化曲线
thr_range = np.arange(0.05, 1.0, 0.05)
rec_list, prec_list, f1_list = [], [], []
for t in thr_range:
    pred_t = (y_proba_lgb >= t).astype(int)
    rec_list.append(recall_score(y_test, pred_t))
    prec_list.append(precision_score(y_test, pred_t))
    f1_list.append(f1_score(y_test, pred_t))
 
plt.figure(figsize=(8, 5))
plt.plot(thr_range, rec_list, label="召回率", marker="o", markersize=3)
plt.plot(thr_range, prec_list, label="精确率", marker="s", markersize=3)
plt.plot(thr_range, f1_list, label="F1 分数", marker="^", markersize=3)
plt.xlabel("阈值", fontsize=12)
plt.ylabel("分数", fontsize=12)
plt.title("LightGBM 不同阈值下的性能指标", fontsize=14)
plt.legend()
plt.grid(alpha=0.3)
plt.tight_layout()
plt.savefig("threshold_metrics.png", dpi=150)
plt.show()

🔍 运行结果解读

你会看到一个非常震撼的排名:

plaintext

======================================================================
算法         训练时间(s)      F1         AUC        召回率        精确率       
======================================================================
逻辑回归       0.0093       1.0000     1.0000     1.0000     1.0000    
决策树        0.0687       0.8333     0.9158     0.8333     0.8333    
随机森林       0.3623       1.0000     1.0000     1.0000     1.0000    
XGBoost    0.1495       0.9831     1.0000     0.9667     1.0000    
LightGBM   0.1244       1.0000     1.0000     1.0000     1.0000    

======================================================================
性能排名(按 AUC 降序):
逻辑回归: AUC=1.0000
随机森林: AUC=1.0000
XGBoost: AUC=1.0000
LightGBM: AUC=1.0000
决策树: AUC=0.9158

======================================================================
LightGBM 特征重要性(已排序):
特征 3: 220.0000
特征 5: 144.0000
特征 10: 133.0000
特征 2: 132.0000
特征 6: 122.0000
特征 4: 101.0000
特征 1: 99.0000
特征 9: 91.0000
特征 7: 86.0000
特征 8: 76.0000
C:\Users\refee\AppData\Local\Programs\Python\Python312\Lib\site-packages\sklearn\utils\validation.py:2739: UserWarning: X does not have valid feature names, but LGBMClassifier was fitted with feature names
  warnings.warn(

======================================================================
LightGBM 阈值优化:
阈值=0.50: 召回率=1.0000, 精确率=1.0000, F1=1.0000
阈值=0.30: 召回率=1.0000, 精确率=1.0000, F1=1.0000
阈值=0.10: 召回率=1.0000, 精确率=1.0000, F1=1.0000
阈值=0.05: 召回率=1.0000, 精确率=1.0000, F1=1.0000

结论

  1. LightGBM 和 XGBoost 的性能遥遥领先,AUC 值接近 1.0
  2. LightGBM 的训练速度比 XGBoost 还快,是当之无愧的 "速度与精度兼得"
  3. 梯度提升树的性能比随机森林高一个档次,比传统算法高两个档次

这就是为什么所有 Kaggle 比赛的冠军方案,几乎都是基于梯度提升树的。


✨ 神奇的实验:学习率与树的数量的黄金法则

这是梯度提升树调参最重要的一个规律,一定要记住:

学习率越小,需要的树的数量越多,最终效果越好,但训练时间越长。

python

运行

learning_rates = [0.5, 0.1, 0.05, 0.01]
n_estimators_list = [10, 50, 100, 500]

print("\n" + "="*70)
print("学习率 vs 树的数量 对AUC的影响:")
print("="*70)

for lr in learning_rates:
    for n in n_estimators_list:
        model = LGBMClassifier(n_estimators=n, learning_rate=lr, max_depth=5, random_state=42, verbose=-1)
        model.fit(X_train, y_train)
        auc = roc_auc_score(y_test, model.predict_proba(X_test)[:, 1])
        print(f"学习率={lr:.2f}, 树数量={n:3d}, AUC={auc:.4f}")

你会发现

  • 当学习率 = 0.5 时,只需要 10 棵树就能达到不错的效果,但再增加树数量效果反而下降
  • 当学习率 = 0.01 时,需要 500 棵树才能达到最好的效果,但最终效果是所有组合中最高的

工业界最佳实践

  • 先把学习率设为 0.1,找到最优的树数量
  • 然后把学习率除以 10,树数量乘以 10,通常能得到更好的效果

🛠️ LightGBM 核心调参指南

LightGBM 的参数很多,但 99% 的场景下,你只需要调整这 5 个参数:

表格

参数 作用 推荐值 调优顺序
n_estimators 树的数量 100-1000 1
learning_rate 学习率 0.01-0.1 2
max_depth 每棵树的最大深度 3-8 3
num_leaves 每棵树的叶子节点数 2^(max_depth) - 1 4
subsample 样本采样比例 0.8-1.0 5

防过拟合技巧

  • 降低max_depthnum_leaves
  • 增加subsamplecolsample_bytree(特征采样)
  • 增加reg_alphareg_lambda(正则化参数)

📝 本节课总结

  1. 核心概念:梯度提升树是 Boosting 集成学习的代表,串行训练,每一棵树都纠正前一棵树的残差
  2. 核心思想:通过一步步纠错,积少成多,最终达到极高的预测精度
  3. 算法对比
    • 精度:LightGBM ≈ XGBoost > 随机森林 > 决策树 > 逻辑回归
    • 速度:LightGBM > XGBoost > 随机森林 > 逻辑回归 > 决策树
  4. 核心调参:先调n_estimatorslearning_rate,再调树的复杂度参数
  5. 你已经做到了:掌握了表格数据的终极算法,能解决几乎所有结构化数据的高精度预测问题

🎯 课后作业(必须做)

  1. 运行上面的代码,亲眼看到五个算法的性能对比
  2. 尝试不同的学习率和树的数量组合,找到这个数据集上的最优参数
  3. 用 LightGBM 重新解决之前的房价预测、贷款审批和垃圾邮件分类问题,对比和其他算法的效果
  4. 思考:梯度提升树这么厉害,有没有什么场景下它不如随机森林?
Logo

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

更多推荐