决策树:从入门到精通,一个算法搞定分类与回归

张开发
2026/4/5 17:52:33 15 分钟阅读

分享文章

决策树:从入门到精通,一个算法搞定分类与回归
还在为选择什么算法发愁决策树既能分类又能回归解释性还超强今天带你彻底搞懂它一、引言如果你正在学习机器学习那么决策树绝对是你绕不开的一道坎。为什么因为它太实用了——银行用它来判断是否给用户批贷款医生用它辅助诊断疾病电商平台用它做精准推荐甚至连工厂的质量检测都在用。它把复杂的决策过程变得像流程图一样直观这种“看得见”的机器学习模型简直是小白入门的绝佳选择。你可能会问市面上这么多机器学习模型为什么非决策树不可因为它够直观、够好用——不需要高深的数学背景画个树状图就能看懂它是怎么做决策的。更重要的是它是随机森林、梯度提升树GBDT、XGBoost、LightGBM等一众“顶流”算法的核心基础理解了决策树就相当于拿到了通往机器学习大门的金钥匙。今天这篇文章将带你从零开始一步步掌握决策树的核心原理、三种主流算法ID3、C4.5、CART的底层机制、剪枝技巧还会附上可直接上手的代码实战。准备一杯咖啡我们开始二、决策树是什么决策树Decision Tree顾名思义是一棵用来做决策的“树”。它通过一系列条件判断一步步缩小范围最终得出预测结果。简单来说它就是一堆“如果...那么...”规则的集合——如果你在工作中遇到过if-else嵌套判断其实就已经摸到了决策树的门槛。一棵完整的决策树包含四个基本组成部分根节点树的起点包含全部训练数据。内部节点代表对某个特征的判断条件比如“天气是晴天吗”。分支根据判断条件分出的路径比如“是”或“否”。叶节点最终的分类或回归结果比如“适宜外出”或“不宜外出”。决策树的迷人之处在于它的可解释性。你不需要打开黑盒直接沿着树的分支走下去就能明白模型为什么做出某个判断。这在金融风控、医疗诊断等需要向用户解释决策依据的场景中简直是刚需。三、决策树是怎么“长”出来的决策树的构建过程可以概括为三个步骤特征选择 → 决策树生成 → 决策树剪枝。这个过程很像小时候玩的“20个问题”游戏你心里想一个东西对方通过不断提问缩小范围直到猜中为止。决策树就是让计算机自动学会该问什么问题、按什么顺序问。具体来说构建一棵决策树遵循以下步骤从根节点开始把所有训练数据放在根节点。选择一个“最优”特征按照这个特征将数据集划分成若干子集。如果某个子集已经被基本正确分类就把它变成叶节点否则对这个子集重复步骤2。递归进行直到所有数据都被正确分类或者没有合适的特征可用为止。这个过程的本质是在特征空间中不断引入“切割线”超平面把空间一步步划分成更小的区域每个区域对应一个类别。你可能会问既然if-else也能做分类为什么还要用决策树区别在于if-else的规则是人工编写的而决策树的规则是算法从数据中自动“学”出来的。几千条规则人工写不了但决策树可以——这就是机器学习的价值。四、核心基石信息论三件套在正式开始讲算法之前我们需要先打好理论基础。无论哪种决策树算法都离不开三个核心概念熵Entropy、信息增益Information Gain和信息增益率Information Gain Ratio。理解了这三样东西决策树的“灵魂”你就抓住了。4.1 熵衡量不确定性的尺子熵这个概念最早来自物理学但在信息论里它用来衡量一个随机变量的不确定性。不确定性越大熵就越大。对于分类问题假设数据集D中第k类样本的比例为p_k熵的计算公式如下H(D)−∑k1Kpklog⁡2pkH(D)−∑k1Kpklog2pk其中K是类别的总数。举个简单的例子二分类问题假设正样本概率为p负样本概率为1-p那么熵就是H−plog⁡2p−(1−p)log⁡2(1−p)H−plog2p−(1−p)log2(1−p)当p0.5正负样本各占一半最混乱时熵取最大值1当p0或1所有样本属于同一类最纯净时熵取最小值0。简单记熵越小数据越“纯”。决策树的目标就是让划分后的子集越来越纯。4.2 条件熵给定信息后的不确定性知道了某个特征之后不确定性能减少多少条件熵回答的就是这个问题。条件熵H(D|A)表示在已知特征A的条件下数据集D的不确定性。如果特征A有n个不同的取值按A划分后得到D₁、D₂、…、Dₙ那么H(D∣A)∑i1n∣Di∣∣D∣H(Di)H(D∣A)∑i1n∣D∣∣Di∣H(Di)4.3 信息增益特征带来的“信息量”信息增益 原始熵 - 条件熵g(D,A)H(D)−H(D∣A)g(D,A)H(D)−H(D∣A)它衡量的是知道了特征A之后不确定性减少了多少。减少得越多说明这个特征越“有价值”。信息增益越大特征越强——这就是ID3算法选择特征的依据。4.4 信息增益率给信息增益“打个折”信息增益有个致命缺陷它偏爱取值多的特征。如果一个特征取值很多哪怕它和分类结果完全无关信息增益也可能很高。为了解决这个问题C4.5算法引入了信息增益率的概念gR(D,A)g(D,A)HA(D)gR(D,A)HA(D)g(D,A)其中$H_A(D)$是特征A的“内在信息”HA(D)−∑i1n∣Di∣∣D∣log⁡2∣Di∣∣D∣HA(D)−∑i1n∣D∣∣Di∣log2∣D∣∣Di∣分母H_A(D)越大特征取值越多分子信息增益就会被“打折扣”得越厉害从而有效校正了对多值特征的偏向性。通俗理解信息增益就像是只看“这个人赚了多少钱”而信息增益率则是看“这个人每投入1块钱赚了多少钱”——后者更能反映真正的效率。五、三大决策树算法深度解析现在正式进入干货环节。我们来逐一拆解ID3、C4.5和CART这三种经典算法每个都会配上手算推导和代码实现。5.1 ID3算法信息增益的忠实信徒ID3Iterative Dichotomiser 3迭代二叉树三代是最早提出的决策树算法之一由Ross Quinlan在1986年提出。它的核心思想非常简单在每一个节点上计算所有特征的信息增益选择信息增益最大的特征作为分裂特征。手算案例用“天气”数据构建ID3决策树让我们用经典“适宜外出”数据集来演示ID3的计算过程。数据集如下序号天气温度风力湿度适宜外出1晴高弱高否2晴高强高否3阴高弱高是4雨中弱高是5雨低弱正常是6雨低强正常否7阴低强正常是8晴中弱高否9晴低弱正常是10雨中弱正常是11晴中强正常是12阴中弱高是13阴高强正常是14雨中强高否总样本数|D| 14其中“适宜外出”有9个正例“不宜外出”有5个负例。Step 1计算根节点的熵H(D)H(D)−914log⁡2914−514log⁡2514≈0.940H(D)−149log2149−145log2145≈0.940Step 2计算每个特征的条件熵以“天气”为例。天气有三种取值晴5个样本其中2个适宜、3个不适宜、阴4个样本全部适宜、雨5个样本其中3个适宜、2个不适宜。H(晴) -2/5·log₂(2/5) - 3/5·log₂(3/5) ≈ 0.971H(阴) -4/4·log₂(4/4) - 0/4·log₂(0/4) 0H(雨) -3/5·log₂(3/5) - 2/5·log₂(2/5) ≈ 0.971条件熵 H(D|天气) (5/14)×0.971 (4/14)×0 (5/14)×0.971 ≈ 0.694Step 3计算信息增益g(D, 天气) 0.940 - 0.694 ≈ 0.246用同样方法计算其他特征的信息增益选择最大值作为根节点的分裂特征。Python实现从零构建ID3决策树import numpy as np import pandas as pd from math import log2 from collections import Counter def entropy(y): 计算熵 n len(y) if n 0: return 0 counter Counter(y) return -sum((count/n) * log2(count/n) for count in counter.values()) def info_gain(X_col, y): 计算单个特征的信息增益 n len(y) total_entropy entropy(y) # 按特征值分组 groups {} for val, label in zip(X_col, y): if val not in groups: groups[val] [] groups[val].append(label) # 计算条件熵 cond_entropy 0 for val, labels in groups.items(): p len(labels) / n cond_entropy p * entropy(labels) return total_entropy - cond_entropy def build_id3_tree(X, y, feature_names, max_depthNone, depth0): 递归构建ID3决策树 # 停止条件所有标签相同 if len(set(y)) 1: return y[0] # 停止条件无特征可用或达到最大深度 if len(feature_names) 0 or (max_depth and depth max_depth): return Counter(y).most_common(1)[0][0] # 选择信息增益最大的特征 best_gain -1 best_idx -1 for i, feat_name in enumerate(feature_names): gain info_gain(X[:, i], y) if gain best_gain: best_gain gain best_idx i if best_idx -1: return Counter(y).most_common(1)[0][0] best_feat feature_names[best_idx] tree {best_feat: {}} # 递归构建子树 remaining_features feature_names[:best_idx] feature_names[best_idx1:] for val in set(X[:, best_idx]): mask X[:, best_idx] val subtree build_id3_tree( X[mask], y[mask], remaining_features, max_depth, depth1 ) tree[best_feat][val] subtree return tree # 示例用上述天气数据训练 # X np.array([...]) # 特征矩阵 # y np.array([...]) # 标签 # tree build_id3_tree(X, y, [天气, 温度, 风力, 湿度]) # print(tree)ID3的优点简单直观、易于理解。ID3的缺点①偏爱取值多的特征②不能处理连续特征③容易过拟合④不能处理缺失值。5.2 C4.5算法ID3的进化版针对ID3的种种缺陷Quinlan在1993年推出了C4.5算法。C4.5的核心改进是用信息增益率代替信息增益来选择分裂特征。手算案例信息增益率计算沿用上面的数据集。我们已经算出g(D, 天气) ≈ 0.246。现在计算特征“天气”的内在信息H_A(D)H_A(D) -(5/14)×log₂(5/14) - (4/14)×log₂(4/14) - (5/14)×log₂(5/14) ≈ 1.577信息增益率 0.246 / 1.577 ≈ 0.156用同样的方法计算其他特征的信息增益率选择最大值作为分裂特征。关键理解假设有一个“编号”特征每个样本都有唯一编号信息增益会是最大值但它对分类毫无意义。信息增益率通过除以内在信息来惩罚这种“无效特征”确保选出的特征真正有价值。C4.5的其他改进C4.5还能处理连续特征。方法是对连续特征的所有取值排序取相邻值的中间点作为候选切分点计算每个候选点的信息增益率选择最大的那个作为切分点。此外C4.5还能处理缺失值——通过为每个样本分配一个权重来处理缺失数据。Python实现C4.5核心框架def info_gain_ratio(X_col, y): 计算信息增益率 gain info_gain(X_col, y) # 计算内在信息分裂信息 n len(y) groups {} for val in X_col: if val not in groups: groups[val] 0 groups[val] 1 intrinsic_info 0 for count in groups.values(): p count / n intrinsic_info - p * log2(p) if intrinsic_info 0: return 0 return gain / intrinsic_info def build_c45_tree(X, y, feature_names, is_continuousNone): 构建C4.5决策树核心框架 if len(set(y)) 1: return y[0] # 计算所有特征的信息增益率 best_ratio -1 best_idx -1 for i in range(X.shape[1]): ratio info_gain_ratio(X[:, i], y) if ratio best_ratio: best_ratio ratio best_idx i best_feat feature_names[best_idx] tree {best_feat: {}} # 处理连续特征需要额外记录切分点 # 处理离散特征则按取值分裂 for val in set(X[:, best_idx]): mask X[:, best_idx] val tree[best_feat][val] build_c45_tree( X[mask], y[mask], [f for i, f in enumerate(feature_names) if i ! best_idx] ) return tree5.3 CART算法既能分类又能回归的全能选手CARTClassification and Regression Trees与前两者最大的不同是CART生成的是二叉树而非多叉树。每一次分裂只把一个节点切分成两个子节点。这个设计看似简单实则大有深意——二叉树让决策过程更加简洁也是CART既能做分类又能做回归的关键。分类树基尼指数CART分类树使用基尼指数Gini Index来选择特征。对于数据集D基尼指数的定义是Gini(D)1−∑k1Kpk2Gini(D)1−∑k1Kpk2其中p_k是第k类样本的比例。直观理解基尼指数表示从数据集中随机抽取两个样本它们属于不同类别的概率。概率越小数据集越纯。对于二分类问题Gini(D) 2p(1-p)。当p0.5时取最大值0.5当p0或1时取最小值0。对于特征A的取值a划分后的基尼指数为Gini(D,A)∣D1∣∣D∣Gini(D1)∣D2∣∣D∣Gini(D2)Gini(D,A)∣D∣∣D1∣Gini(D1)∣D∣∣D2∣Gini(D2)CART会遍历所有特征和所有可能的切分点选择使基尼指数最小的特征切分点组合。关键理解ID3追求信息增益最大化C4.5追求信息增益率最大化CART追求基尼指数最小化。从数学上看基尼指数和熵在衡量不确定性方面非常相似但基尼指数计算更快这也是CART被广泛使用的原因之一。Python实现CART分类树from sklearn.tree import DecisionTreeClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score, classification_report # 加载数据 # X, y load_data() # 假设已加载数据 # 划分训练集和测试集 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42 ) # 创建CART分类器 # criteriongini 使用基尼指数默认值也可设为entropy cart_clf DecisionTreeClassifier( criteriongini, # 基尼指数 max_depth5, # 最大深度 min_samples_split5, # 内部节点再划分所需的最小样本数 min_samples_leaf2, # 叶节点最小样本数 random_state42 ) # 训练 cart_clf.fit(X_train, y_train) # 预测 y_pred cart_clf.predict(X_test) # 评估 print(f准确率: {accuracy_score(y_test, y_pred):.4f}) print(\n分类报告:) print(classification_report(y_test, y_pred))回归树最小二乘法CART不仅能分类还能做回归。回归树的叶节点输出的是该区域内所有样本目标值的均值。分裂时CART回归树使用最小二乘法目标是让划分后两个子区域的平方误差之和最小。对于特征j和切分点s划分后得到两个区域R1(j,s){x∣x(j)≤s},R2(j,s){x∣x(j)s}R1(j,s){x∣x(j)≤s},R2(j,s){x∣x(j)s}最优的c₁和c₂就是各自区域内y的均值。最优切分点通过求解下式得到min⁡j,s[min⁡c1∑xi∈R1(yi−c1)2min⁡c2∑xi∈R2(yi−c2)2]minj,s[minc1∑xi∈R1(yi−c1)2minc2∑xi∈R2(yi−c2)2]Python实现CART回归树from sklearn.tree import DecisionTreeRegressor from sklearn.metrics import mean_squared_error, r2_score # 创建回归树 cart_reg DecisionTreeRegressor( max_depth5, min_samples_split5, min_samples_leaf2, random_state42 ) # 训练 cart_reg.fit(X_train, y_train) # 预测 y_pred_reg cart_reg.predict(X_test) # 评估 print(f均方误差 (MSE): {mean_squared_error(y_test, y_pred_reg):.4f}) print(fR² 分数: {r2_score(y_test, y_pred_reg):.4f})补充说明scikit-learn中决策树的criterion参数——分类问题可选择gini默认或entropy回归问题默认使用均方误差squared_error也可选择绝对误差absolute_error。5.4 三大算法对比总结特性ID3C4.5CART分裂准则信息增益信息增益率基尼指数分类/均方误差回归树结构多叉树多叉树二叉树连续特征处理不支持支持支持缺失值处理不支持支持支持剪枝不支持支持支持回归任务不支持不支持支持适用场景小规模、离散特征通用分类分类回归工业界首选一句话总结ID3是开山鼻祖C4.5是改进版CART是集大成者。在实际项目中CART是绝对的主角。六、过拟合克星决策树剪枝决策树有个致命弱点——太容易过拟合。如果不加限制决策树可以一直分裂下去直到每个叶节点都只包含一个样本训练集准确率100%但换到测试集上就“原形毕露”。剪枝Pruning就是用来解决这个问题的。剪枝分为两种预剪枝和后剪枝。6.1 预剪枝防患于未然预剪枝在树生长过程中就提前设好“刹车”条件比如max_depth限制树的最大深度min_samples_split节点最少样本数min_samples_leaf叶节点最少样本数min_impurity_decrease最小不纯度减少量# 带预剪枝的决策树 tree_prepruned DecisionTreeClassifier( max_depth4, # 树深度不超过4 min_samples_split10, # 节点少于10个样本就不分裂 min_samples_leaf5, # 叶节点至少5个样本 min_impurity_decrease0.01, # 不纯度减少小于1%就不分裂 random_state42 )预剪枝的优点是速度快、计算效率高。缺点是可能“刹车太早”导致模型欠拟合。6.2 后剪枝亡羊补牢后剪枝先把树完全长成再自底向上检查把某个非叶节点替换成叶节点后如果模型性能不降反升就剪掉这个分支。代价复杂度剪枝CCPCART算法最经典的后剪枝方法是代价复杂度剪枝。它的损失函数是Rα(T)R(T)α×∣T∣Rα(T)R(T)α×∣T∣其中R(T)是树的误差错分样本数|T|是树的叶节点数量α是复杂度参数。当α0时完全不剪枝α越大树被剪得越“秃”。ccp_alpha就是控制α的参数。# 使用ccp_alpha进行后剪枝 path cart_clf.cost_complexity_pruning_path(X_train, y_train) ccp_alphas path.ccp_alphas # 训练不同α值的树找到最优的 trees [] for ccp_alpha in ccp_alphas: clf DecisionTreeClassifier(ccp_alphaccp_alpha, random_state42) clf.fit(X_train, y_train) trees.append(clf) # 选择验证集上表现最好的 # best_tree trees[np.argmax(val_scores)]scikit-learn中默认ccp_alpha0不剪枝可以根据验证集性能调参。6.3 剪枝前后效果对比import matplotlib.pyplot as plt # 对比不同剪枝策略的效果 no_prune DecisionTreeClassifier(random_state42).fit(X_train, y_train) pre_prune DecisionTreeClassifier(max_depth4, random_state42).fit(X_train, y_train) post_prune DecisionTreeClassifier(ccp_alpha0.02, random_state42).fit(X_train, y_train) print(无剪枝 - 训练集准确率: {:.4f}, 测试集准确率: {:.4f}.format( no_prune.score(X_train, y_train), no_prune.score(X_test, y_test))) print(预剪枝 - 训练集准确率: {:.4f}, 测试集准确率: {:.4f}.format( pre_prune.score(X_train, y_train), pre_prune.score(X_test, y_test))) print(后剪枝 - 训练集准确率: {:.4f}, 测试集准确率: {:.4f}.format( post_prune.score(X_train, y_train), post_prune.score(X_test, y_test)))七、进阶可视化和实际应用7.1 决策树可视化能用图表表达的尽量不用文字。决策树可视化是理解和调试模型的利器from sklearn.tree import plot_tree import matplotlib.pyplot as plt plt.figure(figsize(20, 10)) plot_tree(cart_clf, feature_namesfeature_names, class_names[不宜外出, 适宜外出], filledTrue, # 颜色填充 roundedTrue, # 圆角节点 fontsize10) plt.title(CART决策树可视化) plt.savefig(decision_tree.png, dpi300, bbox_inchestight) plt.show()7.2 特征重要性分析决策树可以输出每个特征的重要性帮你理解哪些因素真正影响结果importances cart_clf.feature_importances_ for name, imp in zip(feature_names, importances): print(f{name}: {imp:.4f}) # 可视化 plt.figure(figsize(10, 6)) plt.barh(feature_names, importances) plt.xlabel(特征重要性) plt.title(决策树特征重要性分析) plt.tight_layout() plt.savefig(feature_importance.png) plt.show()八、常见问题和实战建议8.1 三大算法怎么选数据规模小、离散特征为主 → ID3虽然很老但学习价值仍在需要处理连续特征或缺失值 → C4.5工业项目、追求最佳性能 → CARTscikit-learn默认就是CART8.2 剪枝参数怎么调记住这个思路从“宽松”到“严格”逐步收紧。先用默认参数跑一下看看训练集和测试集准确率的差距——差距大说明过拟合了收紧max_depth或min_samples_split差距小但准确率低说明欠拟合了放宽剪枝条件。多试几次就能找到“甜点”。8.3 决策树的局限性不稳定数据稍微变化树的结构可能完全改变。偏向于取值多的特征ID3尤其明显。容易过拟合所以必须剪枝。难以捕捉特征之间的交互——比如“天气和风力共同决定是否外出”这种组合关系决策树学习起来比较吃力需要多级分裂才能近似。8.4 进阶学习路径单棵决策树的能力终究有限但把它和集成学习结合就能爆发出惊人的能量随机森林多棵决策树的“投票机制”大幅提升稳定性。梯度提升树GBDT/XGBoost/LightGBM串行优化残差屠榜无数数据竞赛。AdaBoost给分类错误的样本加权强迫模型“重点突破”薄弱环节。理解决策树是通往这些高级算法的必经之路这个地基打得越牢上层建筑就越稳。九、全文总结我们从决策树的基本概念出发一路深入了核心原理熵、信息增益、信息增益率、基尼指数——决策树选择特征的四种“眼光”。三大算法ID3信息增益、C4.5信息增益率、CART基尼指数/均方误差的异同和适用场景。过拟合克星预剪枝和后剪枝的原理与代码实践。实战技巧从零实现到scikit-learn封装可视化到特征重要性分析。决策树用最直观的方式解决了分类和回归问题是所有机器学习学习者必须掌握的核心算法。它就像一个“白盒”帮你理解决策逻辑也为你探索更复杂的算法铺平了道路。现在打开你的Jupyter Notebook跑一遍上面的代码亲手建一棵属于你自己的决策树吧

更多文章