news 2026/10/2 7:38:47

决策树算法对比:ID3、C4.5与CART的演进与实战选择

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
决策树算法对比:ID3、C4.5与CART的演进与实战选择

面试机器学习岗位,十次有八次会被问到那个经典问题:决策树的ID3、C4.5、CART到底有什么区别?我当年也背过答案,什么“信息增益”“增益率”“Gini系数”,滚瓜烂熟,但真到自己拿数据建模型的时候才发现,这三个名字根本不是孤立的知识点,而是同一个“选特征、分裂数据”的思想在不同约束条件下的演化版本。这篇笔记就围绕三者的差异展开:先用一个能跑的数据集把信息增益手算一遍,讲透ID3,再看C4.5如何打补丁,最后说清CART为什么能成为工程界的默认选择,末尾附上我自己调参踩过的几个坑。

1. 三种算法的血缘关系:为什么学了ID3还要学C4.5和CART

1.1 它们都在回答同一个问题:下一个分裂节点选谁

决策树的核心思想可以理解成一个“猜谜游戏”:给定一批样本和一堆特征,我们的任务是在每个节点上找到“最能区分不同类别”的特征,把数据集切成若干子集,然后递归重复这个过程,直到子集足够纯,或者满足停止条件。这里最核心的技术点就变成了一个比较问题:一大批特征摆在你面前,凭什么选A不选B?三个算法的分歧就从这里开始。

  • ID3用信息增益,谁带来的熵减最多就选谁;
  • C4.5用信息增益率,对信息增益做一次归一化再比;
  • CART用Gini不纯度(分类任务)或均方误差(回归任务),并且强制只做二叉分裂。

树形结构天然有两点好处。一是可解释性强,根节点到叶子节点的路径本身就是一条规则,业务方看得懂也说得清;二是对数据分布假设很少,不像线性模型那样要求特征独立、残差异方差等一堆前提。这也是为什么后来随机森林、XGBoost、LightGBM这些集成模型都愿意拿决策树当底座。

1.2 三个算法不是简单的“新替代旧”

这里有必要把时间线捋一下,因为很多人容易搞混:

算法主要提出者大致时间定位
CARTBreiman等1984分类与回归树,二叉分裂,工程导向
ID3Quinlan1986把信息熵引入特征选择,教学友好
C4.5Quinlan1993对ID3的全面升级,解决连续值、缺失值、剪枝

有意思的是,CART其实比ID3更早,但大多数教材仍然先讲ID3,因为它概念最简单,用信息熵讲分裂逻辑最直观。而工程上,Scikit-learn实现的是CART,并没有原生封装ID3和C4.5。这就造成一个现象:很多人笔试能默写公式,但一打开sklearn发现DecisionTreeClassifier里根本没有“信息增益率”这个选项,于是对不上号。理解这一层,你就能明白为什么大家都说“学机器学习最好手推一遍ID3”,因为它是理解后面所有补丁的基准线。

2. ID3最核心的数学直觉:信息增益的计算与“偏爱多取值”的病根

2.1 手把手算一遍信息增益

要理解ID3,先理解信息熵。信息熵衡量的是系统的不确定性,公式长这样:

H(D) = -Σ p_k * log2(p_k)

其中p_k是第k类样本在所有样本中的占比。我用一个经典的小数据集说明,目标是根据天气、风速等特征判断“今天要不要去运动”。样本量不大,14条,类标签是Yes和No,其中Yes有9个,No有5个。

总体信息熵先算出来:

H(D) = -(9/14)*log2(9/14) - (5/14)*log2(5/14) ≈ 0.940

现在看Outlook这个特征,它有三个取值:

  • Sunny:5个样本,其中2个Yes、3个No;
  • Overcast:4个样本,4个Yes、0个No;
  • Rain:5个样本,3个Yes、2个No。

先算每个子集的条件熵:

  • Sunny子集:H = -(2/5)*log2(2/5) - (3/5)*log2(3/5) ≈ 0.971
  • Overcast子集:H = -(4/4)*log2(4/4) = 0
  • Rain子集:H = -(3/5)*log2(3/5) - (2/5)*log2(2/5) ≈ 0.971

再按样本占比加权:

H(D|Outlook) = (5/14)*0.971 + (4/14)*0 + (5/14)*0.971 ≈ 0.693

信息增益就是原来的熵减去特征条件下的熵:

Gain(D, Outlook) = 0.940 - 0.693 = 0.247

同样的方式算一下Windy特征。Windy取False的有8个(6个Yes、2个No),Windy取True的有6个(3个Yes、3个No):

H(D|Windy) = (8/14)*0.811 + (6/14)*1 ≈ 0.892

Gain(D, Windy) = 0.940 - 0.892 = 0.048

Outlook的信息增益明显高于Windy,所以ID3在根节点会优先选择Outlook做分裂。这个逻辑很符合直觉:分裂后子集的“纯度”提升越多,这个特征就越值得优先使用。

2.2 ID3的真实毛病:“编号”特征为什么能让它翻车

ID3的问题不是它不会算,而是它太贪了。它只盯着信息增益的绝对值,而信息增益对“取值个数多”的特征天然有利。你想象一下:如果我在数据里加一列“样本编号”,从1排到14,那么每一个编号下只有一条样本,类别纯度直接拉满,H(D|编号)=0,信息增益就是0.940,瞬间超过Outlook的0.247。

一棵树如果选择编号特征做根节点,等于把每个样本单独归档,形成一层14个叶子的恐怖结构。它当然在训练集上表现完美,但拿到新数据根本没有泛化能力,因为新样本的编号是没见过的。这就是典型的过拟合,表现就是“死记硬背,而不是总结规律”。

这种“偏爱多取值特征”的病根,来自信息增益的数学形式:特征取值越多,每个条件子集越小,越容易撞出纯子集,熵就越容易被压到0。ID3还有几个硬伤:

  • 不能处理连续特征,温度、收入这种数值型数据要先手动离散化;
  • 不能处理缺失值;
  • 从不剪枝,树容易长得过于庞杂。

这些问题就是C4.5要补的课。

3. C4.5的三次补丁:增益率、连续特征、缺失值与剪枝怎么凑齐

3.1 增益率:给“多取值”特征降温

C4.5的第一刀,砍向信息增益对多取值特征的偏爱。它的做法是引入“分裂信息”这个概念:

IV(X) = -Σ (|D_v| / |D|) * log2(|D_v| / |D|)

别看公式眼熟,它就是特征取值分布的信息熵,反映的是“这个特征的取值分得有多散”。然后用它当分母:

GainRatio(X) = Gain(X) / IV(X)

回到刚才的编号特征:14个取值均匀分布,IV = log2(14) ≈ 3.807,信息增益0.940被它一除,增益率只剩0.247,优势被明显压制。而Outlook的IV约为1.577,增益率约0.157,两者对比不再一边倒。

但增益率这个指标也不是完美无缺。换个角度想,如果某个特征只有一种取值,IV就是0,公式直接除以0;退一步讲,取值极少的特征,IV很小,增益率可能异常高。C4.5实际使用时不会无脑选增益率最大的,而是先用一个启发式:先挑出信息增益高于平均水平的特征,在它们里面再选增益率最高的。这一招的目的是在“纯看增益”和“纯看增益率”之间做一个平衡。

3.2 连续特征与缺失值的处理思路

C4.5处理连续特征的思路值得好好理解,后来的CART也沿用了类似方法。做法分三步:

  1. 把连续特征的所有取值排序;
  2. 取相邻两个取值的中点作为候选切分点;
  3. 对每个候选点把数据一分为二(小于等于t的进左子集,大于t的进右子集),逐一计算信息增益,选增益最大的那个阈值。

这本质上是在“把连续特征离散化成二值切分”。好处是你不需要预先知道阈值,算法会在训练时自动找最优。当然代价是计算量上升:一个特征有m个样本,排序要O(m log m),遍历候选点也要O(m)。不过对几百几千条样本的小数据来说,完全不是问题。

缺失值处理是C4.5的另一个亮点。它的做法是软分配:当某个样本在特征A上缺失时,先按其他非缺失样本在特征A各分支的分布比例,把这条样本以不同权重分到不同子节点,权重就是该分支的样本占比。简单说,这条样本不会只去某一个分支,而是“雨露均沾”。这样做比直接丢弃样本更能保留信息,但也让后续计算变得稍微复杂,因为每个子节点里有带权重的样本。

3.3 剪枝:先让树长满,再从叶子往回修

ID3完全不剪枝,导致树很容易长成“记忆器”。C4.5把剪枝机制补了上来,用最多的是后剪枝,核心思路是:先把树完整建好,然后自底向上地检查某个内部节点,看把它替换成叶子节点之后,验证集上的错误率是否下降。如果替换后错误率不升反降,就剪掉这棵子树,让这个节点变成叶子。

这类方法叫“错误率降低剪枝”。它的直觉其实很简单:既然这棵子树带来的预测提升已经不明显,那还不如用一条更简单的规则替代它,降低过拟合风险。值得注意的是,剪枝需要单独的验证集,所以数据划分上要留一手,不能把所有样本都拿去建树。

4. CART的二元分裂哲学:Gini系数与最小二乘如何统一分类和回归

4.1 Gini不纯度:不用算log的快速替代

CART全称是Classification and Regression Tree,分类和回归通吃。分类时它不用信息熵,而是用Gini不纯度:

Gini(D) = 1 - Σ p_k^2

还是用那14条数据的二分类来算:Yes占比9/14,No占比5/14,所以:

Gini(D) = 1 - (9/14)^2 - (5/14)^2 ≈ 0.459

Gini不纯度的物理解释是:从数据集里随机抽两个样本,它们类别不一致的概率。这个值越小,说明数据越纯。它和信息熵的排序方向基本一致,都是“纯度越高值越小”,但Gini的计算只涉及平方和减法,不涉及log运算,在计算机实现上快不少。

CART在选分裂点时的方式是:对每个候选特征、每个候选切分点,算出分裂后左右子集的加权Gini值,然后挑加权Gini最小的方案。你可以理解为它在努力寻找“让两个子集各自尽量纯”的那把刀。

4.2 二叉分裂和特征复用

CART和ID3/C4.5最大的结构性差异是:CART永远是二叉树,每次只切一刀。比如Outlook这个特征有三个取值,ID3会一次性分成三叉:Sunny、Overcast、Rain。CART却会枚举二分组合,尝试{Sunny}对{Overcast, Rain}、{Overcast}对{Sunny, Rain}、{Rain}对{Sunny, Overcast},分别计算加权Gini,选最优的一种切法。

二叉分裂带来了一个关键优势:特征可以被多次使用。ID3的多叉树在某个节点用过Outlook之后,后续分支通常不会再考虑Outlook这个特征了,因为它已经“用完了”。而CART每次只切一刀,同一个连续特征可以在不同深度、不同分支反复出现,比如先在根节点按“温度<=70”切一刀,再在某个子节点里按“温度<=60”切一刀。这种反复分割对复杂边界更有表现力。

对类别型特征,CART枚举二分组合的理论数量是2^(k-1)-1,如果某个类别特征取值很多,这会很贵。Sklearn的做法是把类别特征做OneHot处理,然后当成多个二值特征来处理,虽然会损失一些全局组合信息,但工程上足够稳定。

4.3 回归树与代价复杂度剪枝

CART能做回归,靠的是把分裂准则换成最小二乘误差。假设一个节点里有n个样本,如果按某个切分点分成左右两个子集,左侧样本的均值是y_left,右侧均值是y_right,那么分裂目标是最小化:

Σ_left (y_i - y_left)^2 + Σ_right (y_i - y_right)^2

每个回归叶子节点的预测值,就是落入该节点的训练样本标签均值。这套逻辑让决策树从“分类器”扩展成通用预测器,房价预测、销量预估、概率校准之类的问题都能直接套。

剪枝方面,CART用的是代价复杂度剪枝(CCP)。它引入一个正则化参数alpha,把目标写成:

R(T) + alpha * |T|

其中R(T)是树在训练集上的总误差,|T|是叶子节点数。alpha越大,惩罚越大,树越倾向于被剪矮。Sklearn里对应的就是ccp_alpha参数。这个参数总被人忽略,但实际用它可以从一整棵大树出发,剪出一串不同大小的候选树,再用交叉验证挑一个泛化最好的。

5. 落地选型与实战排坑:一张表讲清楚差异,附参数建议

5.1 三算法对比表

先把三种算法放在同一张表里对比,后面说选型才不容易飘:

维度ID3C4.5CART
提出时间198619931984
分裂准则信息增益信息增益率Gini不纯度(分类)/MSE(回归)
支持连续特征不支持支持支持
支持缺失值不支持支持(权重分配)Sklearn实现里不支持自动填充
树形态多叉多叉二叉
剪枝策略无后剪枝代价复杂度剪枝
适用任务分类分类分类+回归
常用实现手写教学为主Weka J48DecisionTreeClassifier / DecisionTreeRegressor

如果说ID3是“入门教材版”,C4.5是“把ID3的坑修了一遍的加强版”,那CART就是“兼顾工程效率和应用面的实用版”。现代机器学习框架之所以默认CART,不只是因为它比另外两个晚,而是它同时解决了连续特征、回归任务、剪枝和计算效率这四件事。

5.2 集成模型与工程选型:随机森林和梯度提升的底座为什么是CART

很多人学到后面会问:随机森林和决策树到底什么关系?简单说,随机森林就是“多棵CART并行投票”。每棵树用Bagging采样出来的不同子集训练,同时每次分裂只看随机抽出的部分特征,这样能显著降低单棵决策树的方差。XGBoost、LightGBM这些梯度提升框架,基学习器同样是加了正则项的CART。

这里有个选型规律值得记下来:如果只是快速分析基线效果,用sklearn的DecisionTreeClassifier,记得把criterion参数调一下试试,信息熵和Gini在大多数数据集上结果接近,但偶尔Gini会更稳定;如果你是做回归任务,用DecisionTreeRegressor;如果你想上集成模型,别自己手写ID3或C4.5,直接用RandomForest、ExtraTrees、XGBoost更省事,它们的底层都是CART。

C4.5在今天还有没有用?严格说,标准库用得少,但它的思路影响深远。有些老项目里能看到Weka的J48,这就是C4.5的Java实现。如果你在维护遗留项目,理解增益率能帮你解释为什么某棵树会选一个取值很少的离散特征。

5.3 实战排坑与调参顺序

我从实际项目中踩到过几个坑,按频率排序写在下面。

第一,不要一上来就放飞max_depth。决策树默认深度可以长到把所有训练样本都装进叶子,结果训练集准确率接近100%,验证集惨不忍睹。我的习惯是先固定max_depth=3到5跑基线,再看混淆矩阵判断是欠拟合还是过拟合,然后逐步加深。

第二,min_samples_leaf别设成1。叶子节点只有一个样本时,模型对离群点太敏感。分类任务里我会设成训练样本量的1%左右,回归任务里更保守,至少20到50。这个参数的作用比max_depth更柔性,它不允许出现“单样本叶子”,能更温和地控制过拟合。

第三,类别不平衡时别拿accuracy当唯一标准。决策树天然偏向多数类,如果正负样本比例悬殊,需要设置class_weight='balanced',或者用F1、AUC这些指标评估。只看accuracy,很可能得到一个全是负样本也能达到90%“准确率”的假模型。

第四,别以为决策树不需要特征工程就完全不做处理。它对量纲不敏感,确实不用归一化,但类别特征必须编码。Sklearn的决策树不直接支持类别特征,最容易犯的错误是把一个取值5种的类别特征OneHot成4列二值特征,然后每列被分别当作独立特征参与分裂,结果特征重要性被稀释。更稳的做法是用OrdinalEncoder或对有序类别做标签编码,或者直接换支持类别特征的库。

第五,ccp_alpha这个参数值得用起来。很多教程只提max_depth、min_samples_leaf,却忽略了代价复杂度剪枝。实际操作中我的流程是:先随机搜索max_depth、min_samples_leaf、max_features这些常规参数,然后把训练好的树复制一份,在ccp_alpha的小网格里用GridSearchCV再搜一轮,经常能把树简化20%以上而精度不掉。

还有一点经验,特征高度相关时,决策树的特征重要性解释会变得不稳定。比如你有两个几乎一模一样的特征,树可能这次选A、下次选B,重要度被一分为二。这是模型本身机制决定的,不是bug。你在做特征筛选的时候,要留意这一点,别看到一个特征重要性低就直接判定它没用。

最后说一点我个人的体会。决策树三兄弟的演进,本质上是“控制过拟合”和“扩展应用面”两条线的交叉:ID3用信息论打开了一扇门,自己也栽在“偏爱多取值”上;C4.5补上连续值、缺失值和剪枝,让树真正能用于现实数据;CART则靠二叉分裂和Gini系数把分类回归融到同一套框架里,成了工程界的事实标准。如果你现在刚入门,我建议你哪怕只写几十行代码,亲手实现一次ID3的特征选择过程,亲眼看看编号特征怎么把信息增益顶到最高,比背诵任何公式都更能理解后面两个算法每一处改进的动机。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 7:38:41

ESP32双分区OTA与自动回滚实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 7:37:37

GD32F450XX上RT-Thread与LWIP协议栈移植实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 7:35:40

ClickHouse容量统计全解析:从system.parts到分区治理实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 7:35:19

光电二极管与X射线探测器前端低噪声TIA设计实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 7:34:46

电控工程师简历突围:10个可验证开源项目实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 7:34:06

AD9361 HDL工程生成:用ADI TCL脚本在Vivado中高效搭建FPGA设计

做AD9361相关的板子也有些年了&#xff0c;这次要在Vivado里用ADI官方TCL脚本从头生成AD9361的HDL工程&#xff0c;本来以为就是跑个脚本的事&#xff0c;结果版本、路径、IP核升级这些坑一个个冒出来。折腾完回头一看&#xff0c;整个流程其实非常有规律&#xff0c;只要把原理…

作者头像 李华