简介:这份资源是一份面向高校机器学习课程学习者的决策树分类算法实验报告,以毒蘑菇数据集分类为核心案例,适合正在完成课程大作业或需要撰写规范实验报告的学生参考。压缩包内仅含1个docx文档,大小约1.21MB,内容涵盖研究意义、数据描述、模型描述、算法实现、运行结果及总结等完整章节,并配有算法流程图与伪代码说明。实验基于UCI Mushroom数据集,包含8124个样本、22个原始特征,经预处理扩展至117维,采用ID3与CART两种特征选择策略,结合后剪枝优化,并通过混淆矩阵计算准确率、精度与召回率,重点讨论食用物分类中召回率的重要性。目前已有7564人学习下载,读者可借此掌握决策树从数据预处理、特征选择到模型评估的完整流程,理解信息增益与基尼不纯度的差异,并直接借鉴报告结构与实验分析思路,高效完成同类大作业。
1. 决策树分类实验报告:从西瓜数据集到可复现的高分作业
很多人做机器学习决策树分类算法实验报告,最后交上去的是一份“跑通了但说不清”的文档:代码能出图,准确率也还行,可老师一问“为什么选信息增益而不是基尼指数”“剪枝参数怎么定的”,就答不上来。这篇笔记就围绕决策树分类器这个核心,把一份能拿高分的机器学习大作业拆成可复现的步骤。适合正在做机器学习课程设计、期末大作业,或者刚入门想搞懂决策树算法到底怎么落地的人。我会用经典的西瓜数据集和一份真实表格数据两条线,把数据处理、建模、调参、可视化、报告撰写串起来,重点讲清楚每个参数背后的含义和容易翻车的地方。读完你能自己从头搭一套决策树分类流程,并且知道报告里该写什么才显得“真做过”。
2. 决策树分类算法实验报告的技术底座:原理、选型与数据准备
2.1 决策树分类器到底在做什么
决策树分类器的本质是一连串 if-else 规则的集合。它从根节点开始,每次选一个特征对样本进行划分,直到叶子节点给出类别。和逻辑回归、SVM 相比,决策树最大的优势是可解释性强——你可以直接把树画出来,告诉别人“纹理清晰且触感硬滑的就是好瓜”。这在实验报告里是极大的加分项,因为老师能直观看到你的模型学到了什么。
但决策树也有明显的短板:不加限制会无限生长,把训练集里每个样本都单独分到一个叶子,导致过拟合。所以实验报告里必须体现你对预剪枝和后剪枝的理解。常见做法是用max_depth、min_samples_split、min_samples_leaf这些参数控制树的复杂度,而不是等它长完再剪。我一般会在报告里先展示一棵不加限制的树有多深,再对比剪枝后的树,用数据说明泛化能力的变化。
另一个关键点是划分标准。ID3 用信息增益,C4.5 用信息增益率,CART 用基尼指数。scikit-learn 默认是 CART,也就是基尼指数。很多同学报告里写“用了信息增益”,但代码里调的是criterion='gini',这是典型的自相矛盾。你至少要解释清楚:基尼指数计算不涉及对数,速度更快;信息增益对取值多的特征有偏好。选哪个不是拍脑袋,要结合数据特征数量和计算开销来说。
2.2 实验数据准备:从西瓜数据集到通用表格
西瓜数据集是周志华《机器学习》里的经典数据,共 17 个样本,特征包括色泽、根蒂、敲声、纹理、脐部、触感,标签是好瓜/坏瓜。这个数据集小,适合演示原理,但直接拿来做大作业会显得单薄。我的建议是:用西瓜数据集讲清楚原理,再用一份更大的真实数据做完整实验。比如 UCI 的 Bank Marketing 数据集或者 Kaggle 的 Titanic,样本量在几千条,特征有数值有类别,能体现完整的数据处理流程。
数据处理部分要写清楚三件事:缺失值怎么处理、类别特征怎么编码、数值特征要不要离散化。决策树对缺失值敏感,scikit-learn 的决策树不支持缺失值,所以必须填充。常见做法是数值特征用中位数填充,类别特征用众数填充。类别编码用OrdinalEncoder或OneHotEncoder,但要注意:决策树基于特征取值划分,OneHot 之后每个类别单独成列,树可能会过度偏向某个类别。我一般对有序类别用 OrdinalEncoder,对无序类别用 OneHotEncoder,并在报告里说明理由。
下面是一段完整的数据准备代码,以西瓜数据集为例,展示从原始表格到可训练特征的流程:
import pandas as pd import numpy as np from sklearn.preprocessing import OrdinalEncoder from sklearn.model_selection import train_test_split # 西瓜数据集3.0,手动录入 data = { '色泽': ['青绿','乌黑','青绿','青绿','乌黑','青绿','乌黑','乌黑','乌黑','青绿','青绿','青绿','青绿','乌黑','青绿','青绿','乌黑'], '根蒂': ['蜷缩','蜷缩','蜷缩','蜷缩','蜷缩','稍蜷','稍蜷','稍蜷','稍蜷','硬挺','硬挺','蜷缩','稍蜷','稍蜷','蜷缩','蜷缩','蜷缩'], '敲声': ['浊响','沉闷','浊响','浊响','浊响','浊响','浊响','浊响','沉闷','清脆','清脆','浊响','浊响','浊响','浊响','浊响','沉闷'], '纹理': ['清晰','清晰','清晰','清晰','清晰','清晰','稍糊','清晰','稍糊','清晰','模糊','模糊','稍糊','稍糊','清晰','模糊','稍糊'], '脐部': ['凹陷','凹陷','凹陷','凹陷','凹陷','稍凹','稍凹','稍凹','稍凹','平坦','平坦','平坦','凹陷','稍凹','稍凹','平坦','稍凹'], '触感': ['硬滑','硬滑','硬滑','硬滑','硬滑','软粘','软粘','硬滑','硬滑','硬滑','软粘','软粘','硬滑','软粘','硬滑','硬滑','软粘'], '好瓜': ['是','是','是','是','是','是','是','是','否','否','否','否','否','否','否','否','否'] } df = pd.DataFrame(data) # 类别特征编码,有序类别用OrdinalEncoder # 注意:这里为了演示,统一用OrdinalEncoder,实际报告要说明每个特征的有序性 encoder = OrdinalEncoder() feature_cols = ['色泽','根蒂','敲声','纹理','脐部','触感'] df[feature_cols] = encoder.fit_transform(df[feature_cols]) X = df[feature_cols] y = df['好瓜'].map({'是':1, '否':0}) # 划分训练集和测试集,因为样本少,用分层抽样保证类别比例 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42, stratify=y ) print(f"训练集大小:{X_train.shape},测试集大小:{X_test.shape}")这段代码的逻辑是:先把原始表格转成 DataFrame,然后用 OrdinalEncoder 把类别映射成整数。这里有个细节——OrdinalEncoder默认按字母顺序编码,但西瓜数据集的特征是有序的,比如“纹理”的清晰 > 稍糊 > 模糊,直接按字母顺序会打乱顺序。更严谨的做法是手动指定categories参数,按业务顺序排列。代码最后用stratify=y做分层抽样,因为西瓜数据集正负样本比例是 8:9,分层能保证训练集和测试集里好瓜坏瓜的比例一致。参数random_state=42是为了结果可复现,实验报告里必须固定随机种子,否则每次跑出来的树都不一样,没法写结论。
2.3 划分标准的选择:信息增益、增益率与基尼指数
实验报告里必须有一节专门对比划分标准。很多同学只写“用了基尼指数”,但不解释为什么。你可以设计一个小实验:在同一份数据上分别用criterion='gini'和criterion='entropy'训练,对比树的结构和测试准确率。通常两者差异不大,但熵的计算更慢。如果数据特征取值很多,基尼指数更稳定。
信息增益的公式是Gain(D,a) = Ent(D) - Σ (|Dv|/|D|) * Ent(Dv),它倾向于选择取值多的特征。比如给每个样本一个唯一 ID,信息增益会最大,但这样的划分没有泛化意义。增益率在信息增益基础上除以特征固有值IV(a) = -Σ (|Dv|/|D|) log2(|Dv|/|D|),缓解了偏好问题。CART 用的基尼指数Gini(D) = 1 - Σ pk^2,计算更简单。
在报告里,我一般会放一张对比表:
| 划分标准 | 公式核心 | 偏好 | 计算开销 | 适用场景 |
|---|---|---|---|---|
| 信息增益 | 熵减 | 取值多的特征 | 中 | ID3,特征取值少 |
| 增益率 | 增益/固有值 | 取值少的特征 | 高 | C4.5,混合类型特征 |
| 基尼指数 | 不纯度 | 无明显偏好 | 低 | CART,大规模数据 |
这张表能让老师看到你不是只会调库,而是理解背后的权衡。注意,scikit-learn 只支持基尼指数和熵,不支持增益率。如果你报告里写“用 C4.5 算法”,但代码是DecisionTreeClassifier(criterion='entropy'),严格来说这是 ID3 的变体,不是完整的 C4.5。要么改说法,要么自己实现增益率。
3. 用 scikit-learn 跑通决策树分类:建模、调参与可视化
3.1 最小可运行建模流程
有了处理好的数据,下一步就是训练决策树。scikit-learn 的DecisionTreeClassifier封装得很好,但参数不少,每个都影响树的结构。先看一段最小可运行代码:
from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import accuracy_score, classification_report # 初始化决策树,先不加限制,看看树能长多深 clf = DecisionTreeClassifier(random_state=42) clf.fit(X_train, y_train) # 预测并评估 y_pred = clf.predict(X_test) print(f"训练集准确率:{clf.score(X_train, y_train):.4f}") print(f"测试集准确率:{clf.score(X_test, y_test):.4f}") print(f"树的最大深度:{clf.get_depth()}") print(f"叶子节点数:{clf.get_n_leaves()}") print(classification_report(y_test, y_pred))这段代码先训练一棵不加限制的树。在西瓜数据集上,你大概率会看到训练集准确率 100%,测试集准确率可能只有 60% 左右,树深度 5 到 6 层。这就是典型的过拟合。实验报告里要把这个结果放进去,作为“未剪枝”的基线,然后引出调参的必要性。
classification_report会输出精确率、召回率、F1 值。对于类别不平衡的数据,准确率有欺骗性,必须看 F1。比如坏瓜占多数,模型全预测坏瓜也能有高准确率,但好瓜的召回率是 0。报告里要解释每个指标的含义,别只贴数字。
3.2 预剪枝参数怎么调:max_depth、min_samples_split、min_samples_leaf
预剪枝是在树生长过程中提前停止。scikit-learn 提供了几个关键参数:
max_depth:树的最大深度。默认 None,表示不限。一般从 3 开始试,逐步增加。min_samples_split:节点分裂所需的最小样本数。默认 2,太小容易过拟合。可以设成 5 到 20。min_samples_leaf:叶子节点最少样本数。默认 1,设成 3 到 10 能防止孤立样本。max_features:每次分裂考虑的最大特征数。默认 None,即全部特征。特征多时可以设sqrt或log2。ccp_alpha:代价复杂度剪枝参数,用于后剪枝。默认 0,值越大树越简单。
调参不能靠猜,要用网格搜索或学习曲线。下面是一段用GridSearchCV调参的代码:
from sklearn.model_selection import GridSearchCV # 定义参数网格 param_grid = { 'max_depth': [2, 3, 4, 5, 6, None], 'min_samples_split': [2, 5, 10, 20], 'min_samples_leaf': [1, 3, 5, 10], 'criterion': ['gini', 'entropy'] } # 5折交叉验证 grid = GridSearchCV( DecisionTreeClassifier(random_state=42), param_grid, cv=5, scoring='f1', n_jobs=-1 ) grid.fit(X_train, y_train) print(f"最佳参数:{grid.best_params_}") print(f"最佳交叉验证F1:{grid.best_score_:.4f}") print(f"测试集准确率:{grid.score(X_test, y_test):.4f}")这段代码用 5 折交叉验证在训练集上找最优参数,评分用 F1 而不是准确率,因为类别可能不平衡。n_jobs=-1表示用所有 CPU 核心并行。跑完后best_params_会给出最佳组合。注意,网格搜索是在训练集上做的,测试集只用来最终评估,不能拿测试集调参,否则就是数据泄露。
在西瓜数据集上,最佳max_depth通常是 3 或 4,min_samples_leaf是 1 或 2。因为样本太少,参数太大会欠拟合。如果换成 Bank Marketing 这种几千条的数据,min_samples_leaf可以设到 10 以上。报告里要写清楚:参数取值和数据规模强相关,不能照搬。
3.3 后剪枝:用 ccp_alpha 做代价复杂度剪枝
预剪枝有个问题:有些分支当前看起来没用,但后续可能带来性能提升,预剪枝会误杀。后剪枝先让树长完,再自底向上剪掉不划算的子树。scikit-learn 用ccp_alpha控制剪枝强度,值越大剪得越狠。
找最佳ccp_alpha的方法是画剪枝路径:
import matplotlib.pyplot as plt # 获取剪枝路径 path = clf.cost_complexity_pruning_path(X_train, y_train) ccp_alphas = path.ccp_alphas impurities = path.impurities # 训练不同alpha的树 clfs = [] for alpha in ccp_alphas: clf_alpha = DecisionTreeClassifier(random_state=42, ccp_alpha=alpha) clf_alpha.fit(X_train, y_train) clfs.append(clf_alpha) # 画图:alpha vs 准确率 train_scores = [c.score(X_train, y_train) for c in clfs] test_scores = [c.score(X_test, y_test) for c in clfs] plt.figure(figsize=(8, 5)) plt.plot(ccp_alphas, train_scores, marker='o', label='训练集') plt.plot(ccp_alphas, test_scores, marker='s', label='测试集') plt.xlabel('ccp_alpha') plt.ylabel('准确率') plt.legend() plt.title('代价复杂度剪枝路径') plt.show()这段代码先调用cost_complexity_pruning_path得到一系列 alpha 值,然后对每个 alpha 训练一棵树,记录训练集和测试集准确率。图上通常能看到:alpha 很小时,训练集准确率高、测试集低;alpha 增大到某个点,测试集准确率最高;再大就欠拟合。报告里要标出这个最优点,并解释为什么选它。
注意,ccp_alpha和max_depth可以一起用,但一般选一种就行。如果报告里同时用预剪枝和后剪枝,要说明各自的作用,别让老师觉得你在堆参数。
3.4 决策树可视化:把 if-else 规则画出来
决策树最大的优势是可解释,所以实验报告里一定要有可视化。scikit-learn 提供了plot_tree,但图大了会糊。更好的做法是用 Graphviz 导出 PDF,或者用dtreeviz库画更漂亮的图。
from sklearn.tree import plot_tree import matplotlib.pyplot as plt # 用最佳参数重新训练 best_clf = DecisionTreeClassifier( max_depth=3, min_samples_leaf=2, random_state=42 ) best_clf.fit(X_train, y_train) # 可视化 plt.figure(figsize=(12, 8)) plot_tree( best_clf, feature_names=feature_cols, class_names=['坏瓜', '好瓜'], filled=True, rounded=True, fontsize=10 ) plt.title('决策树可视化(max_depth=3)') plt.show()plot_tree的参数含义:feature_names显示特征名,class_names显示类别名,filled=True按类别填充颜色,rounded=True圆角框。生成的图里每个节点会显示划分条件、基尼指数、样本数、类别分布。报告里要解读这棵树:根节点用了哪个特征?为什么?第二层怎么分?叶子节点纯不纯?
如果树太深,plot_tree会看不清,可以只画前几层,或者用max_depth限制。另外,Graphviz 的export_graphviz能导出更专业的图,但需要安装 Graphviz 软件,报告里可以附上安装命令。
4. 实验报告里最容易翻车的五个坑
4.1 数据泄露:在划分训练集之前做了标准化或编码
现象:测试集准确率异常高,接近 100%,但换一份数据就崩。
原因:很多同学先把整个数据集做标准化或编码,再划分训练集和测试集。这样测试集的统计信息(均值、方差、类别分布)泄露到了训练过程。决策树虽然对标准化不敏感,但编码和缺失值填充会泄露。
解决:先train_test_split,再在训练集上fit编码器或填充器,然后transform测试集。用Pipeline能避免这个错误:
from sklearn.pipeline import Pipeline from sklearn.impute import SimpleImputer from sklearn.preprocessing import OrdinalEncoder pipe = Pipeline([ ('imputer', SimpleImputer(strategy='most_frequent')), ('encoder', OrdinalEncoder()), ('clf', DecisionTreeClassifier(random_state=42)) ]) pipe.fit(X_train, y_train)4.2 用测试集调参,然后报告测试集准确率
现象:报告里写“经过调参,测试集准确率达到 95%”,但老师一问调参过程就露馅。
原因:把测试集当成验证集用,反复在测试集上试参数,相当于让模型“见过”测试集。最终报告的准确率是乐观偏差。
解决:训练集再划分出验证集,或者用交叉验证。测试集只在最后评估一次。报告里要写清楚:调参用交叉验证,测试集从未参与调参。
4.3 忽略类别不平衡,只看准确率
现象:准确率 90%,但少数类召回率 0%。
原因:数据里正负样本比例悬殊,模型全预测多数类就能拿高准确率。决策树虽然比朴素贝叶斯好一点,但也会偏向多数类。
解决:用class_weight='balanced'让模型关注少数类,或者用 F1、AUC 作为评估指标。报告里要同时给出混淆矩阵和分类报告。
4.4 树太深导致过拟合,却归因于“数据噪声”
现象:训练集 100%,测试集 60%,报告里写“数据存在噪声,模型泛化能力有限”。
原因:没有剪枝,树把训练集里每个样本的细节都学到了,包括噪声。这不是数据的问题,是模型复杂度的问题。
解决:限制max_depth、min_samples_leaf,或者用ccp_alpha后剪枝。报告里要对比剪枝前后的树深度和测试准确率,用数据说明剪枝有效。
4.5 特征编码顺序错误,导致树的分裂逻辑反了
现象:可视化出来的树,根节点分裂条件看起来不合理,比如“纹理 <= 1.5”把清晰和模糊分到一边。
原因:OrdinalEncoder默认按字母顺序编码,但特征本身是有序的。比如“纹理”的清晰、稍糊、模糊,字母顺序是“模糊、清晰、稍糊”,编码后 0=模糊,1=清晰,2=稍糊,完全打乱了业务顺序。
解决:手动指定categories参数,按业务顺序排列:
encoder = OrdinalEncoder(categories=[ ['模糊', '稍糊', '清晰'], # 纹理从差到好 ['平坦', '稍凹', '凹陷'], # 脐部 ['软粘', '硬滑'], # 触感 ['清脆', '沉闷', '浊响'], # 敲声 ['硬挺', '稍蜷', '蜷缩'], # 根蒂 ['乌黑', '青绿', '浅白'] # 色泽 ])报告里要说明每个特征的编码顺序和业务含义,别让老师猜。
5. 让实验报告更有说服力的进阶技巧:学习曲线与特征重要性
5.1 用学习曲线判断过拟合还是欠拟合
学习曲线横轴是训练集大小,纵轴是准确率,画两条线:训练集得分和验证集得分。如果两条线中间有大间隙,说明过拟合;如果两条线都很低且接近,说明欠拟合。代码很简单:
from sklearn.model_selection import learning_curve import numpy as np train_sizes, train_scores, test_scores = learning_curve( DecisionTreeClassifier(max_depth=3, random_state=42), X, y, cv=5, scoring='f1', train_sizes=np.linspace(0.1, 1.0, 10) ) train_mean = np.mean(train_scores, axis=1) test_mean = np.mean(test_scores, axis=1) plt.figure(figsize=(8, 5)) plt.plot(train_sizes, train_mean, 'o-', label='训练集F1') plt.plot(train_sizes, test_mean, 's-', label='验证集F1') plt.xlabel('训练样本数') plt.ylabel('F1') plt.legend() plt.title('学习曲线') plt.show()在西瓜数据集上,因为样本只有 17 条,学习曲线会很抖,验证集 F1 波动大。这本身就是一个结论:数据量太小,模型评估不可靠。报告里可以建议收集更多数据,或者用交叉验证的均值±标准差来报告结果。
5.2 特征重要性:哪些特征对分类贡献最大
决策树自带feature_importances_属性,返回每个特征的重要性分数,总和为 1。这个分数基于分裂带来的不纯度减少量。代码:
import pandas as pd best_clf = DecisionTreeClassifier(max_depth=3, random_state=42) best_clf.fit(X_train, y_train) importance = pd.DataFrame({ '特征': feature_cols, '重要性': best_clf.feature_importances_ }).sort_values('重要性', ascending=False) print(importance)在西瓜数据集上,纹理的重要性通常最高,其次是根蒂和色泽。报告里要结合业务解释:纹理清晰的好瓜概率大,这和“敲声浊响、根蒂蜷缩”等特征有相关性。但要注意,特征重要性对 correlated 特征会分散分数,不能直接说“某个特征不重要”。如果两个特征高度相关,树可能随机选一个,另一个重要性就低。
5.3 报告结构建议:老师想看到什么
一份高分实验报告通常包含:问题定义、数据描述、数据预处理、方法原理、实验设置、结果分析、结论。其中结果分析是拉开差距的地方。不要只贴准确率,要对比不同参数、不同划分标准、剪枝前后的差异,用表格和图表说话。比如:
| 配置 | 训练集F1 | 测试集F1 | 树深度 | 叶子数 |
|---|---|---|---|---|
| 无剪枝 | 1.000 | 0.600 | 6 | 7 |
| max_depth=3 | 0.900 | 0.800 | 3 | 4 |
| ccp_alpha=0.02 | 0.850 | 0.800 | 3 | 4 |
这张表能直观展示剪枝的效果。最后用第一人称写一句:我一般会在报告最后附上完整代码和随机种子,确保别人能复现我的结果。希望帮到你。
本文还有配套的精品资源,点击获取