AI 工程基础体系 · 第 44/100 篇。内容覆盖机器学习、深度学习与生成式 AI;模型、数据、评测、权限和成本会作为同一生产系统处理。

决策树完整原理:划分、剪枝、缺失值、过拟合与可解释性

决策树是一类通过一系列“如果满足条件,就进入左子树,否则进入右子树”来完成预测的模型。它可以用于分类、回归,也可以作为随机森林、梯度提升树等集成模型的基本组件。

一棵树同时包含三类信息:

  • 内部节点:选择某个特征和阈值进行划分;
  • 分支:表示样本根据条件进入的路径;
  • 叶节点:保存最终预测值,例如分类概率、类别或回归均值。

决策树的核心问题不是“如何沿树执行预测”,而是:

在当前节点中,选择哪个特征、用什么条件划分,才能让划分后的子节点更适合预测?

这个问题涉及划分准则、停止条件、剪枝、缺失值处理和泛化能力。


一、决策树的预测过程

以一个二分类树为例,假设根节点的规则是:

年龄 <= 30?
├── 是:进入左子树
└── 否:进入右子树

样本继续在子树中接受判断,直到抵达叶节点。

分类树的叶节点通常保存训练样本中的类别分布。例如某个叶节点包含:

正类:8 个
负类:2 个

则常见预测结果是:

预测类别 = 正类
预测概率 = [0.2, 0.8]

这里的概率不是由概率模型额外拟合出来的,而通常是叶节点中各类别的样本比例。经过平滑、类别权重或实现细节调整后,具体数值可能有所不同。

回归树的叶节点通常保存该节点训练样本的均值。例如叶节点中的目标值为:

[10, 12, 14]

则平方误差准则下的预测值为:

(10 + 12 + 14) / 3 = 12

因此,回归树本质上是在特征空间中切出若干区域,并在每个区域内用一个常数预测。


二、从根节点开始:候选划分如何产生

2.1 二叉划分

CART(Classification and Regression Trees,分类与回归树)通常使用二叉划分。对于数值特征 xjx_j,候选规则可以表示为:

xjtx_j \le t

其中:

  • xjx_j 是第 jj 个特征;
  • tt 是候选阈值;
  • 满足条件的样本进入左子节点;
  • 不满足条件的样本进入右子节点。

例如,某特征排序后取值为:

10, 12, 15, 20

候选阈值通常可以取相邻值的中点:

11, 13.5, 17.5

阈值为 13.513.5 时,划分为:

左节点:[10, 12]
右节点:[15, 20]

连续特征不会为每一个任意实数都尝试阈值,而只需考虑排序后相邻取值之间的有效边界。

对于类别特征,理论上可以按类别集合划分:

xjSx_j \in S

例如:

城市属于 {北京、上海}?

但实际实现对类别特征的支持差异很大。许多基础决策树实现只接受数值输入,需要先进行编码。独热编码后,一个原本的多类别特征会变成多个 0/1 特征,树可以分别判断:

城市_北京 <= 0.5?

这种编码可用,但可能导致树需要多次切分才能表达一个类别集合。

2.2 不纯度与划分质量

设当前节点包含样本集合 SS,候选划分将它分成左、右两个子集 SLS_LSRS_R。划分后的加权不纯度为:

Isplit=SLSI(SL)+SRSI(SR)I_{\text{split}} = \frac{|S_L|}{|S|}I(S_L) + \frac{|S_R|}{|S|}I(S_R)

划分带来的收益为:

ΔI=I(S)Isplit\Delta I = I(S)-I_{\text{split}}

算法通常选择使 ΔI\Delta I 最大的特征和阈值。

这个公式包含两个重要事实:

  1. 子节点越纯,I(SL)I(S_L)I(SR)I(S_R) 越小;
  2. 小子节点的影响会按样本数加权,不能只看某个很小的子节点是否纯。

三、分类树的划分准则

3.1 熵与信息增益

对于 KK 个类别,设节点中第 kk 类的比例为 pkp_k,熵定义为:

H(S)=k=1Kpklog2pkH(S)=-\sum_{k=1}^{K}p_k\log_2 p_k

直觉上:

  • 节点只有一个类别时,熵为 0;
  • 各类别均匀混合时,熵较高;
  • 二分类中,正负样本各占一半时熵达到最大值 1。

信息增益为:

IG=H(S)SLSH(SL)SRSH(SR)IG = H(S) - \frac{|S_L|}{|S|}H(S_L) - \frac{|S_R|}{|S|}H(S_R)

选择信息增益最大的划分,就是选择使子节点熵下降最多的划分。

3.2 基尼不纯度

基尼不纯度定义为:

G(S)=1k=1Kpk2G(S)=1-\sum_{k=1}^{K}p_k^2

它也可以理解为:从节点分布中随机抽取一个样本,再按照节点中的类别比例随机预测,预测错误的概率。

二分类中:

  • 全部是正类:G=0G=0
  • 正负各半:G=10.520.52=0.5G=1-0.5^2-0.5^2=0.5

基尼下降为:

ΔG=G(S)SLSG(SL)SRSG(SR)\Delta G = G(S) - \frac{|S_L|}{|S|}G(S_L) - \frac{|S_R|}{|S|}G(S_R)

基尼和熵通常会产生相似但不完全相同的树。它们都衡量类别混杂程度,不代表一个准则在所有数据上必然更好。

3.3 完整算例

假设根节点有 8 个样本:

正类:4
负类:4

根节点熵为:

H(S)=48log24848log248=1H(S) = -\frac48\log_2\frac48 -\frac48\log_2\frac48 =1

根节点基尼不纯度为:

G(S)=1(48)2(48)2=0.5G(S)=1-\left(\frac48\right)^2-\left(\frac48\right)^2=0.5

现在考察某个候选划分:

左节点:正类 3,负类 1
右节点:正类 1,负类 3

两个子节点各有 4 个样本。

左节点和右节点的熵相同:

H(SL)=H(SR)=34log23414log2140.811H(S_L)=H(S_R) = -\frac34\log_2\frac34 -\frac14\log_2\frac14 \approx 0.811

加权后的子节点熵为:

Hsplit=48×0.811+48×0.811=0.811H_{\text{split}} = \frac48\times0.811+\frac48\times0.811 =0.811

因此信息增益为:

IG=10.811=0.189IG=1-0.811=0.189

子节点的基尼不纯度为:

G(SL)=G(SR)=1(34)2(14)2=0.375G(S_L)=G(S_R) = 1-\left(\frac34\right)^2-\left(\frac14\right)^2 =0.375

加权后的基尼为:

Gsplit=0.375G_{\text{split}}=0.375

基尼下降为:

ΔG=0.50.375=0.125\Delta G=0.5-0.375=0.125

算法会将这个划分与其他特征、其他阈值进行比较,选择收益最大的候选划分。

3.4 信息增益的高基数偏好

信息增益有一个常见缺陷:它可能偏好取值很多的特征。

例如,一个“订单编号”几乎每个样本都不同。若按订单编号划分,可能把训练集切成许多近似纯净的小节点,训练集上的熵下降很大,但这些划分没有可泛化的信息。

C4.5 曾使用信息增益率减轻这种偏好:

GainRatio=IGSplitInfoGainRatio = \frac{IG}{SplitInfo}

其中:

SplitInfo=cScSlog2ScSSplitInfo = -\sum_{c}\frac{|S_c|}{|S|} \log_2\frac{|S_c|}{|S|}

分裂结果越碎,SplitInfoSplitInfo 越大,从而会对简单的高基数划分施加惩罚。

但信息增益率也不是万能的。实践中更重要的是:

  • 排除 ID、时间戳流水号等标识字段;
  • 使用独立验证集检查泛化;
  • 通过深度、叶节点最小样本数和剪枝控制复杂度;
  • 不把训练集上的纯度当作业务价值。

四、回归树的划分准则

4.1 平方误差与均值

回归树常用节点内平方误差:

SSE(S)=iS(yiyˉS)2SSE(S)=\sum_{i\in S}(y_i-\bar y_S)^2

其中:

  • yiy_i 是第 ii 个样本的目标值;
  • yˉS\bar y_S 是节点中目标值的均值。

在平方误差下,令叶节点预测值为 cc,最小化:

iS(yic)2\sum_{i\in S}(y_i-c)^2

cc 求导:

ciS(yic)2=2iS(yic)\frac{\partial}{\partial c} \sum_{i\in S}(y_i-c)^2 = -2\sum_{i\in S}(y_i-c)

令导数为 0:

iS(yic)=0\sum_{i\in S}(y_i-c)=0

得到:

c=1SiSyi=yˉSc=\frac{1}{|S|}\sum_{i\in S}y_i=\bar y_S

所以,平方误差回归树在叶节点中预测均值,不是一个经验巧合,而是优化问题的解析解。

4.2 算例

假设当前节点的目标值为:

[2, 4, 10, 12]

不划分时:

yˉ=7\bar y=7

平方误差为:

(27)2+(47)2+(107)2+(127)2=25+9+9+25=68(2-7)^2+(4-7)^2+(10-7)^2+(12-7)^2 =25+9+9+25=68

某候选划分产生:

左节点:[2, 4]
右节点:[10, 12]

左节点均值为 3,平方误差为:

(23)2+(43)2=2(2-3)^2+(4-3)^2=2

右节点均值为 11,平方误差为:

(1011)2+(1211)2=2(10-11)^2+(12-11)^2=2

划分后总平方误差为 4,因此误差下降为:

684=6468-4=64

这说明该划分将目标值相近的样本聚到了一起。

4.3 MAE 与中位数

如果使用绝对误差:

iSyic\sum_{i\in S}|y_i-c|

最优的 cc 是节点目标值的中位数,而不是均值。

这带来不同的统计性质:

  • 均值对异常值敏感;
  • 中位数对异常值更稳健;
  • MAE 可能使回归树更关注绝对误差;
  • 不同实现支持的回归准则不同,必须以具体版本文档为准。

五、贪心建树为什么有效,又为什么会失败

5.1 贪心建树

决策树通常采用自顶向下的贪心过程:

  1. 在根节点枚举所有候选特征和阈值;
  2. 选择当前不纯度下降最大的划分;
  3. 对左、右子节点重复相同过程;
  4. 满足停止条件后形成叶节点。

它只优化当前节点的局部收益,不会穷举所有可能的完整树结构。

如果数据有 dd 个特征、nn 个样本,数值特征通常需要排序并评估大量候选阈值。实际实现会使用排序、缓存和剪枝等优化,但训练复杂度仍可能随样本数、特征数和树深度快速增长。

5.2 XOR 反例

考虑两个二值特征 x1,x2x_1,x_2,目标为异或:

x1x_1 x2x_2 yy
0 0 0
0 1 1
1 0 1
1 1 0

根节点中正负各半,熵为 1。

x1x_1 划分:

  • x1=0x_1=0 的目标为 [0,1]
  • x1=1x_1=1 的目标为 [1,0]

两个子节点仍然各有一正一负,熵仍为 1,信息增益为 0。按 x2x_2 也一样。

因此,只看一步收益的贪心算法无法在根节点发现有用划分。实际上,XOR 需要先按任意一个特征划分,再在子节点按另一个特征划分。

这个例子说明:

决策树的贪心策略不保证找到全局最优树;它依赖局部划分能否暴露后续结构。

随机森林、梯度提升树和人工特征构造可以缓解这类问题,但不能改变单棵树的贪心本质。


六、什么时候停止继续分裂

如果无限制地建树,决策树可以不断把样本切成很小的叶节点,甚至让每个训练样本单独占一个叶子。分类训练误差可能降到 0,但这通常意味着记住了噪声。

常见停止条件包括:

  • max_depth:限制最大深度;
  • min_samples_split:节点至少有多少样本才允许继续分裂;
  • min_samples_leaf:每个叶节点至少保留多少样本;
  • max_leaf_nodes:限制叶节点数量;
  • min_impurity_decrease:只有不纯度下降达到阈值才分裂;
  • 分类中的 min_weight_fraction_leaf:按样本权重限制叶节点规模。

停止条件属于预剪枝,也就是在建树过程中阻止复杂结构产生。

它们控制的是不同风险:

  • 最大深度限制规则链条长度;
  • 最小叶节点样本数限制局部统计不稳定;
  • 最大叶节点数限制整体模型容量;
  • 最小不纯度下降避免收益很小的切分。

这些参数不是独立的。例如,较小的 min_samples_leaf 可能允许深树形成大量小叶节点;即使 max_depth 不变,模型仍可能明显过拟合。


七、剪枝:从完整树中删除不必要的分支

7.1 预剪枝与后剪枝

预剪枝在建树时提前停止。

后剪枝先建立较大的树,再删除对验证效果帮助不大的子树。后剪枝更容易评估一个完整候选结构的收益,但训练成本通常更高。

后剪枝的关键问题是:

删除一个子树后,如何比较模型复杂度和预测误差?

7.2 代价复杂度剪枝

代价复杂度剪枝使用目标函数:

Rα(T)=R(T)+αLeaves(T)R_\alpha(T)=R(T)+\alpha |Leaves(T)|

其中:

  • TT 是一棵树;
  • R(T)R(T) 是树的经验风险,例如分类误分类风险或回归残差;
  • Leaves(T)|Leaves(T)| 是叶节点数量;
  • α0\alpha\ge 0 是复杂度惩罚系数。

α=0\alpha=0 时,只关心训练误差,倾向于保留大树。

α\alpha 增大时,每增加一个叶节点都要付出更高代价,树会被逐步压缩。

考虑一个内部节点 tt,其子树记为 TtT_t。如果保留子树,目标中的局部代价为:

R(Tt)+αLeaves(Tt)R(T_t)+\alpha |Leaves(T_t)|

如果把整个子树替换成一个叶节点,局部代价为:

R(t)+αR(t)+\alpha

剪枝更有利的条件是:

R(t)+αR(Tt)+αLeaves(Tt)R(t)+\alpha \le R(T_t)+\alpha |Leaves(T_t)|

整理得:

αR(t)R(Tt)Leaves(Tt)1\alpha \ge \frac{R(t)-R(T_t)} {|Leaves(T_t)|-1}

右侧表示“保留这个子树需要付出的单位复杂度代价”。从最小值开始逐步剪枝,就可以生成一系列嵌套树。

7.3 scikit-learn 中的代价复杂度剪枝

DecisionTreeClassifierDecisionTreeRegressor 提供了 ccp_alpha 参数用于最小代价复杂度剪枝。下面的代码在 Iris 数据集上生成剪枝路径,并选择验证集表现较好的参数。

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

X, y = load_iris(return_X_y=True)

X_train, X_valid, y_train, y_valid = train_test_split(
    X,
    y,
    test_size=0.3,
    stratify=y,
    random_state=42,
)

# 先训练不进行显式复杂度惩罚的树,以获取候选剪枝路径
full_tree = DecisionTreeClassifier(
    random_state=42,
    ccp_alpha=0.0,
)
full_tree.fit(X_train, y_train)

path = full_tree.cost_complexity_pruning_path(X_train, y_train)
alphas = path.ccp_alphas

results = []

for alpha in alphas:
    tree = DecisionTreeClassifier(
        random_state=42,
        ccp_alpha=alpha,
    )
    tree.fit(X_train, y_train)

    train_acc = accuracy_score(y_train, tree.predict(X_train))
    valid_acc = accuracy_score(y_valid, tree.predict(X_valid))

    results.append((alpha, tree.get_n_leaves(), train_acc, valid_acc))

for alpha, leaves, train_acc, valid_acc in results:
    print(
        f"alpha={alpha:.6f}, "
        f"leaves={leaves}, "
        f"train_acc={train_acc:.3f}, "
        f"valid_acc={valid_acc:.3f}"
    )

best_alpha, _, _, _ = max(
    results,
    key=lambda row: row[3],
)

final_tree = DecisionTreeClassifier(
    random_state=42,
    ccp_alpha=best_alpha,
)
final_tree.fit(X_train, y_train)

print("selected alpha:", best_alpha)

输入是 Iris 的数值特征和类别标签。cost_complexity_pruning_path 返回一组候选 α\alpha,通常对应树结构发生变化的节点。代码在训练集上生成候选值,再用验证集选择参数,避免直接根据训练准确率选择最复杂的树。

生产训练时,更稳妥的做法是使用交叉验证选择 ccp_alpha,然后在确定超参数后重新使用训练数据拟合最终模型。若数据存在时间顺序,应采用时间切分,而不是随机打乱。

7.4 剪枝不等于一定提升指标

剪枝减少方差,但可能增加偏差:

  • 树太大:训练误差低,验证误差高;
  • 树太小:训练误差和验证误差都高;
  • 合适复杂度:在当前数据分布下取得较好泛化。

如果验证集很小,选择出的剪枝参数也可能不稳定。此时不能把一次验证集结果当成确定规律,应报告交叉验证波动或多次时间切分结果。


八、过拟合的形成机制与诊断

8.1 决策树为什么容易过拟合

树的划分边界通常是轴对齐的,例如:

x1tx_1\le t

不断组合这些条件后,模型可以构造很细的矩形区域。深树能够表达复杂关系,但也能表达:

  • 测量噪声;
  • 训练集中的偶然异常;
  • 数据采集流程产生的伪规律;
  • 直接泄露标签的信息。

在叶节点只有一个或几个样本时,分类概率和回归均值的统计方差都很大。

8.2 典型表现

分类树常见表现是:

训练准确率:接近 1
验证准确率:明显较低

回归树可能表现为:

训练误差极低
验证误差在树深增加后反而上升

诊断时应绘制或记录:

  • 树深度与训练/验证指标;
  • 叶节点数量;
  • 每个叶节点样本数;
  • 训练集和验证集的类别分布;
  • 时间切分下的指标变化;
  • 不同随机种子下的结果波动。

如果训练指标远高于验证指标,优先检查数据切分和标签泄露,再调整树复杂度。单纯降低深度不能修复错误的评测设计。

8.3 数据泄露会伪装成“树很强”

例如要预测用户是否会取消订单,却把“退款完成时间”作为特征。这个字段在预测时可能尚未产生,但在训练数据中已经包含了标签发生后的信息。

树会迅速选择这类特征,形成看似高质量的纯节点。结果是:

  • 离线指标异常高;
  • 上线后指标快速下降;
  • 特征重要性集中在未来字段;
  • 按事件时间重新构造特征后性能消失。

因此,训练、验证和线上推理必须遵守同一信息时间边界。标签生成时间之后才能获得的字段,不得用于该预测时点的特征。

8.4 类别不平衡

假设正类只占 1%。一棵树始终预测负类,准确率已经达到 99%,但对正类完全没有识别能力。

应根据业务目标选择评测指标,例如:

  • Precision;
  • Recall;
  • F1;
  • ROC-AUC;
  • PR-AUC;
  • 代价敏感的期望损失。

分类树还可以使用 class_weight="balanced" 或自定义类别权重,使不纯度计算考虑不同类别的重要性。权重不是免费修复:它会改变分裂和叶节点预测,应在与业务代价一致的验证集上评估。


九、缺失值:缺失不是一个普通数值

缺失值至少有三种不同含义:

  1. 随机缺失:设备偶尔没有上报;
  2. 条件缺失:只有某类用户才会填写;
  3. 信息性缺失:未填写本身就包含业务信号。

例如“是否填写收入”可能与信用风险相关。简单把缺失值填成均值,可能同时丢失“缺失状态”和原始数值信息。

9.1 常见处理策略

删除样本或特征

只有在缺失比例很低、删除不会改变目标分布时才合理。若高风险用户更容易缺失,直接删除会产生选择偏差。

统计填补

数值特征可以使用均值或中位数,类别特征可以使用众数或特殊类别:

<missing>

中位数通常比均值更不容易受极端值影响。

增加缺失指示变量

对原始特征 xx 增加:

m={1,x 缺失0,x 非缺失m= \begin{cases} 1,& x\text{ 缺失}\\ 0,& x\text{ 非缺失} \end{cases}

然后对 xx 填补,对 mm 保留缺失状态。这样模型可以分别利用“填补后的数值”和“是否缺失”。

原生缺失值分支

某些树实现允许在训练时学习缺失值应该走哪一侧,或为每个节点记录缺失样本的默认方向。这比统一填补更灵活,但具体行为必须查看所用版本和实现文档,不能把一个框架的能力假设为所有决策树都有。

许多常用基础决策树接口在特定版本或输入类型下仍要求有限数值;如果接口不支持 NaN,直接传入会在训练或预测阶段报错。使用 scikit-learn 时,应以当前版本 API 文档为准,并可通过 Pipeline 显式处理缺失。

9.2 可执行的缺失值处理

下面的流程使用中位数填补,并保留缺失指示变量:

import numpy as np

from sklearn.datasets import load_iris
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report

X, y = load_iris(return_X_y=True)

# 构造示例缺失值;真实项目中应来自原始数据
X = X.astype(float)
X[0, 0] = np.nan
X[10, 2] = np.nan

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

model = Pipeline([
    (
        "imputer",
        SimpleImputer(
            strategy="median",
            add_indicator=True,
        ),
    ),
    (
        "tree",
        DecisionTreeClassifier(
            max_depth=4,
            min_samples_leaf=3,
            random_state=42,
        ),
    ),
])

model.fit(X_train, y_train)
pred = model.predict(X_test)

print(classification_report(y_test, pred))

关键点是:SimpleImputer 只在训练集上拟合中位数。若先在全量数据上计算中位数再切分,测试集的分布信息会泄露到训练流程中。Pipeline 将填补和模型绑定,能减少训练、验证、线上预测使用不同处理逻辑的风险。

9.3 缺失机制改变时的失败表现

如果训练期间某个传感器缺失比例为 2%,上线后由于设备升级变成 40%,模型可能大量样本走填补值或缺失分支,导致预测分布改变。

诊断应比较:

  • 训练期和线上各特征缺失率;
  • 缺失率按时间、设备版本、地区、用户群的分布;
  • 缺失样本与非缺失样本的标签率;
  • 线上缺失率变化后的预测概率分布。

缺失处理不仅是预处理问题,也属于数据质量监控和模型行为监控。


十、决策树的可解释性

10.1 路径解释

单棵树的局部解释可以直接写成路径:

花瓣长度 <= 2.45
→ 是
→ 预测类别:setosa

复杂路径则是多个条件的合取:

(x12.45)(x3>1.75)(x23.10)(x_1\le 2.45) \land (x_3>1.75) \land (x_2\le 3.10)

这比深度神经网络的隐式表示更容易向工程师、审计人员或业务人员展示。

scikit-learn 可以输出文本树:

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier, export_text

X, y = load_iris(return_X_y=True)

tree = DecisionTreeClassifier(
    max_depth=3,
    random_state=42,
)
tree.fit(X, y)

print(export_text(
    tree,
    feature_names=load_iris().feature_names,
))

也可以使用 sklearn.tree.plot_tree 绘制节点中的:

  • 划分条件;
  • 不纯度;
  • 样本数;
  • 类别分布;
  • 预测类别。

10.2 全局解释不只是一棵规则表

树的可解释性有边界。

第一,树越深,规则路径越长,虽然仍然形式明确,但人类阅读成本会快速上升。

第二,独热编码会把一个业务特征拆成许多个内部特征。模型可能先判断 城市_北京,再判断 城市_上海,业务解释不一定等于原始字段解释。

第三,特征重要性可能被误读。基于不纯度下降的特征重要性定义为:

Importance(j)=t:split feature=jNtNΔItImportance(j) = \sum_{t:\text{split feature}=j} \frac{N_t}{N} \Delta I_t

其中:

  • tt 是使用特征 jj 的节点;
  • Nt/NN_t/N 是到达该节点的样本比例;
  • ΔIt\Delta I_t 是该节点的不纯度下降。

它会偏好:

  • 可产生更多候选切分的连续特征;
  • 取值很多的类别特征;
  • 被其他相关特征替代或重复使用的特征。

因此,不纯度重要性不等于因果影响,也不一定是稳定的特征筛选结果。

置换重要性通过打乱某个特征,再观察验证指标下降来衡量依赖程度,通常更接近预测贡献,但在强相关特征之间仍可能产生误导:一个特征被打乱后,相关特征可能继续提供相同信息,导致重要性看起来偏低。

10.3 相关性不等于因果性

如果树发现:

是否安装某 App → 是否违约

这只说明该字段有预测关联,不能说明安装 App 导致违约。它可能只是地区、年龄、设备类型或营销渠道的代理变量。

在高风险决策中,还需要评估:

  • 特征是否涉及受保护属性;
  • 是否存在代理变量;
  • 不同群体的误报和漏报是否差异过大;
  • 规则是否违反业务或法律约束;
  • 是否能提供申诉和人工复核路径。

“可以解释”不等于“决策合理”,更不等于“因果成立”。


十一、分类概率、叶节点样本量与校准

决策树的分类概率常由叶节点类别频率得到。假设叶节点中:

正类:1
负类:9

则正类概率约为 0.1。

如果叶节点只有一个样本,那么概率可能直接变成 0 或 1。这种概率通常不稳定,尤其在训练样本少、标签有噪声时。

这解释了为什么:

  • 限制 min_samples_leaf 可以改善概率稳定性;
  • 树的分类准确率不错,不代表概率已经校准;
  • 需要预测风险概率时,应单独检查校准曲线和 Brier score;
  • 可使用校准方法,但校准器必须在独立数据或交叉验证框架中拟合。

例如,一个模型输出 0.8,不一定意味着同类样本中约 80% 真正为正类。分类排序能力和概率校准是两个不同目标。


十二、训练与推理中的特征处理边界

12.1 数值特征不需要标准化

对于普通决策树,规则只关心排序和阈值。例如:

收入 <= 10000

如果把收入统一乘以 0.001,规则会变成:

收入 <= 10

样本的排序关系没有变化,因此树的候选划分本质上不受线性缩放影响。与 KNN、线性模型或神经网络不同,标准化通常不是决策树的必要步骤。

但以下情况仍需注意:

  • 不同实现的数值精度处理可能导致极少量边界差异;
  • 预处理后的字段必须在训练和推理时一致;
  • 对输入做日志变换可能改变排序关系和异常值结构,因此它不是“仅缩放”。

12.2 训练、预测和版本状态

一个可部署的树模型至少依赖:

  1. 训练时的特征名称和顺序;
  2. 缺失值填补规则;
  3. 类别编码映射;
  4. 模型参数和树结构;
  5. 标签编码方式;
  6. 训练时使用的库版本及序列化格式。

只保存树本体而不保存预处理器,可能出现“模型仍能加载,但预测含义已变”的故障。推荐把预处理和模型放在同一个 Pipeline 中,并对输入 schema 做校验。

12.3 错误处理

线上推理至少应显式处理:

  • 缺少必需字段;
  • 字段类型错误;
  • 类别值不在训练字典中;
  • 数值超出合理范围;
  • 缺失率突然升高;
  • 模型文件与预处理版本不匹配。

不要用静默默认值掩盖所有错误。例如,把无法解析的年龄统一填成 0,可能让大量请求进入一个训练中不存在的异常区域。对于关键特征,应区分“合法缺失”和“数据格式错误”。


十三、决策树与集成模型的关系

13.1 随机森林

随机森林训练多棵具有随机性的树:

  • 每棵树使用 bootstrap 样本或随机采样;
  • 每个节点只考虑部分特征;
  • 分类通过投票,回归通过平均。

单棵树的方差较高。对多棵相关性较低的树求平均,可以降低方差。

随机森林通常比单棵树稳定,但可解释性从一条路径变成多棵树的综合行为。可以查看平均特征重要性或使用局部解释方法,但不应把一棵“代表性树”当成整个森林的完整逻辑。

13.2 梯度提升树

梯度提升树顺序训练多个弱树。第 mm 棵树拟合前面模型的残差或损失梯度:

Fm(x)=Fm1(x)+ηhm(x)F_m(x)=F_{m-1}(x)+\eta h_m(x)

其中:

  • FmF_m 是第 mm 轮的模型;
  • hmh_m 是本轮新增树;
  • η\eta 是学习率。

单棵树提供分段规则,提升过程通过许多小树叠加形成复杂函数。它通常有更强的预测能力,但解释不再等同于一组简单业务规则。

13.3 与深度学习和生成式 AI 的边界

决策树特别适合:

  • 结构化表格数据;
  • 规则边界明显的分类问题;
  • 需要低延迟、低资源推理的场景;
  • 需要展示明确路径的审核或辅助决策。

它不擅长直接处理原始图像、长文本和音频。生成式 AI 系统中,决策树可以用于:

  • 请求路由;
  • 风险分层;
  • 模型选择;
  • 成本预算;
  • 内容审核前置分类;
  • 根据用户权限决定是否允许调用某个模型。

但树不能替代生成模型对文本语义的建模。若把高维嵌入直接输入树,可能得到一个可训练的模型,却未必比专门的检索、线性分类器或神经网络更合适。生产系统还要同时评估模型效果、数据权限、响应延迟、推理成本和审计要求。


十四、常见误解与失败模式

误解一:训练准确率为 100% 说明模型很好

无限制的树很容易记住训练集。必须查看独立验证集、时间外测试集或线上回放数据。

误解二:树不需要特征工程

树不需要标准化,但仍然需要处理:

  • 标签泄露;
  • 时间窗口;
  • 类别编码;
  • 缺失状态;
  • 异常值语义;
  • 不合理的 ID 特征;
  • 训练和推理字段不一致。

误解三:特征重要性就是因果影响

树学习的是预测关联,不会自动识别因果关系。重要性还会受到相关特征、候选切分数量和数据抽样的影响。

误解四:剪枝越强越好

剪枝会降低模型容量。若真实关系复杂,过度剪枝会导致欠拟合。应通过与部署目标一致的验证方案选择复杂度,而不是默认追求最小树。

误解五:处理了缺失值就解决了数据质量

填补只是给模型提供输入。它不能修复:

  • 传感器停止上报;
  • 业务字段含义变更;
  • 新类别大量出现;
  • 线上缺失分布漂移;
  • 缺失与标签之间的结构变化。

十五、一个可复用的建模检查框架

训练一棵决策树时,可以按以下因果顺序检查:

  1. 先定义预测时点:明确哪些信息在预测发生时真实可见;
  2. 再定义标签和切分方式:时间数据优先按时间切分,避免未来样本进入训练;
  3. 检查输入类型和缺失机制:区分合法缺失、异常值和格式错误;
  4. 建立受控基线:限制深度或叶节点大小,避免直接训练无限制大树;
  5. 比较划分准则:分类可比较基尼和熵,回归根据异常值敏感性选择平方误差或绝对误差;
  6. 使用验证集或交叉验证调复杂度:包括深度、最小叶节点样本数和 ccp_alpha
  7. 检查训练与验证差距:识别过拟合、泄露和分布不一致;
  8. 检查群体和时间切片:整体指标可能掩盖特定群体的失败;
  9. 解释具体路径和错误样本:不要只看全局重要性;
  10. 保存完整流水线:同时保存预处理、模型、特征 schema 和版本信息。

决策树的价值在于把预测函数分解成可执行的局部条件,但它的可靠性取决于每个条件背后的数据时间、缺失语义、验证设计和部署约束。真正可解释的模型,不只是能打印出规则,还应能说明规则使用了什么信息、在什么数据上验证过,以及在哪些输入条件下不应继续信任它。


系列导航与关联阅读

官方资料

本文依据研究论文、标准组织与主流框架官方文档重新梳理;正文、示例与工程清单由 WR BLOG 编写。