做者:daniel-D 出处:http://www.cnblogs.com/daniel-D/php
在机器学习和数据挖掘中,咱们常常须要知道个体间差别的大小,进而评价个体的类似性和类别。最多见的是数据分析中的相关分析,数据挖掘中的分类和聚类算法,如 K 最近邻(KNN)和 K 均值(K-Means)等等。根据数据特性的不一样,能够采用不一样的度量方法。通常而言,定义一个距离函数 d(x,y), 须要知足下面几个准则:html
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) // 三角形法则: (两边之和大于第三边)python
这篇博客主要介绍机器学习和数据挖掘中一些常见的距离公式,包括:算法
闵可夫斯基距离(Minkowski distance)是衡量数值点之间距离的一种很是常见的方法,假设数值点 P 和 Q 坐标以下:app
那么,闵可夫斯基距离定义为:dom
该距离最经常使用的 p 是 2 和 1, 前者是欧几里得距离(Euclidean distance),后者是曼哈顿距离(Manhattan distance)。假设在曼哈顿街区乘坐出租车从 P 点到 Q 点,白色表示高楼大厦,灰色表示街道:机器学习
绿色的斜线表示欧几里得距离,在现实中是不可能的。其余三条折线表示了曼哈顿距离,这三条折线的长度是相等的。ide
当 p 趋近于无穷大时,闵可夫斯基距离转化成切比雪夫距离(Chebyshev distance):函数
咱们知道平面上到原点欧几里得距离(p = 2)为 1 的点所组成的形状是一个圆,当 p 取其余数值的时候呢?post
注意,当 p <
1 时,闵可夫斯基距离再也不符合三角形法则,举个例子:当 p <
1, (0,0) 到 (1,1) 的距离等于 (1+1)^{1/p} >
2, 而 (0,1) 到这两个点的距离都是 1。
闵可夫斯基距离比较直观,可是它与数据的分布无关,具备必定的局限性,若是 x 方向的幅值远远大于 y 方向的值,这个距离公式就会过分放大 x 维度的做用。因此,在计算距离以前,咱们可能还须要对数据进行 z-transform 处理,即减去均值,除以标准差:
: 该维度上的均值
: 该维度上的标准差
能够看到,上述处理开始体现数据的统计特性了。这种方法在假设数据各个维度不相关的状况下利用数据分布的特性计算出不一样的距离。若是维度相互之间数据相关(例如:身高较高的信息颇有可能会带来体重较重的信息,由于二者是有关联的),这时候就要用到马氏距离(Mahalanobis distance)了。
考虑下面这张图,椭圆表示等高线,从欧几里得的距离来算,绿黑距离大于红黑距离,可是从马氏距离,结果刚好相反:
马氏距离其实是利用 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') # 画 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).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)
马氏距离的变换和 PCA 分解的白化处理很有殊途同归之妙,不一样之处在于:就二维来看,PCA 是将数据主成分旋转到 x 轴(正交矩阵的酉变换),再在尺度上缩放(对角矩阵),实现尺度相同。而马氏距离的 L逆矩阵是一个下三角,先在 x 和 y 方向进行缩放,再在 y 方向进行错切(想象矩形变平行四边形),整体来讲是一个没有旋转的仿射变换。
向量内积是线性代数里最为常见的计算,实际上它仍是一种有效而且直观的类似性测量手段。向量内积的定义以下:
直观的解释是:若是 x 高的地方 y 也比较高, x 低的地方 y 也比较低,那么总体的内积是偏大的,也就是说 x 和 y 是类似的。举个例子,在一段长的序列信号 A 中寻找哪一段与短序列信号 a 最匹配,只须要将 a 从 A 信号开头逐个向后平移,每次平移作一次内积,内积最大的类似度最大。信号处理中 DFT 和 DCT 也是基于这种内积运算计算出不一样频域内的信号组分(DFT 和 DCT 是正交标准基,也能够看作投影)。向量和信号都是离散值,若是是连续的函数值,好比求区间[-1, 1]
两个函数之间的类似度,一样也能够获得(系数)组分,这种方法能够应用于多项式逼近连续函数,也能够用到连续函数逼近离散样本点(最小二乘问题,OLS coefficients)中,扯得有点远了- -!。
向量内积的结果是没有界限的,一种解决办法是除以长度以后再求内积,这就是应用十分普遍的余弦类似度(Cosine similarity):
余弦类似度与向量的幅值无关,只与向量的方向相关,在文档类似度(TF-IDF)和图片类似性(histogram)计算上都有它的身影。须要注意一点的是,余弦类似度受到向量的平移影响,上式若是将 x 平移到 x+1, 余弦值就会改变。怎样才能实现平移不变性?这就是下面要说的皮尔逊相关系数(Pearson correlation),有时候也直接叫相关系数:
皮尔逊相关系数具备平移不变性和尺度不变性,计算出了两个向量(维度)的相关性。不过,通常咱们在谈论相关系数的时候,将 x 与 y 对应位置的两个数值看做一个样本点,皮尔逊系数用来表示这些样本点分布的相关性。
因为皮尔逊系数具备的良好性质,在各个领域都应用普遍,例如,在推荐系统根据为某一用户查找喜爱类似的用户,进而提供推荐,优势是能够不受每一个用户评分标准不一样和观看影片数量不同的影响。
汉明距离(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" 的距离,觉得前者相同父节点路径更长。
上一小节咱们知道,汉明距离能够度量两个长度相同的字符串之间的类似度,若是要比较两个不一样长度的字符串,不只要进行替换,并且要进行插入与删除的运算,在这种场合下,一般使用更加复杂的编辑距离(Edit distance, Levenshtein distance)等算法。编辑距离是指两个字串之间,由一个转成另外一个所需的最少编辑操做次数。许可的编辑操做包括将一个字符替换成另外一个字符,插入一个字符,删除一个字符。编辑距离求的是最少编辑次数,这是一个动态规划的问题,有兴趣的同窗能够本身研究研究。
时间序列是序列之间距离的另一个例子。DTW 距离(Dynamic Time Warp)是序列信号在时间或者速度上不匹配的时候一种衡量类似度的方法。神马意思?举个例子,两份本来同样声音样本A、B都说了“你好”,A在时间上发生了扭曲,“你”这个音延长了几秒。最后A:“你~~~好”,B:“你好”。DTW正是这样一种能够用来匹配A、B之间的最短距离的算法。
DTW 距离在保持信号前后顺序的限制下对时间信号进行“膨胀”或者“收缩”,找到最优的匹配,与编辑距离类似,这其实也是一个动态规划的问题:
实现代码(转自 McKelvin's Blog ):
#!/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))
前面咱们谈论的都是两个数值点之间的距离,实际上两个几率分布之间的距离是能够测量的。在统计学里面常常须要测量两组样本分布之间的距离,进而判断出它们是否出自同一个 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 迭代算法等。
参考资料: