资讯动态

KNN分类算法详解:基于鸢尾花数据集的Python实现与调参实战

发布时间:2026/10/14 11:22:40 来源:尧图企业网站定制
刚接触机器学习的时候我拿K-近邻分类算法KNN配鸢尾花数据集练手跑了十几个版本的代码也踩了不少坑。网上讲这个项目的教程很多但大多数只教怎么跑通不讲为什么这么写。这篇博文打算把这个经典项目彻底拆透从算法原理讲到Python实现再讲到我实际调参和排错的经验。如果你正在学机器学习、准备面试或者刚刚开始写分类算法这篇文章应该能帮你少走不少弯路。1. 项目概述KNN与鸢尾花数据集的组合1.1 KNN算法在解决什么问题KNN全称K-Nearest Neighbors翻译过来就是K个最近的邻居。它是监督学习里最朴素的分类算法之一核心思想一句话能说清判断一个新样本属于哪个类别看它周围最近的K个训练样本是什么类别少数服从多数。用生活里的例子类比一下。你搬进一个新小区想判断这片住户偏年轻还是偏年长最直接的做法就是看看周围几户邻居的年龄层次。如果跟你住得最近的5户里有4户是带娃的年轻家庭你大概率会判断这个小区年轻人居多。KNN就是这个逻辑只不过把距离近量化成了数学上的距离公式。它的三个基本要素是K值的选择、距离度量的方式、分类决策规则。这三个要素直接决定了模型效果后面我会分别展开讲。1.2 为什么选鸢尾花数据集作为入门案例鸢尾花数据集Iris Dataset是机器学习里最经典的练手数据之一。它包含150条样本每条样本记录了一朵鸢尾花的4个特征花萼长度sepal length花萼宽度sepal width花瓣长度petal length花瓣宽度petal width目标是把样本分到3个类别中Setosa、Versicolour、Virginica每个类别恰好50条样本。选这个数据集有几个现实原因。数据足够干净没有缺失值没有明显异常值类别分布完全均匀省去了大量数据清洗的琐事可以把全部注意力放在理解KNN本身。特征有区分度花瓣长度和花瓣宽度这两个特征在不同类别间差异明显分类器天然就有不错的成功率。它还是一个三分类问题比二分类更有代表性能展示KNN在多类别场景下的表现方式。1.3 KNN在业务场景中的真实定位很多人觉得KNN简单就不太重视它。我在实际项目中用过KNN做相似用户召回和异常行为初筛效果并不差。它最适合的场合是小样本、低维度、特征含义明确的场景。当数据量上了百万级别、特征维度上百时KNN的预测速度会拖后腿这时我更倾向于换成树模型或线性模型。但作为理解分类算法原理的参照物KNN的价值无可替代。2. 基于scikit-learn的KNN快速实现2.1 环境准备与依赖安装动手之前先确认Python环境。我用的Python版本是3.10需要安装的基础库有numpy科学计算基础库几乎所有代码都离不开它pandas数据加载与处理matplotlib可视化展示可选scikit-learn机器学习框架提供KNN的现成实现安装命令用pip一行搞定pip install numpy pandas scikit-learn matplotlib如果在国内网络环境下安装速度慢可以加一个镜像源参数pip install numpy pandas scikit-learn matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple2.2 数据加载与初步探索scikit-learn内置了鸢尾花数据集不需要手动下载文件这也是选它做入门项目的一个好处。from sklearn.datasets import load_iris import pandas as pd iris load_iris() df pd.DataFrame(iris.data, columnsiris.feature_names) df[target] iris.target df[target_name] iris.target_names[iris.target] print(df.head()) print(df.info()) print(df.describe())加载完数据一定要先看一眼分布这一步不能省。我自己之前图省事直接建模后来模型效果不稳定回头排查才发现是对数据理解不到位。先跑一下df.info()看有没有缺失值再用df.describe()看特征的均值、方差、最大值最小值心里有数了再进入下一步。2.3 划分训练集与测试集为什么不能跳过机器学习模型的评估必须建立在模型从没见过的数据上否则无法反映真实泛化能力。所以原始数据要划分成训练集和测试集。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.3, random_state42, stratifyy )三个关键参数值得展开说test_size0.3表示30%的数据作为测试集70%作为训练集。这是最常见的选择如果样本总量较小可以提高到0.4否则测试集样本太少评估结果波动大。random_state42固定随机种子保证每次运行都得到完全相同的划分结果。这样做的好处是实验结果可复现也方便不同人之间讨论对比。42这个数字是个程序员梗你可以用任意整数。stratifyy做分层采样。原始数据三种花各50条如果不分层随机划分很可能让某一类在训练集里偏多、另一类偏少模型学不到均匀的类别分布直接影响效果。加上这个参数之后训练集和测试集中各类别比例与原始数据一致。2.4 特征标准化KNN最关键的预处理步骤KNN的核心是距离计算而距离对特征量纲极其敏感。举个简单的例子一个特征是年龄范围在0到100另一个特征是月收入范围在0到3万。如果直接用原始值计算欧氏距离收入差异几乎完全主导了距离值年龄特征即使有区分度也被淹没了。鸢尾花的四个特征量纲都在厘米级别差异没有年龄和收入那么夸张但标准化仍然有必要。做标准化的方式是Z-Score让每个特征变为均值为0、标准差为1的分布。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_transform相当于把测试集的信息也教给了模型评估结果会虚高这在机器学习里叫数据泄漏data leakage。2.5 训练KNN模型并评估效果训练代码本身很简单from sklearn.neighbors import KNeighborsClassifier knn KNeighborsClassifier(n_neighbors5) knn.fit(X_train_scaled, y_train) y_pred knn.predict(X_test_scaled)评估用准确率accuracy作为主要指标配合分类报告查看每一类的表现。from sklearn.metrics import accuracy_score, classification_report accuracy accuracy_score(y_test, y_pred) print(f准确率: {accuracy:.4f}) print(classification_report(y_test, y_pred, target_namesiris.target_names))在random_state42的划分下K5的准确率通常能达到0.93到0.98之间。鸢尾花数据集的特征区分度好标准化之后基本所有类别都能正确识别。错误主要集中在Versicolour和Virginica之间这两类花的特征本身有重叠分类器偶尔会混淆这在实际业务数据里非常常见类间边界模糊时任何分类器都会出错。3. 从零手写KNN理解每一行代码背后的逻辑3.1 为什么不能只停留在调包层面用scikit-learn十几行代码就能跑完整个项目但如果你只停留在这一步KNN对你来说仍然是一个黑盒子。面试时被问KNN的原理是什么你只能回答sklearn里有现成的KNeighborsClassifier这个答案显然不够。我观察到一个规律能不看文档从零写出KNN的人后来学其他算法时上手速度明显更快。因为手写一遍逼你搞清楚三件事——距离怎么算、邻居怎么找、投票怎么投。这三个问题恰好就是KNN算法的全部核心。3.2 一个完整的numpy手写实现不依赖sklearn用numpy实现KNN不到30行代码import numpy as np from collections import Counter class KNN: def __init__(self, k5): self.k k def fit(self, X_train, y_train): # KNN的训练就是单纯记住训练数据 self.X_train X_train self.y_train y_train def predict(self, X_test): # 对每个测试样本分别计算 predictions [self._predict_one(x) for x in X_test] return np.array(predictions) def _predict_one(self, x): # 步骤1计算x与所有训练样本的欧氏距离 distances np.sqrt(np.sum((self.X_train - x) ** 2, axis1)) # 步骤2按距离升序排列取前k个索引 k_indices np.argsort(distances)[:self.k] # 步骤3取这k个索引对应的标签 k_labels self.y_train[k_indices] # 步骤4多数投票 most_common Counter(k_labels).most_common(1) return most_common[0][0]这段代码的几个关键点拆开讲第一距离计算。self.X_train - x利用了numpy的广播机制测试样本与所有训练样本逐一相减得到一个二维矩阵每行是x与某个训练样本的差值。**2做逐元素平方sum(axis1)按行求和再开根号就得到了x到所有训练样本的欧氏距离。第二邻居选择。np.argsort(distances)返回按距离从小到大排序后的索引位置[:self.k]取出前K个。这一行同时完成了排序和截取两个操作是这段代码里最值得品的地方。第三多数投票。Counter(k_labels).most_common(1)统计K个邻居中各标签的出现次数返回出现次数最多的标签及次数。[0][0]取出标签本身。3.3 手写版本与sklearn版本的对比验证为了确认手写逻辑没有问题我把两个版本在同一份数据上做了对比my_knn KNN(k5) my_knn.fit(X_train_scaled, y_train) my_pred my_knn.predict(X_test_scaled) print(f手写KNN准确率: {accuracy_score(y_test, my_pred):.4f})在相同的数据划分和标准化条件下手写版本的准确率与sklearn版本几乎一致都在0.93到0.98之间。原因很简单两者做的就是同一件事sklearn只是在底层用了更高效的算法和向量化加速逻辑完全相同。这个验证很有价值。理解了手写版本之后再回头看sklearn里的KNeighborsClassifier你就能想象出fit和predict在内部做了什么用起来心里有底出了错也知道往哪里排查。4. 关键参数实验与调优思路4.1 K值对模型准确率的影响K是KNN最核心的超参数。我做了个实验遍历K从1到30记录每个K在测试集上的准确率import matplotlib.pyplot as plt k_range range(1, 31) accuracies [] for k in k_range: knn KNeighborsClassifier(n_neighborsk) knn.fit(X_train_scaled, y_train) acc accuracy_score(y_test, knn.predict(X_test_scaled)) accuracies.append(acc) plt.plot(k_range, accuracies) plt.xlabel(K值) plt.ylabel(测试集准确率) plt.title(K值对准确率的影响) plt.show()实验结果有一定规律性K1时准确率已经不低因为鸢尾花数据本身好分类随着K增大准确率小幅波动K变得非常大接近30时参考了几乎全部训练样本边界样本容易被拉偏准确率开始下降。理论上K太小容易过拟合一个噪声点就能彻底改变分类结果K太大容易欠拟合远处无关的样本也参与投票模糊了局部特征。实践经验上可以先取K5作为基准再通过交叉验证搜索K的最优值。用GridSearchCV可以自动搜索from sklearn.model_selection import GridSearchCV param_grid {n_neighbors: range(1, 31)} grid GridSearchCV( KNeighborsClassifier(), param_grid, cv5, scoringaccuracy ) grid.fit(X_train_scaled, y_train) print(f最优K值: {grid.best_params_[n_neighbors]}) print(f最优准确率: {grid.best_score_:.4f})cv5表示5折交叉验证把训练集分成5份每次用4份训练、1份验证轮转5次取平均准确率作为评价结果。这种方式比单次划分测试集更稳定不容易被某一次运气好的划分结果误导。4.2 标准化前后结果对比我做过一组对照实验同一份数据、同一套划分只是把StandardScaler那段处理去掉直接在原始特征上跑KNN。结果准确率明显下降而且K值变化造成的波动更大。原因就是距离度量对量纲敏感。虽然鸢尾花四个特征单位都是厘米但花萼长度分布在4.3到7.9之间花瓣宽度只有0.1到2.5范围相差不小。量纲大的特征在距离计算中占主导量纲小的特征几乎没有话语权模型学习到的规律被扭曲了。标准化之后还有另一个好处数据可视化时聚类更紧凑类别边界更清晰对新手理解分类原理非常有帮助。4.3 距离度量方式的选择scikit-learn的KNeighborsClassifier默认使用闵可夫斯基距离通过p参数控制阶数。p2时就是欧氏距离p1时是曼哈顿距离。在鸢尾花数据集上我对比过欧氏距离和曼哈顿距离结果差异很小。这个数据集的各特征比较符合各维度独立的假设欧氏距离完全够用。但在真实业务数据上如果特征含义完全不同、量纲差异巨大曼哈顿距离有时更稳健因为它对个别维度的极端值不那么敏感。没有绝对最优的距离度量要根据数据特征和业务场景实验确定。另外可以设置weightsdistance让距离更近的邻居有更高的投票权重。这个改动在类别分布不均衡时非常有效比单纯调K值性价比高得多代码只有一行knn_weighted KNeighborsClassifier(n_neighbors5, weightsdistance)5. 常见问题与排查技巧5.1 预测结果与直觉不符先查数据泄漏最容易被忽视的问题是数据泄漏。典型错误是在划分训练集和测试集之前就做了标准化或者对全量数据做了某个处理之后再划分训练集和测试集都有了全局视野测试集不再纯净评估结果虚高。排查方法是检查处理流程的顺序。正确的顺序永远是先划分数据集再在训练集上fit标准化参数然后用同一套参数transform训练集和测试集接着训练模型最后在测试集上评估。5.2 预测速度太慢怎么办KNN是惰性学习训练阶段几乎零开销但预测阶段必须计算每个测试样本与所有训练样本的距离。训练集有10万个样本、测试集有1万个就要计算1亿次距离运算这个开销在真实业务中不可忽略。三个可行的优化方向降维。先用PCA把高维特征压缩到几十维距离计算量显著减少使用KD Tree或Ball Tree加速邻居搜索。KNeighborsClassifier的algorithm参数可以设为kd_tree或ball_tree在低维数据上速度提升明显对大规模预测做分批处理避免一次性载入全部距离矩阵5.3 类别不平衡如何处理如果某一类样本远多于其他类别KNN在多数投票时天然偏向人多的类别。我遇到过二分类任务中80%是正类的情况K值稍微取大一点负类样本即使离新样本更近也会被正类的数量优势压过。可行的处理思路调小K值让它小于少数类样本量用距离加权投票weightsdistance让靠近的邻居更被重视对少数类做SMOTE过采样调整评价指标不要只看准确率改用F1-score、Precision、Recall5.4 特征太多导致维度灾难特征数量大到一定程度时KNN的效果会急剧恶化这就是维度灾难。高维空间里样本之间的距离趋同远近的概念模糊化距离度量丧失了区分能力。解决思路不是加数据或硬调参而是先做特征选择或降维。用PCA把特征压缩回几十维或根据业务背景剔除明显无关的特征效果通常立竿见影。鸢尾花数据集只有4个特征离维度灾难很远但提前理解这个坑换到真实数据集时能少踩雷。5.5 一套实用的排查流程我在实际项目里遇到问题时通常按下面这个顺序排查可以过滤掉绝大多数低级错误打印训练集和测试集的类别分布确认分层采样是否符合预期可视化特征分布检查是否缺失值或极端离群点确认标准化只作用于特征没有误处理标签列用交叉验证替代单次测试集评估检查准确率是否稳定对比手写KNN和sklearn版本的结果定位实现逻辑是否有偏差这套流程简单但有效。很多莫名其妙的模型问题最后都出自前3步。6. 项目的扩展方向与个人实操体会6.1 从鸢尾花项目出发的三种进阶玩法项目跑通之后可以沿着几个方向继续深挖第一种横向对比。用同一份鸢尾花数据跑一遍决策树、逻辑回归、支持向量机对比不同算法的分类边界和准确率。这个过程能带你理解同一问题能被多种算法解决的本质也能直观感受不同算法的适用条件。第二种完整调优流程。用GridSearchCV搜索K值、距离权重、距离度量等多个参数的最优组合记录每一步的准确率变化沉淀出自己的调参经验。初学者容易忽略的一点是调参不是让测试集准确率最高而是让模型在未知数据上表现稳定。第三种特征工程实验。在原始特征基础上构造组合特征比如花瓣面积、花萼与花瓣的比值然后观察模型效果变化。鸢尾花数据上效果提升可能有限但这个过程能帮你看懂特征工程如何影响模型决策。6.2 我在实际应用KNN时的几点心得最后分享一些个人在实际踩坑中的体会。第一K值的选择要结合数据规模。经验上看K值与训练样本总数保持一定比例。一个常用参考是K不超过训练集样本数的平方根在鸢尾花这个150条样本的数据集上K不超过12左右比较合理。但这不是硬性公式要结合交叉验证确认。第二标准化这件事在KNN里从来不是可选项。只要距离参与决策标准化的价值就存在。我在真实项目中对用户行为特征做KNN召回时标准化前后的效果差距非常大主要体现在少数类样本能否被正确召回上。第三不要迷信准确率一个指标。在类别不平衡或错误代价不等的场景下准确率会骗人。比如欺诈检测中把好用户误判为欺诈和把欺诈用户放过代价完全不同。学会看混淆矩阵和precision-recall曲线比纠结0.1个百分点的准确率有意义得多。第四KNN做基线模型非常合适。我在新项目里拿到数据后第一个跑的模型通常是KNN。它没有太多假设能快速给你一个效果上限参考。如果KNN效果都很差那说明数据本身的问题比算法选择更大值得先回去处理数据。这个项目最好的学习方式是先老老实实跑通一遍sklearn版本再手写一遍核心逻辑最后用交叉验证做一遍参数实验。三个步骤走完你对KNN的理解会超过大多数只会调包的人。等后续学到更多算法你会发现KNN是你判断其他算法优劣的参照系打牢这个基础比多会一个模型更有长期价值。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑