☰
基于iris数据集的KNN分类实战:k折交叉验证与测试集预测全流程
2026/10/6 8:11:30 网站建设 项目流程

简介:这份资源围绕鸢尾花数据集的KNN分类实验展开,面向正在学习机器学习基础、需要完成课程作业或入门实战的读者。包内共1个文件,为Python脚本,压缩包约5KB,轻量易读,可直接运行复现完整流程。内容从获取鸢尾花数据并用箱式图观察样本分布入手,先对数据做两种方式的特征预处理,再按80%与20%随机划分训练集和测试集;随后以5折交叉验证在K=3至9之间筛选近邻数,以总体预测错误率为指标绘制K值与错误率的关系曲线,帮助理解超参数选择过程。最终基于测试集评估模型性能,由预测结果生成混淆矩阵,并据此计算各类别的查准率、查全率、F1分值以及宏平均指标。已有328人学习,适合希望掌握KNN调参、交叉验证与分类评价体系的初学者对照练习。

1. 从 iris_KNN.rar 说起:一份经典数据集上的 KNN 工程化落地

iris 数据集几乎是每个做机器学习的人绕不开的起点,150 条样本、4 个特征、3 个类别,干净得像教科书。但真正把它当成一个工程问题来做,事情就没那么简单了。标题里的iris_KNN.rar指向的是一套完整的 KNN 分类实践:用 iris 数据训练 KNN 模型,做 k fold 交叉验证,最后在测试集/预测集上评估效果。很多人第一次跑 KNN 就是在 iris 上,准确率轻松到 95% 以上,于是觉得"这算法没什么可调的"。但如果你要把它做成一个可复现、可交付的方案,k 怎么选、fold 怎么分、测试集和预测集怎么切、标准化放在哪一步,每一个环节都能让结果差出好几个百分点。这篇笔记就是把这套流程从"能跑"推到"能交付",适合刚接触 KNN 想系统落地的人,也适合做过但结果不稳定、想搞清楚边界条件的从业者。

2. KNN 在 iris 上的选型逻辑与最小可跑通流程

2.1 为什么 iris 上 KNN 依然值得认真做一遍

KNN 的核心假设是"相似的样本有相似的标签",它不做显式训练,把全部计算压到预测阶段。这个特性决定了它在 iris 这种低维、小样本、类别边界较清晰的数据上表现天然不错。但"表现不错"和"工程可用"是两回事。iris 只有 150 条数据,如果随机切一次训练/测试集,切到难分的边界样本,准确率可能从 97% 掉到 90%,这种波动在真实项目里就是不可接受的。所以标题里强调 k fold 和测试集/预测集的区分,本质上是在解决评估的稳定性问题。

另一个容易被忽略的点是特征尺度。iris 的四个特征——花萼长度、花萼宽度、花瓣长度、花瓣宽度——量纲虽然都是厘米,但数值范围不同,花瓣长度的方差明显大于花萼宽度。KNN 用欧氏距离度量相似度,如果不做标准化,方差大的特征会主导距离计算,等于人为给花瓣长度加了权重。常见做法是在交叉验证的每一折内部做标准化,而不是在全量数据上先标准化再切分,后者会造成数据泄漏。

2.2 用 sklearn 跑通 KNN + k fold 的最小代码

下面这段代码是我一般会用来做基线验证的版本,结构清晰,每一步都有明确目的。

import numpy as np from sklearn.datasets import load_iris from sklearn.model_selection import StratifiedKFold, cross_val_score, train_test_split from sklearn.preprocessing import StandardScaler from sklearn.neighbors import KNeighborsClassifier from sklearn.pipeline import Pipeline # 加载 iris 数据 iris = load_iris() X, y = iris.data, iris.target # 用 Pipeline 把标准化和 KNN 串起来,避免数据泄漏 pipe = Pipeline([ ('scaler', StandardScaler()), ('knn', KNeighborsClassifier(n_neighbors=5, metric='minkowski', p=2)) ]) # 分层 k fold,保证每折类别比例一致 skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) scores = cross_val_score(pipe, X, y, cv=skf, scoring='accuracy') print(f"5折交叉验证准确率: {scores.mean():.4f} ± {scores.std():.4f}") # 留出独立测试集,模拟真实预测场景 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) pipe.fit(X_train, y_train) test_acc = pipe.score(X_test, y_test) print(f"独立测试集准确率: {test_acc:.4f}")

这段代码的关键设计有三处。第一,Pipeline把StandardScaler和KNeighborsClassifier封装在一起,交叉验证时标准化只在训练折上 fit,验证折只做 transform,杜绝了泄漏。第二,StratifiedKFold而不是普通KFold,因为 iris 三类各 50 条,普通随机切分可能出现某折里某类样本极少的情况,分层能保证每折类别分布一致。第三,random_state=42固定随机种子,保证结果可复现,这在团队协作里是基本要求。

参数方面,n_neighbors=5是默认值,也是 iris 上比较稳的起点;metric='minkowski'配合p=2就是欧氏距离,如果想试曼哈顿距离把p改成 1 即可。test_size=0.2意味着 30 条测试样本,对于 iris 来说已经够用,但如果要做更严格的评估,可以把这个比例提到 0.3。

2.3 k fold 的折数怎么定、测试集和预测集到底怎么分

k fold 的折数选择有个经验区间:5 到 10 折。折数太少,每折验证集样本多但训练集少,偏差大;折数太多,训练集接近全量,评估结果偏乐观,而且计算开销线性增长。iris 只有 150 条,我一般用 5 折,每折验证集 30 条,训练集 120 条,平衡得比较好。如果数据量到几千条以上,10 折更常见。

测试集和预测集的区分是另一个容易混淆的点。测试集是在模型开发阶段用来评估泛化能力的,你有标签,可以算准确率;预测集是模型上线后真正要预测的新数据,通常没有标签。在 iris 这个场景里,我们做的其实是"留出法":从 150 条里切出 20% 作为测试集,剩下 80% 做交叉验证选参数,最后用选好的参数在测试集上跑一次,得到最终评估。这个测试集只能看一次,反复调参再去看它,它就变成了验证集,评估结果会失真。

提示:如果要做超参数搜索,用GridSearchCV嵌套在训练集内部做,不要碰测试集。测试集是最后的"后悔药",看多了就不准了。

3. 把 KNN 调稳:参数搜索、距离度量与特征工程

3.1 n_neighbors 和 weights 的联合调参

n_neighbors是 KNN 最核心的参数。k 太小,模型对噪声敏感,决策边界碎片化;k 太大,邻域覆盖过广,边界模糊,欠拟合。在 iris 上,k 从 1 到 20 扫一遍,通常能看到准确率在 k=3 到 k=9 之间比较平稳,k=1 时因为过拟合会略低,k 超过 15 后开始下降。

weights参数控制邻居投票的权重方式,默认是uniform,即所有邻居等权;改成distance后,距离越近的邻居权重越大,公式是距离倒数加权。在类别边界附近,distance往往能带来一点提升,因为它让近邻的话语权更大。下面这段代码把两个参数一起搜。

from sklearn.model_selection import GridSearchCV param_grid = { 'knn__n_neighbors': list(range(1, 21)), 'knn__weights': ['uniform', 'distance'], 'knn__p': [1, 2] # 1=曼哈顿距离, 2=欧氏距离 } grid = GridSearchCV( pipe, param_grid, cv=StratifiedKFold(n_splits=5, shuffle=True, random_state=42), scoring='accuracy', n_jobs=-1 ) grid.fit(X_train, y_train) print(f"最优参数: {grid.best_params_}") print(f"最优交叉验证得分: {grid.best_score_:.4f}")

注意参数名带knn__前缀,因为它们在 Pipeline 里属于knn这一步。n_jobs=-1用满所有 CPU 核并行搜索。搜索空间是 20×2×2=80 种组合,每种跑 5 折,总共 400 次拟合,iris 数据量小,几秒就能跑完。

3.2 距离度量对 iris 分类边界的影响

KNN 默认用闵可夫斯基距离,p=2 时是欧氏距离,p=1 时是曼哈顿距离。欧氏距离对特征尺度敏感,曼哈顿距离在高维下更稳健,但 iris 只有 4 维,两者差异不大。真正值得注意的是:标准化之后,欧氏距离和曼哈顿距离在 iris 上的准确率通常只差 0.5 到 1 个百分点,但如果没做标准化,曼哈顿距离有时反而更稳,因为它不会像欧氏距离那样把大数值特征的差异平方放大。

还有一个不常用但值得知道的选项是metric='cosine',余弦距离关注方向而非绝对距离。iris 的特征都是正数,余弦距离的效果一般不如欧氏距离,但如果你把特征做了中心化,余弦距离可以一试。我的建议是:iris 上优先用标准化 + 欧氏距离,这是最稳的组合,其他度量作为对比实验即可。

3.3 特征缩放放在交叉验证里面的原因

前面反复强调标准化要放进 Pipeline,这里展开说清楚为什么。假设你先对全量 150 条数据做标准化,均值和标准差是从全部数据算出来的,然后切分训练集和验证集。此时验证集的标准化用到了训练集的统计信息,虽然看起来只是均值和标准差,但这已经构成了信息泄漏。在 iris 上,这种泄漏带来的准确率虚高可能只有 0.3 到 0.5 个百分点,不容易察觉,但在特征量纲差异大或数据量更小的场景里,泄漏可以让评估结果完全失真。

正确做法就是 Pipeline:交叉验证的每一折,fit只在训练折上调用,transform同时作用于训练折和验证折。这样验证折的标准化参数完全来自训练折,模拟了真实预测时"用训练数据统计量处理新数据"的流程。这个习惯一旦养成,后面做任何需要预处理的模型都不会翻车。

4. 避坑与排查:iris KNN 实践中的五个血泪教训

4.1 准确率忽高忽低,换个子就变

现象:同样的代码,别人跑出来 97%,你跑出来 93%,或者你自己每次跑结果都不一样。

原因:没有固定random_state。train_test_split和StratifiedKFold默认都带随机性,不设种子每次切分不同,iris 数据量小,切分波动被放大。

解决:所有涉及随机的函数都显式传random_state,团队协作时把这个种子写进配置。另外评估时不要只看单次结果,看交叉验证的均值和标准差,标准差超过 2 个百分点就说明评估不稳定,需要增加折数或检查数据切分方式。

4.2 测试集准确率远低于交叉验证得分

现象:交叉验证 96%,测试集只有 88%。

原因:测试集太小,30 条里错 3 到 4 条,准确率就掉 10 个点。iris 的测试集波动本身就大,这不是模型问题,是评估样本量的问题。

解决:如果数据量允许,把测试集比例提到 0.3,或者用多次留出法取平均。更严谨的做法是报告混淆矩阵,看清楚错在哪一类。iris 里最容易混的是 versicolor 和 virginica,setosa 几乎不会错,所以整体准确率受这两个类的边界样本影响很大。

4.3 忘了标准化,花瓣长度主导了距离

现象:模型能跑,但准确率卡在 92% 上不去,调 k 也没用。

原因:没做特征缩放。花瓣长度数值范围约 1 到 7 厘米,花萼宽度约 2 到 4.5 厘米,欧氏距离里花瓣长度的贡献被放大,等于只用了部分特征。

解决:加StandardScaler,并且放进 Pipeline。验证方法很简单:对比标准化前后的交叉验证得分,iris 上通常能差 2 到 4 个百分点。

4.4 用预测集调参,测试集变成"作弊集"

现象:反复在测试集上试不同 k,最后选了个测试集准确率最高的,上线后效果差很多。

原因:测试集被当成了验证集用,模型间接拟合了测试集。测试集看一次是评估,看十次就是调参。

解决:严格三分——训练集调参、验证集选模型、测试集只做最终评估。如果数据量不够三分,用交叉验证代替验证集,测试集留到最后。

4.5 k 取偶数导致投票平票

现象:某些样本预测结果不稳定,换一次运行就变。

原因:iris 是三分类,k 取偶数时理论上可能出现平票,虽然 sklearn 内部有处理机制(按距离加权或取较小类标签),但结果可能不符合预期。

解决:三分类场景下 k 优先取奇数,3、5、7、9 都是常见选择。二分类同理,k 取奇数能避免平票。这是个小细节,但在边界样本上确实会影响结果。

5. 从 iris 到真实数据:KNN 的边界与一个验证技巧

iris 上跑通 KNN 只是起点,真正要判断这套方案值不值得迁移到你的业务数据,得先看清 KNN 的边界。KNN 的计算复杂度在预测阶段是 O(n×d),n 是训练样本数,d 是特征维度。iris 只有 150 条,随便跑;但如果你的数据有几十万条、上百个特征,每次预测都要算一遍全量距离,延迟会高到不可接受。这时候要么用 KD-Tree、Ball-Tree 做近邻索引,要么换模型。sklearn 的KNeighborsClassifier在n_neighbors较大或数据维度较高时会自动选择树结构,但维度超过 20 左右,树结构的加速效果就急剧下降,这是维度灾难的直接体现。

另一个边界是类别不平衡。iris 三类各 50 条,天然平衡。真实数据里如果某类只占 1%,KNN 的多数投票会被大类主导,少数类几乎预测不出来。这时候要用weights='distance'或者对少数类过采样,但过采样在 KNN 里要小心,合成样本可能落在其他类邻域里,反而制造噪声。

最后分享一个我在 iris 上常用的验证技巧:用学习曲线看模型是否还有提升空间。sklearn.model_selection.learning_curve可以画出训练集大小与准确率的关系。如果训练准确率和交叉验证准确率都收敛在一个较低的值,说明模型欠拟合,KNN 的 k 可能太大或者特征不够;如果训练准确率远高于验证准确率,说明过拟合,k 太小。iris 上正常的学习曲线应该在 100 条训练样本左右就基本收敛到 95% 以上,如果收敛点明显偏低,先检查标准化和切分逻辑,而不是急着换模型。

from sklearn.model_selection import learning_curve import matplotlib.pyplot as plt train_sizes, train_scores, val_scores = learning_curve( pipe, X, y, cv=5, scoring='accuracy', train_sizes=np.linspace(0.2, 1.0, 8), random_state=42 ) plt.plot(train_sizes, train_scores.mean(axis=1), label='训练集') plt.plot(train_sizes, val_scores.mean(axis=1), label='交叉验证') plt.xlabel('训练样本数'); plt.ylabel('准确率'); plt.legend() plt.show()

这个图我每次接手一个新的分类任务都会先画一遍,比单看一个准确率数字信息量大得多。iris 是个安全的试验场,但别让它的高准确率给你虚假的信心。真实数据上的 KNN,参数、距离、缩放、索引,每一项都要重新验证。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询