如果你打开视频平台时,发现它总能精准推荐你喜欢的爱情片或动作片;如果你曾好奇植物识别 APP 如何区分不同品种的鸢尾花 ------ 这些场景背后,可能都藏着一种简单却实用的机器学习算法:K 近邻算法(KNN) 。
不同于复杂的深度学习模型,KNN 的逻辑像我们生活中 "随大流" 的判断方式:想知道一个新事物属于哪类,看看它周围最亲近的 "邻居" 大多是什么类别,跟着选就对了。今天我们就从电影分类的实际场景出发,拆解 KNN 的核心原理、距离计算方法,再通过代码实战掌握鸢尾花分类,让零基础也能轻松入门。
一、什么是 KNN 算法?------"近朱者赤" 的分类逻辑
KNN(k-Nearest Neighbor,K 近邻算法)的核心定义特别直白:每个样本都可以用它最接近的 K 个 "邻居"(邻近值)来代表。
打个比方:如果新上映一部电影,我们不知道它是 "爱情片" 还是 "动作片",但知道它有 18 个打斗镜头、90 个接吻镜头。这时我们可以找一批已知类别的电影(比如《宠爱》是爱情片,《反贪风暴 4》是动作片),计算新电影与这些已知电影的 "相似度"(距离),再挑出最像的 3 个(或 5 个、7 个)邻居 ------ 如果这 3 个邻居里有 2 个是爱情片,那新电影就归为爱情片。
这就是 KNN 的本质:"无训练过程的懒惰学习" ------ 它不需要像线性回归那样先 "训练" 出一个模型公式,而是直接用已有的数据(样本集)给新数据 "贴标签",核心全在 "找邻居"。
KNN 的 5 步核心流程
文档中明确给出了 KNN 的标准步骤,我们用 "给未知电影分类" 的例子翻译一下:
- 算距离:用新数据(未知电影的 "打斗镜头、接吻镜头")和样本集中每个已知数据(如《宠爱》的 6.0 分对应的镜头数)的特征做对比,计算两者的 "距离"(相似度);
- 排顺序:把所有已知数据按 "距离由近到远" 排序(越近越相似);
- 选邻居:从排序结果里挑前 K 个(比如 K=3)距离最近的 "邻居";
- 算频率:统计这 K 个邻居里,每个类别的出现次数(比如 3 个邻居中有 2 个爱情片、1 个动作片);
- 定类别:出现次数最多的类别,就是新数据的分类(新电影归为爱情片)。
二、关键前提:怎么计算 "邻居" 的距离?
要找 "邻居",首先得定义 "远近"------ 这就是 KNN 中的 "距离度量"。文档中重点介绍了两种最常用的距离计算方法,我们用 "电影镜头数" 的二维场景(x = 打斗镜头,y = 接吻镜头)来理解:
1. 欧式距离:两点之间的 "直线距离"
欧式距离(欧几里得距离)是我们最熟悉的距离概念:比如在地图上两点之间的直线距离,就是二维空间的欧式距离。
- 二维空间公式(适用于电影分类,只有 "打斗" 和 "接吻" 两个特征): 若已知电影 A(x₁,y₁)、未知电影 B(x₂,y₂),则距离为: \(d = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2}\)
- 三维空间公式(比如多了 "时长" 一个特征): \(d = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2 + (z_1 - z_2)^2}\)
- n 维空间公式(适用于更多特征,如电影的 "评分、时长、演员数量" 等): \(d = \sqrt{\sum_{i=1}^n (x_{1i} - x_{2i})^2}\)
举个例子:已知爱情片《California Man》(打斗 3 次,接吻 104 次),未知电影(18,90),两者的欧式距离是: \(d = \sqrt{(18-3)^2 + (90-104)^2} = \sqrt{15^2 + (-14)^2} = \sqrt{421} ≈ 20.5\)
2. 曼哈顿距离:"出租车绕街区" 的距离
曼哈顿距离得名于纽约曼哈顿的街区布局 ------ 出租车从 A 点到 B 点,不能直接穿楼,只能沿街道横竖走,总路程就是曼哈顿距离。
- 二维空间公式: \(d = |x_1 - x_2| + |y_1 - y_2|\)
还是用上面的例子,《California Man》与未知电影的曼哈顿距离是: \(d = |18-3| + |90-104| = 15 + 14 = 29\)
简单来说:欧式距离是 "直线抄近路",曼哈顿距离是 "绕街区走直线",具体用哪种,取决于数据特征的场景(比如地图导航用曼哈顿,两点定位用欧式)。
三、K 值怎么选?------ 影响结果的关键参数
KNN 中的 "K" 是 "邻居数量",这个参数直接决定分类结果。文档中用一个直观的例子说明了差异:
- 当 K=3 时,新数据的 3 个邻居中 "蓝三角" 更多,判定为蓝三角;
- 当 K=5 时,5 个邻居中 "红圆" 更多,判定为红圆。
K 值选择的 3 个核心原则
- K 太小易 "偏激":比如 K=1 时,只看最近的 1 个邻居 ------ 如果这个邻居是个 "特例"(比如一部爱情片偏偏有很多打斗镜头),新数据就会被误判,导致 "过拟合"(模型太死板,只认个别案例);
- K 太大易 "模糊":比如 K=100(样本集只有 20 个数据),相当于把所有样本都当邻居,远的、不相关的也算进来,导致 "欠拟合"(模型分不清类别,结果不准);
- 常规范围:文档中明确提到 "K 一般不大于 20",实际应用中常用奇数(如 3、5、7),避免出现 "平票"(比如 K=4 时,2 个爱情片、2 个动作片,无法判断)。
四、实战 1:用 KNN 给未知电影 "贴标签"
文档中给出了一组真实的电影数据,我们用 KNN 亲手判断 "未知电影(打斗 18 次,接吻 90 次)" 的类别:
已知电影数据(特征:打斗镜头、接吻镜头)
电影类型 | 电影名称 | 打斗镜头 | 接吻镜头 |
---|---|---|---|
爱情片 | 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 II | 98 | 2 |
步骤 1:计算未知电影与所有已知电影的欧式距离
已知电影 | 欧式距离(与未知电影) |
---|---|
California Man | ≈20.5 |
He's Not Really into Dudes | ≈18.9 |
Beautiful Woman | ≈19.2 |
Kevin Longblade | ≈118.8 |
Robo Slayer 3000 | ≈117.1 |
Amped II | ≈116.2 |
步骤 2:按距离排序,选 K=3 个邻居
最近的 3 个邻居依次是:
- He's Not Really into Dudes(爱情片,距离 18.9)
- Beautiful Woman(爱情片,距离 19.2)
- California Man(爱情片,距离 20.5)
步骤 3:判定类别
3 个邻居全是 "爱情片",因此未知电影归为爱情片------ 这和我们的直觉一致(接吻镜头 90 次远多于打斗镜头 18 次)。
五、实战 2:用 Sklearn 实现鸢尾花分类
除了电影,KNN 也能解决生物分类问题(如鸢尾花品种识别)。文档中给出了基于 Sklearn 的实战代码,我们一步步拆解,零基础也能跑通:
鸢尾花数据集背景
鸢尾花数据集包含 3 个品种(Setosa、Versicolor、Virginica),每个品种有 150 个样本,每个样本有 4 个特征:
- 花萼长度(sepal length)
- 花萼宽度(sepal width)
- 花瓣长度(petal length)
- 花瓣宽度(petal width)
完整代码与解析
# 1. 导入必要的库
from sklearn.datasets import load_iris # 加载鸢尾花数据集
from sklearn.model_selection import train_test_split # 划分训练集/测试集
from sklearn.neighbors import KNeighborsClassifier # KNN分类器
# 2. 加载数据集并查看基本信息
iris = datasets.load_iris()
print("鸢尾花特征名称:", iris.feature_names) # 输出4个特征名
print("鸢尾花品种名称:", iris.target_names) # 输出3个品种名(0=setosa,1=versicolor,2=virginica)
print("数据集形状(样本数, 特征数):", iris.data.shape) # (150, 4)
# 3. 划分训练集(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固定划分方式,保证结果可复现
)
# 4. 创建KNN模型并训练
# n_neighbors=5(选5个邻居),metric="euclidean"(用欧式距离)
knn = KNeighborsClassifier(n_neighbors=5, metric="euclidean")
knn.fit(x_train, y_train) # 用训练集训练模型(KNN实际是"记住"训练数据)
# 5. 评估模型性能
train_score = knn.score(x_train, y_train) # 训练集准确率(模型对训练数据的拟合程度)
test_score = knn.score(x_test, y_test) # 测试集准确率(模型对新数据的泛化能力)
print(f"训练集准确率:{train_score:.2f}") # 通常接近1.0,如0.98
print(f"测试集准确率:{test_score:.2f}") # 好的模型应接近训练集准确率,如0.98
# 6. 用模型预测新数据
# 假设新采集一朵鸢尾花:花萼长5.1cm、宽3.5cm,花瓣长1.4cm、宽0.2cm
new_iris = [[5.1, 3.5, 1.4, 0.2]]
y_pred = knn.predict(new_iris) # 预测品种
print("新鸢尾花品种:", iris.target_names[y_pred[0]]) # 输出setosa(山鸢尾)
代码运行结果解读
- 训练集准确率≈0.98:说明模型对已知鸢尾花数据的分类很准;
- 测试集准确率≈0.98:说明模型对 "没见过" 的新鸢尾花也能准确分类(泛化能力好);
- 新数据预测为 Setosa:符合该品种 "花瓣短、花萼宽" 的特征,结果正确。
六、KNN 的优缺点:什么时候该用它?
通过前面的案例,我们能直观感受到 KNN 的特点,总结一下它的适用场景和局限:
优点 | 缺点 |
---|---|
1. 逻辑简单,零基础易理解,无需复杂训练; 2. 适合多分类问题(如电影分 3 类、鸢尾花分 3 类); 3. 对异常值不敏感(只要 K 选得合理,个别异常邻居不影响结果)。 | 1. 计算量大:样本数越多,要计算的距离越多(比如 10 万样本,新数据要算 10 万次距离); 2. 受特征尺度影响:比如 "电影评分(1-10)" 和 "镜头数(0-200)",镜头数的数值太大,会主导距离计算(需先归一化数据); 3. 不适合高维数据:特征太多(如 1000 个特征)时,距离计算会 "失效"(维度灾难)。 |
七、总结:KNN 的核心是 "找对邻居"
回顾全文,KNN 算法的本质其实是 "基于相似性的投票":
- 核心步骤:算距离→找邻居→看投票;
- 关键参数:K 值(一般≤20,选奇数);
- 距离选择:欧式距离(直线相似)、曼哈顿距离(街区相似);
- 实战技巧:用 Sklearn 快速实现,记得划分训练集 / 测试集,评估泛化能力。
如果你是机器学习新手,KNN 绝对是值得入门的第一个算法 ------ 它没有复杂的数学推导,却能解决很多实际问题(电影分类、推荐系统、疾病诊断等)。不妨试着修改鸢尾花代码中的 K 值(比如 K=3 或 K=7),看看测试集准确率会不会变化;或者用电影数据的曼哈顿距离重新计算,感受不同距离度量的影响。