关于KNN中高斯加权函数欧式距离的问题

K近邻法(KNN)原理小结
K近邻法(k-nearst neighbors,KNN)是一种很基本的机器学习方法了,在我们平常的生活中也会不自主的应用。比如,我们判断一个人的人品,只需要观察他来往最密切的几个人的人品好坏就可以得出了。这里就运用了KNN的思想。KNN方法既可以做分类,也可以做回归,这点和决策树算法相同。
KNN做回归和分类的主要区别在于最后做预测时候的决策方式不同。KNN做分类预测时,一般是选择多数表决法,即训练集里和预测的样本特征最近的K个样本,预测为里面有最多类别数的类别。而KNN做回归时,一般是选择平均法,即最近的K个样本的样本输出的平均值作为回归预测值。由于两者区别不大,虽然本文主要是讲解KNN的分类方法,但思想对KNN的回归方法也适用。由于scikit-learn里只使用了蛮力实现(brute-force),KD树实现(KDTree)和球树(BallTree)实现,本文只讨论这几种算法的实现原理。其余的实现方法比如BBF树,MVP树等,在这里不做讨论。
1. KNN算法三要素
KNN算法我们主要要考虑三个重要的要素,对于固定的训练集,只要这三点确定了,算法的预测方式也就决定了。这三个最终的要素是k值的选取,距离度量的方式和分类决策规则。
对于分类决策规则,一般都是使用前面提到的多数表决法。所以我们重点是关注与k值的选择和距离的度量方式。
对于k值的选择,没有一个固定的经验,一般根据样本的分布,选择一个较小的值,可以通过交叉验证选择一个合适的k值。
选择较小的k值,就相当于用较小的领域中的训练实例进行预测,训练误差会减小,只有与输入实例较近或相似的训练实例才会对预测结果起作用,与此同时带来的问题是泛化误差会增大,换句话说,K值的减小就意味着整体模型变得复杂,容易发生过拟合;
选择较大的k值,就相当于用较大领域中的训练实例进行预测,其优点是可以减少泛化误差,但缺点是训练误差会增大。这时候,与输入实例较远(不相似的)训练实例也会对预测器作用,使预测发生错误,且K值的增大就意味着整体的模型变得简单。
一个极端是k等于样本数m,则完全没有分类,此时无论输入实例是什么,都只是简单的预测它属于在训练实例中最多的类,模型过于简单。
对于距离的度量,我们有很多的距离度量方式,但是最常用的是欧式距离,即对于两个n维向量x和y,两者的欧式距离定义为:
D(x,y)=(x1?y1)2+(x2?y2)2+...+(xn?yn)2???????????????????????????????&=&i=1n(xi?yi)2??????????&D(x,y)=(x1?y1)2+(x2?y2)2+...+(xn?yn)2=&i=1n(xi?yi)2
大多数情况下,欧式距离可以满足我们的需求,我们不需要再去操心距离的度量。
当然我们也可以用他的距离度量方式。比如曼哈顿距离,定义为:
D(x,y)=|x1?y1|+|x2?y2|+...+|xn?yn|=&i=1n|xi?yi|D(x,y)=|x1?y1|+|x2?y2|+...+|xn?yn|=&i=1n|xi?yi|
更加通用点,比如闵可夫斯基距离(Minkowski Distance),定义为:
D(x,y)=(|x1?y1|)p+(|x2?y2|)p+...+(|xn?yn|)p??????????????????????????????????&p=&i=1n(|xi?yi|)p???????????&pD(x,y)=(|x1?y1|)p+(|x2?y2|)p+...+(|xn?yn|)pp=&i=1n(|xi?yi|)pp
可以看出,欧式距离是闵可夫斯基距离距离在p=2时的特例,而曼哈顿距离是p=1时的特例。
2. KNN算法蛮力实现
从本节起,我们开始讨论KNN算法的实现方式。首先我们看看最想当然的方式。
既然我们要找到k个最近的邻居来做预测,那么我们只需要计算预测样本和所有训练集中的样本的距离,然后计算出最小的k个距离即可,接着多数表决,很容易做出预测。这个方法的确简单直接,在样本量少,样本特征少的时候有效。但是在实际运用中很多时候用不上,为什么呢?因为我们经常碰到样本的特征数有上千以上,样本量有几十万以上,如果我们这要去预测少量的测试集样本,算法的时间效率很成问题。因此,这个方法我们一般称之为蛮力实现。比较适合于少量样本的简单模型的时候用。
既然蛮力实现在特征多,样本多的时候很有局限性,那么我们有没有其他的好办法呢?有!这里我们讲解两种办法,一个是KD树实现,一个是球树实现。
3. KNN算法之KD树实现原理
KD树算法没有一开始就尝试对测试样本分类,而是先对训练集建模,建立的模型就是KD树,建好了模型再对测试集做预测。所谓的KD树就是K个特征维度的树,注意这里的K和KNN中的K的意思不同。KNN中的K代表特征输出类别,KD树中的K代表样本特征的维数。为了防止混淆,后面我们称特征维数为n。
KD树算法包括三步,第一步是建树,第二部是搜索最近邻,最后一步是预测。
3.1 KD树的建立
我们首先来看建树的方法。KD树建树采用的是从m个样本的n维特征中,分别计算n个特征的取值的方差,用方差最大的第k维特征nknk来作为根节点。对于这个特征,我们选择特征nknk的取值的中位数nkvnkv对应的样本作为划分点,对于所有第k维特征的取值小于nkvnkv的样本,我们划入左子树,对于第k维特征的取值大于等于nkvnkv的样本,我们划入右子树,对于左子树和右子树,我们采用和刚才同样的办法来找方差最大的特征来做更节点,递归的生成KD树。
具体流程如下图:
比如我们有二维样本6个,{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)},构建kd树的具体步骤为:
1)找到划分的特征。6个数据点在x,y维度上的数据方差分别为6.97,5.37,所以在x轴上方差更大,用第1维特征建树。
2)确定划分点(7,2)。根据x维上的值将数据排序,6个数据的中值(所谓中值,即中间大小的值)为7,所以划分点的数据是(7,2)。这样,该节点的分割超平面就是通过(7,2)并垂直于:划分点维度的直线x=7;
3)确定左子空间和右子空间。 分割超平面x=7将整个空间分为两部分:x&=7的部分为左子空间,包含3个节点={(2,3),(5,4),(4,7)};另一部分为右子空间,包含2个节点={(9,6),(8,1)}。
4)用同样的办法划分左子树的节点{(2,3),(5,4),(4,7)}和右子树的节点{(9,6),(8,1)}。最终得到KD树。
最后得到的KD树如下:
3.2 KD树搜索最近邻
当我们生成KD树以后,就可以去预测测试集里面的样本目标点了。对于一个目标点,我们首先在KD树里面找到包含目标点的叶子节点。以目标点为圆心,以目标点到叶子节点样本实例的距离为半径,得到一个超球体,最近邻的点一定在这个超球体内部。然后返回叶子节点的父节点,检查另一个子节点包含的超矩形体是否和超球体相交,如果相交就到这个子节点寻找是否有更加近的近邻,有的话就更新最近邻。如果不相交那就简单了,我们直接返回父节点的父节点,在另一个子树继续搜索最近邻。当回溯到根节点时,算法结束,此时保存的最近邻节点就是最终的最近邻。
从上面的描述可以看出,KD树划分后可以大大减少无效的最近邻搜索,很多样本点由于所在的超矩形体和超球体不相交,根本不需要计算距离。大大节省了计算时间。
我们用3.1建立的KD树,来看对点(2,4.5)找最近邻的过程。
先进行二叉查找,先从(7,2)查找到(5,4)节点,在进行查找时是由y = 4为分割超平面的,由于查找点为y值为4.5,因此进入右子空间查找到(4,7),形成搜索路径&(7,2),(5,4),(4,7)&,但 (4,7)与目标查找点的距离为3.202,而(5,4)与查找点之间的距离为3.041,所以(5,4)为查询点的最近点;以(2,4.5)为圆心,以3.041为半径作圆,如下图所示。可见该圆和y = 4超平面交割,所以需要进入(5,4)左子空间进行查找,也就是将(2,3)节点加入搜索路径中得&(7,2),(2,3)&;于是接着搜索至(2,3)叶子节点,(2,3)距离(2,4.5)比(5,4)要近,所以最近邻点更新为(2,3),最近距离更新为1.5;回溯查找至(5,4),直到最后回溯到根结点(7,2)的时候,以(2,4.5)为圆心1.5为半径作圆,并不和x = 7分割超平面交割,如下图所示。至此,搜索路径回溯完,返回最近邻点(2,3),最近距离1.5。
对应的图如下:
3.3 KD树预测
有了KD树搜索最近邻的办法,KD树的预测就很简单了,在KD树搜索最近邻的基础上,我们选择到了第一个最近邻样本,就把它置为已选。在第二轮中,我们忽略置为已选的样本,重新选择最近邻,这样跑k次,就得到了目标的K个最近邻,然后根据多数表决法,如果是KNN分类,预测为K个最近邻里面有最多类别数的类别。如果是KNN回归,用K个最近邻样本输出的平均值作为回归预测值。
4. KNN算法之球树实现原理
KD树算法虽然提高了KNN搜索的效率,但是在某些时候效率并不高,比如当处理不均匀分布的数据集时,不管是近似方形,还是矩形,甚至正方形,都不是最好的使用形状,因为他们都有角。一个例子如下图:
如果黑色的实例点离目标点星点再远一点,那么虚线圆会如红线所示那样扩大,导致与左上方矩形的右下角相交,既然相 交了,那么就要检查这个左上方矩形,而实际上,最近的点离星点的距离很近,检查左上方矩形区域已是多余。于此我们看见,KD树把二维平面划分成一个一个矩形,但矩形区域的角却是个难以处理的问题。
为了优化超矩形体导致的搜索效率的问题,牛人们引入了球树,这种结构可以优化上面的这种问题。
我们现在来看看球树建树和搜索最近邻的算法。
4.1 球树的建立
球树,顾名思义,就是每个分割块都是超球体,而不是KD树里面的超矩形体。
我们看看具体的建树流程:
1) 先构建一个超球体,这个超球体是可以包含所有样本的最小球体。
2) 从球中选择一个离球的中心最远的点,然后选择第二个点离第一个点最远,将球中所有的点分配到离这两个聚类中心最近的一个上,然后计算每个聚类的中心,以及聚类能够包含它所有数据点所需的最小半径。这样我们得到了两个子超球体,和KD树里面的左右子树对应。
3)对于这两个子超球体,递归执行步骤2). 最终得到了一个球树。
可以看出KD树和球树类似,主要区别在于球树得到的是节点样本组成的最小超球体,而KD得到的是节点样本组成的超矩形体,这个超球体要与对应的KD树的超矩形体小,这样在做最近邻搜索的时候,可以避免一些无谓的搜索。
4.2 球树搜索最近邻
使用球树找出给定目标点的最近邻方法是首先自上而下贯穿整棵树找出包含目标点所在的叶子,并在这个球里找出与目标点最邻近的点,这将确定出目标点距离它的最近邻点的一个上限值,然后跟KD树查找一样,检查兄弟结点,如果目标点到兄弟结点中心的距离超过兄弟结点的半径与当前的上限值之和,那么兄弟结点里不可能存在一个更近的点;否则的话,必须进一步检查位于兄弟结点以下的子树。
检查完兄弟节点后,我们向父节点回溯,继续搜索最小邻近值。当回溯到根节点时,此时的最小邻近值就是最终的搜索结果。
从上面的描述可以看出,KD树在搜索路径优化时使用的是两点之间的距离来判断,而球树使用的是两边之和大于第三边来判断,相对来说球树的判断更加复杂,但是却避免了更多的搜索,这是一个权衡。
5. KNN算法的扩展
这里我们再讨论下KNN算法的扩展,限定半径最近邻算法。
有时候我们会遇到这样的问题,即样本中某系类别的样本非常的少,甚至少于K,这导致稀有类别样本在找K个最近邻的时候,会把距离其实较远的其他样本考虑进来,而导致预测不准确。为了解决这个问题,我们限定最近邻的一个最大距离,也就是说,我们只在一个距离范围内搜索所有的最近邻,这避免了上述问题。这个距离我们一般称为限定半径。
接着我们再讨论下另一种扩展,最近质心算法。这个算法比KNN还简单。它首先把样本按输出类别归类。对于第 L类的ClCl个样本。它会对这ClCl个样本的n维特征中每一维特征求平均值,最终该类别所有维度的n个平均值形成所谓的质心点。对于样本中的所有出现的类别,每个类别会最终得到一个质心点。当我们做预测时,仅仅需要比较预测样本和这些质心的距离,最小的距离对于的质心类别即为预测的类别。这个算法通常用在文本分类处理上。
6. KNN算法小结
KNN算法是很基本的机器学习算法了,它非常容易学习,在维度很高的时候也有很好的分类效率,因此运用也很广泛,这里总结下KNN的优缺点。
KNN的主要优点有:
1) 理论成熟,思想简单,既可以用来做分类也可以用来做回归
2) 可用于非线性分类
3) 训练时间复杂度比支持向量机之类的算法低,仅为O(n)
4) 和朴素贝叶斯之类的算法比,对数据没有假设,准确度高,对异常点不敏感
5) 由于KNN方法主要靠周围有限的邻近的样本,而不是靠判别类域的方法来确定所属类别的,因此对于类域的交叉或重叠较多的待分样本集来说,KNN方法较其他方法更为适合
6)该算法比较适用于样本容量比较大的类域的自动分类,而那些样本容量较小的类域采用这种算法比较容易产生误分
KNN的主要缺点有:
1)计算量大,尤其是特征数非常多的时候
2)样本不平衡的时候,对稀有类别的预测准确率低
3)KD树,球树之类的模型建立需要大量的内存
4)使用懒散学习方法,基本上不学习,导致预测时速度比起逻辑回归之类的算法慢
5)相比决策树模型,KNN模型可解释性不强
以上就是KNN算法原理的一个总结,希望可以帮到朋友们,尤其是在用scikit-learn学习KNN的朋友们。漫谈机器学习中的距离和相似性度量方法 - 文章 - 伯乐在线
& 漫谈机器学习中的距离和相似性度量方法
在机器学习和数据挖掘中,我们经常需要知道个体间差异的大小,进而评价个体的相似性和类别。最常见的是数据分析中的相关分析,数据挖掘中的分类和聚 类算法,如 K 最近邻(KNN)和 K 均值(K-Means)等等。根据数据特性的不同,可以采用不同的度量方法。一般而言,定义一个距离函数 d(x,y), 需要满足下面几个准则:
1) d(x,x) = 0
// 到自己的距离为0
2) d(x,y) &= 0
// 距离非负
3) d(x,y) = d(y,x)
// 对称性: 如果 A 到 B 距离是 a,那么 B 到 A 的距离也应该是 a
4) d(x,k)+ d(k,y) &= d(x,y)
// 三角形法则: (两边之和大于第三边)
这篇博客主要介绍机器学习和数据挖掘中一些常见的距离公式,包括:
闵可夫斯基距离
欧几里得距离
曼哈顿距离
切比雪夫距离
余弦相似度
皮尔逊相关系数
杰卡德相似系数
1. 闵可夫斯基距离
闵可夫斯基距离(Minkowski distance)是衡量数值点之间距离的一种非常常见的方法,假设数值点 P 和 Q 坐标如下:
那么,闵可夫斯基距离定义为:
该距离最常用的 p 是 2 和 1, 前者是欧几里得距离(Euclidean distance),后者是曼哈顿距离(Manhattan distance)。假设在曼哈顿街区乘坐出租车从 P 点到 Q 点,白色表示高楼大厦,灰色表示街道:
绿色的斜线表示欧几里得距离,在现实中是不可能的。其他三条折线表示了曼哈顿距离,这三条折线的长度是相等的。
当 p 趋近于无穷大时,闵可夫斯基距离转化成切比雪夫距离(Chebyshev distance):
我们知道平面上到原点欧几里得距离(p = 2)为 1 的点所组成的形状是一个圆,当 p 取其他数值的时候呢?
注意,当 p & 1 时,闵可夫斯基距离不再符合三角形法则,举个例子:当 p & 1, (0,0) 到 (1,1) 的距离等于 (1+1)^{1/p} & 2, 而 (0,1) 到这两个点的距离都是 1。
闵可夫斯基距离比较直观,但是它与数据的分布无关,具有一定的局限性,如果 x 方向的幅值远远大于 y 方向的值,这个距离公式就会过度放大 x 维度的作用。所以,在计算距离之前,我们可能还需要对数据进行 z-transform 处理,即减去均值,除以标准差:
: 该维度上的均值
: 该维度上的标准差
可以看到,上述处理开始体现数据的统计特性了。这种方法在假设数据各个维度不相关的情况下利用数据分布的特性计算出不同的距离。如果维度相互之间数据相关(例如:身高较高的信息很有可能会带来体重较重的信息,因为两者是有关联的),这时候就要用到马氏距离(Mahalanobis distance)了。
2. 马氏距离
考虑下面这张图,椭圆表示等高线,从欧几里得的距离来算,绿黑距离大于红黑距离,但是从马氏距离,结果恰好相反:
马氏距离实际上是利用 Cholesky transformation 来消除不同维度之间的相关性和尺度不同的性质。假设样本点(列向量)之间的协方差对称矩阵是
, 通过 Cholesky Decomposition(实际上是对称矩阵 LU 分解的一种特殊形式,可参考之前的)可以转化为下三角矩阵和上三角矩阵的乘积:
。消除不同维度之间的相关性和尺度不同,只需要对样本点 x 做如下处理: 。处理之后的欧几里得距离就是原样本的马氏距离:为了书写方便,这里求马氏距离的平方):
下图蓝色表示原样本点的分布,两颗红星坐标分别是(3, 3),(2, -2):
由于 x, y 方向的尺度不同,不能单纯用欧几里得的方法测量它们到原点的距离。并且,由于 x 和 y 是相关的(大致可以看出斜向右上),也不能简单地在 x 和 y 方向上分别减去均值,除以标准差。最恰当的方法是对原始数据进行 Cholesky 变换,即求马氏距离(可以看到,右边的红星离原点较近):
将上面两个图的绘制代码和求马氏距离的代码贴在这里,以备以后查阅:
# -*- coding=utf-8 -*-
# code related at: http://www.cnblogs.com/daniel-D/
import numpy as np
import pylab as pl
import scipy.spatial.distance as dist
def plotSamples(x, y, z=None):
stars = np.matrix([[3., -2., 0.], [3., 2., 0.]])
if z is not None:
x, y = z * np.matrix([x, y])
stars = z * stars
pl.scatter(x, y, s=10)
# 画 gaussian 随机点
pl.scatter(np.array(stars[0]), np.array(stars[1]), s=200, marker='*', color='r')
# 画三个指定点
pl.axhline(linewidth=2, color='g') # 画 x 轴
pl.axvline(linewidth=2, color='g')
pl.axis('equal')
pl.axis([-5, 5, -5, 5])
# 产生高斯分布的随机点
mean = [0, 0]
cov = [[2, 1], [1, 2]]
x, y = np.random.multivariate_normal(mean, cov, 1000).T
plotSamples(x, y)
covMat = np.matrix(np.cov(x, y))
# 求 x 与 y 的协方差矩阵
Z = np.linalg.cholesky(covMat).I
# 仿射矩阵
plotSamples(x, y, Z)
# 求马氏距离
print '\n到原点的马氏距离分别是:'
print dist.mahalanobis([0,0], [3,3], covMat.I), dist.mahalanobis([0,0], [-2,2], covMat.I)
# 求变换后的欧几里得距离
dots = (Z * np.matrix([[3, -2, 0], [3, 2, 0]])).T
print '\n变换后到原点的欧几里得距离分别是:'
print dist.minkowski([0, 0], np.array(dots[0]), 2), dist.minkowski([0, 0], np.array(dots[1]), 2)
123456789101112131415161718192021222324252627282930313233343536373839404142
# -*- coding=utf-8 -*-&# code related at: http://www.cnblogs.com/daniel-D/&import numpy as npimport pylab as plimport scipy.spatial.distance as dist&def plotSamples(x, y, z=None):&&&&&stars = np.matrix([[3., -2., 0.], [3., 2., 0.]])&&&&if z is not None:&&&&&&&&x, y = z * np.matrix([x, y])&&&&&&&&stars = z * stars&&&&&pl.scatter(x, y, s=10)&&&&# 画 gaussian 随机点&&&&pl.scatter(np.array(stars[0]), np.array(stars[1]), s=200, marker='*', color='r')&&# 画三个指定点&&&&pl.axhline(linewidth=2, color='g') # 画 x 轴&&&&pl.axvline(linewidth=2, color='g')&&# 画 y 轴&&&&&pl.axis('equal')&&&&pl.axis([-5, 5, -5, 5])&&&&pl.show()&# 产生高斯分布的随机点mean = [0, 0]&&&&&&# 平均值cov = [[2, 1], [1, 2]]&& # 协方差x, y = np.random.multivariate_normal(mean, cov, 1000).TplotSamples(x, y)&covMat = np.matrix(np.cov(x, y))&&&&# 求 x 与 y 的协方差矩阵Z = np.linalg.cholesky(covMat).I&&# 仿射矩阵plotSamples(x, y, Z)&# 求马氏距离 print '\n到原点的马氏距离分别是:'print dist.mahalanobis([0,0], [3,3], covMat.I), dist.mahalanobis([0,0], [-2,2], covMat.I)&# 求变换后的欧几里得距离dots = (Z * np.matrix([[3, -2, 0], [3, 2, 0]])).Tprint '\n变换后到原点的欧几里得距离分别是:'print dist.minkowski([0, 0], np.array(dots[0]), 2), dist.minkowski([0, 0], np.array(dots[1]), 2)
马氏距离的变换和 PCA 分解的颇 有异曲同工之妙,不同之处在于:就二维来看,PCA 是将数据主成分旋转到 x 轴(正交矩阵的酉变换),再在尺度上缩放(对角矩阵),实现尺度相同。而马氏距离的 L逆矩阵是一个下三角,先在 x 和 y 方向进行缩放,再在 y 方向进行错切(想象矩形变平行四边形),总体来说是一个没有旋转的仿射变换。
3. 向量内积
向量内积是线性代数里最为常见的计算,实际上它还是一种有效并且直观的相似性测量手段。向量内积的定义如下:
直观的解释是:如果 x 高的地方 y 也比较高, x 低的地方 y 也比较低,那么整体的内积是偏大的,也就是说 x 和 y 是相似的。举个例子,在一段长的序列信号 A 中寻找哪一段与短序列信号 a 最匹配,只需要将 a 从 A 信号开头逐个向后平移,每次平移做一次内积,内积最大的相似度最大。信号处理中 DFT 和 DCT 也是基于这种内积运算计算出不同频域内的信号组分(DFT 和 DCT 是正交标准基,也可以看做投影)。向量和信号都是离散值,如果是连续的函数值,比如求区间[-1, 1] 两个函数之间的相似度,同样也可以得到(系数)组分,这种方法可以应用于多项式逼近连续函数,也可以用到连续函数逼近离散样本点(最小二乘问题,)中,扯得有点远了- -!。
向量内积的结果是没有界限的,一种解决办法是除以长度之后再求内积,这就是应用十分广泛的余弦相似度(Cosine similarity):
余弦相似度与向量的幅值无关,只与向量的方向相关,在文档相似度()和图片相似性()计算上都有它的身影。需要注意一点的是,余弦相似度受到向量的平移影响,上式如果将 x 平移到 x+1, 余弦值就会改变。怎样才能实现平移不变性?这就是下面要说的皮尔逊相关系数(Pearson correlation),有时候也直接叫相关系数:
皮尔逊相关系数具有平移不变性和尺度不变性,计算出了两个向量(维度)的相关性。不过,一般我们在谈论相关系数的时候,将 x 与 y 对应位置的两个数值看作一个样本点,皮尔逊系数用来表示这些样本点分布的相关性。
由于皮尔逊系数具有的良好性质,在各个领域都应用广泛,例如,在推荐系统根据为某一用户查找喜好相似的用户,进而,优点是可以不受每个用户评分标准不同和观看影片数量不一样的影响。
4. 分类数据点间的距离
汉明距离(Hamming distance)是指,两个等长字符串s1与s2之间的汉明距离定义为将其中一个变为另外一个所需要作的最小替换次数。举个维基百科上的例子:
还可以用简单的匹配系数来表示两点之间的相似度——匹配字符数/总字符数。
在一些情况下,某些特定的值相等并不能代表什么。举个例子,用 1 表示用户看过该电影,用 0 表示用户没有看过,那么用户看电影的的信息就可用 0,1 表示成一个序列。考虑到电影基数非常庞大,用户看过的电影只占其中非常小的一部分,如果两个用户都没有看过某一部电影(两个都是 0),并不能说明两者相似。反而言之,如果两个用户都看过某一部电影(序列中都是 1),则说明用户有很大的相似度。在这个例子中,序列中等于 1 所占的权重应该远远大于 0 的权重,这就引出下面要说的杰卡德相似系数(Jaccard similarity)。
在上面的例子中,用 M11 表示两个用户都看过的电影数目,M10 表示用户 A 看过,用户 B 没看过的电影数目,M01 表示用户 A 没看过,用户 B 看过的电影数目,M00 表示两个用户都没有看过的电影数目。Jaccard 相似性系数可以表示为:
Jaccard similarity 还可以用集合的公式来表达,这里就不多说了。
如果分类数值点是用树形结构来表示的,它们的相似性可以用相同路径的长度来表示,比如,“/product/spot/ballgame /basketball” 离“product/spot/ballgame/soccer/shoes” 的距离小于到 “/product/luxury/handbags” 的距离,以为前者相同父节点路径更长。
5. 序列之间的距离
上一小节我们知道,汉明距离可以度量两个长度相同的字符串之间的相似度,如果要比较两个不同长度的字符串,不仅要进行替换,而且要进行插入与删除的运算,在这种场合下,通常使用更加复杂的编辑距离(Edit distance, Levenshtein distance)等算法。编辑距离是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一 个字符,删除一个字符。编辑距离求的是最少编辑次数,这是一个动态规划的问题,有兴趣的同学可以自己研究研究。
时间序列是序列之间距离的另外一个例子。DTW 距离(Dynamic Time Warp)是序列信号在时间或者速度上不匹配的时候一种衡量相似度的方法。神马意思?举个例子,两份原本一样声音样本A、B都说了“你好”,A在时间上发生了扭曲,“你”这个音延长了几秒。最后A:“你~~~好”,B:“你好”。DTW正是这样一种可以用来匹配A、B之间的最短距离的算法。
DTW 距离在保持信号先后顺序的限制下对时间信号进行“膨胀”或者“收缩”,找到最优的匹配,与编辑距离相似,这其实也是一个动态规划的问题:
实现代码(转自
#!/usr/bin/python2
# -*- coding:UTF-8 -*-
# code related at: http://blog.mckelv.in/articles/1453.html
import sys
distance = lambda a,b : 0 if a==b else 1
def dtw(sa,sb):
&&&dtw(u"干啦今今今今今天天气气气气气好好好好啊啊啊", u"今天天气好好啊")
MAX_COST = 1&&32
#初始化一个len(sb) 行(i),len(sa)列(j)的二维矩阵
len_sa = len(sa)
len_sb = len(sb)
# BUG:这样是错误的(浅拷贝): dtw_array = [[MAX_COST]*len(sa)]*len(sb)
dtw_array = [[MAX_COST for i in range(len_sa)] for j in range(len_sb)]
dtw_array[0][0] = distance(sa[0],sb[0])
for i in xrange(0, len_sb):
for j in xrange(0, len_sa):
if i+j==0:
if i & 0: nb.append(dtw_array[i-1][j])
if j & 0: nb.append(dtw_array[i][j-1])
if i & 0 and j & 0: nb.append(dtw_array[i-1][j-1])
min_route = min(nb)
cost = distance(sa[j],sb[i])
dtw_array[i][j] = cost + min_route
return dtw_array[len_sb-1][len_sa-1]
def main(argv):
s1 = u'干啦今今今今今天天气气气气气好好好好啊啊啊'
s2 = u'今天天气好好啊'
d = dtw(s1, s2)
if __name__ == '__main__':
sys.exit(main(sys.argv))
123456789101112131415161718192021222324252627282930313233343536373839404142
#!/usr/bin/python2# -*- coding:UTF-8 -*-# code related at: http://blog.mckelv.in/articles/1453.html&import sys&distance = lambda a,b : 0 if a==b else 1&def dtw(sa,sb):&&&&'''&&&&&&&dtw(u"干啦今今今今今天天气气气气气好好好好啊啊啊", u"今天天气好好啊")&&&&2&&&&'''&&&&MAX_COST = 1&&32&&&&#初始化一个len(sb) 行(i),len(sa)列(j)的二维矩阵&&&&len_sa = len(sa)&&&&len_sb = len(sb)&&&&# BUG:这样是错误的(浅拷贝): dtw_array = [[MAX_COST]*len(sa)]*len(sb)&&&&dtw_array = [[MAX_COST for i in range(len_sa)] for j in range(len_sb)]&&&&dtw_array[0][0] = distance(sa[0],sb[0])&&&&for i in xrange(0, len_sb):&&&&&&&&for j in xrange(0, len_sa):&&&&&&&&&&&&if i+j==0:&&&&&&&&&&&&&&&&continue&&&&&&&&&&&&nb = []&&&&&&&&&&&&if i & 0: nb.append(dtw_array[i-1][j])&&&&&&&&&&&&if j & 0: nb.append(dtw_array[i][j-1])&&&&&&&&&&&&if i & 0 and j & 0: nb.append(dtw_array[i-1][j-1])&&&&&&&&&&&&min_route = min(nb)&&&&&&&&&&&&cost = distance(sa[j],sb[i])&&&&&&&&&&&&dtw_array[i][j] = cost + min_route&&&&return dtw_array[len_sb-1][len_sa-1]&def main(argv):&&&&s1 = u'干啦今今今今今天天气气气气气好好好好啊啊啊'&&&&s2 = u'今天天气好好啊'&&&&d = dtw(s1, s2)&&&&print d&&&&return 0&if __name__ == '__main__':&&&&sys.exit(main(sys.argv))
6. 概率分布之间的距离
前面我们谈论的都是两个数值点之间的距离,实际上两个概率分布之间的距离是可以测量的。在统计学里面经常需要测量两组样本分布之间的距离,进而判断出它们是否出自同一个 population,常见的方法有卡方检验(Chi-Square)和 KL 散度( KL-Divergence),下面说一说 KL 散度吧。
先从信息熵说起,假设一篇文章的标题叫做“黑洞到底吃什么”,包含词语分别是 {黑洞, 到底, 吃什么}, 我们现在要根据一个词语推测这篇文章的类别。哪个词语给予我们的信息最多?很容易就知道是“黑洞”,因为“黑洞”这个词语在所有的文档中出现的概率太低 啦,一旦出现,就表明这篇文章很可能是在讲科普知识。而其他两个词语“到底”和“吃什么”出现的概率很高,给予我们的信息反而越少。如何用一个函数 h(x) 表示词语给予的信息量呢?第一,肯定是与 p(x) 相关,并且是负相关。第二,假设 x 和 y 是独立的(黑洞和宇宙不相互独立,谈到黑洞必然会说宇宙),即 p(x,y) = p(x)p(y), 那么获得的信息也是叠加的,即 h(x, y) = h(x) + h(y)。满足这两个条件的函数肯定是负对数形式:
对假设一个发送者要将随机变量 X 产生的一长串随机值传送给接收者, 接受者获得的平均信息量就是求它的数学期望:
这就是熵的概念。另外一个重要特点是,熵的大小与字符平均最短编码长度是一样的(shannon)。设有一个未知的分布 p(x), 而 q(x) 是我们所获得的一个对 p(x) 的近似,按照 q(x) 对该随机变量的各个值进行编码,平均长度比按照真实分布的 p(x) 进行编码要额外长一些,多出来的长度这就是 KL 散度(之所以不说距离,是因为不满足对称性和三角形法则),即:
KL 散度又叫相对熵(relative entropy)。了解机器学习的童鞋应该都知道,在 Softmax 回归(或者 Logistic 回归),最后的输出节点上的值表示这个样本分到该类的概率,这就是一个概率分布。对于一个带有标签的样本,我们期望的概率分布是:分到标签类的概率是 1, 其他类概率是 0。但是理想很丰满,现实很骨感,我们不可能得到完美的概率输出,能做的就是尽量减小总样本的 KL 散度之和(目标函数)。这就是 Softmax 回归或者 Logistic 回归中 Cost function 的优化过程啦。(PS:因为概率和为 1,一般的 logistic 二分类的图只画了一个输出节点,隐藏了另外一个)
待补充的方法:
卡方检验 Chi-Square
衡量 categorical attributes 相关性的 mutual information
Spearman’s rank coefficient
Earth Mover’s Distance
SimRank 迭代算法等。
参考资料:
可能感兴趣的话题
关于伯乐在线博客
在这个信息爆炸的时代,人们已然被大量、快速并且简短的信息所包围。然而,我们相信:过多“快餐”式的阅读只会令人“虚胖”,缺乏实质的内涵。伯乐在线内容团队正试图以我们微薄的力量,把优秀的原创文章和译文分享给读者,为“快餐”添加一些“营养”元素。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2018 伯乐在线

我要回帖

更多关于 高斯核函数与欧式距离 的文章

 

随机推荐