资讯动态

决策树从信息增益到剪枝:算法原理与随机森林实战

发布时间:2026/9/30 15:39:49 来源:尧图企业网站定制
前几天有个学弟在准备机器学习期末问我“学长决策树是不是就是一堆if-else套起来”我说你这个直觉没错但真要把决策树的理论讲透远不止if-else这么简单。信息增益为什么能当特征选择的依据ID3、C4.5、CART到底差在哪剪枝为什么比多长几层更重要缺失值到底怎么处理这些问题在期末卷子和面试题里反复出现也是真正区分“背概念”和“理解算法”的分水岭。我按自己的理解把决策树理论从头到尾串一遍从认识猫的分类直觉到信息熵、增益率、基尼指数再到剪枝、连续值和缺失值处理最后聊聊为什么随机森林能救活一棵单薄的树。不管你是机器学习入门、准备期末复习还是面试前临时抱佛脚这篇文章都值得花半小时读透。1. 从“认识猫”说起为什么决策树能承担分类任务1.1 一个小孩学认猫背后就是决策树如果你从没跟小孩解释过“猫”这个概念他现在只能通过你的描述来认。你可能会说先听声音会“喵喵叫”的大概率是猫再看耳朵尖耳朵、短脸、胡须长多半是猫最后看尾巴长尾巴喜欢摇的也可能是猫。这一连串判断串起来就是一棵树。根节点是“叫声”内部节点是“耳朵”“尾巴”叶节点是“猫”或“不是猫”。机器学习里的“标签”就是“猫”这个类别结论“特征”是耳朵、声音、胡须、毛色这些可观测属性。我们喂给模型大量带标签的样本比如“尖耳朵会喵喵叫长胡须 → 猫”“垂耳朵汪汪叫 → 狗”。决策树的任务就是从这些样本里自动找出那套“先看什么、再看什么”的判断顺序而不是靠人手动写规则。这里要强调一下决策树是监督学习算法训练数据必须有标签。没有标签节点纯度就无从计算算法也不知道该往哪个方向分裂。热搜里那个“机器学习 认识猫 标签”的说法说的就是这个起点——类别标签是分类任务的锚点。1.2 判断顺序为什么比规则本身更重要同样是if-else顺序不同分类效果可能天差地别。假如一棵决策树的根节点是“毛色是橘色”那么橘猫很快能被分出来但白猫、黑猫、狸花猫会全部掉进“不是猫”的分支分类惨不忍睹。如果根节点换成“会不会喵喵叫”大部分猫都能在第一层被正确识别剩下难分的样本再交给下一层判断。所以决策树的核心问题不是“怎么设计规则”而是“规则按什么顺序排”。判别能力最强的特征应该放在靠近根节点的位置让每次分裂都能最大程度把不同类别分开。这引出了机器学习里非常重要的概念——纯度。一个节点如果包含的样本全部属于同一类别那它非常纯如果正例负例各一半那它非常混乱。决策树的生长过程本质上就是不断追求让子节点越来越纯的过程。只要有一个指标能量化“纯”或者“不纯”我们就可以比较不同特征的划分效果。下一节要讲的信息熵就是用来干这件事的。2. 特征选择的三个核心指标信息增益、增益率与基尼指数2.1 信息熵不纯度的一把尺子信息熵是香农提出用来度量不确定性的。对样本集合D假设一共有K个类别第k类样本占比为p_k那么D的信息熵定义为H(D) -Σ_{k1..K} p_k · log2(p_k)这个公式初看抽象其实含义很简单一个集合越混乱熵越大越整齐熵越小。极端情况如果样本全部属于同一类p_k1其他p0那么H(D) -1·log2(1) - 0·log2(0) 0。另一极端二分类各占一半p_1p_20.5那么H(D) -0.5·log2(0.5)*2 1。我用一个具体例子帮助你建立直觉。假设有14个样本9个标签是“出去玩”5个标签是“不出去”那么H(D) -9/14 · log2(9/14) - 5/14 · log2(5/14) ≈ 0.9400.940比特意味着这个集合的不确定性还挺高。如果有一个特征能把数据划分成几个子集每个子集的熵都显著降低那这个特征就是有价值的。降低的熵就是我们说的“信息增益”。2.2 信息增益与ID3选让熵降得最猛的特征划分前集合D有一个熵H(D)。使用特征A把D划分成V个子集D_1到D_V之后我们可以计算划分后的加权平均熵也叫条件熵H(D|A) Σ_{i1..V} (|D_i| / |D|) · H(D_i)信息增益就是两者的差值Gain(D, A) H(D) - H(D|A)信息增益越大说明使用特征A划分之后节点纯度提升得越明显。ID3算法就是每次选择信息增益最大的特征进行分裂。还是用玩不玩的例子。假设特征“天气”有三个取值晴、雨、阴。晴有5个样本其中2个去玩、3个不去熵H ≈ -2/5·log2(2/5) - 3/5·log2(3/5) ≈ 0.971雨有5个样本其中3个去玩、2个不去熵≈ 0.971阴有4个样本全部去玩熵 0条件熵 5/14 · 0.971 5/14 · 0.971 4/14 · 0 0.694信息增益 0.940 - 0.694 0.246。再看特征“风速”取“大”和“小”。大有6个样本3去3不去熵 1小有8个样本6去2不去熵≈ -6/8·log2(6/8) - 2/8·log2(2/8) ≈ 0.811条件熵 6/14 · 1 8/14 · 0.811 ≈ 0.892信息增益 0.940 - 0.892 0.048。0.246大于0.048所以ID3在这一步会选“天气”作为根节点。逻辑也很通你判断要不要出去玩天气显然比风速更关键。2.3 增益率与C4.5治一治“多取值”特征信息增益有个著名毛病它天然偏爱取值多的特征。最极端的例子是在特征列表里放一个“编号”。每个样本编号不同按编号划分后每个子节点只有一个样本每个子集的熵都是0信息增益直接等于H(D)达到最大值。但这样的树毫无泛化能力因为它记住的是每个样本的身份证号而不是背后的规律。缓解办法是C4.5使用的增益率。在信息增益的基础上除以一个“固有值”来惩罚取值多的特征Gain_ratio(D, A) Gain(D, A) / IV(A)其中IV(A) -Σ_{i1..V} (|D_i| / |D|) · log2(|D_i| / |D|)还是用编号特征。14个样本每个取值出现1次固有值IV -14 · (1/14 · log2(1/14)) log2(14) ≈ 3.807。信息增益0.940除以3.807增益率只有0.247。如果“天气”的固有值大约是-5/14·log2(5/14) -5/14·log2(5/14) -4/14·log2(4/14) ≈ 1.577那么它的增益率是0.246/1.577≈0.156。看起来增益率也不是特别高但至少不会像信息增益那样被“编号”这种无聊特征带跑偏。C4.5实际实现里会再保守一点先从信息增益高于平均水平的特征里挑选再从这些候选里选增益率最高的避免增益率过度惩罚那些本来很有用的高基数特征。这个细节很多人不知道面试被问到才想起来。2.4 基尼指数与CART换一种不纯度度量CART决策树不使用信息熵而用基尼值衡量不纯度Gini(D) 1 - Σ_{k1..K} p_k²基尼值的直觉是从集合中随机抽两个样本它们的类别不一样的概率。二分类下如果正例比例为p基尼值就是2p(1-p)。p0.5时基尼值最大等于0.5p0或1时基尼值为0集合最纯。特征A的基尼指数是对各子节点基尼值的加权平均Gini_index(D, A) Σ_{i1..V} (|D_i| / |D|) · Gini(D_i)CART选择让基尼指数最小的特征。和信息增益选最大相反因为基尼指数越小越纯。这三个指标可以放到一起对比方便记指标核心思想对应算法选择方向特点信息增益熵下降量ID3越大越好偏爱取值多的特征增益率信息增益/固有值C4.5越大越好对多取值做惩罚基尼指数随机抽两个样本类别不同概率CART越小越好计算更快不涉及log实际工程里CART的基尼指数用得最广因为sklearn里的决策树就是CART不需要算log速度快效果也和信息增益很接近。3. 树是怎么长出来的生成流程、停止条件与特殊值处理3.1 递归生成一棵树的完整流程决策树生成是一个递归过程伪代码可以写成下面这样def build_tree(D, A): 如果D中所有样本属于同一类别: 返回单节点叶节点标记为该类别 如果A为空或者D在A上所有特征取值都相同: 返回单节点叶节点标记为D中样本数最多的类别 从特征集A中选择最优划分特征a* 对a*的每个取值v: 生成一个分支 D_v D中在特征a*上取值为v的样本 如果D_v为空: 把分支节点标记为叶节点类别为D中样本数最多的类别 否则: 以D_v和A删除a*后的特征集递归调用 build_tree这段伪代码里有两个关键点需要特别说明。第一遇到叶节点时用“多数类”作为输出不只是为了凑数而是给无法继续划分的样本一个概率意义上的最佳猜测。比如深层的子节点里还有3个样本2个是猫1个是狗那么输出“猫”是经验风险最小的选择。第二当某个分支的样本为空时不能用空节点应付而应该把父节点的多数类填进去。因为预测时可能遇到训练集中没出现过的特征组合这时候至少要给一个“最大概率”的答案而不是报错。这也是决策树能处理分布外组合的兜底机制。3.2 连续特征二分法处理前面几个例子里的天气、风速都是离散特征。现实中还会遇到温度、年龄、收入这种连续特征。连续特征无法像“晴雨阴”那样枚举取值但可以用二分法。思路是先把连续特征在节点上的所有取值排序然后取相邻两个取值的平均值作为候选划分点。比如温度排序后是22、24、26、28、30候选划分点就是23、25、27、29。对每个候选点把样本分成“≤阈值”和“阈值”两堆再分别计算信息增益或基尼指数。增益最大的那个阈值就作为当前节点的划分阈值。这里有一个和离散特征很不一样的地方离散特征在一个分支路径上用过一次以后通常不会再出现因为继续按同样取值划分没什么意义但连续特征可以在不同深度被反复使用只要每次划分的阈值不同。比如根节点用“温度≤25”切一刀深层节点还可以用“温度≤20”再切一刀这完全是允许的。CART的回归版本也类似但不是用熵或基尼而是用均方误差。它选择让划分后两个子集的均方误差之和最小的切分点和阈值这也是CART能处理回归任务的原因。3.3 缺失值两个问题一起解决真实数据里特征缺失太常见了所以理论必须回答两个问题特征有缺失时信息增益怎么算缺失样本该往哪个子节点分C4.5的做法很巧妙。对于第一个问题假设特征A在14个样本里有2个缺失那就只用12个完整样本计算特征A的信息增益得到一个原始增益再乘以完整样本比例12/14 0.857作为校正后的信息增益。如果一个特征缺失太多乘完系数后增益也会缩水算法天然会避开这种特征。对于第二个问题缺失样本不能直接丢掉否则数据浪费太多。C4.5的做法是给样本配一个权重把缺失样本按“完整样本在各子节点中的分布比例”复制到所有分支。比如在“天气”这个节点上完整样本落到晴、雨、阴的比例分别是5:5:2那么那个缺失天气的样本会以0.417的权重进入晴分支0.417的权重进入雨分支0.166的权重进入阴分支。后续计算熵和多数类时都带着这些权重一起算。这种加权分配在预测时同样适用当一条新样本在某个特征上缺失就同时走所有分支最后把各叶节点的结果按权重汇总。虽然实现起来麻烦但比直接扔掉信息合理得多。4. 剪枝对抗过拟合最朴素也最有效的办法4.1 为什么长满的树不好如果不加任何限制决策树会一直生长直到每个叶节点都纯训练集准确率能到100%。但这种完美只属于训练集换一批数据立刻原形毕露。原因在于树长得越深假设空间越大越容易把训练数据里的噪声、偶然模式当成规律。用统计学习的话说树的复杂度变高泛化误差界会随之变大。你在期末复习里如果看到“泛化误差界”这个词理解成“模型太复杂测试误差容易反弹”就够了。解决办法就是剪枝主动砍掉一些分支让树变小。剪枝是决策树理论里“正则化”思想的体现也是面试极爱问的点。4.2 预剪枝边建边看预剪枝在树生长过程中提前判断当前这个节点值不值得继续分裂判断标准通常是验证集精度。如果分裂后验证集精度比不分裂时高就允许分裂否则直接把这个节点变成叶节点标记为多数类。预剪枝的优点是效率高不用把整棵树长完再修。缺点是容易欠拟合因为它是贪心的有时候当前这次分裂看起来验证集精度没提升但再往下多分两层精度反而会上来。你提前剪掉了后面的好结构也见不到了。这种情况在特征交互复杂的数据集里经常发生。sklearn里常用的max_depth、min_samples_split、min_samples_leaf本质都是预剪枝。调这些参数时你其实就是在手工决定“树长到多大算合适”。4.3 后剪枝先长完再修后剪枝先把决策树完整生长出来让它在训练集上充分拟合然后自底向上检查每个非叶节点如果把以该节点为根的子树替换成一个叶节点验证集精度没有下降甚至上升那就剪掉这棵子树。举个例子某个内部节点的子树在验证集上精度是82%但如果把它换成叶节点直接选多数类验证集精度变成86%说明这棵子树只是过拟合了训练集对验证集没有帮助该剪。反之如果替换后精度掉到80%保留子树。后剪枝通常比预剪枝保留更多有效分支泛化性能更好因为它不是贪心决策而是站在“完整树”这个全局视角修剪。代价是训练开销大——得先把树长满再逐个节点评估。4.4 验证集、交叉验证和代价复杂度不管预剪枝还是后剪枝都绕不开一个问题用什么数据来评价“剪了更好”还是“不剪更好”如果继续用训练集评估结果一定是树越复杂越好剪枝就失去意义。所以必须单独划出一部分验证集模拟模型没见过的数据。如果数据量小单次划分验证集太浪费也可以用交叉验证。交叉验证把数据分成几折轮流拿一折当验证集最后综合精度决定是否保留某个子树。CART的后剪枝更理论化用代价复杂度剪枝。它给每个叶节点加上一个惩罚项R_subtree α·|leaves|其中R_subtree是子树在训练集上的误差|leaves|是叶节点数量α是正则化参数。α越大树越倾向于少叶节点剪得越狠。通过调整α可以生成一串不同大小的候选树再用交叉验证从中选最优。这个思路和线性回归里的正则化一脉相承理解了它很多树模型的超参就不难理解了。5. 从单棵树到随机森林理论为什么有效5.1 树的最大问题是方差单棵决策树有一个致命弱点对数据变化太敏感。训练集稍微换掉几个样本生成的树结构可能完全变样。因为根节点的特征选择受到几个样本的影响根节点一变整棵子树都跟着变。这是高方差的表现也是它容易过拟合的根源。解决高方差最直接的办法是集成多训练几棵树让它们投票或取平均。即使单棵树预测有波动多棵树波动的方向不同平均后能让最终结果更稳定。随机森林就是这个思路的典型代表。5.2 Bagging 降方差随机特征去相关随机森林对训练集做有放回抽样每次抽出一份和原数据集一样大的样本子集去训练一棵树这叫Bagging。因为是有放回抽样不同树用的样本大约有三分之二重叠三分之一不同树的差异就出来了。但光有样本扰动还不够。树在特征选择上很贪婪如果某个特征特别强几乎所有树都会在根节点用它结果树和树之间高度相似集成效果大打折扣。随机森林又加了一个扰动每次分裂时不是从全量特征里选最优而是先随机抽一个特征子集再从子集里选最优。特征子集大小在分类任务里常取sqrt(特征总数)极大降低了树之间的相关性。这就是随机森林的两个“随机”样本随机、特征随机。两个随机组合起来让每棵树尽可能不一样。统计上m棵树平均预测的方差大致是单棵树方差的(1 (m-1)ρ)/mρ是树间相关系数。样本随机和特征随机都是在压低ρ从而让集成后的方差降得更明显。5.3 袋外误差与特征重要性随机森林由于用有放回抽样训练每棵树大约有三分之一的样本没被抽到这些样本叫袋外样本。它们可以天然当验证集每棵袋外样本预测错了多少汇总起来就是袋外误差。不需要额外划分验证集也能对泛化误差有不错的估计这比单棵树的剪枝评估更省事。特征重要性也能借助同样的思路计算把某个特征的取值在所有袋外样本上随机打乱如果袋外误差明显上升说明这个特征很重要如果误差几乎不变说明它可有可无。这种基于扰动的变量重要性方法比直接看树的深度可靠得多也是随机森林理论里值得提的一个亮点。6. 期末复习和面试前必须想明白的几个高频问题6.1 手推信息增益注意这些细节期末最常见的题型是给你一张小表让你手算信息增益并构造一棵决策树。步骤看起来不难但有几个细节总有人丢分。第一log的底数通常取2算出来单位是比特但无论底数是多少信息增益的比较结论不变所以不要纠结“取10还是取2”。第二条件熵的权重一定不能漏是各子集样本数占总数的比例。第三如果类别分布是两个极端比如全正类熵直接写0别硬套公式算错。我复习时习惯先列一个三层表第一列特征取值第二列各类别样本数第三列子集熵。然后按公式一步步带最后比较增益。多算两遍手感就出来了。6.2 ID3、C4.5、CART对比表这张对比表几乎是面试和期末的必考内容算法特征选择树结构连续值缺失值回归剪枝ID3信息增益多叉不支持不支持不支持不支持C4.5增益率多叉支持支持不支持后剪枝CART基尼指数二叉树支持支持代理分裂支持代价复杂度剪枝记住几个关键差异ID3最早但毛病多C4.5在ID3上补了连续值和缺失值CART是二叉树且能做回归是sklearn实际采用的版本。6.3 决策树为什么不用标准化真的不怕异常值吗线性模型要标准化因为特征量纲会影响梯度下降和距离计算。决策树不一样它只比较阈值x t还是x ≤ t。对特征做任何单调变换比如把收入从元换成万元划分点和信息增益都不会变。所以决策树本身不需要标准化。那它怕异常值吗没有线性模型那么怕但也不是完全免疫。决策树对连续特征排序后取相邻点均值当阈值单个极端异常值只会变成某个区间的一个端点对分裂点影响有限。但如果异常值带来一个极小分支这个分支可能过拟合。实际处理中我一般还是会看一眼连续特征的分布极端离群点太离谱时先做缩尾或截断再建模能让树更稳。6.4 容易被问懵的边界情况最后说几个容易被问懵的边界情况。如果某节点里所有样本在特征上取值完全相同但标签不同怎么办答案是直接返回叶节点标记为多数类而不是继续尝试划分。因为此时已没有可用信息继续分只会硬造规则纯属过拟合。如果某个特征取值特别多比如用户ID、时间戳即使用了增益率仍然可能被选中。这时候要做的是业务判断而不是纯靠算法硬扛。把这种高基数特征去掉或者做分箱比调参更有效。还有一个很多初学者忽略的点剪枝的评估必须用验证集不能用训练集。用训练集评估剪枝永远是“不剪更好”那就没有任何剪枝意义了。理解了这一点就理解了为什么交叉验证在树模型里这么重要。最后分享一个我复习决策树的小习惯光看公式很容易忘我会把课堂上的小数据集手动算一遍信息增益再用sklearn的决策树接口跑一遍对比tree_.feature和我的手工结果。往往一两组数据下来理论里的模糊点就全部对上了。这也是我给所有准备期末或面试的人的建议——决策树理论不复杂但一定要自己动手推一遍纸上的0.940和0.246比背十遍公式都记得牢。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑