目标
在本章中,我们将了解 k-Nearest Neighbour (kNN) 算法的概念。
算法原理
kNN 是监督学习中最简单的分类算法之一。其原理是在特征空间中搜索与测试数据最匹配的数据。我们将通过下面的图片来了解它。

图片中有两个系列: 蓝色正方形和红色三角形。我们将每个家庭称为一个 “类”。他们的房子显示在他们的城市地图中,我们称之为特征空间。我们可以将特征空间视为所有数据的 投影空间 。例如,考虑一个二维坐标空间。每个基准都有两个特征,即 x 坐标和 y 坐标。你可以在二维坐标空间中表示这个基准,对吗?现在假设有三个特征,您将需要三维空间。现在考虑 N 个特征:你需要 N 维空间,对吗?这个 N 维空间就是特征空间。在我们的图片中,你可以把它看作有两个特征的二维案例。
现在考虑一下,如果镇上来了一位新成员并创建了一个新家(如绿色圆圈所示),会发生什么情况。他应该被添加到蓝色或红色家庭(或类别)中。我们把这个过程称为 “分类”。这个新成员到底应该如何分类呢?既然我们使用的是 kNN,那么就让我们应用该算法。
一个简单的方法是检查谁是他的 近邻 。从图像中可以看出,他是红三角家族的成员。因此他被归类为红三角。这种方法被简单地称为最近邻分类法,因为分类只取决于最近邻。
但这种方法存在一个问题!红色三角形可能是最近的邻居,但如果附近也有很多蓝色方块呢?那么蓝方块在该区域的优势就会大于红三角,所以只检查最近的一个是不够的。相反,我们可能需要查看最近的 k 个家族。然后,无论哪个家族的成员占多数,新来的人都应该属于那个家族。在我们的图片中,假设 k=3,即考虑 3 个最近的邻居。新成员有两个红邻和一个蓝邻(有两个蓝邻距离相等,但由于 k=3,我们只能取其中一个),所以他还是应该加入红邻家族。但是如果 k=7 呢?那么他就有 5 个蓝色邻居和 2 个红色邻居,应该加入蓝色家族。请注意,如果 k 不是奇数,就会出现平局,就像上述 k=4 的情况一样。我们会发现,新成员的四个近邻中,有 2 个红色近邻和 2 个蓝色近邻,因此我们需要选择一种方法来打破平局,从而进行分类。因此,再次重申,由于分类取决于 k 个近邻,因此这种方法称为 k-近邻法。
同样,在 kNN 中,我们确实考虑了 k 个近邻,但我们对所有近邻都给予了同等重视,对吗?这合理吗?例如,以 k=4 的并列情况为例。我们可以看到,2 个红色邻居实际上比另外 2 个蓝色邻居更接近新成员,因此他更有资格被加入红色家族。我们如何从数学上解释这一点呢?我们根据每个邻居与新成员的距离给他们加一些权重:离他近的邻居权重高,离他远的邻居权重低。然后,我们将每个家族的总权重分别相加,并将新来者归入总权重较高的家族。这就是所谓的修正 kNN 或加权 kNN。
那么,你在这里看到了哪些重要的东西呢?
- 因为我们必须检查新来者到所有现有房屋的距离,以找到最近的邻居,所以你需要掌握镇上所有房屋的信息,对吗?如果有大量的房屋和家庭,就会占用大量内存和计算时间。
- 任何 "训练 "或准备的时间几乎为零。我们的 "学习 "只包括记忆(存储)数据,然后再进行测试和分类。
现在让我们看看这种算法在 OpenCV 中的应用。
OpenCV 中的 kNN
我们将在这里做一个简单的例子,和上面一样,有两个族(类)。在下一章中,我们将做一个更好的例子。
在这里,我们将红色系列标记为 0 类(用 0 表示),蓝色系列标记为 1 类(用 1 表示)。我们创建 25 个邻域或 25 个训练数据,并将它们分别标记为 Class-0 或 Class-1。我们可以借助 NumPy 中的随机数生成器来完成这项工作。
然后,我们可以借助 Matplotlib 绘制结果。红色邻域显示为红色三角形,蓝色邻域显示为蓝色正方形。
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
# 包含 25 个已知/训练数据的 (x,y) 值的特征集
trainData = np.random.randint(0,100,(25,2)).astype(np.float32)
# 给每个数据贴上红色或蓝色标签,数字为 0 和 1
responses = np.random.randint(0,2,(25,1)).astype(np.float32)
# 提取红色相邻数据并绘制
red = trainData[responses.ravel()==0]
plt.scatter(red[:,0],red[:,1],80,'r','^')
# 取蓝色邻域并绘制
blue = trainData[responses.ravel()==1]
plt.scatter(blue[:,0],blue[:,1],80,'b','s')
plt.show()
您将得到与第一张图片类似的结果。由于使用的是随机数生成器,所以每次运行代码都会得到不同的数据。
接下来启动 kNN 算法,并传递 trainData 和响应以训练 kNN。(在引擎盖下,它会构建一棵搜索树:有关更多信息,请参阅下面的 "附加资源 "部分)。
然后,我们将使用 OpenCV 中的 kNN 将一个新来者分类为一个家族。在运行 kNN 之前,我们需要了解一些测试数据(新来者的数据)。我们的数据应该是一个浮点数组,大小为 测试数据数×特征数 。然后,我们要找到新来者的近邻。我们可以指定 k:我们想要多少个邻居。(这里我们用的是 3):
- 根据我们之前看到的 kNN 理论,给新来者贴上的标签。如果要使用最近邻算法,只需指定 k=1。
- k 个最近邻居的标签。
- new-comer(新来者)到每个近邻的相应距离。
让我们来看看它是如何工作的。新来者的标记为绿色。
newcomer = np.random.randint(0,100,(1,2)).astype(np.float32)
plt.scatter(newcomer[:,0],newcomer[:,1],80,'g','o')
knn = cv.ml.KNearest_create()
knn.train(trainData, cv.ml.ROW_SAMPLE, responses)
ret, results, neighbours ,dist = knn.findNearest(newcomer, 3)
print( "result: {}\n".format(results) )
print( "neighbours: {}\n".format(neighbours) )
print( "distance: {}\n".format(dist) )
plt.show()
我得到了以下结果:
result: [[ 1.]]
neighbours: [[ 1. 1. 1.]]
distance: [[ 53. 58. 61.]]
上面说,我们新来的 3 个最近的邻居都来自蓝色家族。因此,他被标记为蓝色家族的一员。从下面的图中可以明显看出这一点:

如果有多个新来者(测试数据),可以直接以数组形式传递。相应的结果也会以数组形式出现。
# 10 个新来者
newcomers = np.random.randint(0,100,(10,2)).astype(np.float32)
ret, results,neighbours,dist = knn.findNearest(newcomer, 3)
# 结果也将包含 10 个标签。
其他资源
NPTEL 模式识别笔记,第 11 章
维基百科关于近邻搜索的文章
维基百科上关于 k-d 树的文章
练习
尝试用更多类别和不同的 k 选择重复上述过程。在相同的二维特征空间中,如果类别越多,选择 k 的难度是否越大?
本文详细介绍了k-NearestNeighbour(kNN)算法的基本概念,包括其原理、分类过程以及解决平局问题的方法。重点展示了在OpenCV中的应用实例,涉及数据准备、训练和测试。最后讨论了kNN在多类别和不同k值下的挑战。
74

被折叠的 条评论
为什么被折叠?



