🏠 总目录📚 本教程 04 · 决策树与集成
📑 本页目录(点开跳转)

04 · 决策树与集成学习

52 分钟 | ⭐⭐ 表格数据的王者,Kaggle 板块的地基


🎯 一句话

一棵树很弱(第 1 章那个 91.8%),但把几百棵弱树按正确方式组合起来,就是表格数据上至今难以撼动的最强模型——XGBoost / LightGBM 的全部秘密就在这。

特征平面第①刀第②刀x₁ < 3.5 ?是 → Ax₂ < 7 ?是 → B否 → A一棵树 = 一串「切一刀」——而且每刀都必须平行于坐标轴⭐ 所以斜的决策边界,树要用很多刀去逼近成阶梯 —— 这就是它的归纳偏置
左边是被切开的特征平面,右边是对应的树 —— 一一对应。⭐ 注意每刀都必须平行于坐标轴:所以斜的决策边界,树只能用很多刀逼近成阶梯。这就是树的归纳偏置。

📍 这一章的链是:怎么切一刀(ID3 → C4.5 → CART)→ 怎么把树组合起来(Bagging / Boosting / Stacking)→ GBDT 每棵新树到底在学什么。 链的最后一环——「怎么把『下一棵树该怎么长』写成一个能求闭式解的目标函数」——单独放在 04b · XGBoost 的推导,读完这一章再去。


🌲 一、决策树:一堆 if-else

                  worst perimeter <= 106?
                    ╱                ╲
                  是                  否
                  ╱                    ╲
      worst concave pts <= 0.14?      恶性 (95%)
          ╱          ╲
        是            否
        ╱              ╲
    良性 (98%)      恶性 (80%)

💡 它就是在特征空间里切方块:每次沿着某个特征切一刀,把数据分成两半。

怎么决定「切哪个特征、切在哪」

贪心:试遍所有特征的所有切分点,选让子节点最"纯"的那个。

「纯度」的两种度量:

指标 公式 直觉
基尼不纯度 $1-\sum p_k^2$ 随机抽两个样本,类别不同的概率。CART 默认
信息熵 $-\sum p_k\log p_k$ 不确定性。信息增益 = 熵减少了多少

💡 人话

切一刀之前左右混在一起(不纯),切完之后左边基本都是良性、右边基本都是恶性(纯)。 纯度提升最多的那一刀,就是最好的切法。 两个指标实践中差别很小,用默认的基尼就行。

关键特性

特性 说明
不需要标准化 只看特征的相对大小,尺度无关(对比第 3 章线性模型)
天然处理非线性和特征交互 if-else 嵌套自带交叉
可解释 能画出来给业务看
极易过拟合 不限制的话,每个叶子一个样本 = 完全背答案(第 1 章的 100%)
不稳定 数据变一点,树的结构可能全变

控制过拟合的四个旋钮max_depth(最常用)、min_samples_leafmin_samples_splitmax_leaf_nodes


🪓 二、分裂准则的演进:ID3 → C4.5 → CART

上一节说「选让子节点最纯的那一刀」。但「纯」到底怎么量,历史上换过三代。这条演进线不只是考点——GBDT / XGBoost / LightGBM 家族的每一棵树都长在第三代(CART)上面,不搞清楚它,后面几节会变成背结论。

① ID3:信息增益

$$\text{Gain}(D,a) = H(D) - \sum_{v} \frac{|D^v|}{|D|}\, H(D^v)$$

💡 人话:熵 $H$ 是「你还有多不确定」。切一刀,不确定性掉了多少;掉得最多的那一刀就选它

⚠️ 但它有一个致命偏好:一个特征的取值越多,信息增益天然越大。

举个能算的例子。20 个样本,10 正 10 负,所以 $H(D)=1$ bit:

候选特征 切完之后 信息增益
A:一个真有用的二值特征 左(9 正 1 负)、右(1 正 9 负),加权熵 0.469 $1-0.469=\mathbf{0.531}$
B:用户 ID(20 个取值,一人一个) 20 个叶子各 1 个样本,熵全是 0 $1-0=\mathbf{1.000}$ ← 赢了

ID3 会毫不犹豫地选用户 ID,而这棵树在新用户身上一个都答不出来——它把这 20 个样本背下来了。

② C4.5:信息增益率——除以「这个特征自己有多碎」

C4.5 的补丁很直接:给切得太碎的特征加罚分

$$\text{GainRatio}(D,a)=\frac{\text{Gain}(D,a)}{IV(a)},\qquad IV(a)=-\sum_v \frac{|D^v|}{|D|}\log_2\frac{|D^v|}{|D|}$$

💡 IV(固有值 / intrinsic value)到底是什么:把「样本落进了哪个分支」本身当成一个随机变量,算的熵。⭐ 注意 IV 和标签 $y$ 一点关系都没有,它纯粹度量「这一刀切得多碎、多均匀」。切成越多份、越均匀,IV 越大。

接着上面的数算:

特征 信息增益 IV 增益率
A(二值) 0.531 $\log_2 2=1.000$ 0.531 ✅ 反超
B(用户 ID,20 个取值) 1.000 $\log_2 20=4.322$ 0.231

换了判据,A 就赢了。 这就是「为什么要除以固有值」的全部答案:分子奖励「分得准」,分母惩罚「分得碎」。

⚠️ 但增益率又矫枉过正了——它反过来偏爱取值少的特征。所以 C4.5 实际用的是两步启发式:先筛出信息增益高于平均水平的候选,再在这一批里挑增益率最高的。(面试能说出「C4.5 不是单纯用增益率」是加分项。)

C4.5 相比 ID3 还多做了三件事:能处理连续特征(排序后取相邻取值的中点当候选切分点)、能处理缺失值(按各分支的样本比例,把缺失样本带权重分到每一支)、有后剪枝

③ CART:基尼系数——把 log 换成乘法

$$\text{Gini}(D)=1-\sum_k p_k^2$$

为什么更快:熵要算 $\log$,基尼只要乘法和减法。一棵树训练时要算几十万次纯度,这个常数差是实打实的。

为什么可以这么换:把 $-\ln p$ 在 $p=1$ 处做一阶泰勒展开得到 $-\ln p \approx 1-p$,代回熵的定义就成了 $\sum_k p_k(1-p_k)=1-\sum_k p_k^2$。⭐ 基尼就是熵的一阶近似——两条曲线形状几乎重合(二分类下 $p=0.5$ 时熵取最大值 1 bit、基尼取最大值 0.5,最大值在同一点),所以实践中选哪个对结果几乎没影响。

⚠️ 很多人搞错的一点:基尼并没有解决「偏好多值特征」。 还是上面那组数——A 的基尼下降是 0.32,用户 ID 的基尼下降是 0.50基尼照样选用户 ID。这个毛病是所有「增益类判据」共有的,不是熵独有的。

CART 的解法不是换判据,是换结构

⚠️ 这就是本章后面那个「特征重要性偏向高基数特征」的坑的根: CART 只是把病压住了,没治好。取值多的特征仍然有更多候选切分点可以试,仍然更容易被选中。

三代对比

ID3 C4.5 CART
判据 信息增益 信息增益率(两步启发式) 基尼(分类)/ 平方误差(回归)
树形 多叉 多叉 只能二叉
连续特征 ✅ 中点候选
缺失值 ✅ 按权重分到各支 ✅ 代理切分
能不能做回归 ✅ ⭐
剪枝 悲观后剪枝 代价复杂度剪枝(CCP)

🔑 最后一行是本节通向后面所有内容的接口: ⭐ Boosting 家族里的每一棵树都是 CART 回归树。 因为每棵新树要拟合的是「负梯度」——那是一串连续实数,不是类别标签。 ID3 和 C4.5 只会分类,从一开始就没有资格进这个家族;sklearn 里也只实现了 CART。


🌳 三、集成学习:三种组合方式

Bagging并行 · 各train各的树1树2树3树4数据→ 投票/平均降【方差】Boosting串行 · 补前一个的错树1树2补错树3再补树4再补→ 加权求和降【偏差】都是「很多棵树」,但组织方式和治的病完全不同⭐ Bagging 的树可以并行训、深一点无所谓;Boosting 必须串行、每棵要浅(否则第一棵就过拟合了)所以随机森林调「树多少棵」,GBDT 调「学习率 + 树深」
都是「很多棵树」,但组织方式和治的病完全不同:Bagging 并行降方差,Boosting 串行降偏差。⭐ 所以随机森林主要调「多少棵」,GBDT 主要调「学习率 + 树深」。

单棵树不稳定又易过拟合。把很多棵组合起来——但怎么组合,分成三条完全不同的路线:

① Bagging(并行,降方差)
  • 怎么组合:同一份数据 → 有放回抽样 3 次 → 分别训出树1 / 树2 / 树3 → 投票 / 平均
  • 代表:随机森林
💡 每棵树都在略微不同的数据上训练,它们的错误互相抵消 → 更稳
② Boosting(串行,降偏差)⭐ 最强
  • 怎么组合:树1 → 找出树1错在哪 → 树2 专门补这些错 → 找出还错在哪 → 树3 再补 → …
  • 代表:GBDT / XGBoost / LightGBM / CatBoost
💡 每棵新树都在「纠正前面所有树的残余错误」
③ Stacking(分层,学习怎么融合)
  • 怎么组合:树 + 线性 + 神经网络 → 各自的预测 → 再训一个模型学「怎么组合它们」
💡 Kaggle 冲榜常用,工程复杂

🔗 Kaggle 第 3 章模型融合 讲的就是 ③ 的各种实战技巧。


🌲 四、随机森林:Bagging + 双重随机

   随机 1:每棵树用【有放回抽样】的数据(Bootstrap)
   随机 2:每次分裂只从【随机选的 k 个特征】里挑 ⭐ 关键

   为什么要第二个随机?
   → 否则每棵树都会先用那个最强特征分裂 → 树长得都一样 → 平均了也没用
   → 强制它们看不同的特征,才能真正"多样"

💡 核心直觉集成的收益来自成员之间的「差异」。全都一样的树,平均一百次还是那棵树。

优点:几乎不用调参、不易过拟合、能给特征重要性、可并行 缺点:精度通常略输 Boosting、模型体积大


🚀 五、GBDT:Boosting 的核心思想

这是本章最重要的部分——Kaggle 的主力算法。

它在做什么

   目标:预测房价,真实值 100 万

   树1 预测 → 80万        残差(还差) = 20万
   树2 专门学「怎么预测这 20 万」→ 预测 15万   残差 = 5万
   树3 专门学这 5 万 → 预测 4万                残差 = 1万
   ...
   最终预测 = 80 + 15 + 4 + ... = 逼近 100万 ✅

💡 人话

每棵新树不预测答案本身,而是预测「前面所有树加起来还差多少」。 像考试订正错题:第一遍做完,专门针对错的部分再学一遍,反复迭代。

数学上:每棵新树拟合的是损失函数的负梯度(所以叫「梯度」提升)。 用 MSE 损失时,负梯度恰好就是残差——上面那个直觉是严格成立的。

三个关键超参(调参时主要动这三个)

参数 作用 经验值
n_estimators 树的数量 越多拟合越强 100–2000,配合早停
learning_rate 学习率 ⭐ 每棵树的贡献打几折 0.01–0.1。小 lr + 多树 = 更好但更慢
max_depth 树深 单棵树复杂度 3–8(比随机森林浅得多!)

🔑 最重要的一条调参直觉learning_raten_estimators 是一对——学习率减半,树的数量大致要翻倍。 先固定 lr=0.1 调其他参数,最后再降 lr、加树数换取最后那点精度。

为什么 GBDT 的树要浅?

   随机森林:每棵树都想【独立解决问题】→ 要深(完全生长)
   GBDT:   每棵树只需【纠正一点点错误】→ 要浅(深度 3-8)

   GBDT 用浅树是故意的:单棵弱一点,才不会一步跨太大而过拟合

🚪 这里有一句话故意没讲完

「每棵新树拟合负梯度」——⭐ 负梯度只告诉你往哪个方向走,没告诉你该走多远。 GBDT 用 learning_rate 把步子统一压小,是绕开这个问题而不是解决它。

👉 04b · XGBoost 的推导:从负梯度到闭式解 该去的时候:要面试(「XGBoost 为什么用二阶泰勒展开」是必考)、要自定义损失函数、或者想知道 LightGBM 凭什么快、CatBoost 在防什么。 可以先不去的时候:只想会调参把分刷上去 —— 直接跳 Kaggle 第 1 章,那里有现成模板。 ⚠️ 但三巨头对比表在 04b,而且在那里它是推出来的不是背的。


🔨 动手:亲眼看集成的威力

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import (RandomForestClassifier,
                              GradientBoostingClassifier, BaggingClassifier)
import numpy as np

X, y = load_breast_cancer(return_X_y=True)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3,
                                          random_state=42, stratify=y)

print(f"{'模型':<22}{'训练集':>9}{'测试集':>9}")
for name, m in [
    ("单棵决策树",        DecisionTreeClassifier(random_state=0)),
    ("Bagging(100棵)",   BaggingClassifier(n_estimators=100, random_state=0)),
    ("随机森林(100棵)",   RandomForestClassifier(n_estimators=100, random_state=0)),
    ("GBDT(100棵,深度3)", GradientBoostingClassifier(n_estimators=100,
                                                    max_depth=3, random_state=0)),
]:
    m.fit(X_tr, y_tr)
    print(f"{name:<22}{m.score(X_tr,y_tr):>8.1%}{m.score(X_te,y_te):>9.1%}")

print("\nGBDT:学习率与树数量的配合")
print(f"{'树数':<8}{'学习率':<10}{'测试集':>9}")
for n, lr in [(10,0.1),(50,0.1),(200,0.1),(500,0.1),(500,0.01)]:
    m = GradientBoostingClassifier(n_estimators=n, learning_rate=lr,
                                   max_depth=3, random_state=0).fit(X_tr, y_tr)
    print(f"{n:<8}{lr:<10}{m.score(X_te,y_te):>8.1%}")

print("\n特征重要性 Top5:")
rf = RandomForestClassifier(n_estimators=200, random_state=0).fit(X_tr, y_tr)
names = load_breast_cancer().feature_names
for i in np.argsort(-rf.feature_importances_)[:5]:
    print(f"  {names[i]:<25} {rf.feature_importances_[i]:.3f}")

实测结果

模型                          训练集      测试集
单棵决策树                   100.0%    91.8%     ← 第1章那棵
Bagging(100棵)              100.0%    94.2%     ← +2.4%
随机森林(100棵)              100.0%    94.2%
GBDT(100棵,深度3)           100.0%    94.2%

GBDT:学习率与树数量的配合
树数      学习率        测试集
10        0.1        93.0%
200       0.1        94.7%
500       0.1        95.3%     ← 更多树还在涨
500       0.01       94.2%     ← lr 太小,500 棵不够用

特征重要性 Top5:
  worst perimeter           0.140
  worst concave points      0.132
  worst radius              0.115

💡 注意:所有集成模型训练集都是 100%,但测试集比单棵树高 2–3.5%。 训练集 100% 不等于过拟合——关键看测试集有没有跟着掉。


⚠️ 特征重要性的三个坑

树模型能给特征重要性,但别太当真

说明
偏向高基数特征 取值越多的特征(如 ID)越容易被选中分裂,重要性虚高
相关特征分摊 两个高度相关的特征会平分重要性,看起来都不重要
只反映训练集 不代表因果,也不代表在新数据上有用

第一个坑的根就是第二节那件事:信息增益、增益率、基尼全都偏爱取值多的特征(用户 ID 的信息增益能到满分 1.000)。 CART 靠「只能二叉」把病压住了,但取值多 = 候选切分点多 = 被选中的机会多,这个偏向从来没被真正消除

更可靠的做法Permutation Importance(打乱某列看性能掉多少)或 SHAP


🔗 和站内其他章的关系

相关的地方 根因在这
Kaggle 第 1、2 章大量用 LightGBM 就是本章的 GBDT
Kaggle 第 3 章模型融合 Stacking / Blending 的实战版
推荐算法第 7 章「用 GBDT 看特征重要性」 本章的特征重要性(连同它的坑)
推荐算法第 18 章的蒸馏 Boosting 的"用弱模型逼近强模型"思想有相通处

看完这一章,接着往哪走

去哪 为什么要去那里
04b · XGBoost 的推导 这一章故意没讲完的那半句在那里:负梯度只给方向不给步长,XGBoost 怎么用二阶泰勒展开把「下一棵树该怎么长」变成有闭式解的目标函数;三巨头对比表也在那里,而且是推出来的
第 16 章 · 特征工程基础 那里的 OOF 目标编码是「怎么用类别特征而不泄漏」的手工做法——树模型碰上高基数类别特征时的标准解
数学原理 · 08 偏差方差分解 本章说 Bagging 降方差、Boosting 降偏差,那两个词的严格定义和分解式在那里
第 5 章 · 评估与过拟合 本章实测里「训练集 100% 但测试集只有 91.8%」到底算不算过拟合——判断标准在那一章
附录 C · 手撕代码速查 面试让你当场写而不是说的时候用:K-means、AUC、逻辑回归梯度下降都在那里,20–40 行能默写的版本

✅ 检查点

  1. 决策树怎么决定在哪里切分?两种纯度指标是什么?
  2. ID3 的信息增益有什么致命偏好?C4.5 除以「固有值」是在惩罚什么?
  3. 基尼比熵快在哪?它解决「偏向多值特征」了吗?CART 靠什么压住?
  4. 为什么 Boosting 家族的树只能是 CART,不能是 ID3 / C4.5?
  5. Bagging 和 Boosting 的根本区别?各降低什么?随机森林为什么要「随机选特征」?
  6. GBDT 里每棵新树学的是什么?为什么树要比随机森林浅?learning_rate 和 n_estimators 什么关系?
  7. 实测里所有集成模型训练集都是 100%,这算过拟合吗?该看哪个数?
  8. 特征重要性有哪三个坑?其中「偏向高基数特征」的根在本章哪一节?
👀 答案
  1. 贪心试遍所有特征的所有切分点,选让子节点最纯的那一刀。指标:基尼不纯度(默认)、信息熵。
  2. 取值越多,信息增益天然越大——20 个样本时「用户 ID」拿满分 1.000,真正有用的二值特征只有 0.531,ID3 会选 ID,等于把样本背下来。固有值 $IV$ 惩罚的是「这一刀切得多碎」,和标签无关:ID 的 IV $=\log_2 20=4.322$,增益率掉到 0.231,二值特征的 0.531 反超。⚠️ 增益率又偏爱少值特征,所以 C4.5 实际是两步:先筛增益高于平均的,再挑增益率最高的。
  3. 快在不用算 log(基尼是 $-\ln p\approx 1-p$ 的一阶泰勒近似,和熵曲线几乎重合)。⚠️ 没解决——同一组数里 A 的基尼下降 0.32、用户 ID 是 0.50,照样选 ID。CART 靠只长二叉树(20 个取值也只能切两支)+ min_samples_leaf + 剪枝压住,但没治好。
  4. 因为每棵新树拟合的是负梯度——连续实数,不是类别标签,只有 CART 能做回归。sklearn 里也只实现了 CART。
  5. Bagging 并行、每棵树独立在抽样数据上训练后投票,降方差;Boosting 串行、每棵新树纠正前面的错误,降偏差。随机选特征是因为:否则每棵树都先用最强特征分裂、长得都一样,集成的收益全来自成员差异
  6. 学的是前面所有树的残差(严格说是损失的负梯度)。浅是故意的:每棵树只需纠正一点点,深了就一步跨太大而过拟合,所以 3–8 层。lr 和树数是一对:学习率减半,树数大致翻倍。
  7. 不算。⭐ 训练集 100% 不等于过拟合,要看测试集有没有跟着掉——这里单棵树是 100.0% / 91.8%,三种集成都是 100.0% / 94.2%,测试集反而涨了 2.4 个点。
  8. 偏向高基数特征、相关特征分摊重要性、只反映训练集不代表因果。用 Permutation Importance 或 SHAP 更可靠。第一个坑的根在第二节:信息增益、增益率、基尼全都偏爱取值多的特征(用户 ID 增益能到满分 1.000),CART 只是靠「只能二叉」把它压住,没消除。

🛑 可以停在这里

走神救援

决策树=切方块贪心试遍所有切分点,选让子节点最纯的一刀。优点:不用标准化自带特征交互;⚠️缺点:极易过拟合(不限制就是每叶一个样本=第 1 章那个 100% / 91.8%);最常用的旋钮是 max_depth。⭐判据换过三代ID3 信息增益=切前熵 − 切后加权熵,⚠️致命偏好:取值越多增益越大——20 个样本 10 正 10 负时,「用户 ID」(20 个取值)增益满分 1.000,真正有用的二值特征只有 0.531,ID3 会选 ID,等于把样本背下来C4.5 除以固有值 IV(IV=「这一刀切得多碎」的熵,和标签无关):ID 的 IV=log₂20=4.322,增益率掉到 0.231,二值特征 0.531 反超;⚠️但增益率又偏爱少值特征,所以 C4.5 实际是两步:先筛增益高于平均的、再挑增益率最高的。CART 用基尼 1−Σp²,快在不用算 log(基尼是 −ln p ≈ 1−p 的一阶泰勒近似,和熵曲线几乎重合);⚠️基尼没解决多值偏好(同一组数:A 基尼下降 0.32、ID 是 0.50,照样选 ID),CART 靠只长二叉树压住——这就是「特征重要性偏向高基数」那个坑的根。⭐Boosting 家族只能用 CART,因为负梯度是连续实数,ID3/C4.5 只会分类集成三条路:⭐Bagging 并行、降方差(随机森林=有放回抽样+每次分裂只从随机 k 个特征里挑,⭐否则每棵树都先用最强特征分裂、长得都一样——集成的收益全来自成员差异);⭐Boosting 串行、降偏差GBDT:每棵新树预测"前面所有树加起来还差多少",即损失的负梯度);Stacking 分层。GBDT 三参数:学习率 0.01–0.1、树深 3–8浅是故意的:每棵树只需纠正一点点)、树数;⭐学习率和树数是一对:减半就要翻倍实测:单棵树 100.0%/91.8% → Bagging、随机森林、GBDT 都是 100.0%/94.2%(+2.4%);GBDT 10 棵 93.0%、200 棵 94.7%、500 棵 95.3% 还在涨,但学习率降到 0.01 时 500 棵只有 94.2%,根本不够用。💡训练集 100% 不等于过拟合,关键看测试集有没有跟着掉。⚠️特征重要性三个坑:偏向高基数、相关特征分摊、只反映训练集——改用 Permutation Importance 或 SHAP。⭐本章故意留了半句:负梯度只给方向不给步长,GBDT 用 learning_rate 压小步子是绕开不是解决——真正解决它的是 04b 的二阶泰勒展开

下一节 👉 04b-XGBoost的推导.md ⭐⭐ 本章那半句没讲完的话

🎁 只想会调参、不想看推导的话,也可以从这里直接去 05-评估与过拟合.md(⭐⭐⭐ 全教程最重要的一章),04b 随时回来补都行

打卡记录保存在你的浏览器里,首页能看到总进度