资讯动态

KNN算法实战:从数据可视化到模型预测全流程解析

发布时间:2026/10/5 4:27:07 来源:尧图企业网站定制
最近在做一个机器学习方向的小项目时我把 KNN 算法完整走了一遍从原始数据读取、画图探索、特征分布分析到标准化、K 值调参、模型评估最后还接了一个基于 Flask ECharts 的可视化页面来展示预测结果。很多朋友问 KNN 到底怎么用于实际项目网上文章要么只讲理论要么贴一段代码就算完事。这篇就结合我自己的实操记录把整个“从数据可视化到模型预测”的全流程拆开讲清楚顺便把踩过的坑也一并交代了。项目本身不复杂用的是一个公开的红酒数据集任务是让模型根据 13 个理化指标判断一瓶酒属于三个品种中的哪一个。但你别小看这种“简单”项目里面涉及数据体检、特征缩放、交叉验证、模型解释等一整套标准动作。这篇文章适合刚入门机器学习、希望看懂一个完整项目链路的人也适合已经在业务里做数据分析和 POC 验证、想快速用 KNN 建立 baseline 的同学。不管你是做电商用户分群、农产品等级判定还是网约车需求预测这套思路都是可以复用的。1. 项目定位与整体思路拆解1.1 为什么用 KNN 作为完整项目首选先说为什么选 KNN。很多人在实际项目里一上来就上 XGBoost、神经网络说实话在小规模样本场景里KNN 反而是非常可靠的基线模型。它的核心思想就是“近朱者赤近墨者黑”判断一个新样本属于哪一类就看它在特征空间里距离最近的 K 个样本是哪些然后少数服从多数。KNN 最大的特点是惰性学习训练阶段基本不做事只是把数据存下来真正的计算发生在预测阶段。这意味着你不需要担心复杂的训练收敛问题也不会有过深的网络结构需要调试。对于一个小团队做需求验证或者你刚接触机器学习、想快速看到结果KNN 是最容易上手的选择。另外KNN 的决策过程非常透明你可以明确地说出“这个样本为什么被判为类别 A因为它最近的 5 个邻居里有 4 个是 A”。这种可解释性在某些业务场景里比黑盒模型更受欢迎。当然KNN 也不是万能的后文我会讲到它在高维大数据上的局限性。但在样本量几百到几千、特征维度几十以下的场景中它作为 baseline 是极其合格的。这次项目里我选的数据集一共 178 条样本、13 个特征、3 个类别正好落在 KNN 最舒适的工作区间。1.2 数据任务定义红酒品种识别项目使用的是 sklearn 自带的 wine 数据集很有名也是很多教材的标准案例。它采集了意大利某地区三种不同品种的红酒样本每种酒记录 13 个化学成分指标。你可以把这个任务理解为如果品酒师拿到了理化检测报告能不能不看口感、只凭数据判断这瓶酒属于哪个品种这 13 个特征分别是特征名含义alcohol酒精含量malic_acid苹果酸含量ash灰分ash_alcalinity灰分碱度magnesium镁含量total_phenols总酚含量flavanoids黄酮类化合物含量nonflavanoid_phenols非黄酮类酚含量proanthocyanins原花青素含量color_intensity颜色强度hue色调od280_od315OD280/OD315 稀释葡萄酒吸光度proline脯氨酸含量目标变量是三个类别分别对应三种品种我通常直接叫class_0、class_1、class_2。做分类任务前先看一下类别分布三个类别的样本量都接近 59 条左右非常均衡所以评估时可以直接用准确率作为主要指标不需要为不平衡样本做额外处理。在写模型之前我习惯先把整个项目流程在脑子里过一遍加载数据做数据体检看缺失值、重复值、数据类型和分布情况用可视化手段理解特征之间的差异性和相关性对特征做标准化处理让所有指标处于同一尺度划分训练集和测试集用交叉验证和网格搜索选 K 值和距离权重评估模型看混淆矩阵和分类报告对未知样本做预测并解释结果可选把数据和预测结果导出用 ECharts 做一个可视化展示页面。这个流程并不特定于红酒数据你可以直接把同一套代码迁移到自己的表格数据上。后文就按这个顺序依次展开。2. 数据可视化实战2.1 先做数据体检再谈模型很多人拿到数据直接就跑机器学习模型这是我不太推荐的。KNN 对数据分布和特征尺度极其敏感你连数据长什么样都不知道后面出了问题根本没法排查。我拿到数据后第一件事永远是做“体检”也就是快速看一眼整体结构。以下是我在项目里常用的几行代码import pandas as pd from sklearn.datasets import load_wine wine load_wine() df pd.DataFrame(wine.data, columnswine.feature_names) df[target] wine.target print(df.info()) print(df.describe().T) print(df[target].value_counts())df.info()看有没有空值、字段类型是否正确df.describe()看每个特征的取值范围。这一步极其重要因为你会立刻发现一个问题proline这个特征的数值范围从几百到几千而hue大概只有 0.5 到 1.8alcohol是 11 到 14。如果放任这种量纲差异直接算欧氏距离那么距离计算基本会被proline一个特征绑架其他特征全部沦为摆设。这也是 KNN 场景下必须做标准化的根本原因后文专门讲。我实测下来这个数据集的缺失值基本没有重复值也没发现算是比较干净的。但你在处理自己的业务数据时一定要处理缺失值和异常值否则单个离群点会影响一票邻居的判定。2.2 特征分布用箱线图和散点图理解类别差异体检完毕接下来就该画图了。数据可视化的核心目的不是“好看”而是帮你在建模前理解哪些特征对区分三个类别有用哪些特征几乎没信息量。我最常看的是箱线图因为不同类别在同一特征上的分布重叠程度直接决定了该特征的判别力。拿几个典型特征来说import matplotlib.pyplot as plt import seaborn as sns fig, axes plt.subplots(2, 2, figsize(12, 8)) features [alcohol, color_intensity, flavanoids, proline] for ax, feature in zip(axes.ravel(), features): sns.boxplot(xtarget, yfeature, datadf, axax) plt.tight_layout() plt.show()以alcohol为例如果三个类别的箱体上下边缘错开明显说明这个特征对分类很有价值。proline的取值跨度大但类别间也有不少重叠单独看它要小心因为它数值大且对距离影响大稍不注意就成了“主裁”。再看flavanoids这个特征在三类间的分离度通常比较好是很有判别力的指标。箱线图看单个特征散点图矩阵则能看特征两两组合下的类别分布。可以抽三到五个重要特征做 pairplot当场就能感受到哪些特征组合能把三类样本“撑开”成几团。这种直观感知对后面选择 K 值和判断模型上限非常有帮助。另外我还会画一张相关性热力图corr df.drop(columnstarget).corr() plt.figure(figsize(10, 8)) sns.heatmap(corr, annotTrue, cmapcoolwarm, fmt.2f) plt.show()相关性能帮你发现特征是否冗余。比如总酚和黄酮类化合物往往高度相关这本身不致命但你要知道它们在距离计算中等于同一个信息被放了双倍权重。KNN 不像线性模型那样对多重共线性有硬性限制但理解相关性会让你在解释模型时有更合理的底气。2.3 用 PCA 把高维数据降下来看13 个特征没法直接画二维散点图所以我最常用的一招是先做 PCA 降维把数据压缩到 2 维或 3 维然后画散点图看类别的分离情况。这个方法在业务汇报里也很好用业务方不懂什么是特征空间但一眼就能看出“三个品种能不能被分开”。from sklearn.decomposition import PCA pca PCA(n_components2) X_pca pca.fit_transform(wine.data) df_pca pd.DataFrame(X_pca, columns[PC1, PC2]) df_pca[target] wine.target plt.figure(figsize(8, 6)) sns.scatterplot(datadf_pca, xPC1, yPC2, huetarget, paletteSet2, s60) plt.xlabel(第一主成分) plt.ylabel(第二主成分) plt.title(PCA降维后的样本分布) plt.show()从我的实测结果来看PCA 降维后第一主成分和第二主成分加起来大约能保留一半以上的原始方差。散点图上三类样本大体分离但边界处有明显交叠区域这意味着 KNN 会有一定的误分类率但整体精度可期。你可以把这张图理解为“数据可分离性”的最直观证据如果 PCA 降维后三类完全重叠那后面再怎么调参也很难出好结果。我也试过用 t-SNE 画图效果通常比 PCA 更“散得开”但 t-SNE 对困惑度等参数比较敏感每次运行结果可能不一样。我会把这个当作探索性工具而不是建模前的标准动作。正规流程里先 PCA 保稳定性和可复现性要讲故事时再考虑 t-SNE。3. KNN 核心细节解析与实操要点3.1 特征缩放标准化不是可选项是必选项KNN 的所有决策都建立在“特征空间中的距离”上而距离对尺度极度敏感。我拿身高和体重举例身高单位用米时是 1.6 到 1.9体重用公斤是 50 到 90如果直接算欧氏距离两者还勉强处于同一数量级可如果身高单位换成厘米160 到 190那距离计算里体重的影响就几乎被抹掉了。特征缩放解决的就是这种“不同量纲把距离拉偏”的问题。实操中我优先使用StandardScaler让每个特征变成均值为 0、方差为 1 的标准正态分布。这一步 KNN 基本是必做的尤其当数据集中存在proline这种大范围特征时。from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_scaled scaler.fit_transform(wine.data)有一个关键细节很容易踩坑scaler 只能用训练集拟合再同时转换训练集和测试集。如果你先对整个数据集做标准化再切训练测试集那测试集的信息已经在训练阶段被用过了这叫数据泄漏。正确的做法是先把数据 split 好再让 scaler 在训练集上fit最后transform两边。3.2 K 值选择肘部法则与交叉验证K 值是 KNN 最核心的超参数。K 太小模型对噪声敏感决策边界扭曲容易过拟合K 太大模型变得过于平滑可能把距离很远的样本也拉进“邻居”范围导致欠拟合。实际项目里我一般会先跑一个“K 值扫描”脚本把 1 到 20 的 K 在训练集和测试集上的准确率都画出来用肘部法则找拐点。但在真正上线前我不会只盯着一次划分的结果而是用交叉验证做更稳健的选择。scikit-learn 里可以直接用GridSearchCVfrom sklearn.model_selection import GridSearchCV from sklearn.neighbors import KNeighborsClassifier param_grid { n_neighbors: range(1, 21), weights: [uniform, distance], p: [1, 2] } knn KNeighborsClassifier() grid GridSearchCV(knn, param_grid, cv5, scoringaccuracy, n_jobs-1) grid.fit(X_train, y_train) print(grid.best_params_) print(grid.best_score_)p1对应曼哈顿距离p2对应欧氏距离。我在这个数据集上做网格搜索结果通常落在K7到K15附近weightsdistance有时会比 uniform 好一点点但差距不大。你需要理解的是网格搜索不是“调出完美参数”而是帮你避免拍脑袋选 K。只要测试集分数和训练集分数没有出现大的剪刀差这个 K 值就是可用的。关于 K 的奇偶性三分类问题里建议选奇数减少平局概率。如果是二分类奇偶同样重要。当然平局问题不只能靠 K 解决后文会细说。3.3 距离度量、投票规则与算法复杂度KNN 默认用欧氏距离也就是多维空间中的直线距离。如果你有文本或高维稀疏特征余弦相似度会更合适曼哈顿距离对异常值没那么敏感在某些场景下也有效。scikit-learn 中可以通过metric参数做定制比如metricmanhattan。投票规则方面weightsuniform是等权投票weightsdistance则是按距离倒数加权距离近的邻居话语权更大。加权通常在样本分布不够均匀时能带来正收益我自己更喜欢从 uniform 开始因为更容易解释如果分数不理想再切到 distance。我强调一点KNN 的训练阶段几乎没有计算量但预测阶段要为每个新样本算一遍它和所有训练样本的距离时间复杂度是 O(N*D)。对比一下决策树预测一次可能只要走几层判断KNN 则是开足马力的全量距离计算。数据量上了几十万条KNN 的预测延迟会明显上升。scikit-learn 提供了algorithm参数可以在brute、kd_tree、ball_tree之间切换但维度一旦高起来树结构同样会退化所以别指望它能根治性能瓶颈。3.4 什么叫“维度灾难”为什么高维下 KNN 容易被坑最后必须聊一下维度灾难。简单说特征数量越多样本在高维空间里就越稀疏所有样本两两之间的距离会趋于接近“最近邻”和“最远邻”的差异逐渐缩小KNN 的“近邻”概念就变得很模糊。我习惯把 KNN 的适用边界记在脑子里样本量不大几千以内、特征维度不高几十以内时用着很顺手一旦特征上百甚至上千优先考虑 PCA 降维或换模型。这次红酒数据集只有 13 个特征属于非常友好的范围。但我在其他项目里试过把 KNN 用在几百个特征的表单数据上表现明显下降后面靠 PCA 压缩到几十维才恢复效果。这也是为什么我在前面的可视化阶段就引入 PCA它既是探索工具也可能是后续建模的预处理手段。4. 模型预测全流程实现4.1 划分数据集与标准化实操这部分是硬实操。我先把数据加载并拆分保证试验可复现from sklearn.model_selection import train_test_split X wine.data y wine.target X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy )stratifyy是必须写的尤其类别不均衡时它可以保证切分后训练集和测试集的类别比例与原始数据一致。random_state42固定随机种子这样别人复现你的实验时能得到相同结果这项习惯一定要养成。接着做标准化scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)注意fit只发生在训练集上测试集直接transform。这个顺序错掉就是你以后排查“为什么模型分数虚高”的根源之一。4.2 Baseline 模型与评估指标详解我先不急着调参直接用K5和weightsuniform建一个基线模型看表现如何from sklearn.neighbors import KNeighborsClassifier from sklearn.metrics import confusion_matrix, classification_report, accuracy_score knn KNeighborsClassifier(n_neighbors5) knn.fit(X_train_scaled, y_train) y_pred knn.predict(X_test_scaled) print(准确率:, accuracy_score(y_test, y_pred)) print(confusion_matrix(y_test, y_pred)) print(classification_report(y_test, y_pred))我实测的准确率大概在 0.94 左右测试集 36 个样本里误分了两三个。这个基线成绩已经不错进一步调参也许只能提升几个百分点。所以我要特别强调一个观点不要在 baseline 表现已经很好的数据集上疯狂调参追求小数点后的提升没有意义你需要把精力花在理解每一类错误发生在哪。混淆矩阵能直接告诉你哪两类容易混。红酒数据集里class_1和class_2交叠区域更容易误判这和前面 PCA 散点图看到的现象是吻合的。分类报告中的精确率precision、召回率recall和 F1 值帮你判断具体类别的表现精确率关注的是“被判为该类的结果里有多少是对的”召回率关注的是“该类样本有多少被正确找回来”。4.3 网格搜索选参画出 K 值影响曲线Baseline 之后我用网格搜索统一调参。这一步除了拿到最优参数还能画出不同 K 下的准确率变化曲线方便直观判断过拟合和欠拟合区间。我在网格搜索里加入cv5也就是五折交叉验证。每一折都会把训练数据再分成四份训练、一份验证轮流做一次评估最后取平均分。这种做法比简单划分训练测试集要稳定因为它避免了随机划分带来的运气成分。调用完GridSearchCV后我建议顺手把cv_results_里的参数和分数导出来画一张折线图横坐标是 K 值纵坐标是交叉验证平均准确率。你会发现K 很小时训练集准确率很高但验证曲线抖动随着 K 增大两条曲线逐渐靠拢最后出现平稳区域甚至缓慢下降。这个平稳区间的 K 就是你要选的。4.4 对新样本做预测并解读概率模型的终极目的是预测“没见过”的新样本。这里我构造一个新酒样本特征值按合理范围随机填写然后用之前训练好的 pipeline 做预测import numpy as np from sklearn.pipeline import make_pipeline pipeline make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighborsgrid.best_params_[n_neighbors])) pipeline.fit(X_train, y_train) new_sample np.array([ 13.5, 2.1, 2.4, 17.5, 100.0, 2.3, 2.9, 0.3, 1.6, 5.1, 1.0, 3.1, 780.0 ]).reshape(1, -1) prob pipeline.predict_proba(new_sample)[0] print(预测类别:, pipeline.predict(new_sample)[0]) print(各类别概率:, prob)predict_proba返回的是 K 个邻居中每个类别的比例。比如输出[0.0, 0.75, 0.25]说明最近的 8 个邻居里有 6 个属于类别 12 个属于类别 2。这个概率可以作为置信度参考。实际业务里我会给这个结果加一个规则如果最高概率低于阈值比如二分类低于 0.6就标记为“需人工复核”而不是直接让系统做决定。这比单纯看准确率有意义得多。4.5 用 Flask 与 ECharts 把结果可视化展示项目做到这如果你想要一个交付级别的小工具可以顺手把预测结果做成网页展示。这也是现在企业级数据可视化里常干的事后端 Python 负责模型预测前端用 ECharts 画散点图业务方打开网页就能看到不同样本在降维空间里的分布和预测结果。我选 ECharts 而不是纯 matplotlib是因为它支持交互鼠标悬停能看到样本详情、点击图例可以筛选类别。以下是一个极简的 Flask 接口思路from flask import Flask, render_template, jsonify app Flask(__name__) app.route(/api/predict) def predict_api(): # 加载模型、对新样本做预测 return jsonify({ prediction: int(pred), probability: prob.tolist(), pc1: pca_x, pc2: pca_y }) if __name__ __main__: app.run(debugTrue)前端用 ECharts 读取/api/predict或一个静态 JSON然后渲染散点图。你还可以把 PCA 降维后的二维坐标和 KNN 预测结果都放进 JSON。这样用户看到的不只是一个冷冰冰的“类别编号”而是一个带空间位置的样本点。我当时做这个页面花了不到两个小时但汇报时的效果比一堆指标截图好很多。业务方终于能直观理解“预测边界长什么样”这也算是数据可视化的真正价值。5. 常见问题与排查技巧实录5.1 KNN 效果差先按顺序排查这五点如果模型跑出来分数低得离谱我一般不看模型本身先查数据链路有没有做标准化。KNN 不做标准化量纲大的特征直接支配距离这是大家最容易翻车的地方。有没有数据泄漏。检查scaler.fit是不是只用了训练集检查特征选择是不是在划分训练测试集之前就用了全量数据。K 值是否合理。忘了调参而直接用 K1 的模型对噪声会非常敏感。类别是否均衡。类别不平衡时准确率会骗人需要看混淆矩阵必要时用 SMOTE 过采样或换weightsdistance。特征是否太杂。如果数据里有大量无关特征PCA 或特征选择比换算法更重要。5.2 预测速度太慢怎么优化KNN 速度慢的根本原因是预测时要做全量距离计算。我的处理优先级是先用 PCA 降维减少特征数这一步其实对距离计算量的影响最直接再考虑algorithmball_tree或kd_tree低维场景下有一定加速效果如果样本量大可以用“原型选择”方法精简训练集只保留决策边界附近的样本如果数据到了百万级老实换 Logistic Regression、决策树或向量化模型KNN 不适合硬扛。5.3 标准化后结果反而更差是怎么回事有少数情况你会发现标准化后模型分数还不如不标准化。这通常是因为特征本身已经处于相近的尺度或者某些特征的原始数值幅度本身就代表了业务上的重要性。比如在某些画像打分场景中某个特征的取值范围 0 到 100本身就暗示了它的权重强行归一化反而抹掉了这种信息。我的建议是不要盲信“必须标准化”而是对比验证做一次标准化的交叉验证分数做一次不做标准化的谁高用谁。不过在绝大多数混合量纲场景里标准化仍然是你该做的默认选择。5.4 平局问题与概率校准三分类里出现平局的概率不小尤其当 K 取偶数时可能两个类别各得一半票数。scikit-learn 多数情况下会按类别顺序取分数较高的那一个但这是实现细节不能依赖。解决办法最直接的是 K 选奇数其次是使用距离加权投票让更近的邻居拥有更大话语权降低平局概率。另外注意predict_proba给出的概率只是“邻居投票比例”并不是真正的概率校准结果。如果一个样本附近三类的比例是 1:1:1那概率就是均匀分布这种样本应该被判为“不确定”。你在业务对接时需要把这个概念讲清楚否则业务方看到一个 33% 的置信度会一头雾水。5.5 常见问题速查表现象可能原因解决方向准确率虚高测试却一塌糊涂数据泄漏确保 scaler 只用训练集 fit特征选择只用训练集训练集分数远高于测试集K 值太小增大 K画准确率随 K 变化曲线测试分数平平训练分数也低K 太大或特征判别力不足减小 K检查可视化特征分布误分类集中在特定两个类别这两个类别在特征空间重叠大增加该类样本数量尝试距离加权预测极慢数据量大、特征多PCA 降维换树结构算法或换模型标准化前后结果差异巨大特征量纲本来就接近或业务语义特殊分别做交叉验证保留分数高的一侧新样本概率输出都接近 1/K样本落在类别重叠区域标记低置信度样本人工复核6. 写在项目结束后的一些体会这个项目做完我最深的感受是KNN 看起来简单但真正把它用在完整流程里每一步都有值得商榷的细节。可视化不只是画几张图而是帮你提前预判模型能达到的上限标准化不是机械操作而是对“距离”语义的一次校准调参更不是玄学而是对偏差和方差的平衡。最后分享一个小技巧如果你在业务中也想快速落地这类模型不要一上来就追求最复杂的方法。先把数据读进来画两三个分布图跑一个 KNN baseline把结果展示给业务方看往往能最快得到有效反馈。我在多个项目里都用这种“先可视化再 baseline再优化”的节奏既不浪费时间也不会偏离业务目标。这个流程的扩展空间也很大。换成农产品分级数据、网约车订单特征数据或者用户行为表格整个代码管道几乎可以原样复用只要把特征列和数据源换掉即可。KNN 虽然老但在小样本、可解释、快速验证这些场景里它依然是一名值得信任的老兵。

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

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

免费获取报价 →
↑