一、距离度量
首先,数据集中的特征(列)数量不是选择用于 kNN 的距离度量的因素。有不少已发表的研究正是针对这个问题,通常的比较基础是:
如果您事先不了解数据的抽样分布,至少有一项(有据可查且彻底的)研究得出结论,欧几里得距离是最佳选择。
用于大型 Web 推荐引擎以及当前学术研究的 YEuclidean 度量。欧几里得计算的距离具有直观的意义和计算尺度——即欧几里得距离的计算方式相同,无论两点是在二维空间还是在二十二维空间。
它对我来说只失败了几次,每个案例欧几里得距离都失败了,因为底层(笛卡尔)坐标系是一个糟糕的选择。而且您通常会认识到这一点,因为例如路径长度(距离)不再是相加的 - 例如,当度量空间是棋盘时,曼哈顿距离比欧几里得更好,同样当度量空间是地球并且您的距离是反式时- 大陆航班,适合极坐标系的距离度量是个好主意(例如,伦敦到维也纳是 2.5 小时,维也纳到圣彼得堡是另外 3 小时,或多或少在同一方向,但伦敦到圣彼得堡. 圣彼得堡不是 5.5 小时,而是 3 小时多一点。)
但除了您的数据属于非笛卡尔坐标系的情况之外,距离度量的选择通常并不重要。(参见一个 CS 学生的这篇博文,通过检查它们对 kNN 分类器的影响来比较几个距离度量——卡方给出了最好的结果,但差异并不大;更全面的研究在学术论文中,Comparative Study of最近邻的距离函数——Mahalanobis(本质上是欧几里得归一化以考虑维度协方差)在这项研究中是最好的。
一个重要的附带条件:要使距离度量计算有意义,您必须重新缩放你的数据——如果不这样做,很少有可能建立一个 kNN 模型来生成准确的预测。例如,如果您正在构建一个 kNN 模型来预测运动表现,并且您的期望变量是身高 (cm)、体重 (kg)、体脂 (%) 和静息脉搏(每分钟心跳数),那么典型的数据点可能看起来像这样:[ 180.4, 66.1, 11.3, 71 ]。显然,距离计算将以身高为主,而体脂百分比的贡献几乎可以忽略不计。换句话说,如果数据报告不同,体重以克而不是公斤为单位,那么 86.1 的原始值将是 86,100,这将对您的结果产生很大影响,这正是您所做的不想。
X_new = (X_old - mu) / sigma
二、数据结构
如果您担心 kd-tree 结构的性能,Voronoi Tessellation是一个概念上简单的容器,但它会大大提高性能并比 kd-Trees 更好地扩展。

这不是保留 kNN 训练数据的最常用方法,尽管为此目的应用 VT 以及随之而来的性能优势已得到充分证明(例如,请参阅此Microsoft Research 报告)。这样做的实际意义在于,如果您使用的是“主流”语言(例如,在TIOBE 索引中),那么您应该找到一个库来执行 VT。我知道在 Python 和 R 中,每种语言都有多个选项(例如,用于 R 的voronoi包在CRAN上可用)
对 kNN 使用 VT 的工作方式如下:
从您的数据中,随机选择 w 个点——这些是您的 Voronoi 中心。Voronoi 单元封装了离每个中心最近的所有相邻点。想象一下,如果您为每个 Voronoi 中心分配不同的颜色,那么分配给给定中心的每个点都被涂上该颜色。只要你有足够的密度,这样做会很好地显示每个 Voronoi 中心的边界(作为分隔两种颜色的边界。
如何选择 Voronoi 中心?我使用两个正交指南。随机选择 w 个点后,计算训练数据的 VT。接下来检查分配给每个 Voronoi 中心的数据点的数量——这些值应该大致相同(给定数据空间中的均匀点密度)。在二维中,这将导致 VT 具有相同大小的图块。这是第一条规则,这是第二条规则。通过迭代选择 w——以 w 作为变量参数运行 kNN 算法,并测量性能(通过查询 VT 返回预测所需的时间)。
所以想象一下你有一百万个数据点......如果这些点被保存在一个普通的二维数据结构中,或者在一个 kd-tree 中,你将平均为每个点执行几百万个距离计算您希望预测其响应变量的新数据点。当然,这些计算是在单个数据集上执行的。对于 V/T,最近邻搜索分两步依次执行,针对两个不同的数据群——首先针对 Voronoi 中心,然后一旦找到最近的中心,单元格内的点对应于搜索该中心以找到实际的最近邻居(通过连续的距离计算)结合起来,这两个查找比单个蛮力查找要快得多。这很容易看出:对于 1M 数据点,假设您选择 250 个 Voronoi 中心来细分您的数据空间。平均而言,每个 Voronoi 单元将有 4,000 个数据点。因此,您无需执行平均 500,000 次距离计算(蛮力),而是执行得更少,平均仅为 125 + 2,000。
三、计算结果(预测的响应变量)
从一组 kNN 训练数据中计算预测值有两个步骤。第一个是识别 n,或用于此计算的最近邻居的数量。第二个是如何加权他们对预测值的贡献。
W/r/t 第一个组件,您可以通过解决优化问题(非常类似于最小二乘优化)来确定 n 的最佳值。这就是理论;在实践中,大多数人只使用 n=3。无论如何,在 n=1、n=2、n=3 等的一组测试实例(以计算预测值)上运行您的 kNN 算法并将误差绘制为 n 的函数是很简单的。如果您只是想要一个合理的 n 值来开始,那么再次使用 n = 3。
第二个部分是如何加权每个邻居的贡献(假设 n > 1)。
最简单的加权技术只是将每个邻居乘以一个加权系数,该系数只是 1/(dist * K),或者是从该邻居到测试实例的距离的倒数,通常乘以一些经验得出的常数 K。我不喜欢这种技术,因为它经常过度加权最近的邻居(同时降低更远的邻居的权重);这样做的意义在于,给定的预测几乎可以完全依赖于单个邻居,这反过来又增加了算法对噪声的敏感性。
一个必须更好的加权函数,它基本上避免了这个限制是高斯函数,在 python 中,它看起来像这样:
def weight_gauss(dist, sig=2.0) :
return math.e**(-dist**2/(2*sig**2))
要使用您的 kNN 代码计算预测值,您将识别您希望预测其响应变量的数据点的 n 个最近邻居(“测试实例”),然后调用 weight_gauss 函数,对 n 个邻居中的每一个调用一次,通过在每个邻居之间的距离测试点。此函数将返回每个邻居的权重,然后将其用作加权平均计算中该邻居的系数。