
1. 项目概述从超市货架到算法核心最近在整理一个社区兴趣小组的分享材料主题是“用生活案例讲透机器学习”。我发现一个挺有意思的现象很多朋友一听到“KNN算法”K-Nearest Neighbors K最近邻算法就觉得这是教科书里冷冰冰的数学公式离自己的生活很远。但实际上你每天无意识做的很多决定背后的逻辑和KNN如出一辙。举个例子你去一家新开的超市想买一瓶好喝的酸奶。货架上琳琅满目你大概率不会挨个尝一遍而是会快速扫一眼找那些“看起来”跟你常喝的那个品牌包装风格相似、价格区间接近、甚至生产日期都差不多的产品。这个“找相似”的过程本质上就是KNN算法在帮你做决策——你基于几个关键特征包装、价格、品牌调性在脑海中快速找到了与已知“好酸奶”最相似的几个“邻居”然后从中做出选择。这个项目我们就彻底抛开那些让人望而生畏的数学推导聚焦于如何用Python代码把KNN算法在生活中的几个经典案例给实实在在地实现出来。无论你是刚入门数据分析的学生还是想在工作中应用一些简单分类思路的业务人员这篇文章都会手把手带你走一遍从问题定义、数据准备、到模型构建和结果分析的全过程。你会发现这个所谓的“机器学习十大算法”之一理解和使用起来可以像在超市选酸奶一样直观。2. KNN算法核心思想与生活映射在开始敲代码之前我们必须把KNN的“灵魂”搞清楚。它的核心思想极其简单就一句话物以类聚人以群分。判断一个未知事物属于哪一类就看它周围特征空间里距离最近的、我们已经知道类别的事物大多数属于哪一类。2.1 算法三要素K值、距离度量、决策规则理解了这个我们再把它拆解成三个可以操作、可以调节的关键部分这直接决定了你代码实现的效果。1. 距离度量我们如何定义“相似”或“接近”这就是算法中的“距离函数”。生活中判断两个酸奶是否相似你可能同时考虑了价格差比如差5块钱就觉得贵了、包装颜色都是蓝白色系、保质期长短。在数学上我们需要把这些特征量化然后计算一个总的“差异度”。最常用的就是“欧氏距离”你可以把它想象成多维空间中的直线距离。假设我们用“甜度”0-10分和“酸度”0-10分来给酸奶打分。酸奶A是37酸奶B是56。它们的欧氏距离就是√[(5-3)² (6-7)²] √(41) ≈ 2.24。这个值越小说明两款酸奶口味越接近。除了欧氏距离还有曼哈顿距离像在城市里沿街区走路把横竖距离直接相加、余弦相似度更关注口味方向的相似性而不是绝对数值等。选择哪种取决于你的特征是什么。比如比较两篇文章用词频作为特征余弦相似度往往比欧氏距离更有效。2. K值选择我们需要参考几个“邻居”的意见这是KNN中最关键的参数。K太小比如K1模型会变得非常敏感容易受到个别噪声数据比如一个标错标签的酸奶的干扰导致预测结果不稳定这叫做“过拟合”。想象一下你只根据一个朋友的推荐就决定买一款酸奶风险很大。 K太大虽然抗噪声能力变强但可能会把“远处”根本不像的样本也包含进来导致分类边界模糊无法捕捉细微的规律这叫做“欠拟合”。就像你问了一百个人其中很多人的口味跟你天差地别他们的意见反而会误导你。3. 决策规则如何汇总邻居们的意见最常见的就是“少数服从多数”。在K个最近邻居中哪个类别的样本数最多就把未知样本判给那个类别。这简单有效。在一些场景下我们还可以考虑“加权投票”距离越近的邻居话语权越重。比如那个和你口味几乎一模一样的死党的推荐肯定比一个泛泛之交的推荐更重要。2.2 为什么从生活案例入手直接学习sklearn的KNeighborsClassifier三行代码就能跑通一个模型但那样你只记住了“怎么做”完全不知道“为什么这么做”以及“什么时候该怎么做”。通过生活案例我们可以直观理解参数影响你可以真切地感受到改变K值如何影响分类边界。亲手处理数据从一堆杂乱的生活记录比如电影评分、购物清单中构造出算法能“吃”的特征这是数据分析的核心能力。建立算法直觉未来遇到新的分类问题你的第一反应会是“这个能不能用KNN那种找邻居的思路试试”而不是机械地套用模型。接下来我们将用三个逐步深入的案例把上述思想全部代码化。3. 案例一电影类型分类器入门实战假设你是个电影迷有一个记录了自己看过的电影以及喜好的小本子。现在有一部新电影《神秘之旅》你想预测自己会不会喜欢它。我们可以把这个问题构建成一个二分类问题喜欢Like或不喜欢Dislike。3.1 数据准备与特征工程我们首先需要把主观的“电影感受”转化成数字。import pandas as pd import numpy as np # 模拟历史数据每部电影用两个特征量化一个是‘动作镜头比例’一个是‘剧情深度评分’ # 标签是‘Like’1和‘Dislike’0 movie_data { ‘电影名‘: [‘终极战场‘, ‘温暖时光‘, ‘城市之巅‘, ‘心灵角落‘, ‘爆裂赛车‘, ‘岁月静好‘], ‘动作镜头比例‘: [0.9, 0.1, 0.7, 0.2, 0.95, 0.15], ‘剧情深度评分‘: [0.3, 0.85, 0.4, 0.9, 0.2, 0.8], ‘喜好‘: [1, 0, 1, 0, 1, 0] # 1: Like, 0: Dislike } df_movies pd.DataFrame(movie_data) print(“历史电影数据“) print(df_movies)输出会显示一个简单的表格。这里做了关键的特征工程我用“动作镜头比例”0-1和“剧情深度评分”0-1两个维度来刻画一部电影。这基于一个假设你的喜好与这两个因素强相关。在实际项目中特征的选择直接决定了算法的天花板。3.2 从零实现KNN核心逻辑我们不直接调库先自己实现核心的“找邻居”和“投票”过程这能让你彻底明白原理。def euclidean_distance(point1, point2): “”“计算两点之间的欧氏距离”“” return np.sqrt(np.sum((np.array(point1) - np.array(point2)) ** 2)) def my_knn_predict(training_data, training_labels, new_point, k3): “”“ 手动实现KNN预测 training_data: 训练集特征二维数组 training_labels: 训练集标签一维数组 new_point: 待预测的新数据点 k: 邻居个数 ”“” # 1. 计算新点到所有训练点的距离 distances [] for i, train_point in enumerate(training_data): dist euclidean_distance(new_point, train_point) distances.append((dist, training_labels[i])) # 记录距离和对应的标签 # 2. 按距离排序取前k个最近的邻居 distances.sort(keylambda x: x[0]) k_nearest distances[:k] # 3. 统计k个邻居中各类别的数量 label_counts {} for _, label in k_nearest: label_counts[label] label_counts.get(label, 0) 1 # 4. 返回数量最多的类别 predicted_label max(label_counts, keylabel_counts.get) return predicted_label, k_nearest # 返回预测结果和邻居信息方便分析 # 准备数据 X_train df_movies[[‘动作镜头比例‘, ‘剧情深度评分‘]].values y_train df_movies[‘喜好‘].values # 新电影《神秘之旅》的特征假设 new_movie [0.75, 0.35] # 使用我们自己的KNN函数进行预测 prediction, neighbors my_knn_predict(X_train, y_train, new_movie, k3) print(f“\n新电影《神秘之旅》的特征{new_movie}“) print(f“预测结果{‘喜欢‘ if prediction 1 else ‘不喜欢‘}“) print(f“最近的{k}个邻居及其距离和标签{neighbors}“)运行这段代码你会看到算法找到了距离《神秘之旅》最近的3部历史电影并根据它们的喜好标签假设2部喜欢1部不喜欢做出了“喜欢”的预测。你可以尝试修改new_movie的特征值或k的值观察预测结果如何变化。3.3 使用sklearn验证与可视化为了验证我们手写逻辑的正确性并直观看到分类边界我们引入sklearn和matplotlib。from sklearn.neighbors import KNeighborsClassifier import matplotlib.pyplot as plt # 使用sklearn的KNN knn_model KNeighborsClassifier(n_neighbors3, metric‘euclidean‘) knn_model.fit(X_train, y_train) sklearn_prediction knn_model.predict([new_movie]) print(f“sklearn验证预测结果{‘喜欢‘ if sklearn_prediction[0] 1 else ‘不喜欢‘}“) # 应该和我们手写的结果一致 # 可视化 plt.figure(figsize(10, 6)) # 绘制历史数据点 like_movies df_movies[df_movies[‘喜好‘] 1] dislike_movies df_movies[df_movies[‘喜好‘] 0] plt.scatter(like_movies[‘动作镜头比例‘], like_movies[‘剧情深度评分‘], c‘green‘, s100, label‘喜欢‘, edgecolors‘black‘) plt.scatter(dislike_movies[‘动作镜头比例‘], dislike_movies[‘剧情深度评分‘], c‘red‘, s100, label‘不喜欢‘, marker‘s‘, edgecolors‘black‘) # 绘制新电影点 plt.scatter(new_movie[0], new_movie[1], c‘blue‘, s200, label‘新电影《神秘之旅》‘, marker‘*‘, edgecolors‘black‘) # 为最近邻画上连线 for dist, _ in neighbors: # 这里简化演示实际应根据距离找到对应点 pass # 在实际中可以计算并画出连线更直观显示“邻居” plt.xlabel(‘动作镜头比例‘) plt.ylabel(‘剧情深度评分‘) plt.title(‘电影喜好KNN分类K3‘) plt.legend() plt.grid(True, linestyle‘--‘, alpha0.5) plt.xlim(0, 1) plt.ylim(0, 1) plt.show()这张散点图能让你一目了然新电影蓝色星星在特征空间中的位置更靠近哪些绿色喜欢的点从而理解KNN的决策依据。实操心得1特征缩放Feature Scaling的重要性在这个案例中我们的两个特征“动作比例”和“剧情评分”都在0到1之间尺度一致。但如果一个特征是“预算万美元”范围在0-10000另一个是“豆瓣评分”范围在0-10那么计算距离时“预算”的差异会完全主导结果淹没“评分”的影响。因此在实际应用KNN前必须对特征进行标准化如Z-score或归一化缩放到[0,1]这是使用KNN的铁律。sklearn的StandardScaler或MinMaxScaler可以轻松完成。4. 案例二鸢尾花分类经典数据集与调参上一个案例数据太简单我们用一个机器学习领域的“Hello World”数据集——鸢尾花Iris数据集来深入。这个数据集包含了150朵鸢尾花的测量数据每朵花有4个特征萼片长宽、花瓣长宽需要分为3个品种Setosa, Versicolor, Virginica。4.1 数据加载与探索from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 加载数据 iris load_iris() X, y iris.data, iris.target feature_names, target_names iris.feature_names, iris.target_names print(“数据集形状“, X.shape) # (150, 4) print(“特征名“, feature_names) print(“目标类别名“, target_names) print(“\n前5条数据“) print(X[:5]) print(“对应标签“, y[:5])4.2 完整的机器学习流程这里我们将体验一个标准的建模流程数据拆分、预处理、训练、评估。# 1. 拆分训练集和测试集7:3比例 X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.3, random_state42) print(f“训练集大小{X_train.shape} 测试集大小{X_test.shape}“) # 2. 特征标准化非常重要 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) # 在训练集上计算均值和标准差并转换 X_test_scaled scaler.transform(X_test) # 使用训练集的参数转换测试集避免数据泄露 # 3. 训练KNN模型先随便选一个K5 knn KNeighborsClassifier(n_neighbors5) knn.fit(X_train_scaled, y_train) # 4. 在测试集上评估 train_score knn.score(X_train_scaled, y_train) test_score knn.score(X_test_scaled, y_test) print(f“K5时训练集准确率{train_score:.4f} 测试集准确率{test_score:.4f}“)4.3 K值调优与交叉验证K5一定是最好的吗我们需要系统性地寻找最优K值。这里引入交叉验证来更稳健地评估模型性能。from sklearn.model_selection import cross_val_score # 尝试不同的K值 k_range range(1, 31) cv_scores [] for k in k_range: knn KNeighborsClassifier(n_neighborsk) scores cross_val_score(knn, X_train_scaled, y_train, cv5, scoring‘accuracy‘) # 5折交叉验证 cv_scores.append(scores.mean()) # 取5次验证的平均准确率 # 找出最优K值 optimal_k k_range[cv_scores.index(max(cv_scores))] print(f“通过交叉验证得到的最优K值是{optimal_k} 其平均准确率为{max(cv_scores):.4f}“) # 可视化K值与准确率的关系 plt.figure(figsize(10, 6)) plt.plot(k_range, cv_scores, marker‘o‘, linestyle‘-‘) plt.axvline(xoptimal_k, color‘r‘, linestyle‘--‘, labelf‘Optimal K{optimal_k}‘) plt.xlabel(‘K值‘) plt.ylabel(‘交叉验证平均准确率‘) plt.title(‘KNN模型K值选择鸢尾花数据集‘) plt.legend() plt.grid(True) plt.show()运行这段代码你会得到一条曲线通常形状是K很小时准确率可能波动或较低过拟合K增大准确率上升并达到一个峰值K继续增大准确率缓慢下降欠拟合。那个峰值对应的K就是我们想要的。实操心得2交叉验证是避免“运气”的利器如果我们只把数据简单分成一份训练集和一份测试集那么测试集的结果可能因为数据划分的偶然性而偏高或偏低。交叉验证比如5折把训练集分成5份轮流用其中4份训练1份验证重复5次最后取平均成绩。这能更好地评估模型在“未知数据”上的泛化能力让K值的选择更可靠。永远不要只依赖一次train_test_split的结果来做重大决策。4.4 模型最终评估与混淆矩阵用最优K值重新训练模型并在真正的测试集一开始预留的30%上做最终评估。# 用最优K值重新训练最终模型 final_knn KNeighborsClassifier(n_neighborsoptimal_k) final_knn.fit(X_train_scaled, y_train) # 最终测试集评估 final_test_score final_knn.score(X_test_scaled, y_test) print(f“使用最优K{optimal_k}的模型在独立测试集上的最终准确率为{final_test_score:.4f}“) # 查看更详细的评估报告混淆矩阵 from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay y_pred final_knn.predict(X_test_scaled) print(“\n分类报告“) print(classification_report(y_test, y_pred, target_namestarget_names)) # 绘制混淆矩阵 cm confusion_matrix(y_test, y_pred) disp ConfusionMatrixDisplay(confusion_matrixcm, display_labelstarget_names) disp.plot(cmapplt.cm.Blues) plt.title(‘混淆矩阵‘) plt.show()混淆矩阵能告诉你模型具体在哪里犯了错。比如它可能总是把Versicolor和Virginica搞混但能完美识别Setosa。这能指导你后续改进方向例如是否需要增加这两个易混淆品种的特征。5. 案例三手写数字识别多分类与性能考量手写数字识别MNIST数据集简化版是一个经典的多分类问题0-9共10类。这里我们用sklearn自带的一个小型手写数字数据集来演示它比完整的MNIST小但足以说明问题。5.1 处理高维数据与图像特征from sklearn.datasets import load_digits digits load_digits() X_digits, y_digits digits.data, digits.target print(“手写数字数据集形状“, X_digits.shape) # (1797, 64) print(“一幅图像的维度“, digits.images[0].shape) # (8, 8) 表示8x8像素的灰度图 print(“标签示例“, y_digits[:10]) # 可视化几个样本 fig, axes plt.subplots(2, 5, figsize(10, 5)) for i, ax in enumerate(axes.flat): ax.imshow(digits.images[i], cmap‘binary‘) ax.set_title(f“Label: {y_digits[i]}“) ax.axis(‘off‘) plt.show()这里每张8x8的图片被“拉平”成了一个64维的向量64个特征。每个特征代表一个像素点的灰度值。KNN算法需要计算待识别图片与所有训练图片在这64维空间中的距离。5.2 KNN在大数据量下的挑战与优化当训练样本量很大比如数万张图片时KNN的预测速度会成为一个大问题因为它需要计算新样本与所有训练样本的距离。这就是KNN著名的“懒惰学习”特性——训练时几乎不做事所有计算都推迟到预测时。# 拆分数据 X_train_d, X_test_d, y_train_d, y_test_d train_test_split(X_digits, y_digits, test_size0.2, random_state42) # 标准化对于像素值0-16标准化依然有益 scaler_d StandardScaler() X_train_d_scaled scaler_d.fit_transform(X_train_d) X_test_d_scaled scaler_d.transform(X_test_d) # 训练一个KNN模型 knn_digits KNeighborsClassifier(n_neighbors5) knn_digits.fit(X_train_d_scaled, y_train_d) # 评估 train_acc_d knn_digits.score(X_train_d_scaled, y_train_d) test_acc_d knn_digits.score(X_test_d_scaled, y_test_d) print(f“手写数字识别 - 训练集准确率{train_acc_d:.4f} 测试集准确率{test_acc_d:.4f}“) # 预测单张图片并计时 import time sample_image X_test_d_scaled[0].reshape(1, -1) start_time time.time() prediction knn_digits.predict(sample_image) end_time time.time() print(f“预测单张图片的类别是{prediction[0]} 耗时{(end_time - start_time)*1000:.2f} 毫秒“)实操心得3KNN的优缺点与适用场景通过这个案例我们可以总结KNN的典型特点优点原理简单易于理解和实现。无需训练过程适合数据动态更新的场景新增数据直接加入数据集即可。对数据分布没有假设适用于各种奇形怪状的数据分布。缺点预测速度慢样本量大、特征维度高时计算距离开销巨大。这是其最大瓶颈。内存消耗大需要存储全部训练数据。对不相关特征和尺度敏感高维稀疏数据下效果差必须做特征选择和标准化。类别不平衡时大类别占优。优化方向使用KD-Tree或Ball-Tree数据结构sklearn的KNeighborsClassifier默认会自动选择最优算法这些数据结构能极大加速近邻搜索。降维对于图像等高维数据可以先使用PCA等降维技术在保留大部分信息的前提下减少特征数。近似最近邻搜索如使用Locality Sensitive Hashing (LSH)用精度换速度适用于海量数据。5.3 错误分析与案例观察即使准确率达到98%那2%的错误也值得研究。看看模型到底认错了哪些数字。# 找出预测错误的样本 y_pred_d knn_digits.predict(X_test_d_scaled) incorrect_indices np.where(y_pred_d ! y_test_d)[0] print(f“总共预测错误的样本数{len(incorrect_indices)}“) if len(incorrect_indices) 0: # 可视化前几个错误样本 fig, axes plt.subplots(2, 5, figsize(12, 6)) for i, idx in enumerate(incorrect_indices[:10]): ax axes.flat[i] ax.imshow(X_test_d[idx].reshape(8, 8), cmap‘binary‘) ax.set_title(f“True: {y_test_d[idx]}, Pred: {y_pred_d[idx]}“) ax.axis(‘off‘) plt.suptitle(‘部分错误分类样本‘, fontsize16) plt.show()观察这些被错误分类的图片你会发现它们往往确实“长得模棱两可”比如数字“1”写得像“7”“9”写得像“4”。这说明了KNN乃至很多分类模型的局限性它依赖于特征空间的几何距离。如果两个不同类别的样本在特征空间里因为书写风格而靠得很近模型就很容易犯错。要解决这个问题可能需要更复杂的特征如HOG、SIFT等图像特征或更强大的模型如神经网络。6. 常见问题、调参技巧与实战建议走完三个案例你应该对KNN的整个流程有了切身感受。最后我把一些散落的、但至关重要的经验和技巧集中在这里帮你避开我踩过的坑。6.1 K值选择终极指南选择K没有银弹但有一套系统的方法从经验值开始通常从k sqrt(训练样本数)的近似值开始尝试。对于小数据集几百条K取3,5,7对于大数据集可以取大一些。使用交叉验证曲线就像我们在鸢尾花案例里做的那样绘制K值与交叉验证准确率的曲线选择曲线高点或开始进入平台区的K值。考虑业务场景如果希望模型更稳健抗噪声能力强可以选稍大的K如果数据非常干净且类别边界清晰想捕捉细微差别可以选小一点的K。K值最好取奇数对于二分类问题这可以避免平票情况。多分类问题虽无此限制但奇数仍是一个好习惯。6.2 距离度量的选择sklearn的KNeighborsClassifier提供了多种距离度量通过metric参数设置‘euclidean‘默认欧氏距离最常用适用于连续特征。‘manhattan‘曼哈顿距离当特征维度很高且各维度重要性相当时有时比欧氏距离好。‘chebyshev‘切比雪夫距离取各维度绝对差的最大值。‘minkowski‘闵可夫斯基距离是欧氏和曼哈顿距离的泛化通过参数p控制p2是欧氏p1是曼哈顿。‘cosine‘余弦相似度常用于文本、推荐系统衡量方向相似性。对于图像、数值型数据欧氏距离通常是安全的起点。对于文本数据余弦相似度是首选。6.3 特征工程比算法本身更重要在KNN中特征的质量直接决定了性能上限。必须做标准化/归一化重申一遍这是使用KNN的前置条件。处理缺失值KNN无法直接处理缺失值。需要先进行填充如用均值、中位数或删除。特征选择如果特征维度过高成百上千不仅计算慢还会遭遇“维度灾难”——在高维空间中所有点之间的距离都趋于相等使得KNN失效。可以使用方差过滤、互信息法、基于模型的特征重要性等方法进行降维。创造新特征有时原始特征的组合或衍生可能比原始特征更有效。例如在电影案例中“动作镜头比例”和“剧情深度评分”的比值可能是一个区分“无脑爽片”和“文艺剧情片”的更强特征。6.4 实战问题排查清单当你实现的KNN模型效果不佳时可以按这个清单自查问题现象可能原因排查与解决方向准确率过低远低于预期1. 特征未标准化2. K值选择极端过大或过小3. 特征与目标无关4. 数据噪声极大1. 检查并执行特征标准化2. 绘制K-准确率曲线调整K值3. 进行特征与目标的相关性分析4. 尝试清洗数据或使用更大的K值平滑噪声训练集准确率高测试集低过拟合K值太小模型过于复杂增大K值使用交叉验证选择训练集和测试集准确率都低欠拟合K值太大模型过于简单或特征无法有效区分类别减小K值重新审视特征工程增加有效特征预测速度极慢数据量太大特征维度太高1. 使用algorithm参数指定‘kd_tree‘或‘ball_tree‘2. 对数据进行降维PCA3. 考虑使用近似最近邻算法对某个类别预测极差类别不平衡该类样本太少1. 使用weights‘distance‘进行距离加权投票2. 对少数类样本进行过采样如SMOTE6.5 一个完整的模板化代码流程最后给你一个可以套用到大多数分类问题上的KNN代码模板# 1. 导入库 import numpy as np import pandas as pd from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV from sklearn.preprocessing import StandardScaler from sklearn.neighbors import KNeighborsClassifier from sklearn.metrics import classification_report, confusion_matrix import matplotlib.pyplot as plt # 2. 加载与探索数据 # df pd.read_csv(‘your_data.csv‘) # X df.drop(‘target_column‘, axis1) # y df[‘target_column‘] # 3. 数据拆分 X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) # 4. 特征标准化 (铁律) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 5. 初步建模与K值调优 (使用交叉验证) k_range list(range(1, 31)) param_grid {‘n_neighbors‘: k_range} knn KNeighborsClassifier() grid_search GridSearchCV(knn, param_grid, cv5, scoring‘accuracy‘) grid_search.fit(X_train_scaled, y_train) print(f“最优K值{grid_search.best_params_[‘n_neighbors‘]}“) print(f“最优交叉验证分数{grid_search.best_score_:.4f}“) # 6. 用最优参数训练最终模型 best_knn grid_search.best_estimator_ # 7. 在独立测试集上进行最终评估 y_pred best_knn.predict(X_test_scaled) print(“\n测试集分类报告“) print(classification_report(y_test, y_pred)) print(“\n混淆矩阵“) print(confusion_matrix(y_test, y_pred)) # 8. 可选可视化调参过程 results pd.DataFrame(grid_search.cv_results_) plt.plot(k_range, results[‘mean_test_score‘], marker‘o‘) plt.xlabel(‘K值‘) plt.ylabel(‘平均交叉验证准确率‘) plt.title(‘K值调优曲线‘) plt.grid(True) plt.show()把这个流程保存下来下次遇到一个新的分类数据集你可以直接从这个模板开始快速跑通一个基线模型然后再根据具体问题进行深入优化。记住KNN是一个强大的基准模型它的表现可以为你后续尝试更复杂模型提供一个重要的参考标杆。很多时候简单有效的KNN可能比你费尽心思调参的复杂模型效果更好。