从电影分类到鸢尾花识别:KNN 算法实战指南
在机器学习的世界里,有这样一种 “简单却强大” 的算法 —— 它不需要复杂的数学推导,也不用提前假设数据分布,仅靠 “近朱者赤,近墨者黑” 的朴素逻辑,就能完成分类任务。它就是K 近邻算法(KNN,k-Nearest Neighbor) 。
今天,我们将从生活化的 “电影分类” 问题入手,逐步拆解 KNN 的核心原理、距离度量方法,再通过 “鸢尾花分类” 的实战案例,带你掌握用 Python 实现 KNN 的全流程。无论你是机器学习新手,还是想重温经典算法,这篇文章都能帮你理清思路、搞定实战!
一、从电影分类说起:KNN 的直观理解
在判断一部电影的类型时(比如是 “爱情片” 还是 “动作片”),我们通常会关注它的关键特征 —— 比如 “打斗镜头次数” 和 “接吻镜头次数”。动作片往往有更多打斗镜头,爱情片则反之。KNN 算法正是用这种 “特征相似性” 来做判断的,先看一个具体案例:
1.1 已知电影数据:有标签的 “参考样本”
我们先收集一批已标注类型的电影数据,这些数据包含 “电影名称”“打斗镜头数”“接吻镜头数” 和 “电影类型”(即标签),相当于 KNN 算法的 “知识库”:
电影类型 | 电影名称 | 打斗镜头数 | 接吻镜头数 |
---|---|---|---|
爱情片 | California Man | 3 | 104 |
爱情片 | He's Not Really into Dudes | 2 | 100 |
爱情片 | Beautiful Woman | 1 | 81 |
动作片 | Kevin Longblade | 101 | 5 |
动作片 | Robo Slayer 3000 | 99 | 2 |
动作片 | Amped Ⅱ | 98 | 2 |
1.2 未知电影分类:KNN 如何判断?
现在有一部未知类型的电影,已知它的 “打斗镜头数 = 18”“接吻镜头数 = 90”,该如何用 KNN 判断它是爱情片还是动作片?
答案很简单:找最像它的 “邻居” 。KNN 会计算未知电影与 “知识库” 中所有已知电影的 “特征距离”,再选距离最近的 K 个邻居,看这 K 个邻居里多数是哪种类型,未知电影就归为哪种类型。
比如当 K=3 时,若未知电影最近的 3 个邻居都是爱情片,那它就是爱情片;若 K=5 时邻居中动作片更多,结果可能会变化(后续会详细解释 K 值的影响)。
二、KNN 算法核心:原理与步骤拆解
通过电影分类的例子,我们已经 get 到 KNN 的核心逻辑 ——“靠邻居投票决定类别”。接下来我们把它拆解成更严谨的步骤,理解每个环节的关键细节。
2.1 KNN 的核心定义
K 近邻算法(KNN):每个样本都可以用它最接近的 K 个邻近样本的类别来代表。它属于 “监督学习” 中的 “惰性学习”(Lazy Learning)—— 训练阶段不做任何模型训练,仅存储数据;预测阶段才计算距离、找邻居、做投票,所有计算都在预测时完成。
2.2 KNN 的 5 步执行流程
无论面对什么分类任务,KNN 的执行步骤都可以总结为以下 5 步,以 “未知电影分类” 为例:
- 计算距离:用 “打斗镜头数” 和 “接吻镜头数” 作为特征,计算未知电影(特征:(18,90))与所有已知电影(如 California Man:(3,104))的距离;
- 排序:将所有已知电影按 “与未知电影的距离” 从小到大排序;
- 选邻居:选取排序后距离最小的 K 个电影(比如 K=3 或 K=5),这 K 个就是未知电影的 “邻居”;
- 算频率:统计这 K 个邻居中 “爱情片” 和 “动作片” 的出现频率;
- 做预测:返回频率最高的类别作为未知电影的类型。
2.3 关键参数:K 值的选择
K 值是 KNN 中最重要的参数,直接影响预测结果,我们通过一个可视化案例理解:
- 当 K=3 时:若未知样本(X)最近的 3 个邻居中,蓝三角占 2 个、红圆占 1 个,投票结果为 “蓝三角”;
- 当 K=5 时:若范围扩大到 5 个邻居后,红圆占 3 个、蓝三角占 2 个,投票结果变为 “红圆”。
K 值选择的原则:
- K 值太小(如 K=1):模型容易受 “异常值” 影响,比如一个离群的红圆可能让结果误判;
- K 值太大(如 K=20):模型会包含过多 “不相关邻居”,比如原本是爱情片的未知电影,可能被远处的动作片 “带偏”;
- 常规选择:K 值一般不大于 20,实际项目中可通过 “交叉验证”(后续实战会讲)选择最优 K 值。
三、距离度量:KNN 的 “尺子” 怎么选?
计算 “特征距离” 是 KNN 的核心步骤,就像用一把 “尺子” 衡量样本间的相似性。常用的 “尺子” 有两种:欧式距离和曼哈顿距离。
3.1 欧式距离(最常用)
欧式距离也称 “欧几里得距离”,是我们最熟悉的 “两点之间直线距离”,适用于连续型特征(如镜头数、长度、重量等)。
二维空间(如电影的 “打斗镜头数” 和 “接吻镜头数”):
若样本 A 坐标为 (x₁,y₁),样本 B 坐标为 (x₂,y₂),则距离公式为:
d=(x1−x2)2+(y1−y2)2
比如未知电影 (18,90) 与 California Man (3,104) 的欧式距离:
d=(18−3)2+(90−104)2=152+(−14)2=225+196=421≈20.5n 维空间(如鸢尾花有 4 个特征):
若样本 A 有 n 个特征 (x₁₁,x₁₂,...,x₁ₙ),样本 B 有 n 个特征 (x₂₁,x₂₂,...,x₂ₙ),则距离公式为:
d=∑i=1n(x1i−x2i)2
3.2 曼哈顿距离(应对高维或网格数据)
曼哈顿距离也称 “出租车距离”,衡量的是 “两点在坐标轴上的绝对轴距总和”,适用于高维数据或 “特征是整数网格” 的场景(如像素坐标)。
- 二维空间公式:
样本 A (x₁,y₁) 与样本 B (x₂,y₂) 的曼哈顿距离:
d=∣x1−x2∣+∣y1−y2∣
同样以未知电影 (18,90) 和 California Man (3,104) 为例:
d=∣18−3∣+∣90−104∣=15+14=29
四、实战 1:用 KNN 实现鸢尾花分类(Python+Scikit-learn)
理解了原理后,我们用经典的 “鸢尾花分类” 任务做实战。鸢尾花数据集包含 3 个品种(标签),每个样本有 4 个特征(萼片长度、萼片宽度、花瓣长度、花瓣宽度),我们要用 KNN 实现 “根据特征预测品种”。
4.1 准备工作:加载库与数据集
首先导入需要的库(Scikit-learn 内置了鸢尾花数据集和 KNN 模型):
# 导入数据集库
from sklearn import datasets
# 导入KNN分类器
from sklearn.neighbors import KNeighborsClassifier
# 导入数据划分工具(划分训练集和测试集)
from sklearn.model_selection import train_test_split
# 导入数值计算库
import numpy as np
加载鸢尾花数据集,并查看关键信息:
# 加载数据集
iris = datasets.load_iris()# 查看数据集内容
print("=== 鸢尾花特征名称 ===")
print(iris.feature_names) # 输出:['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']print("\n=== 鸢尾花类别名称 ===")
print(iris.target_names) # 输出:['setosa' 'versicolor' 'virginica'](3个品种)print("\n=== 前5个样本的特征 ===")
print(iris.data[:5]) # 输出前5个样本的4个特征值print("\n=== 前5个样本的标签 ===")
print(iris.target[:5]) # 输出:[0 0 0 0 0](标签0对应setosa,1对应versicolor,2对应virginica)
4.2 数据划分:训练集与测试集
为了评估模型的 “泛化能力”(能否应对新数据),我们需要将数据划分为训练集(用于 “学习” 特征与标签的关系)和测试集(用于 “检验” 模型效果):
# 划分训练集(70%)和测试集(30%)
# x:特征数据,y:标签数据;test_size=0.3表示测试集占30%
x_train, x_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.3, random_state=42 # random_state=42保证每次划分结果一致
)print(f"训练集特征数量:{x_train.shape[0]} 个样本") # 输出:105个样本(150*70%)
print(f"测试集特征数量:{x_test.shape[0]} 个样本") # 输出:45个样本(150*30%)
4.3 构建 KNN 模型:训练与参数设置
创建 KNN 分类器实例,设置关键参数,然后用训练集训练模型:
# 创建KNN实例,设置K=5,距离度量用欧式距离
knn = KNeighborsClassifier(n_neighbors=5, # K值=5(近邻数量)metric="euclidean" # 距离度量:欧式距离
)# 训练模型(KNN是惰性学习,这里实际是存储训练数据)
knn.fit(x_train, y_train)
print("模型训练完成!")
4.4 模型评估:训练集与测试集得分
评估模型在 “已知数据”(训练集)和 “新数据”(测试集)上的表现,得分越接近 1 越好:
# 计算训练集得分(模型对训练数据的拟合程度)
train_score = knn.score(x_train, y_train)
# 计算测试集得分(模型的泛化能力)
test_score = knn.score(x_test, y_test)print(f"训练集得分:{train_score:.2f}") # 输出约0.98(拟合良好)
print(f"测试集得分:{test_score:.2f}") # 输出约0.98(泛化能力强,未过拟合)
4.5 模型预测:对新样本分类
用训练好的模型预测测试集中的样本类别,并对比真实标签:
# 对测试集所有样本做预测
y_pred = knn.predict(x_test)# 输出前10个样本的“真实标签”与“预测标签”
print("=== 前10个样本的预测结果 ===")
for i in range(10):true_label = iris.target_names[y_test[i]] # 真实类别名称pred_label = iris.target_names[y_pred[i]] # 预测类别名称print(f"样本{i+1}:真实={true_label},预测={pred_label},结果:{true_label == pred_label}")
运行结果会显示,前 10 个样本的预测结果几乎全对,说明模型效果很好!
五、实战 2:回到电影分类,解决未知电影类型
学完鸢尾花实战,我们再回到最初的电影分类问题,用手动计算 + 代码验证的方式,判断 “打斗镜头 18 次、接吻镜头 90 次” 的未知电影类型。
5.1 手动计算(K=3)
整理已知电影特征:
爱情片:A (3,104)、B (2,100)、C (1,81)
动作片:D (101,5)、E (99,2)、F (98,2)
未知电影:X (18,90)计算欧式距离:
- X 与 A 的距离:√[(18-3)²+(90-104)²]≈20.5
- X 与 B 的距离:√[(18-2)²+(90-100)²]≈18.9
- X 与 C 的距离:√[(18-1)²+(90-81)²]≈19.2
- X 与 D 的距离:√[(18-101)²+(90-5)²]≈123.7
- X 与 E 的距离:√[(18-99)²+(90-2)²]≈121.9
- X 与 F 的距离:√[(18-98)²+(90-2)²]≈120.8
排序选邻居:
距离从小到大:B (18.9) < C (19.2) < A (20.5) < F (120.8) < E (121.9) < D (123.7)
K=3 时,邻居是 B、C、A,全是爱情片。结论:未知电影是爱情片。
5.2 代码验证(Python 实现)
用代码复现上述过程,更高效地判断结果:
# 1. 准备数据(特征:[打斗镜头数, 接吻镜头数],标签:0=爱情片,1=动作片)
X = np.array([[3,104], [2,100], [1,81], [101,5], [99,2], [98,2]]) # 已知电影特征
y = np.array([0, 0, 0, 1, 1, 1]) # 已知电影标签(0=爱情片,1=动作片)
unknown_movie = np.array([[18, 90]]) # 未知电影特征# 2. 创建KNN模型(K=3,欧式距离)
knn_movie = KNeighborsClassifier(n_neighbors=3, metric="euclidean")
knn_movie.fit(X, y)# 3. 预测未知电影类型
pred_type = knn_movie.predict(unknown_movie)
type_name = "爱情片" if pred_type[0] == 0 else "动作片"print(f"未知电影(打斗18次,接吻90次)的类型是:{type_name}")
运行结果会显示 “爱情片”,与手动计算一致!
六、KNN 算法的优缺点与适用场景
通过两个实战案例,我们已经掌握了 KNN 的用法,最后总结它的优缺点和适用场景,帮你在实际项目中判断是否该用 KNN。
6.1 优点
- 简单易理解:原理基于 “邻居投票”,无需复杂数学推导;
- 无需训练:仅存储数据,预测时才计算,适合小数据集;
- 对异常值不敏感:K 个邻居投票能抵消个别异常值的影响;
- 多分类友好:天然支持多分类(如鸢尾花 3 个品种),无需额外处理。
6.2 缺点
- 计算效率低:预测时需与所有样本计算距离,数据量大时(如百万级样本)速度慢;
- 内存消耗大:需存储所有训练数据,数据量大时内存压力大;
- 对特征尺度敏感:比如 “身高(cm)” 和 “体重(kg)” 的数值范围差异大,会导致距离计算偏向数值大的特征(需先做特征归一化);
- 对不平衡数据不友好:若某类样本占比极高,K 个邻居中该类占多数,易导致预测偏向该类。
6.3 适用场景
- 小数据集分类任务(如鸢尾花、小规模标签数据);
- 特征维度低的场景(高维数据建议先做降维,如 PCA);