恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

鸢尾花数据集的决策树实战:从原理到调优与可视化

  • 首页
  • 资讯中心
  • /
  • 鸢尾花数据集的决策树实战:从原理到调优与可视化

相关资讯

基于Node.js+Vue的大学生创业资助管理系统设计与实现 2026/9/15 5:50:07
ThinkPHP与Laravel双框架约稿平台开发实践 2026/9/15 5:50:07
白萝卜检测数据集处理与YOLO训练实战全流程 2026/9/15 5:50:07

最新资讯

LAG3重组兔单抗:免疫检查点调控机制与抗体开发要点
AI提效失败的真相:开发工作流诊断比选型更重要
东南大学第二十一届智能汽车竞赛校赛 · 规则详解
SpringBoot+Vue图书管理系统毕设开发与答辩实战指南
UART RTL设计实战:从协议原理到FPGA上板调试
长会话对话历史的虚拟滚动与高度动态估算

今日推荐

GDPR下大数据架构重构与隐私保护实践
多组学数据平台架构设计与优化实践
企业主数据管理系统架构设计与实施全解析

本周热门

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化
Flutter应用改名全指南:从Android到iOS的配置与工具实践

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

鸢尾花数据集的决策树实战:从原理到调优与可视化

发布时间:2026/9/15 5:50:07
鸢尾花数据集的决策树实战:从原理到调优与可视化 简介面向数据挖掘与机器学习初学者的鸢尾花分类决策树实验代码采用C语言完整实现从数据读取、预处理到模型训练与预测的流程。实验基于经典的鸢尾花数据集利用花瓣长度、花瓣宽度、萼片长度、萼片宽度四个特征判别Setosa、Versicolour、Virginica三种类别。压缩包共1个文件为3KB的cpp源码代码中划分了数据读取与预处理、决策树模型定义、最佳分裂标准计算、递归建树及预测等模块便于逐步理解算法细节。目前已有3413人浏览学习。通过这份代码读者可以掌握信息增益、基尼不纯度等分裂准则的C实现方式并体会决策树处理多分类问题的完整工程过程相比单纯调库手写算法更能加深对树结构、递归划分和停止条件的理解。代码注释与函数划分清晰适合课程实验、课后巩固或毕业设计参考。1. 为什么一个 10 分钟能跑完的实验值得写一整篇鸢尾花分类实验是机器学习里最接近“Hello World”的题目但它和打印一行字符串完全不同数据集只有 150 条样本、4 个特征却同时考验你对数据清洗、特征理解、算法选型、过拟合控制和结果解释的把握。决策树作为这个实验里最直观的分类器训练完还能把整棵树的判定逻辑画出来告诉别人“模型为什么这么分”。这种可解释性在真实业务里价值很高风控、医疗辅助诊断、设备故障定位这些场景至今还在用它做基线模型。这篇文章要做的不是贴一段代码让你跑通而是把背后的选择题讲清楚为什么决策树适合做小样本多分类、特征怎么量化和切分、树深多少算合适、剪枝参数调哪个先哪个后。整个过程用 pandas 加载鸢尾花数据集用 scikit-learn 的决策树实现从数据处理一路做到树的可视化和边界分析。适合刚学机器学习想完整走一遍流程的人也适合已经用过决策树但没仔细抠过参数的人。2. 先认识鸢尾花数据集三类样本和四个特征的分布逻辑2.1 为什么是鸢尾花小样本多分类的经典结构鸢尾花数据集Iris dataset收集了三种鸢尾花各 50 条样本特征是花萼长度、花萼宽度、花瓣长度、花瓣宽度单位是厘米。三类分别是 setosa、versicolor、virginica。这个数据集的特殊之处在于它天然适合演示分类问题有一类是线性可分的另外两类存在重叠。这意味着一个模型如果只做简单划分很容易在 versicolor 和 virginica 的边界上出错正好用来观察决策树的切分行为。150 条样本对现代计算机来说非常小但小样本恰恰放大了过拟合的风险。决策树如果完全不限制生长它能把训练集里每一条样本都包住测试集上表现反而不稳定。用这个数据集做实验核心不是“跑出多高的准确率”而是理解树的生长逻辑和泛化能力之间的平衡。2.2 用 pandas 加载并检查数据结构动手第一步是用 pandas 读取数据但这里有一个常见的坑直接从 sklearn 载入的是 NumPy 数组没有列名。这个阶段用 pandas 的read_csv或load_iris转 DataFrame 都可以我一般先从sklearn.datasets加载原始数据再转成 DataFrame 查看结构。这样做能保留特征名和标签名后续画图、做特征重要性分析时不用对着下标猜。import pandas as pd from sklearn.datasets import load_iris iris load_iris() df pd.DataFrame(datairis.data, columnsiris.feature_names) df[target] iris.target df[species] df[target].map({0: setosa, 1: versicolor, 2: virginica}) print(df.head()) print(df.info()) print(df.groupby(species).describe())代码逻辑iris.data是形状为 (150, 4) 的特征矩阵iris.target是整数标签iris.feature_names是对应的特征名。先构造成 DataFrame再把整数标签映射为物种名称方便观察。df.info()检查有没有缺失值groupby加describe能一次性看到每个类别在各特征上的均值、标准差、最小值、最大值。2.2.1 四个特征的分布差异决定了决策树的切分顺序对这三类鸢尾花做特征分布对比能发现一个关键规律花瓣长度和花瓣宽度在 setosa 和另外两类之间有明显间隔而花萼宽度和花萼长度的重叠度较高。决策树在每一层选择特征时依据的是不纯度下降量所以前几层大概率会在花瓣特征上做切分。这个现象后面可以通过打印树的 feature importance 来验证也解释了为什么决策树不需要做特征标准化。2.3 用一个散点图矩阵确认线性可分性在训练任何模型之前先做可视化是值得的。用 pandas 内置的scatter_matrix或者 seaborn 的pairplot画特征两两组合的散点图能直观看到哪些特征组合能把三类分开。这一步不只为了好看还直接影响你对模型上限的判断——如果两两特征组合都完全重叠任何基于特征切分的树模型都不可能做到高准确率。import matplotlib.pyplot as plt from pandas.plotting import scatter_matrix colors {setosa: #2ca02c, versicolor: #ff7f0e, virginica: #1f77b4} scatter_matrix(df[iris.feature_names], cdf[species].map(colors), figsize(12, 10), alpha0.6) plt.show()这段代码把 4 个特征两两组合成 16 个子图对角线是单特征分布直方图。跑完之后重点看花瓣长度和花瓣宽度组合的子图setosa 会形成一团远离另外两类的点而 versicolor 和 virginica 之间有部分交叠。这个交叠区域就是决策树后续需要反复切分的地方也是过拟合最容易发生的区域。3. 决策树的切分逻辑信息熵、信息增益与基尼系数的选择3.1 决策树的生长过程本质上是在回答“怎么切最划算”决策树每次选择一个特征和一个阈值把当前节点的样本分成左右两支目标是让切分后的两个子节点内部尽可能“纯”——也就是同一类样本尽量聚在一起。衡量“纯”的指标常见有两个信息熵entropy和基尼系数gini。scikit-learn 里DecisionTreeClassifier的criterion参数默认是gini也可以改成entropy。信息熵的计算公式是H(D) -Σ p_k * log2(p_k)其中p_k是第 k 类样本在当前节点中的占比。当所有样本都属于同一类时熵为 0当类别均匀分布时熵最大。信息增益就是父节点熵减去子节点熵的加权和。基尼系数的公式是Gini(D) 1 - Σ p_k^2它表示从节点中随机抽两个样本类别不一致的概率。数值越小纯度越高。3.1.1 gini 和 entropy 实验差异很小但计算成本不同在实际实验里gini 和 entropy 在鸢尾花数据上得到的树结构可能不同但准确率差异通常非常小。gini 的计算不涉及对数运算运行更快entropy 对类别分布的变化更敏感在某些数据集上生成的树会更“细致”。对鸢尾花这个规模的数据二者差别可以忽略但作为实验的一部分把两个都跑一遍并对比输出来感受差异比光看文档来得实在。3.2 从 150 条样本到一棵树的完整过程先看一个最小可运行的决策树代码然后逐行解释树的生长过程加载数据、切分训练集和测试集、定义模型、训练、评估。from sklearn.model_selection import train_test_split from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import accuracy_score, classification_report X df[iris.feature_names] y df[target] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) clf DecisionTreeClassifier( criteriongini, max_depth4, min_samples_leaf2, random_state42 ) clf.fit(X_train, y_train) y_pred clf.predict(X_test) print(accuracy:, accuracy_score(y_test, y_pred)) print(classification_report(y_test, y_pred, target_namesiris.target_names))参数说明test_size0.3表示 30% 的数据留作测试集stratifyy保证切分后训练集和测试集中三类样本比例与原数据一致在类别均衡的鸢尾花数据上作用不明显但遇到不平衡数据时必须加max_depth4限制树的最大深度为 4 层防止树无限生长min_samples_leaf2要求每个叶子节点至少包含 2 条样本这是一个预剪枝策略。random_state42固定随机种子保证每次运行结果一致方便复现。3.3 为什么鸢尾花实验里要同时限制 max_depth 和 min_samples_leaf很多人第一次做这个实验时只设max_depth觉得树不要太深就够了。但实际操作中max_depth控制的是树的整体高度如果某个节点分裂后两个子节点里有一个只有一条样本这个叶子仍然是不稳定的。min_samples_leaf直接限制了叶子节点的最小样本量相当于在更细的粒度上做约束。两个参数一起调树会更稳定。另外还有一个容易被忽略的参数min_impurity_decrease。它表示只有当分裂带来的不纯度下降量超过这个阈值时才允许分裂。这个参数常用于后剪枝场景但在鸢尾花这样的小数据集上不推荐一开始就设因为样本量少不纯度下降量天然波动大设了很容易导致整棵树都长不出来。4. 在本地跑通鸢尾花决策树的最小实现数据处理到树结构可视化4.1 特征矩阵构造与标签编码的可复现做法鸢尾花数据不需要处理缺失值但真实项目中一定会遇到。这里给出一个标准处理流程先用isnull().sum()检查缺失再决定是删除还是填充。最常见的填充方式是类别变量用众数、数值变量用中位数。鸢尾花数据集没有这个问题但实验里应该保留这一步因为换数据集时流程可以直接复用。df.isnull().sum() # 无缺失则继续 X df[iris.feature_names].copy() # 只保留特征列 y df[target].copy() # 标签已经是整数不需要额外编码这里X是 DataFramey是 Series符合 scikit-learn 的输入要求。如果从 CSV 加载原始数据标签可能是字符串这时需要用sklearn.preprocessing.LabelEncoder或 pandas 的astype(category).cat.codes转为整数。注意不要在特征矩阵里混入原始标签列否则模型会把“正确答案”当成特征学进去。4.2 决策树结构可视化把黑盒还原成 if-else 规则训练完模型之后最好把树画出来看一眼。scikit-learn 在较新版本里推荐用sklearn.tree.plot_tree不需要额外安装 graphviz直接基于 matplotlib 绘制对中文环境也友好。from sklearn.tree import plot_tree import matplotlib.pyplot as plt plt.figure(figsize(20, 10)) plot_tree( clf, feature_namesiris.feature_names, class_namesiris.target_names, filledTrue, roundedTrue, fontsize12 ) plt.show()filledTrue让每个节点的背景色根据类别纯度渐变颜色越纯说明该节点里某一类占比越高。roundedTrue让节点边框圆角化全局figsize加大防止文字重叠。画出图后重点看根节点的分裂条件大概率是“花瓣宽度是否小于等于 0.8”这正是数据分布分析时看到的强区分特征。如果想把树结构导出成文本可以用sklearn.tree.export_textfrom sklearn.tree import export_text tree_rules export_text(clf, feature_namesiris.feature_names, max_depth4) print(tree_rules)这样输出的是一段缩进格式的规则列表适合放在代码仓库里做文档也方便在终端里快速确认每一层分裂用的特征和阈值。4.3 用决策边界图观察模型在哪类样本上容易出错树结构告诉我们模型怎么分但视觉上最直观的还是决策边界图。鸢尾花有 4 个特征没法把所有维度画在一个平面里常见做法是选两个特征组合成二维平面来画。比如选花瓣长度和花瓣宽度这两个特征区分度最高画出来的边界最清晰。import numpy as np def plot_decision_boundary(X_data, y_data, model, feat_idx(2, 3), axNone): if ax is None: ax plt.gca() x_min, x_max X_data.iloc[:, feat_idx[0]].min() - 0.5, X_data.iloc[:, feat_idx[0]].max() 0.5 y_min, y_max X_data.iloc[:, feat_idx[1]].min() - 0.5, X_data.iloc[:, feat_idx[1]].max() 0.5 xx, yy np.meshgrid(np.linspace(x_min, x_max, 300), np.linspace(y_min, y_max, 300)) grid np.c_[xx.ravel(), yy.ravel()] # 用完整特征矩阵的均值填充未参与作图的特征 full_grid np.tile(X_data.mean().values, (grid.shape[0], 1)) full_grid[:, feat_idx[0]] grid[:, 0] full_grid[:, feat_idx[1]] grid[:, 1] Z model.predict(full_grid).reshape(xx.shape) ax.contourf(xx, yy, Z, alpha0.3, cmapcoolwarm) scatter ax.scatter(X_data.iloc[:, feat_idx[0]], X_data.iloc[:, feat_idx[1]], cy_data, cmapcoolwarm, edgecolork) ax.set_xlabel(iris.feature_names[feat_idx[0]]) ax.set_ylabel(iris.feature_names[feat_idx[1]]) return ax plot_decision_boundary(X_test, y_test, clf) plt.show()这个函数的核心逻辑是在二维平面上生成密集网格点每个点通过均值填充其他两个特征构造出完整的 4 维向量交给模型预测再用contourf画出分类区域。这种“填充均值”的做法在特征独立时近似成立鸢尾花特征相关性中等用来观察大致边界没问题。图上能清楚看到 setosa 区域被一刀切出来versicolor 和 virginica 之间出现折线状边界这就是决策树的轴平行切分特点。5. 决策树的参数调优与剪枝从过拟合到泛化5.1 同一个数据上不同 max_depth 的表现差异决策树不限制深度时训练集准确率可以到 100%但测试集准确率可能下降。做一个简单的对比实验把max_depth从 1 调到 10分别记录训练集和测试集准确率画出曲线。train_accs [] test_accs [] depths range(1, 11) for depth in depths: model DecisionTreeClassifier( criteriongini, max_depthdepth, min_samples_leaf1, random_state42 ) model.fit(X_train, y_train) train_accs.append(accuracy_score(y_train, model.predict(X_train))) test_accs.append(accuracy_score(y_test, model.predict(X_test))) plt.plot(depths, train_accs, labeltrain accuracy, markero) plt.plot(depths, test_accs, labeltest accuracy, markers) plt.xlabel(max_depth) plt.ylabel(accuracy) plt.legend() plt.show()这个曲线图通常会出现一个典型形态训练准确度一路攀升测试准确度在某个深度可能是 3 或 4之后开始波动或下降。这个转折点就是模型从欠拟合走向过拟合的分界。注意鸢尾花数据集小测试集只有 45 条样本准确率波动区间大单次实验的曲线不会完全平滑所以判断过拟合要结合多次随机切分的平均表现不要因为某一次测试准确率降低就急着下结论。5.2 预剪枝参数组合的推荐顺序调参时不要一上来就网格搜索全参数。我是按这个顺序调的先固定random_state保证每次结果可比调max_depth观察训练集和测试集准确率的差距设定min_samples_leaf为 2 或 3看测试准确率是否提升最后调max_leaf_nodes它是另一种控制树复杂度的手段。max_leaf_nodes限制叶子节点的总数比max_depth更直接。因为深度相同情况下如果某个节点一直分裂可能一棵树虽然深度不大但叶子很多。max_leaf_nodes10表示整棵树最多 10 个叶子结点每次分裂前先检查当前叶子数是否已达上限。clf_pruned DecisionTreeClassifier( criteriongini, max_depth4, min_samples_leaf3, max_leaf_nodes10, random_state42 )以上组合的作用是max_depth4限制整体高度min_samples_leaf3让每个叶子至少覆盖 3 条样本max_leaf_nodes10从总数上封顶。三个条件同时满足树结构会明显比默认配置简单训练集准确率会降低但测试集上的稳定性更好。5.3 用网格搜索确认参数区间调参数靠手试效率低用GridSearchCV做小范围搜索关键是把cv设置合理。鸢尾花数据 150 条用默认的 5 折交叉验证即可每折 30 条样本分类器是稳定的。from sklearn.model_selection import GridSearchCV param_grid { max_depth: [2, 3, 4, 5], min_samples_leaf: [1, 2, 3, 4], max_leaf_nodes: [None, 5, 8, 10] } grid_search GridSearchCV( DecisionTreeClassifier(random_state42), param_grid, cv5, scoringaccuracy ) grid_search.fit(X_train, y_train) print(best params:, grid_search.best_params_) print(best cv score:, grid_search.best_score_)注意GridSearchCV虽然会返回best_params_但不要直接信任它作为最终模型的参数。样本量小的时候交叉验证分数本身有波动跑几次网格搜索得到的“最优参数”可能不同。正确做法是把网格搜索当成范围确认工具然后用业务可解释性来校准。比如网格搜索给出max_depth5但树的图形看起来太碎那就退到max_depth3——在可解释性和准确率之间取平衡。5.4 特征重要性的误读与正确解读方式决策树的feature_importances_属性返回每个特征的重要性得分总和为 1。这个得分是怎么算的它是每个特征在所有节点上带来的不纯度下降量加权求和后归一化的结果。在鸢尾花数据上通常能看到花瓣宽度的重要性远高于花萼宽度。for name, importance in zip(iris.feature_names, clf.feature_importances_): print(f{name}: {importance:.3f})这个数值容易产生一个误导认为重要性低的特征就对分类没有贡献。实际上如果两个特征高度相关树可能只选其中一个来分裂另一个的重要性就被压低了但删除它并不一定影响模型表现。决策树只能告诉你“在当前树的生长过程中某个特征被用了多少”不能下结论说某个特征没有信息量。要验证特征价值应该做删除特征后的模型对比实验。6. 对比实验决策树与随机森林在鸢尾花数据上的稳定性6.1 为什么要拿决策树和随机森林对比决策树的一个问题是对数据的微小变化敏感训练集里删掉一条样本生成的树结构可能完全不同。随机森林通过有放回抽样和随机特征子集来降低方差在鸢尾花数据上的默认表现通常不会比单棵决策树差太多但在多次随机切分实验里它的准确率波动更小。这个对比实验能帮助理解“集成方法为什么能提升稳定性”这个抽象概念。from sklearn.ensemble import RandomForestClassifier rf RandomForestClassifier( n_estimators100, max_depth4, min_samples_leaf2, random_state42 ) rf.fit(X_train, y_train) rf_pred rf.predict(X_test) print(decision tree accuracy:, accuracy_score(y_test, y_pred)) print(random forest accuracy:, accuracy_score(rf_test, rf_pred))6.2 多轮随机切分下的准确率分布单次对比没有统计意义。我写一个循环每次用不同random_state切分数据训练两类模型记录 30 轮准确率再比较均值与标准差。dt_scores [] rf_scores [] for seed in range(30): X_tr, X_te, y_tr, y_te train_test_split( X, y, test_size0.3, random_stateseed, stratifyy ) dt DecisionTreeClassifier(max_depth4, min_samples_leaf2, random_state42) dt.fit(X_tr, y_tr) dt_scores.append(accuracy_score(y_te, dt.predict(X_te))) rfc RandomForestClassifier(n_estimators100, max_depth4, min_samples_leaf2, random_state42) rfc.fit(X_tr, y_tr) rf_scores.append(accuracy_score(y_te, rfc.predict(X_te))) import numpy as np dt_scores np.array(dt_scores) rf_scores np.array(rf_scores) print(决策树:, f均值{dt_scores.mean():.4f}, f标准差{dt_scores.std():.4f}) print(随机森林:, f均值{rf_scores.mean():.4f}, f标准差{rf_scores.std():.4f})运行结果一般是随机森林的标准差更小。但注意随机森林在特征只有 4 个的数据集上优势有限因为每次分裂时可选择的特征子集只有 2 个默认max_featuressqrt随机性带来的收益被限制住了。这个结论同样重要集成方法不是万能的特征维度太少时提升空间有限。6.3 过拟合验证训练集准确率 vs 测试集准确率的差值判断模型是否过拟合看训练集和测试集准确率的差值就够了。差值接近 0说明模型泛化良好差值很大说明模型记住了训练集的噪声。把这个差值同时打印出来和准确率放在一起判断避免只看准确率一个数字。dt_train_acc accuracy_score(y_train, dt.predict(X_train)) dt_test_acc accuracy_score(y_test, dt.predict(X_test)) print(f决策树 train-test gap: {dt_train_acc - dt_test_acc:.4f})对鸢尾花数据来说max_depth3到4之间通常能找到一个低差距又保持高准确率的区间。可以试试把max_depth设为 10看看差距如何变大——这是理解过拟合最直接的体验训练集表现很好测试集表现骤降树的结构图也变得非常庞大。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号