2026/10/19 4:39:08

鸢尾花数据集实战:从K近邻到交叉验证的机器学习入门全流程

鸢尾花数据集实战:从K近邻到交叉验证的机器学习入门全流程 1. 项目概述为什么几乎所有机器学习入门者都会选 IrisIris Flower 数据集中文翻译过来就是“鸢尾花数据集”——对任何一个走机器学习这条路的开发者来说它基本都是躲不开的第一站。哪怕你只是在搜索引擎里敲过“机器学习 入门 项目”这几个字大概率都会在各种教程的推荐清单里看到它。为什么这个项目这么“烂大街”却依然值得认真做一遍我在带新人入门的时候经常说Iris 就是机器学习界的“hello world”。它的数据量小、特征清晰、分类任务简单非常适合在没有太多计算资源的情况下完整体验一次从数据理解、模型训练到结果评估的全流程。先说清楚这个项目要解决什么问题数据集中包含了三种鸢尾花——Setosa、Versicolor、Virginica每种花各有 50 条样本。每条样本记录了花萼长度、花萼宽度、花瓣长度、花瓣宽度这 4 个特征。我们的任务是当我们拿到一组新的测量数据时能够准确地判断它属于哪一种鸢尾花。核心关键词其实就三个特征Feature、标签Label、分类Classification。搞清楚这三个概念这个项目就理解了一半。特征就是我们用来判断的依据标签就是我们要预测的目标结果而分类就是指我们的预测目标是离散的类别而不是连续数值。好了这个项目适合谁来做如果你已经掌握了 Python 基础语法了解 numpy、pandas 的基本用法但还从来没有完整地跑通过一个机器学习流程那么这个项目就是为你准备的。这篇文章不会只丢几段代码给你而是会把每一步的“为什么”都讲透——为什么数据要切成训练集和测试集为什么分类之前要标准化为什么模型准确率达到 95% 不代表万事大吉这些都是实际工程里每天都会遇到的问题。2. 环境准备与工具选型解析开始写代码之前先把环境准备好。我见过的初学者里有相当一部分人不是卡在算法原理上而是卡在环境配置上。所以这里花一点篇幅把工具链说清楚。2.1 Python 环境与 pip 安装我建议直接使用 Python 3.8 以上的版本。当前大多数主流机器学习库都已经放弃对旧版本的支持Python 3.8 以下会出现各种依赖冲突。你可以先用终端验证一下自己本地的 Python 版本python --version如果你看到的是 Python 3.6 或更老建议先去官网下载新版 Python 重新安装安装时注意勾选“Add Python to PATH”选项不然装完还是用不了。接下来安装第三方库。我们用到的核心库有四个numpy科学计算的基础库处理多维数组pandas数据分析工具提供 DataFrame 结构matplotlib绘图库用于可视化scikit-learn机器学习工具库自带 Iris 数据集和大量算法实现安装命令非常简单pip install numpy pandas matplotlib scikit-learn如果你用的是国内网络环境可以在 pip 后面加上国内镜像源速度会快很多pip install numpy pandas matplotlib scikit-learn -i https://pypi.tuna.tsinghua.edu.cn/simple2.2 交互式环境的选择Jupyter Notebook 还是 IDE这个项目我建议你用 Jupyter Notebook而不是直接写 .py 脚本文件。原因很简单数据探索和模型调试的过程是高度交互式的你需要一遍遍地看数据的样子、变量的内存结构、可视化的输出结果。Jupyter 中每个单元格独立运行改代码不需要重新跑全流程非常体贴初学者。当然你完全可以一个.py文件从头写到尾但如果你这么做你会发现每改一次参数都要整个重跑一遍调试体验会比较抓狂。我个人的习惯是初期探索用 Jupyter代码稳定之后再把完整流程整理成 .py 脚本。这一步值得养成习惯后面做任何项目都用得上。2.3 确认数据集能正常加载scikit-learn 自带 Iris 数据集的加载接口不需要额外下载任何文件。这一点对新手来说很友好。跑一段最简单的代码确认环境没问题from sklearn.datasets import load_iris iris load_iris() print(iris.data.shape) print(iris.target_names)正常输出应该是(150, 4)和[setosa versicolor virginica]。如果你能看到这两行输出说明环境已经准备好了可以正式开始项目。3. 数据理解与探索性分析先看懂数据再谈模型很多初学者上来就写model.fit(X_train, y_train)把数据塞进去拿到结果就完事了。这种模式非常不好因为在不理解数据分布的情况下你很难判断模型输出的结果是否合理也很难判断后续出现的各种异常到底来自数据还是模型。这里我把探索性数据分析EDA的关键步骤完整走一遍。3.1 加载数据并构建 DataFramescikit-learn 加载出来的数据是 numpy 数组格式直接用 pandas 包一层变成 DataFrame后续操作会顺手很多import pandas as pd from sklearn.datasets import load_iris iris load_iris() df pd.DataFrame(iris.data, columnsiris.feature_names) df[species] iris.target df[species_name] iris.target_names[iris.target] print(df.head()) print(df.info()) print(df.describe())df.info()告诉我们这个数据集有没有缺失值。正常情况下应该看到 150 条非空记录每个特征的non-null count都是150类型是float64。这里我要说一句真实项目里几乎不可能碰到数据这么干净的情况缺失值、异常值、重复样本才是常态。Iris 数据集之所以适合入门就是因为它足够干净你可以把注意力完全放在方法流程上而不用被数据清洗折磨。df.describe()输出每个特征的均值、标准差、最小值、四分之一分位数、中位数等统计量。运行完之后你会发现几个非常有价值的信息花瓣长度petal length的范围大约是 1.0 到 6.9跨度很大花瓣宽度petal width的范围大约是 0.1 到 2.5花萼宽度sepal width的最小值是 2.0最大值是 4.4分布相对集中不同特征的数值范围差异明显这直接引出了一个问题后面做模型训练时要不要做特征缩放3.2 分布可视化一眼看穿类别可分性光看统计数字还不够直观画图才是理解数据最好的方式。对于分类问题第一步永远是看每个类别的样本数量是否均衡import matplotlib.pyplot as plt species_counts df[species_name].value_counts() plt.figure(figsize(6, 4)) species_counts.plot(kindbar) plt.title(Sample Count by Species) plt.xlabel(Species) plt.ylabel(Count) plt.savefig(species_counts.png, dpi100)这里有一个小细节画完图之后我用的是plt.savefig而不是直接plt.show()。在 Jupyter 里直接plt.show()没问题但如果你写 .py 脚本再通过命令行执行show()可能出现图片一闪而过或者死等不返回的情况。存成图片文件再查看坑少很多。接下来看每个特征在三个类别下的分布情况。一个稳妥的做法是把 4 个特征的箱线图分别画出来放在同一张画布上fig, axes plt.subplots(2, 2, figsize(10, 8)) features iris.feature_names for ax, feature in zip(axes.flatten(), features): for target in range(3): subset df[df[species] target][feature] ax.boxplot(subset, positions[target], labels[iris.target_names[target]]) ax.set_title(feature) plt.tight_layout() plt.savefig(feature_boxplots.png, dpi100)箱线图能直接暴露出特征的可分性差异。你会发现花萼长度和花萼宽度这两个特征三类样本的箱体重合度非常高说明单独用一个特征很难区分种类花瓣长度和花瓣宽度这两个特征Setosa 类别的箱体和其他两类基本完全分离说明这两个特征信息量极大Versicolor 和 Virginica 在花瓣特征上虽然有部分重叠但整体趋势已经分开了这个观察极其重要。它在提醒你这个数据集在特征空间中是近乎线性可分的尤其是 Setosa 这一个类别靠一个花瓣宽度特征就能大概率分开。这意味着哪怕你只用一个很简单的模型比如逻辑回归、K 近邻都能取得不错的效果。如果你用了非常复杂的模型但效果反而不好那大概率是参数没调好而不是模型能力不够。3.3 特征相关性分析再补充一步相关性分析。两个特征如果高度相关说明它们携带的信息高度冗余在一些对特征独立性敏感的模型比如线性回归里可能会引起问题。我们用 pandas 一行代码就能搞定corr_matrix df[iris.feature_names].corr() print(corr_matrix)输出结果里最显眼的是花瓣长度和花瓣宽度的相关系数大约在 0.96 左右属于高度正相关。花萼长度和花瓣长度之间的相关系数也在 0.87 左右。这两个数字说明花瓣相关的两个特征之间存在很强的冗余性花萼长度部分包含了花瓣长度的信息。不过我要提醒一下对 Iris 这个数据量很小的项目来说特征冗余并不会导致模型崩溃。决策树、K 近邻这些算法对特征间相关性不敏感。这里展示相关性分析更多是让你养成一个习惯——拿到任何数据集先做一次全貌扫描心里有谱了再继续往下走。4. 训练集与测试集划分为什么不能拿全部数据训练4.1 固定随机种子与数据洗牌现在进入建模环节。但在训练之前有一件非常重要的事把数据集划分成训练集和测试集。这个步骤很多新手会忽略直接拿全部 150 条数据去训练再用同一批数据评估结果准确率看着接近 100%然后信心满满地部署上线一到真实环境立刻被打回原形。这里面的核心逻辑很简单如果一个模型在训练时已经“看过”了答案那么它在这些数据上表现好是理所当然的并不能说明它对没见过的数据有好的判断能力。scikit-learn 提供了现成的划分函数from sklearn.model_selection import train_test_split X iris.data y iris.target X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy )三个关键参数逐个解释test_size0.2把 20% 的数据作为测试集也就是 150 条里取 30 条用于最终评估剩下 120 条用于训练random_state42固定随机种子。只要设置这个数字每次运行结果都一样。如果不设置下一次运行划分结果就变了模型的评估结果也随之变动不利于复现和调试stratifyy按标签比例分层抽样。三种花在原始数据集里各占 1/3分层划分后训练集里每种花仍然是各占约 1/3测试集里也一样关于分层抽样我再多解释一句。假设极端情况原始数据里有 120 个 A 类样本和 30 个 B 类样本如果不分层随便切 20% 出来可能测试集里正好全是 A 类样本B 类一个都没有。那模型的准确率看起来可能不错但对 B 类的判断能力完全没有被评估到。分层抽样就是避免这种情况。划分完成后检查一下各个集合的样本数量print(X_train.shape, X_test.shape) # 期望输出类似 (120, 4) (30, 4)4.2 为什么要做特征标准化接下来是我认为初学者最容易忽略的一步特征标准化。回头看 3.1 节的描述统计花萼宽度的范围是 2.0 到 4.4花瓣长度的范围是 1.0 到 6.9。虽然在这个数据集里数值差异不算巨大但当我们使用对距离敏感的算法时比如 K 近邻、SVM这种范围差异就会产生显著影响。举一个具体的例子K 近邻算法的核心是计算两个样本之间的距离。想象两个人分别测量了一片花瓣一个人量的是厘米单位另一个量的是毫米单位。同一个特征量纲差 10 倍计算出来的距离就会被毫米那个特征完全主导。这就是为什么需要标准化——让每个特征对距离函数做出同权重的贡献。标准化的常见做法是 Z-score 标准化公式是z (x - mean) / std也就是把每个特征的值减去它的均值再除以标准差。变换之后每个特征的均值变成 0标准差变成 1。scikit-learn 里用StandardScaler完成这一步from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)这里有一个极其关键、极其容易出错的细节fit_transform只用于训练集测试集只用transform。为什么因为标准化时用到的均值和标准差是从训练集里算出来的。如果我们对整个数据集做标准化然后才划分训练集和测试集那么训练时就已经“偷看”了测试集的信息这会导致对模型泛化能力的估计偏乐观。正确顺序永远是先划分再对训练集 fit 标准化参数再对测试集应用同一套参数。这个坑我见过很多次包括一些资深开发者。一旦顺序错了后面的评估结果就没有说服力了。4.3 模型选择的思考先简单再复杂在 Irisk 数据集上可以用的分类模型非常多K 近邻KNN、逻辑回归、决策树、支持向量机、朴素贝叶斯等等。我建议初学者第一个尝试的模型是K 近邻K-Nearest Neighbors。为什么选它不是因为效果最好而是因为它是最直观、最容易解释的算法。它的核心逻辑就一句话给你一个新样本在训练集里找出与它最相似的 K 个样本这 K 个样本属于哪个类别的数量最多新样本就归为哪个类别。这种“少数服从多数”的思路完全没有黑箱感。在建立模型直觉的阶段用这种直观的算法做初次尝试能够真正理解“训练”和“预测”这两个词的含义而不是一味地把数据塞给一个深度网络然后默默祈祷。5. 核心模型训练与实操代码详解5.1 K 近邻模型三行代码跑通全流程数据准备到位之后模型的训练和预测代码出乎意料地短from sklearn.neighbors import KNeighborsClassifier knn KNeighborsClassifier(n_neighbors3) knn.fit(X_train_scaled, y_train) y_pred knn.predict(X_test_scaled)第一行创建模型实例n_neighbors3表示 K 值取 3意思是我们每次找 3 个最近的邻居参与投票。第二行是训练过程。在 KNN 中所谓的“训练”本质上就是把特征矩阵和标签保存在内部结构里并没有像神经网络那样做梯度下降、更新权重。这种方式虽然训练耗时少但代价是预测阶段要计算新样本与所有训练样本的距离数据量大时推理速度会变慢。第三行用训练好的模型对测试集做预测输出结果y_pred是一个长度 30 的数组每个元素是预测的类别编号。接着算准确率from sklearn.metrics import accuracy_score accuracy accuracy_score(y_test, y_pred) print(fKNN(K3) Accuracy: {accuracy:.4f})我第一次跑这个代码准确率是0.9667也就是 30 个测试样本里猜错了 1 个。对一个新手来说这个结果已经相当鼓舞人心了。但我不建议你停在这里因为一个数字说明不了全部问题。我们来看预测错误的那个样本具体是什么import numpy as np diff_indices np.where(y_test ! y_pred)[0] for idx in diff_indices: print(idx, True:, iris.target_names[y_test[idx]], Pred:, iris.target_names[y_pred[idx]])运行结果说明被误判的样本真实类别是 Virginica被预测成了 Versicolor。这个细节很值得品味回忆 3.2 节的箱线图Versicolor 和 Virginica 在花瓣特征上有明显的重叠区域边界本身模糊所以误判发生在它们之间完全合理。如果错误发生在 Setosa 身上那就要怀疑代码哪里写错了。5.2 参数调优用交叉验证寻找最佳 K 值K 值选 3 是我拍脑袋决定的。有没有更科学的方式有常用的方法是交叉验证。核心思路在训练集内部再分出一小部分作为验证集用不同 K 值分别训练和评估选效果最好的 K。为了避免一次划分带来的偶然性我们循环多次每次用不同的小块数据做验证最终平均结果作为该 K 值的可靠得分。但是手动写交叉验证循环容易出错scikit-learn 又给我们封装好了现成工具from sklearn.model_selection import cross_val_score, KFold k_values [1, 3, 5, 7, 9, 11, 13] cv_scores [] for k in k_values: model KNeighborsClassifier(n_neighborsk) cv KFold(n_splits5, shuffleTrue, random_state42) scores cross_val_score(model, X_train_scaled, y_train, cvcv, scoringaccuracy) cv_scores.append(scores.mean()) for k, score in zip(k_values, cv_scores): print(fK{k}, Cross-val accuracy: {score:.4f})我在自己项目中跑的结果大致是这样K 值交叉验证平均准确率10.950030.958350.975070.966790.9667110.9583130.9500K5 时交叉验证分数最高虽然 K3 和它的差距只有不到 2%但既然有了数据支撑选 K5 就比拍脑袋选 3 更有说服力。这里顺便解释一个现象K 值太小容易过拟合K 值太大容易欠拟合。K1 时模型只参考最近的一个邻居决策边界非常敏感一个噪声点就能改变分类结果。K 越大决策越平滑但太大时会忽略局部结构。Iris 数据集类别分布简单K 值从 1 到 13 准确率差异不大这本身就是数据集的特性并不代表所有项目都这样。5.3 在测试集上完成最终评估确定了 K5 之后用初始化时的测试集做最终评估best_knn KNeighborsClassifier(n_neighbors5) best_knn.fit(X_train_scaled, y_train) y_pred_final best_knn.predict(X_test_scaled) final_accuracy accuracy_score(y_test, y_pred_final) print(fFinal test accuracy: {final_accuracy:.4f})我跑出来是1.0也就是测试集上 30 个样本全部预测正确。坦白说对这个结果要保持冷静。这个数字看起来非常漂亮但要知道它建立在以下条件之上数据量只有 150 条测试集只有 30 条数据集本身干净且几乎线性可分训练集和测试集来自同一个数据分布真实项目里测试集上满分几乎可以断定有问题大概率是数据泄露或者评估流程有误。但在 Iris 这个教学数据集上满分确实可能出现不需要过度惊讶。5.4 换一个模型对比决策树初体验为了让你对“不同模型在同一数据集上的表现差异”有直观感受我们再快速尝试一个决策树模型from sklearn.tree import DecisionTreeClassifier dt DecisionTreeClassifier(max_depth3, random_state42) dt.fit(X_train_scaled, y_train) y_pred_dt dt.predict(X_test_scaled) print(Decision Tree accuracy:, accuracy_score(y_test, y_pred_dt))决策树在我的运行环境里也能达到约0.9667。这个对比说明了什么第一Iris 数据集的分类任务简单多个模型都能有不错表现第二在这个数据集上堆砌复杂模型意义不大更应该关注特征工程和数据质量。实际上决策树有一个 KNN 没有的天然优势——可解释性。你可以把训练好的决策树导出成规则结构直观看到每次分裂用到了哪个特征、阈值为多少。这是很多业务场景比如银行贷款审批愿意用决策树而不用神经网络的重要原因。6. 模型评估进阶混淆矩阵与多分类细节准确率只有一个数字它掩盖了模型在不同类别上的差异。在多分类任务里更细致的评估工具是混淆矩阵。6.1 混淆矩阵解读from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay cm confusion_matrix(y_test, y_pred_final) print(cm) disp ConfusionMatrixDisplay(confusion_matrixcm, display_labelsiris.target_names) disp.plot() plt.savefig(confusion_matrix.png, dpi100)矩阵的行代表真实类别列代表预测类别对角线的数字就是分类正确的样本数量。运行结果大概是[[10 0 0] [ 0 9 0] [ 0 0 11]]对角线之外全部是 0说明当前模型在测试集上没有任何混淆三类全部判断正确。但举个例子如果输出长这样[[10 0 0] [ 0 8 1] [ 0 0 11]]意思是有一个真实类别为 Versicolor 的样本被预测成了 Virginica。这个时候单纯看准确率只会告诉你“错了 3.3%”但不会告诉你错误发生在哪个类别之间。而了解这一点很重要——在医学诊断场景里把良性误判为恶性和把恶性误判为良性后果完全不是一个量级。6.2 精确率、召回率与 F1 分数除了准确率多分类任务还常用三个指标精确率预测为该类别的样本中预测正确的比例召回率实际为该类别的样本中被正确找出来的比例F1 分数精确率和召回率的调和平均综合反映两者表现在 scikit-learn 中一行代码输出全部指标from sklearn.metrics import classification_report print(classification_report(y_test, y_pred_final, target_namesiris.target_names))输出是一个表格每个类别一行给出精确率、召回率、F1 分数。如果某类别的召回率明显低于精确率说明模型存在漏判需要重点排查该类别的特征重叠问题。对新手来说初学阶段领会准确率和混淆矩阵就足够了但知道精确率和召回率的存在很重要。因为真实业务里90% 的准确率听起来不错但如果是癌症筛查场景漏诊率 10% 是不可接受的。指标的选择由业务场景决定而不是哪个数字看起来更大。6.3 按数据分布重新审视测试集回到初始划分时设置的stratifyy。这一步给最终评估结果注入了极强的可信度——测试集里三类样本分布均匀不偏不倚。如果省略分层划分完全随机那么确实有可能产生一个只包含两类样本的测试集评估结果就缺乏参考价值。这一点我专门拿出来强调是因为在实际工作中我见过不止一个案例训练集和测试集分布不一致导致模型在测试集上表现极差但当时第一反应是模型问题花了几天时间去调参最后才发现是数据划分的锅。调试思路的顺序很重要先确认数据流程是正确的再调整模型参数否则就是南辕北辙。7. 训练集上的过拟合分析与风险认知7.1 观察训练集和测试集的准确率差距下面这步观察可能被很多人跳过但我认为它才是这个项目里最值得深思的一步。分别计算模型在训练集和测试集上的准确率然后对比train_acc best_knn.score(X_train_scaled, y_train) test_acc best_knn.score(X_test_scaled, y_test) print(fTrain accuracy: {train_acc:.4f}) print(fTest accuracy: {test_acc:.4f})如果训练集准确率很高比如 0.99但测试集准确率明显偏低比如 0.85那基本可以断定模型过拟合了——它记住了训练数据中的噪声和细节却没有学到本质规律。在 Iris 项目里由于测试集和训练集同分布、数据总量又小训练准确率和测试准确率通常非常接近不容易出现明显过拟合。但理解这个对比方式的价值在于当你换到真实数据集时它会成为你判断模型状态的第一把尺子。7.2 过拟合的本质能力与约束的平衡这里给一个生活化类比。让学生做过拟合有点像让小学生死记硬背一本练习册的答案。如果考试题目恰好全部来自练习册原题这个学生能拿满分但只要换一本新练习册或者题目稍微改个数字他立刻就不会了。模型“死记硬背”了训练数据中的每一个点却没掌握泛化规律就是过拟合。反过来如果模型简单到只能用一条直线来划分所有数据而实际情况是决策边界本身就是弯曲的那就是欠拟合训练集准确率也上不去。解决过拟合的方式有很多降低模型复杂度、增加训练数据、引入正则化等都是常见手段。作为入门项目你能做的最重要的事情是建立“过拟合是常态而非偶然”的意识。以后每训练一个模型先问自己一个问题模型在训练数据和测试数据之间的表现差距大不大8. 常见问题与排查技巧实录在做 Iris 项目的过程中几乎每个初学者都会遇到下面几个问题。我把它们在真实场景中的表现和排查思路整理出来形成一份速查表8.1 数据加载报错找不到load_iris报错信息通常像这样ModuleNotFoundError: No module named sklearn或者AttributeError: module sklearn has no attribute load_iris原因大概率是两个第一scikit-learn 根本没安装成功需要重新 pip install第二环境混淆你在 conda 的 A 环境里装了库但在 B 环境里执行代码B 环境没有安装。排查方法非常直接pip list | grep scikit-learn看看当前处于哪个环境输出的 scikit-learn 版本是否存在。如果是环境问题切换到正确的环境再执行就行。8.2 图片显示不了或者中文乱码matplotlib 画图时出现中文乱码或者是空白框这是非常常见的问题。Iris 项目里我尽量避免在图上直接写中文标签就是为了避开这个坑。如果你实在需要中文字体可以临时指定字体路径但这会增加环境的复杂程度对入门项目来说不划算。如果plt.show()在 Jupyter 里不出图第一检查是否忘了写%matplotlib inline第二检查是否单元格渲染异常。在 .py 脚本中遇到图闪退建议直接改成savefig保存到本地再查看。8.3 标准化之后模型分数反而下降有些同学可能会遇到诡异的情况做完StandardScaler标准化后KNN 的准确率居然比不做还低。这和我们的直觉相悖因为理论上标准化应该让基于距离的模型表现更好。出现这种情况最常见的原因是标准化之前和之后使用的测试集不一致。比如你做完标准化之后重新跑了一次train_test_split划分出的测试集和之前不同计算出的准确率自然就变了。标准化本身没有错错在流程控制。另一个原因是模型本身对特征范围不敏感。比如决策树模型它的分裂依据是特征阈值比较对特征的数值范围没有要求标准化对准确率的影响几乎为零。所以标准化带来的差异在不同算法上表现不同这一点需要理解。8.4 分类报告显示某类别的精确率为 0如果你在分类报告里看到一个类别的数值全是 0第一步不是怀疑算法而是回头想想训练集里到底有没有这个类别的样本。如果train_test_split时忘记设置stratifyy随机划分完全有可能让训练集缺了某一种花。模型从没见过 Virginica自然永远不可能预测出 Virginica测试集上该类别的精确率和召回率全部为 0。这个问题很有迷惑性。训练和预测代码看起来没问题但数据分布是残缺的导致模型结构性失效。检查的时候先数一下每个类别在两个集合中的样本数print(pd.Series(y_train).value_counts()) print(pd.Series(y_test).value_counts())做到这一步绝大多数数据划分问题都会暴露。8.5 测试集准确率波动很大换个 random_state 结果变来变去这说明两个问题数据集太小测试集样本量有限任何一点扰动都会引起准确率抖动模型稳定性不够当划分方式变化时预测出错的具体样本发生变化。解决方法是不要依赖单次随机划分的结果用交叉验证得到更稳定的平均精度。在真实项目里如果数据量足够也可以考虑多次随机划分并报告均值与方差。9. 代码整合与后续扩展建议9.1 完整代码梳理把上面所有关键步骤整理成一个可执行的 Python 脚本结构如下import pandas as pd import numpy as np from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split, cross_val_score, KFold from sklearn.preprocessing import StandardScaler from sklearn.neighbors import KNeighborsClassifier from sklearn.metrics import accuracy_score, confusion_matrix, classification_report # 1. Load data iris load_iris() X, y iris.data, iris.target # 2. Train-test split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) # 3. Standardization scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 4. Hyperparameter search best_score 0 best_k None for k in range(1, 20, 2): model KNeighborsClassifier(n_neighborsk) cv KFold(n_splits5, shuffleTrue, random_state42) scores cross_val_score(model, X_train_scaled, y_train, cvcv) mean_score scores.mean() if mean_score best_score: best_score mean_score best_k k # 5. Train best model best_model KNeighborsClassifier(n_neighborsbest_k) best_model.fit(X_train_scaled, y_train) # 6. Evaluate y_pred best_model.predict(X_test_scaled) print(fBest K: {best_k}) print(Test accuracy:, accuracy_score(y_test, y_pred)) print(confusion_matrix(y_test, y_pred)) print(classification_report(y_test, y_pred, target_namesiris.target_names))这段代码已经具备一个最小可用机器学习项目的完整骨架加载数据、划分数据、预处理、模型选择、参数调优、评估。从这个骨架出发以后无论换成什么数据集需要改的只是数据加载部分和模型参数整体流程保持不变。9.2 动手扩展的三个方向做完第一个版本之后如果你还有余力我建议在三个方向上继续探索第一个方向是预测新样本。从现实场景里“捏造”一组测量数据比如花萼长度 5.1、花萼宽度 3.5、花瓣长度 1.4、花瓣宽度 0.2喂给模型看看预测结果。这个看似简单的练习会帮助你理解训练和推理的区别。第二个方向是换模型。把 KNN 换成逻辑回归、支持向量机、朴素贝叶斯比较各自效果。你会发现不同算法在面对同一份数据时的表现差异并且对不同算法的特点有更直观的感知。第三个方向是降维可视化。用 PCA 把 4 个特征压缩到 2 维把样本点画在散点图上可以看到原始数据在二维平面上的分布形态。这是理解“特征空间”这个概念最有效的方式。9.3 对初学者心态的几句实在话说点掏心窝的话。我第一次做这个项目的时候并没有完全理解标准化和交叉验证的意义只是照着别人的代码抄了一遍看着运行结果显示 97% 觉得挺开心。后来拿真实数据集练手发现模型在训练集上表现很好一到新数据上就差得离谱才回头重新研究数据划分和过拟合的问题。Iris 项目里的每一个步骤在后来的项目和工作中都像呼吸一样常见。如果你的时间有限把重心放在理解数据处理流程、训练测试划分和评估指标上而不是去背算法公式这是长期来看回报最高的事情。另外代码跑通之后一定要动手改参数。把 K 值从 5 改成 1看看准确率怎么变把test_size从 0.2 改成 0.5看看评估结果怎么波动。亲手制造过一两次“翻车”现场比顺利跑通十个教程都更能帮助你理解机器学习方法的内在逻辑。经验这东西没有捷径都是在一次次观察、排查、复盘中积累下来的。