I. 距离度量
首先,在选择kNN中使用的距离度量时,数据集中特征(列)的数量并不是一个因素。已经有很多发表的研究专门针对这个问题,比较常见的基础是:
如果你没有关于你的数据被采样自哪种分布的先前知识,至少有一项(经过充分文献记录和详细描述的)研究认为欧氏距离是最佳选择。
在巨型网络推荐引擎以及当前学术研究中,欧几里得度量被用作计算距离的方法。欧几里得距离具有直观的意义,并且计算规模可扩展——即无论两点处于二维空间还是22维空间中,欧几里得距离的计算方式都相同。
它只在我几次失败时失效,每一次欧几里得距离失败都是因为底层(笛卡尔)坐标系选择不当。你通常会认识到这一点,例如路径长度(距离)不再是可加的——例如当度量空间是棋盘时,曼哈顿距离比欧几里得距离更好,同样地,当度量空间是地球时,如果你的距离是跨越多个大陆的航班,则适合于极坐标系的距离度量是一个好主意(例如,伦敦到维也纳需要2.5小时,维也纳到圣彼得堡又需要另外3小时,基本上是同一个方向,但是伦敦到圣彼得堡不是5.5小时,而是略超过3小时)。
但除了那些数据属于非笛卡尔坐标系的情况外,距离度量的选择通常并不重要。 (请参见这位CS学生的博客文章,通过检查距离度量对kNN分类器的影响来比较几种距离度量-卡方距离给出了最佳结果,但差异并不大; 一份更全面的研究在学术论文中,最近邻距离函数的比较研究-马哈拉诺比斯(基本上是欧几里得距离通过考虑维度协方差而归一化)在这项研究中表现最佳。)
重要提示:为了使距离度量计算有意义,您必须重新缩放数据——很少有可能构建一个kNN模型来生成准确的预测而不进行此操作。例如,如果您正在构建一个kNN模型来预测运动表现,并且您的期望变量是身高(厘米)、体重(千克)、体脂肪(%)和静息心率(每分钟心跳数),那么典型的数据点可能看起来像这样:[180.4,66.1,11.3,71]。显然,距离计算将被身高主导,而体脂肪百分比的贡献几乎可以忽略不计。换句话说,如果数据以不同的方式报告,例如将体重以克为单位而不是千克为单位,则原始值86.1将变成86100,这将对结果产生很大影响,这正是您不想要的。最常见的缩放技术可能是减去平均值并除以标准差(平均值和标准差分别针对数据集中的每一列或特征进行计算;X指数据行中的单个条目/单元格):
X_new = (X_old - mu) / sigma
II. 数据结构
如果您关心kd树结构的性能,那么Voronoi Tessellation是一个概念上简单的容器,但它将显著提高性能并比kd树更好地扩展。
虽然这不是持久化kNN训练数据的最常见方式,但VT的应用以及随之而来的性能优势已有充分记录(例如,请参阅此Microsoft Research报告)。这个实际意义在于,只要您使用的是“主流”语言(例如在TIOBE指数中),那么您应该能够找到执行VT的库。我知道在Python和R中,每种语言都有多个选项(例如,在CRAN上可用的R软件包voronoi)。
使用VT进行kNN的工作方式如下:
从您的数据中随机选择w个点 - 这些是您的Voronoi中心。 Voronoi单元封装了所有最接近每个中心的相邻点。想象一下,如果为每个Voronoi中心分配不同的颜色,那么分配给给定中心的每个点都会被涂上该颜色。只要您具有足够的密度,这样做将很好地显示每个Voronoi中心的边界(作为分隔两种颜色的边界)。
如何选择Voronoi中心?我使用两条正交线指南。在随机选择w个点之后,计算训练数据的VT。接下来检查分配给每个Voronoi中心的数据点数量--这些值应该大致相同(假设你的数据空间具有均匀的点密度)。在二维空间中,这会导致大小相同的VT瓷砖。这是第一条规则,这是第二条规则。通过迭代来选择w--以w为可变参数运行kNN算法,并测量性能(查询VT返回预测所需的时间)。
想象一下你有一百万个数据点......如果这些点被持久化到普通的2D数据结构或kd-tree中,在每个新的预测响应变量的数据点上平均执行几百万次距离计算。当然,这些计算是在单个数据集上执行的。使用V/T,最近邻搜索分两步进行,依次针对两种不同的数据人群--首先是针对Voronoi中心,然后是找到最近的中心后,再搜索对应于该中心的单元格内的点,以找到实际的最近邻居(通过连续的距离计算)。组合起来,这两个查找比单个暴力查找快得多。这很容易理解:对于1M个数据点,假设你选择250个Voronoi中心来铺设数据空间。平均而言,每个Voronoi单元格将具有4000个数据点。因此,您不需要执行平均500,000次距离计算(暴力搜索),而只需执行更少的平均125+2,000次即可。
III. 计算结果(预测响应变量)
从一组kNN训练数据中计算预测值有两个步骤。第一个是确定n,或者说是用于计算的最近邻居的数量。第二个是如何加权它们对预测值的贡献。
关于第一个组件,你可以通过解决一个优化问题(与最小二乘优化非常相似)来确定最佳的n值。这是理论;在实践中,大多数人只使用n=3。无论如何,运行kNN算法来计算n=1、n=2、n=3等测试实例的预测值并绘制误差作为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个最近邻居,他们与响应变量(即“测试实例”)的数据点最接近,然后为n个邻居中的每一个调用weight_gauss函数,将每个邻居与测试点之间的距离作为参数传入。该函数将返回每个邻居的权重,然后将其用作加权平均计算中的系数。