ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

决策树核心原理与工程实践:从信息增益到剪枝及缺失值处理

决策树核心原理与工程实践:从信息增益到剪枝及缺失值处理

开学那会儿翻开这本书的目录,第四章“决策树”三个字平平无奇,我甚至一度觉得它是最没技术含量的一章——不就是if-else嵌套吗?直到用Python里的库跑完几个模型的对比,又回头啃了两遍这章内容,才发现自己错得离谱。纯啃公式的读书笔记太多了,这一篇我尽量换个讲法:不按原书章节顺序走,而是先把“一棵树到底是怎么长出来的”讲明白,再回到那些容易被跳过的难点上,比如剪枝、连续值、缺失值、多变量树。如果你也是正在啃这本书的初学者,或者用了一段时间树模型想看下底层逻辑,这篇笔记应该能帮你省不少力气。

1. 决策树的核心逻辑:一套“先问哪个问题”的分层筛选机制

这一节在原书里属于基础铺垫,但很多人恰恰是在这里就开始吃夹生饭。决策树的推理逻辑,说白了就是一句大白话:遇到一个新样本,我不知道它属于哪一类,就反复问它“你的某个特征是什么”,每问一次缩小一圈候选范围,最后剩下来的类别就是预测结果。

这个过程放到数据结构里就是一棵树。顶部叫根节点,中间每个判断点叫内部节点,最底部不再继续分叉的叫叶节点,每个叶节点对应一个决策结果。整棵树的生成过程,是从根节点开始递归地“选属性、分数据、建子树”,直到数据被分隔得足够纯,或者可用特征用完为止。书中给的基本流程里有两个细节值得圈出来:

  • 每一个非叶节点只挑一个最优属性划分,这个“最优”是局部意义上的最优,决策树整体并不保证全局最优。
  • 递归终止有三个条件:当前节点样本全属于同一类别;当前属性集为空或所有样本在剩余属性上取值相同;样本集合为空。第三个条件容易被漏掉,它对应一种特殊处理——把父节点中样本最多的类别作为叶节点的标记。

这种“分而治之”的策略之所以几十年不过时,在于它和人类的分类认知高度吻合。我拿生活中最常见的场景举例:你妈给你介绍对象,对方资料摆在桌上,你第一眼看的既不是学历也不是收入,而是“在不在一个城市”。见了面看“聊不聊得来”,再往下看“有没有存款”。每问一个问题都砍掉一批选项,这就是一棵活脱脱的决策树。树模型的强项不是像线性回归那样给出一个光滑的连续打分,而是把规则摊开,每一步都讲得出理由——所以它在风控、医疗诊断这类需要解释性的场景里依然是主力。

这里的原书内容我建议反复读三遍,核心不是背流程,而是理解“递归”“划分”“纯度”这几个词背后到底对应了什么操作。

2. 属性选择到底在优化什么:信息熵、信息增益、增益率、基尼系数的来龙去脉

如果决策树生成过程是一台机器,那“选哪个属性作为当前节点”就是这台机器的发动机。书里真正难啃的第一块硬骨头就在这里——明明叫决策树,为什么非要扯上信息论里的“熵”。

信息熵这个概念当初也绕了我一阵。你把它理解成不确定性程度就对了。一箱球如果全是红的,闭着眼睛猜都能猜对,熵为0;如果五种颜色各占五分之一,猜错概率高,熵就大。公式是:Ent(D) = -Σ p(k) × log2(p(k)),其中k是类别下标,p(k)是第k类样本占比。这个公式不要求你会推导,但要求你能说出一个直觉含义:熵越小,数据越纯。

  • 信息增益说白了就是“划分之前的不确定性”减去“划分之后的不确定性”。Gain(D, a) = Ent(D) - Σ(|Dv| / |D| × Ent(Dv)),其中v是属性a的各个取值。这个差值越大,说明用了这个属性之后,类别变得更确定了,那它就应该被优先拿来划分。ID3决策树就是按信息增益选属性的。
  • 信息增益有个明显毛病——它天然偏好取值数目多的属性。比如你拿样本编号当属性,每个编号一个值,信息增益直接拉满,但这显然毫无泛化能力。为了掰回这种倾向,C4.5改成用增益率,公式是Gain_ratio(D, a) = Gain(D, a) / IV(a),其中IV(a)是属性a的固有值,等于 -Σ(|Dv| / |D| × log2(|Dv| / |D|))。取值多的属性,IV值通常更大,等于给信息增益做了个除法惩罚。但惩罚也不能太狠,否则又会偏向取值少的属性,所以C4.5的做法是先挑信息增益高于平均水平的属性,再从里面选增益率最高的。
  • 基尼系数又是另一套思路,它不用熵的log运算,公式是Gini(D) = 1 - Σ p(k)²。直观含义是:从数据集里随便抽两个样本,它们类别不一致的概率。概率越小,数据集越纯。CART决策树就是基于基尼指数来选划分属性的。

我曾经在项目里只盯着准确率看,对Node上的impurity(不纯度)指标毫不在意。后来遇到一棵深度夸张的树,打印出的结构完全不符合业务直觉,回头排查才发现是不同节点用了不同的分裂标准,模型库默认的配置和书上写的算法根本对不上。所以我建议你把这些数学指标动手算一遍——取一个20条样本的小表,分别用三种指标算同一个属性的得分,你就知道它们的结果差在哪了。不亲手算,只看公式,你永远记不牢。

为了更直观,这里放一个我当年手工推演的对照小表(虚构数据集:“天气、温度、是否出门”):

划分属性信息增益增益率基尼指数(按划分后加权均值)
天气0.2470.2860.350
温度0.1070.1390.408
选择结论选“天气”若增益率比较,仍可能偏向“天气”选“天气”

不同指标在大部分简单数据上结论一致,但在特征取值数差异很大的数据集上,ID3和C4.5会有明显分歧。这也是为什么工程上更常用CART,因为它计算快,而且后面能直接对接梯度提升框架。

3. 剪枝处理:为什么树不能任它长到完美

在我第一次用默认参数训练决策树时,训练集的准确率直逼100%,一到测试集立刻掉到刚及格的水平。当时的我把锅甩给数据集太小,后来读了剪枝一节才恍然大悟——树长过头了,把训练集里的细枝末节全记下来了,这种状态叫过拟合。

剪枝是决策树对抗噪声和过拟合的主要手段。书里讲了两种思路:预剪枝和后剪枝。

  • 预剪枝的策略是边构建边剪:每次尝试划分一个节点时,先用验证集估算一下如果这个划分不做的精度,再估算一下做了的精度,只有后者更高才允许生成。它可以防止树长得太大,减少训练时间,但风险在于“只看一步”——当前收益不高不代表后续几步不会带来更大收益。这种贪心式的近视眼可能会导致某些有潜力的分支还没展开就夭折了,精度提升有限。
  • 后剪枝的策略是先让你把那棵树完整长出来,然后自底向上对每个非叶节点做手脚:如果把以它为首的分支整个换成一个叶节点,验证集精度不降反升,那就果断砍掉。这个方案保留了更多分支空间,通常比预剪枝更能降低过拟合风险,但代价是训练时间明显增加。

我曾拿一个中等规模的数据集做过对比:用预剪枝生成的树小了一半,训练时间少了一大截,但测试集精度比后剪枝低了三个多点。可见“懒得多花时间”这种心态在机器学习里经常要付出精度代价。

书里还暗示了一个在实际建模时很重要、但写得比较含蓄的点:剪枝用的验证集数据会参与“训练”吗?严格说,验证集参与了模型超参数的选择也就是结构的选择,这部分信息依然可能造成一定程度的间接过拟合。很多人在做交叉验证时会忽略这层耦合关系。我的习惯是:如果数据量充足,预留30%作为最终测试集,剪枝只在内部做交叉验证;数据量紧张时,宁可少剪几刀,也要保证测试集是“干净”的。

4. 连续值与缺失值处理:容易被漏掉的工程细节

如果说剪枝是这章最容易丢分的主观题,那连续值和缺失值就是最容易被跳过的客观题。很多人学完ID3、C4.5就觉得自己会决策树了,一上手遇到“年龄:27.5岁”这种连续特征就愣住,因为信息增益那套理论默认属性是离散的。书里给的标准解法叫二分法,思路极简:属性有N个不同取值就排序,把相邻取值的中间点作为候选划分点,把连续值变成“大于等于这个点”和“小于这个点”两个离散块,然后按离散属性去算信息增益。注意,连续值特征可以被多次使用,不同层次的节点可以在不同切分点上重复利用它,这一点和离散属性“用一次就不能再用”完全不同。

这一节还有一处对小白尤其不友好,就是缺失值的处理。训练集里如果某条样本缺少了当前划分属性上的取值,既不能直接扔掉(样本太少会浪费信息),也不能随便填个均值(会引入噪声)。书里给出的方案是:

  • 找划分属性时,用无缺失值样本作为子集计算信息增益,再按缺失样本比例折算。
  • 某个样本在划分属性上缺失时,把它按不同权重划分到所有子节点,权重和样本落入子节点的比例相关。
  • 权重机制贯穿始终:每个样本初始权重为1,缺失值样本每次划分都按比例更新权重,最后被用于叶节点的类别统计。

这个机制读起来很容易被绕晕,我建议你在纸上画一个只有10条数据的小例子,手动走一遍“1号样本缺失颜色特征”时的权重流动过程,比看十遍公式都管用。真要在具体库中处理时,实现略有差异但思路相同,处理好缺失值后,模型对比才有意义。

5. 从单棵树到一片森林:这本书埋下的进阶线索

CART树在实践中的使用是深度学习中也会用到的构件。因为任何基于决策树的集成方法,除了极少数情况外,都需要在每一轮迭代中拟合一棵树;这棵树的分裂质量直接决定了集成的上限。所以看第四章的时候多留一个心眼,底层逻辑扎实了,后面学梯度提升时会顺利得多。在此之前,你手头的工具配置就可以先跑通了。装好环境后,随便找一个公开的表格数据(比如鸢尾花、泰坦尼克),用默认参数拟合一棵CART树,再打印出树结构图,你会对“划分”“纯度”“叶节点样本数”这些词有非常直观的认知。不用急着调参,主要为了建立体感,让博士式的符号变成看得见的图形。

剪枝在库中通常由成本复杂度参数控制,数值越大、树被砍得越狠,可以简单地把这个参数理解成后剪枝力度的一种实现。这部分再往后就是调参艺术了。

6. 多变量决策树:线性的根,逻辑的叶

这一节在初读时很容易被当成“选读内容”掠过,但它其实回答了一个关键问题——决策树的分类边界为什么总是横平竖直的?

普通决策树在每个节点只做单属性判断,学到的分类边界是轴平行的,也就是和坐标轴垂直的线。如果真实数据的最优边界是斜的,普通决策树就尴尬了——它必须用一堆阶梯状的折线去反复逼近那条斜线,树深直接翻倍,泛化能力自然受影响。

多变量决策树的想法是:一个节点的划分不用单个属性,而用多个属性的线性组合。每个非叶节点维护一组系数,相当于在以这些属性为坐标的空间中画一条任意方向的斜线作为边界。这样针对斜向分布的类别,树不用长很高就能分开。书的第四章在这里点到为止,但对读后续章节很重要,因为很多集成模型中的随机特征子空间、线性树等概念都能从这里找到思想源头。

我在工作中用到的调参和模型融合方案,也是基于这种思路做一些组合特征。比如给决策树喂一些手工构造的“组合后特征”而非原始单特征,往往能显著压缩树深度。这也算是多变量树思想在工程里的“穷人手写版”吧。

7. 动手实现一版迷你决策树:从零开始不长篇大论

这章的笔记如果看完就散,那确实太可惜了。建议用最精简的方式在十几分钟内把核心跑通,我写过一版简化实现,核心伪代码如下:

函数 生成节点(样本集D, 属性集A): 如果 D 全部属于同一类: 返回叶节点 如果 A 为空 或 D 在A上取值相同: 返回叶节点,标记为 D 中最多的类 对每个属性 a in A: 计算按 a 划分后的纯度指标 选出最优属性 best_a 创建节点 node,标记为 best_a 对 best_a 的每个取值 v: 子集 Dv = D 中 best_a == v 的样本 如果 Dv 为空: 创建叶节点,标记为 D 中最多的类 否则: 递归调用 生成节点(Dv, A \ {best_a})

这里最有价值的是条件“D在A上取值相同”。很多人实现时会漏掉它,导致递归无限循环——所有样本在剩余属性上完全一样了,再往下分已经不可能,但代码意识不到,还在硬选属性。等你写代码亲自踩一次这个坑,就知道书中为什么特意加这个终止条件了。写完这个骨架后,再跑一下剪枝对比,课堂味和工程味就都有了。

8. 笔记之外:结合实战的三个提醒

很多人在读书笔记阶段非常认真,但一到自己的数据集上就陷入“默认参数流”。决策树模型的有效性并不取决于那个“魔力参数”被调得有多准,而取决于数据和模型的匹配度。针对树的调优,我个人的经验有以下几点:

  • 树的深度和叶子节点最小样本数比分裂标准对结果影响大得多。设置叶子节点最小样本数可以连续抑制过拟合,比每次调裁剪复杂度参数更易解释。
  • 特征数值范围敏感度低,但特征重要性会被取值多的特征带偏。决策树的信息增益偏好会在真实数据中导致偏差,用C4.5思路(先算增益均值再选增益率最大)往往比直接调默认参数更稳。
  • 树模型天然适合处理混合类型特征,但前提是把缺失值编码成显式状态,而不是全部填成-1或者0。缺失本身可能是重要的信号。

书里不会告诉你的是:同样的超参数,在数据量变化超过一个数量级之后,需要完全重调。树的复杂度本质上由样本密度决定,样本越多,树越有空间长深。我在多个项目中的保守做法是,先固定一个合理的叶子最小样本数,再通过交叉验证选择深度和特征数量,并把这种配置写入项目记录中。没有记录不清的调参经验,跟过拟合有什么区别?

9. 回头看:这一章到底教会了什么

第四章看上去信息量巨大,把原始树的诞生过程拆开来看,不过几年时间沉淀下来的几个经验:找最有区分度的特征、控制生成规模、容忍真实数据的脏乱、必要时允许边界倾斜。如果把这本书当作一本“手艺集”,第四章就是最核心的入门工具章。读完笔记之后,别忘了回原书看每个小节最后的小结和习题。习题虽少,但每道题都值得认真推理,尤其是那些要求你“用公式计算”的题——不亲手算一遍,你真的会以为自己懂了。

这一章读完之后,再往后看神经网络、支持向量机这些黑箱模型时,你会多一个对比参照:黑箱模型牺牲了可解释性,换来了表达复杂边界的能力;而决策树一直坚持把决策摊在台面上,代价是边界表达受限。没有十全十美的模型,只有适不适合你当前场景的选择。就我个人而言,很多模型上线后最煎熬的环节就是向业务方解释预测逻辑,每到这种时候我都会怀念决策树的直白。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进