机器学习—K近邻算法(KNN)
第一部分:KNN算法详解 (K-近邻算法)
1. 核心思想
KNN(K-Nearest Neighbors)是一种监督学习算法,既可以用于分类任务,也可以用于回归任务。它的核心思想是:一个样本的类别/数值,由其在特征空间中最邻近的 K 个样本的类别/数值来决定。
2. 算法步骤 (以分类任务为例)
假设我们有一个带标签的训练数据集,现在来了一个新的、没有标签的数据点,我们要预测它的类别。
Step 1: 确定超参数 K
K是一个正整数,代表我们要参考“邻居”的数量。这个值需要我们预先设定。比如,我们选择 K=3。
Step 2: 计算距离
计算这个新的数据点与训练集中每一个数据点的距离。距离的度量方式有很多种,最常用的是:
欧氏距离 (Euclidean Distance):
$$ d(x, y) = \sqrt{\sum_{i=1}^{n}(x_i - y_i)^2} $$曼哈顿距离 (Manhattan Distance):
$$ d(x, y) = \sum_{i=1}^{n}|x_i - y_i| $$闵可夫斯基距离 (Minkowski Distance):
$$ d(x, y) = (\sum_{i=1}^{n}|x_i - y_i|^p)^{1/p} $$
Step 3: 找到最近的 K 个邻居
根据上一步计算出的距离,对所有训练样本进行排序,找出距离最近的 K 个样本。
Step 4: 做出决策
- 用于分类: 在这 K 个邻居中,采用“少数服从多数”的原则,哪个类别的样本最多,新的数据点就被预测为哪个类别。
- 用于回归: 预测一个具体的数值。通常是取这 K 个邻居的数值的平均值或加权平均值(例如,距离越近的邻居权重越高)。
第二部分:以MNIST数据集为例,说明距离的计算过程
第一步:理解数据本身——图像如何表示为向量
MNIST 数据集中的每一张图片都是一个 28x28 像素的灰度图。
- 图像: 在我们眼中,它是一张图片,比如一个手写的 “7”。
- 计算机的表示: 对计算机来说,它是一个 28x28 的矩阵。矩阵中的每一个元素代表一个像素点,其值在 0 到 255 之间,表示该点的灰度(0代表纯黑,255代表纯白)。
为了在 KNN 算法中计算距离,我们首先需要把这个二维的图像矩阵“拉平”(Flatten),变成一个一维的向量。
- 原始矩阵: 一个 28x28 的矩阵
- 拉平操作: 将矩阵的第一行、第二行、…、第二十八行,依次拼接起来,形成一个长长的一维向量。
- 特征向量: 这个向量的维度就是
28 * 28 = 784
。所以,每一张手写数字图片,都被表示为了一个 784 维空间中的一个点。
这个 784 维的向量就是这张图片的特征向量 (Feature Vector)。
第二步:计算距离——在784维空间中丈量
现在,我们有了两个手写数字图片,比如图片A和图片B。它们分别被转换成了两个 784 维的向量:
- 向量
A = (a₁, a₂, a₃, ..., a₇₈₄)
- 向量
B = (b₁, b₂, b₃, ..., b₇₈₄)
其中 aᵢ
和 bᵢ
分别是两张图片第 i
个像素点的灰度值。
接下来,我们就可以像在二维、三维空间中一样,计算这两个高维向量之间的距离了。最常用的仍然是欧氏距离。
欧氏距离计算公式:
$$ d(A, B) = \sqrt{(a_1 - b_1)^2 + (a_2 - b_2)^2 + \dots + (a_{784} - b_{784})^2} $$
$$ d(A, B) = \sqrt{\sum_{i=1}^{784}(a_i - b_i)^2} $$
直观理解这个距离的含义:
这个距离衡量的是两张图片在逐个像素点上的差异程度。
- 如果两张图片非常相似(比如两个不同人写的、但都很标准的 “1”),那么它们对应位置的像素值会很接近,
(aᵢ - bᵢ)²
的值会很小,最终计算出的总距离也会很小。 - 如果两张图片差异巨大(比如一个是 “1”,一个是 “8”),那么它们在很多位置上的像素值都会有很大差别,
(aᵢ - bᵢ)²
的值会很大,最终计算出的总距离也就会很大。
举一个简化的例子:
假设我们处理的是 3x3 的迷你图片,而不是 28x28。
图片A (一个”X”):
1 | [[255, 0, 255], |
拉平后的向量 A = (255, 0, 255, 0, 255, 0, 255, 0, 255)
图片B (一个”O”):
1 | [[ 0, 255, 0], |
拉平后的向量 B = (0, 255, 0, 255, 0, 255, 0, 255, 0)
计算A和B的欧氏距离:
$$ d(A, B) = \sqrt{ (255-0)^2 + (0-255)^2 + (255-0)^2 + \dots } $$
你会发现每一项都是 (±255)²
,这个距离会非常大。
现在,再来一张图片C (一个略有不同的”X”):
1 | [[250, 5, 250], |
拉平后的向量 C = (250, 5, 250, 10, 245, 10, 250, 5, 250)
计算A和C的欧氏距离:
$$ d(A, C) = \sqrt{ (255-250)^2 + (0-5)^2 + (255-250)^2 + \dots } $$
$$ d(A, C) = \sqrt{ 5^2 + (-5)^2 + 5^2 + \dots } $$
你会发现每一项的差值都很小,所以 d(A, C)
会远远小于 d(A, B)
。
因此,在KNN算法中,如果拿图片C去预测,它很可能会找到图片A作为它的近邻,从而被正确地分类为”X”。
面试中的引申问题:
数据归一化在这里重要吗?
- 回答: 是的,很重要。虽然MNIST所有特征(像素值)的量纲都是一样的(0-255),但进行归一化(比如将所有像素值除以255,缩放到[0, 1]区间)是一个非常好的习惯。这样做可以改善模型的数值稳定性,并且在后续使用梯度下降等优化算法时(虽然KNN不用,但深度学习用)能加快收敛速度。所以,这是一个标准的预处理步骤。
784维是不是太高了?这有什么问题吗?
- 回答: 是的,784维属于高维数据。这会引出我们之前讨论过的**“维度灾难”**问题。在高维空间中,数据点会变得稀疏,距离的区分度可能会下降。尽管如此,KNN在MNIST上依然能取得不错的(超过95%)的准确率,证明了这种基于像素的距离度量在一定程度上是有效的。
- 进一步加分: 我们可以通过主成分分析(PCA)等降维技术,将784维的特征向量降到更低的维度(比如50维或100维),同时保留大部分信息。在降维后的空间里再使用KNN,通常可以大幅提升计算速度,并且有时还能因为去除了噪声而提升模型性能。
第三部分:以MNIST数据集为例,说明KNN的执行步骤
数据集背景:
- 训练集 (Training Set): 60,000 张 28x28 像素的图片,每张图片都有一个明确的标签(0, 1, 2, …, 9)。这是我们的“参考答案库”。
- 测试集 (Test Set): 10,000 张 28x28 像素的图片,同样带有标签。我们用它来评估模型的效果,但在预测时,我们会假装不知道它的标签。
KNN在MNIST上的执行步骤
预备阶段:数据准备 (Data Preparation)
在运行算法之前,我们必须先处理数据。
数据加载: 从文件中加载60,000张训练图片、对应的60,000个标签,以及10,000张测试图片和对应的10,000个标签。
数据“拉平”(Flattening):
- 每一张图片是一个
28x28
的二维矩阵。 - 为了计算距离,我们将其转换为一个
1x784
的一维向量(特征向量)。 - 操作后,我们的训练集就变成了一个
60000 x 784
的巨大矩阵,每一行代表一张图片。
- 每一张图片是一个
数据归一化 (Normalization):
- 原始像素值的范围是 [0, 255]。不同图片中笔画的深浅可能会影响距离计算。
- 我们将所有像素值都除以 255,将它们缩放到 [0, 1] 区间。这是一个标准的预处理步骤,能提升模型的稳定性和性能。
- 现在,训练集矩阵里的每个元素都在0和1之间。
算法执行阶段 (以预测一张新的测试图片为例)
假设我们从10,000张测试图片中,拿出第一张图片,想预测它是什么数字。我们称之为test_image_1
。
第1步:确定超参数 K
这是我们必须预先决定的。K值的选择会影响最终结果。我们先选择一个常见的、较小的值,比如 K=5。
第2步:计算距离
这是最核心、也是最耗时的一步。
- 拿出
test_image_1
的784维向量。 - 将这个向量与训练集中每一张图片(共60,000张)的784维向量,逐一计算欧氏距离。
- 这个过程会产生 60,000个 距离值。每一个距离值都代表了
test_image_1
与某一张训练图片的“相似程度”(距离越小越相似)。
第3步:找到最近的 K 个邻居
- 我们有了一个包含60,000个距离值的列表。
- 对这个列表进行升序排序,找到值最小的前 K 个。在我们这个例子里,就是找到最小的 5个 距离值。
- 同时,记录下这5个距离值所对应的训练图片的原始标签。
第4步:投票决定类别
- 假设我们找到的5个最近邻居,它们的标签分别是:
{7, 9, 7, 1, 7}
。 - 我们进行“投票统计”:
- 数字 “7” 出现了 3 次。
- 数字 “9” 出现了 1 次。
- 数字 “1” 出现了 1 次。
- 根据“少数服从多数”原则,数字 “7” 的票数最多。
第5步:做出预测
- 我们的KNN模型最终预测
test_image_1
的类别为 7。
模型评估阶段 (Evaluation)
预测完一张图片还不够,我们需要知道模型的整体性能。
第6步:评估准确率
- 我们拿出
test_image_1
的真实标签。假设它的真实标签确实是 “7”,那么这次预测就是正确的。如果真实标签是 “2”,那这次预测就是错误的。 - 重复第2步到第5步,对测试集中所有10,000张图片都进行一次预测。
- 统计所有预测正确的次数。
- 计算最终的准确率:
$$ \text{Accuracy} = \frac{\text{Number of Correct Predictions}}{\text{Total Number of Test Images}} = \frac{\text{预测正确的图片数}}{10000} $$
例如,如果我们正确预测了9,680张图片,那么模型的准确率就是 9680 / 10000 = 96.8%
。
第四部分:核心考察点
考点1:KNN的优缺点是什么?
优点 (Pros):
- 简单直观: 算法思想简单,易于理解和实现。是很好的入门和基线模型(Baseline)。
- 非参数模型 (Non-parametric): 对数据分布没有假设(比如不像线性回归那样假设数据是线性的)。这使得KNN在处理一些非线性、复杂边界问题时有奇效。
- 无需训练 (Lazy Learning): KNN是“懒惰学习”的代表。它没有显式的训练过程,只是把训练数据存储起来。训练时间开销为零。
缺点 (Cons):
- 计算成本高昂: 预测阶段的计算量巨大。对于一个新样本,需要和所有N个训练样本计算距离,时间复杂度为O(N*d),其中d是特征维度。当数据集很大时,预测会非常慢。
- 内存占用大: 需要存储全部训练数据,对内存消耗大。
- 对K值敏感: K值的选择直接影响模型性能,需要通过交叉验证等方法小心选择。
- 受样本不均衡问题影响大: 如果某些类别的样本数量远多于其他类别,那么在投票时,数量多的类别会占据天然优势。
- 维度灾难 (Curse of Dimensionality): 随着特征维度的增加,KNN的性能会急剧下降。
考点2:K值的选择与影响 (Bias-Variance Tradeoff)
较小的 K 值:
- 模型会变得更复杂,决策边界会更不规则。
- 容易受到噪声数据的影响,导致过拟合 (Overfitting)。
- 表现为低偏差 (Low Bias) 和 高方差 (High Variance)。
较大的 K 值:
- 模型会变得更简单,决策边界会更平滑。
- 会忽略数据中局部的、细微的结构,导致欠拟合 (Underfitting)。
- 表现为高偏差 (High Bias) 和 低方差 (Low Variance)。
如何选择K值?
- 交叉验证 (Cross-Validation): 最常用的方法。将训练集分成多份,轮流作为验证集来测试不同K值的效果,选择在验证集上平均表现最好的K值。
- 通常K值会选择一个奇数,以避免在二分类问题中出现票数相等的情况。
考点3:为什么KNN需要对数据进行归一化(Normalization/Standardization)?
- 核心原因: KNN是基于距离的算法。如果不同特征的量纲(尺度)差异巨大,那么距离的计算将完全由尺度大的特征主导。
- 举例说明: 假设有两个特征:身高(单位:cm,范围150-200)和体重(单位:kg,范围40-100)。在计算欧氏距离时,身高的差值平方项会比体重的差值平方项大得多,这会使得模型在计算距离时,几乎只考虑了身高,而忽略了体重这个特征。这显然是不合理的。
- 解决方案:
- 归一化 (Normalization): 将数据缩放到 [0, 1] 区间。公式:
X_norm = (X - X_min) / (X_max - X_min)
- 标准化 (Standardization): 将数据缩放到均值为0,标准差为1的分布。公式:
X_std = (X - mean(X)) / std(X)
- 面试时能说出这个原因并给出例子,就是满分回答。
- 归一化 (Normalization): 将数据缩放到 [0, 1] 区间。公式:
考点4:如何理解KNN中的“维度灾难”?
- 直观理解: 在高维空间中,数据点会变得非常稀疏。想象一下,在一个一维线段上撒10个点很容易密集,但在一个三维立方体里撒10个点,它们会非常分散。
- 距离失效: 在高维空间中,任意两点之间的欧氏距离的差值会变得很小,趋向于一致。换句话说,对于一个给定的查询点,它到“最近”邻居的距离和到“最远”邻居的距离相差无几。这使得“近邻”这个概念失去了意义。
- 如何缓解?
- 特征选择/降维: 使用PCA等方法对数据进行降维,去除冗余和不重要的特征。
考点5:如何提高KNN的效率?
- 问题根源: KNN的瓶颈在于预测时需要进行大量的距离计算(暴力搜索)。
- 解决方案: 使用空间索引数据结构来加速近邻搜索,避免全局扫描。
- KD-Tree (K-Dimensional Tree): 这是一种对K维空间中的点进行划分的数据结构。它通过在不同维度上轮流切分数据空间,构建一个二叉树。在搜索时,可以快速剪枝掉那些不可能包含最近邻居的子空间,从而大大减少距离计算的次数。它在低维(例如d < 20)时效率很高。
- Ball-Tree: 当维度非常高时,KD-Tree的效率会下降。Ball-Tree通过将数据点划分在一系列嵌套的超球体(Hypersphere)中来构建树。它对高维数据更具鲁棒性。
考点6:KNN 和 K-Means 有什么区别?
特性 | KNN (K-近邻) | K-Means (K-均值) |
---|---|---|
算法类别 | 监督学习 (Supervised Learning) | 无监督学习 (Unsupervised Learning) |
解决问题 | 分类 或 回归 | 聚类 (Clustering) |
数据要求 | 需要带标签的训练数据 | 只需要数据点,无需标签 |
核心思想 | 基于邻居投票/平均来预测新样本 | 找到K个簇中心,使数据点到其所属簇中心的距离之和最小 |
“K”的含义 | 邻居的数量(超参数) | 簇的数量(超参数) |