
简介这份资源面向机器学习初学者与数据挖掘实践者聚焦决策树这一经典监督学习模型帮助读者从原理到代码完整掌握分类与回归任务中的树模型实现。压缩包共28个文件约2.43MB包含10个py脚本、6个ipynb笔记本、4份pdf讲义、3个txt说明及xlsx数据集与png可视化图兼顾PyCharm与Jupyter两种运行方式。内容覆盖ID3、C4.5、CART三大算法的特征选择、节点分裂与剪枝策略并配有员工离职预测案例实战以及K折交叉验证与GridSearch网格搜索的参数调优环节还涉及graphviz插件安装与决策树可视化。已有526人学习下载读者可借助源码理解信息增益、基尼不纯度等准则动手搭建预测模型并完成评估适合作为课程作业、项目练手与面试复习的参考素材。1. 决策树模型源码包从一份 zip 到能跑通的第一棵树很多人拿到「机器学习与算法源代码5决策树模型.zip」这类压缩包第一反应是解压、找 main、直接运行然后被一堆相对路径、缺失依赖和编码报错劝退。这个标题背后其实是一套很典型的机器学习入门工程用决策树把一份带标签的表格数据训练成可解释的分类或回归模型并附上可复现的源代码。它解决的不是「算法有多玄」的问题而是「我能不能在本地把这棵树完整跑起来并且知道每个参数在干什么」。适合刚接触机器学习、正在做期末复习或课程设计的人也适合想拿一份干净源码对照《机器学习》周志华那类教材补实现细节的工程师。下面按「先立住原理、再动手复现、最后排坑」的顺序拆开讲代码以 Python 从零实现和 scikit-learn 两条线并行方便你对照理解。2. 决策树到底在切什么信息增益、基尼指数与剪枝的选型逻辑2.1 一棵树的分裂标准从熵到基尼指数决策树的核心动作只有一个在每个节点上选一个特征和一个阈值把当前数据集切成两份让切完之后的子集「更纯」。衡量纯度最常用的两个指标是信息熵和基尼指数。信息熵来自信息论取值越大表示越混乱基尼指数可以理解为从数据集中随机抽两个样本、类别不一致的概率计算量比熵小因为不用算对数。信息增益就是父节点熵减去子节点熵的加权和ID3 用它选特征C4.5 用信息增益率修正了「取值多的特征占便宜」的问题CART 用基尼指数并且只做二叉分裂。你在源码包里看到的criterion参数本质就是在选这套标准。分类任务常用gini或entropy回归任务用squared_error老版本叫mse。选哪个不是玄学数据量大、追求速度用 gini类别不平衡、想更细致区分用 entropy但差距通常不大。2.2 预剪枝和后剪枝什么时候该让树停下来不限制深度的决策树一定能把训练集拟合到接近零误差代价是每个叶子只剩一两个样本测试集上直接翻车。控制过拟合有两条路预剪枝在生长过程中就设限比如max_depth、min_samples_split、min_samples_leaf、max_leaf_nodes后剪枝先让树长满再自底向上把收益不大的子树替换成叶子代价复杂度剪枝CCP就是代表scikit-learn 里对应ccp_alpha。实操里我一般先用预剪枝把max_depth卡在 3 到 10 之间快速看效果再用ccp_alpha做一轮后剪枝微调。原因是预剪枝快、可控但可能欠拟合后剪枝效果通常更好但计算更贵。源码包里如果只实现了 ID3 而没有剪枝那它更适合教学不适合直接上生产数据。2.3 连续值、缺失值和多分类源码里最容易偷懒的三处教学版决策树经常只处理离散特征、无缺失、二分类这三处恰好是真实数据里最常见的。连续值处理的标准做法是排序后取相邻值中点作为候选切分点选增益最大的那个缺失值可以用权重分配法C4.5 的做法把样本按权重同时分到所有子节点或者简单点直接填充多分类只要把熵和基尼的计算从二类推广到 K 类即可公式不变只是求和范围变大。看一份决策树源码值不值得细读就看它有没有认真处理这三点。只支持离散二分类的当教学demo看支持连续值和剪枝的才有对照价值。3. 把 zip 跑起来环境、数据加载与第一棵树的训练3.1 环境准备与依赖确认拿到源码包先别急着运行第一步是确认 Python 版本和依赖。多数这类源码包基于 Python 3.8 以上依赖 numpy、pandas、scikit-learn画图可能用 matplotlib。建议单独建虚拟环境避免和系统包冲突。# 创建并激活虚拟环境 python -m venv dt_env source dt_env/bin/activate # Windows 用 dt_env\Scripts\activate # 安装核心依赖版本按源码包 requirements 为准 pip install numpy pandas scikit-learn matplotlib逻辑说明虚拟环境把这份源码的依赖和系统隔离出问题直接删掉重建不用收拾残局。参数说明python -m venv后跟环境目录名激活命令因操作系统而异。如果源码包里有requirements.txt优先用pip install -r requirements.txt版本冲突时再逐个降级。3.2 数据加载与特征处理决策树对特征缩放不敏感不需要标准化但类别特征要编码缺失值要处理。下面这段是通用的加载和检查流程。import pandas as pd import numpy as np from sklearn.model_selection import train_test_split # 读取数据注意编码中文数据常见 gbk 或 utf-8 df pd.read_csv(data.csv, encodingutf-8) # 基本检查形状、缺失、类别分布 print(df.shape) print(df.isnull().sum()) print(df.iloc[:, -1].value_counts()) # 假设最后一列是标签 # 类别特征独热编码标签单独取出 X pd.get_dummies(df.drop(columns[label])) y df[label] # 分层切分保证训练测试集类别比例一致 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy )逻辑说明先看数据长什么样再动手缺失和类别分布直接决定后面要不要填充、要不要用分层切分。参数说明encoding按实际文件编码改读进来乱码就换gbktest_size0.2是常见比例数据少可以到 0.3stratifyy在类别不平衡时必加否则测试集可能缺某一类random_state固定后结果可复现调参时别改。3.3 训练第一棵树并看关键参数先用 scikit-learn 跑通基线再对照源码包里的手写实现理解每个参数的作用。from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import accuracy_score, classification_report # 初始化决策树先给一组保守参数 clf DecisionTreeClassifier( criteriongini, # 分裂标准 max_depth5, # 最大深度防过拟合第一道闸 min_samples_split10, # 节点样本数少于该值不再分裂 min_samples_leaf5, # 叶子最少样本数 random_state42 ) clf.fit(X_train, y_train) y_pred clf.predict(X_test) print(准确率:, accuracy_score(y_test, y_pred)) print(classification_report(y_test, y_pred))逻辑说明先给一组偏保守的参数拿到基线再逐步放开看效果变化。参数说明max_depth从 3 开始试每加 2 看一次测试集min_samples_split和min_samples_leaf越大树越简单太小等于没限制criterion分类用 gini 或 entropy回归换成squared_error。如果源码包是手写实现重点看它怎么递归选最优特征、怎么判断停止条件这两处对应上面的参数。3.4 可视化把树画出来才算真看懂决策树最大的优势是可解释不画出来等于浪费。from sklearn.tree import export_text, plot_tree import matplotlib.pyplot as plt # 文本形式适合快速看规则 print(export_text(clf, feature_nameslist(X.columns))) # 图形形式适合放进报告 plt.figure(figsize(16, 8)) plot_tree(clf, feature_nameslist(X.columns), class_names[str(c) for c in clf.classes_], filledTrue, roundedTrue, fontsize9) plt.savefig(tree.png, dpi150, bbox_inchestight)逻辑说明export_text输出 if-else 规则方便直接抄进业务逻辑plot_tree出图看结构。参数说明filledTrue按类别上色颜色越深纯度越高fontsize调小避免文字重叠dpi提高导出清晰度。看树时重点看根节点用了哪个特征、深度是否过深、叶子样本数是否太少。4. 参数怎么调决策树的关键参数与调参顺序4.1 必调参数清单与影响方向参数作用调大后果建议范围max_depth树最大深度过拟合3~15min_samples_split分裂所需最小样本数欠拟合2~50min_samples_leaf叶子最小样本数欠拟合1~30max_features每次分裂考虑的特征数随机性增强sqrt / log2 / Noneccp_alpha后剪枝强度树变小0~0.1 试class_weight类别权重偏向少数类balanced调参顺序我一般按影响从大到小先max_depth再min_samples_leaf然后min_samples_split最后ccp_alpha。max_features在单棵树里影响不大主要是给随机森林用的。4.2 用交叉验证代替单次切分单次 train_test_split 的结果波动大尤其数据量小的时候。用交叉验证看均值和方差更稳。from sklearn.model_selection import GridSearchCV param_grid { max_depth: [3, 5, 7, 10, None], min_samples_leaf: [1, 5, 10, 20], criterion: [gini, entropy] } grid GridSearchCV( DecisionTreeClassifier(random_state42), param_grid, cv5, # 5 折交叉验证 scoringf1_weighted, # 类别不平衡时比 accuracy 稳 n_jobs-1 ) grid.fit(X_train, y_train) print(最优参数:, grid.best_params_) print(最优得分:, grid.best_score_)逻辑说明网格搜索把参数组合逐个用交叉验证评估选平均得分最高的。参数说明cv5是常用折数数据少用 10scoring按任务选分类看 f1 或 roc_auc回归看 neg_mean_squared_errorn_jobs-1用满 CPU。注意max_depthNone配合交叉验证时可能很慢先小范围试。4.3 后剪枝用 ccp_alpha 做一轮精修预剪枝调完后用代价复杂度剪枝再压一轮往往能再降一点过拟合。path clf.cost_complexity_pruning_path(X_train, y_train) alphas path.ccp_alphas # 对每个 alpha 训练一棵树看测试集表现 scores [] for a in alphas: t DecisionTreeClassifier(random_state42, ccp_alphaa) t.fit(X_train, y_train) scores.append(t.score(X_test, y_test)) best_alpha alphas[scores.index(max(scores))] print(最优 ccp_alpha:, best_alpha)逻辑说明cost_complexity_pruning_path给出候选 alpha 序列逐个训练选测试集最好的。参数说明alpha 越大剪得越狠树越小alpha 为 0 等于不剪。注意这里用测试集选 alpha 有轻微信息泄露严谨做法是嵌套交叉验证但快速实验可以接受。5. 避坑与排查决策树源码包最常见的五个翻车点5.1 现象训练集准确率 100%测试集只有 60%原因树长得太深每个叶子记住个别样本典型过拟合。解决先设max_depth或min_samples_leaf再用ccp_alpha后剪枝同时检查特征里有没有 ID 类唯一标识列有就删掉。5.2 现象源码包运行报 FileNotFoundError 或路径错误原因源码里写死了绝对路径或者数据文件相对路径和你的运行目录不一致。解决统一用os.path.dirname(__file__)拼相对路径或者把数据文件放到和脚本同级目录运行时确认当前工作目录。5.3 现象中文标签或特征读进来乱码原因文件编码和read_csv的encoding参数不匹配。解决先试utf-8乱码换gbk或gb18030实在不确定用chardet检测。写文件时同样指定encodingutf-8-sig避免 Excel 打开乱码。5.4 现象类别不平衡准确率虚高但少数类全错原因多数类占比高模型全预测多数类也能拿高准确率。解决看classification_report的 recall 和 f1别只看 accuracy设class_weightbalanced或者对少数类过采样评估指标换成 f1 或 auc。5.5 现象手写源码和 sklearn 结果对不上原因分裂标准实现细节不同比如连续值切分点取法、平局时选哪个特征、停止条件差异。解决先在小数据集上逐步打印每层分裂的特征和阈值和 sklearn 的export_text对照确认双方用的是同一个 criterion 和同样的停止条件。对不上不一定是 bug可能是实现约定不同。6. 从单棵树到集成把这份源码用出更高价值的一个技巧单棵决策树方差大、容易过拟合真正在比赛和生产里能打的是基于它的集成方法。你手里这份决策树源码最大的价值不是直接拿去预测而是当作理解随机森林和梯度提升树的跳板。随机森林用 bagging 思路训练多棵树再投票把方差降下来梯度提升树用 boosting 思路每棵新树拟合前面模型的残差把偏差降下来。两者的基学习器都是决策树你把单棵树的参数和剪枝搞明白调集成模型时就知道每个参数在动什么。一个具体技巧用feature_importances_做特征筛选再喂给集成模型。决策树给出的特征重要性虽然对高基数特征有偏好但作为第一轮粗筛足够快。import pandas as pd from sklearn.ensemble import RandomForestClassifier # 用单棵树的重要性做初筛 importances pd.Series(clf.feature_importances_, indexX.columns) selected importances[importances 0.01].index.tolist() print(保留特征数:, len(selected)) # 用筛选后的特征训练随机森林 rf RandomForestClassifier( n_estimators200, # 树的数量越多越稳但越慢 max_depth8, min_samples_leaf3, random_state42, n_jobs-1 ) rf.fit(X_train[selected], y_train) print(随机森林准确率:, rf.score(X_test[selected], y_test))逻辑说明先用单棵树的重要性去掉几乎不贡献的特征减少噪声和维度再训练随机森林。参数说明n_estimators从 100 起到 500 后收益递减max_depth在集成里可以比单棵树深一些因为投票会抵消部分过拟合n_jobs-1并行训练。注意特征重要性阈值别设太高0.01 只是粗筛筛完最好再用交叉验证确认效果没掉。验证方法上我习惯做三件事一是对比筛选前后交叉验证得分掉了就说明筛过头二是看随机森林的 OOB 得分oob_scoreTrue它相当于免费的验证集三是把最终模型的特征重要性和单棵树对比如果排序差异很大说明特征间有较强相关需要进一步处理。我自己踩过最深的一个坑是早期拿到一份决策树源码没看停止条件就直接套到一份几万行的数据上结果树长到几千个节点训练跑了十几分钟测试集一塌糊涂。后来养成习惯任何树模型先卡深度、先看数据分布、先跑小样本。源码包是起点不是终点把它当教材逐行读一遍比直接调库收获大得多。希望帮到你。本文还有配套的精品资源点击获取