您的位置:首页 > 编程语言 > Python开发

常用距离算法和相关系数及其Python实现

2016-10-19 00:13 573 查看
在机器学习的过程中,经常会遇到需要考察具有某些特征的两个样本之间相似程度的情况。计相似程度的计算可以使用距离算法或是相关系数,直观来说,距离和相关系数的变化呈负相关,距离越小,相关系数越大,越相似,反之亦然。

接下来记录几种简单实用的距离算法和相关系数,以n维空间中的两个点x(x1,x2,...,xn), y(y1,y2,...,yn) 为例,并给出python实现。

距离算法

欧几里得距离(Euclidean Distance)

公式:dist(x,y)=∑i=1n(xi−yi)2−−−−−−−−−−√

定义:欧几里得距离是欧氏空间中两点间“普通”(即直线)距离。没什么好说的。

#输入:x,y为维度相同的包含各特征值的list
#输出:输入值x,y的欧几里得距离
def dis_euc(x, y):
return sum([(x[i]-y[i])**2 for i in range(len(x))])**0.5


曼哈顿距离(Manhattan distance)

公式:dist(x,y)=∑i=1n|xi−yi|

定义:曼哈顿距离的正式意义为L1-距离或城市区块距离,也就是在欧几里得空间的固定直角坐标系上两点所形成的线段对轴产生的投影的距离总和。

曼哈顿距离的命名原因是从规划为方型建筑区块的城市(如曼哈顿)间,最短的行车路径而来。一张图可以直观的说明它与欧氏距离的关系:



图中的绿线为欧几里得距离,即为两点之间的直线长度;红、黄、蓝三条线均为两点之间的城市区块路径,三条线的长度相等,即为曼哈顿距离。

#输入:x,y为维度相同的包含各特征值的list
#输出:输入值x,y的欧几里得距离
def dis_man(x, y):
return sum([abs(x[i]-y[i]) for i in range(len(x)])


明可夫斯基距离(minkowski distance)

公式:dist(x,y)=(∑i=1n|xi−yi|p)1/p

定义:明氏距离是欧氏空间中的一种测度,被看做是欧氏距离和曼哈顿距离的一种推广

p取1或2时的明氏距离是最为常用的,p=2即为欧氏距离,而p=1时则为曼哈顿距离。当p取无穷时的极限情况下,可以得到切比雪夫距离

#输入:x,y为维度相同的包含各特征值的list,p为int
#输出:输入值x,y的明可夫斯基距离
def dis_man(x, y, p):
return sum([(x[i]-y[i])**p for i in range(len(x)])**(1/p)


这样求出来的距离值,越相似的样本,距离值越小,完全一致则为0,相差越大则值越大。但在统计学中,用相关系数来考察两个样本的相关程度时有如下的约定

当相关系数为0时,X和Y两变量无关系。

当X的值增大(减小),Y值增大(减小),两个变量为正相关,相关系数在0.00与1.00之间。

当X的值增大(减小),Y值减小(增大),两个变量为负相关,相关系数在-1.00与0.00之间。

相关性
−0.09 to 0.00.0 to 0.09
−0.3 to −0.10.1 to 0.3
−0.5 to −0.30.3 to 0.5
−1.0 to −0.50.5 to 1.0
类比于上述约定,我们也可以将欧几里得距离求出的值转化为类似相关系数的形式,Python实现如下:

#输入:x,y为维度相同的包含各特征值的list
#输出:输入值x,y的欧几里得距离评价

def sim_distance(x, y):
dist = sum([(x[i]-y[i])**2 for i in range(len(x))])**0.5
return 1 / (1+dist)


欧氏距离(或者说所有的明氏距离)都有两个明显的缺点

没有考虑到各维度分量数值分布的差别,数量级小的维度在数量级大的维度面前体现不出其差异性。

对于各维度的分量一视同仁,均将其视作单纯的数值进行计算,没有考虑到其代表的单位或是方向;

举两个栗子:

栗子一

Metacritic 是一家专门的对影视、游戏、音乐做评分的网站,他的评分有两套体系:1、Metascore:网站综合各权威媒体评论文章做出的打分,数值从1-100; 2、Userscore:网站用户评分的均值,数值从1-10。以两个最近上市的游戏为例:Watch Dogs 2的Metascore为74,Userscore为7.7;Planet Coaster的Metascore为84,Userscore为8.1。现在要考察媒体评分和用户评价之间的差别,可以得到两个样本点M(74, 84),U(7.7, 8.1)。直观的来看不难发现其实两套评价体系对这两款游戏的评价是差不多的,但如果去计算M和U的欧氏距离,dist_euc(M, U)=100.78,大了去了!

那么该如何解决数量级大的维度对结果具有绝对影响呢?很自然的想法就是对每个维度进行标准化,使每个维度差值对结果的贡献(或者说权重)相差不多,这就是标准化欧式距离:

标准化欧几里得距离(Standardized Euclidean Distance)

公式:dist(x,y)=∑i=1n(xi−misi−yi−misi)2−−−−−−−−−−−−−−−−−−−−√=∑i=1n(xi−yisi)2−−−−−−−−−−−√ 其中mi,si为每个维度上的均值和标准差

定义:对每个维度做标准化之后再求欧式距离,如果将标准差的倒数看成一个权重,也可称之为加权欧氏距离(Weighted Euclidean distance)。

#输入:x,y为维度相同的包含各特征值的list
#输出:输入值x,y的标准化欧式距离
def dis_seuc(x, y):
sd = [(((x[i]-(x[i]+y[i])/2)**2 + (y[i]-(x[i]+y[i])/2)**2 )/ 2)**0.5 for i in range(len(x))]
return sum([((x[i]-y[i])/sd[i])**2 for i in range(len(x))])**0.2


再算一下, dist_seuc(M, U) = 1.5157165665103982,好像媒体和玩家的评分差别也没那么大 : )

栗子二

假如时代广场街头采访路人关于数据挖掘的话题,得到了下面三个回答:

1. A: “Yeah I like datamining, it’s so cool~”

2. B: “Datamining? Never heard of it.”

3. C: “I’m learning datamining. Datamining is more than just cool, it’s the coolest thing ever.”

对三个字符串进行分词、词干提取、去除stopwords后,得到三个list,取”like”, “datamining”, “never”, “cool”为特征维度,以词频为权值可以得到下表:

namelikedataminingnevercool
A1101
B0110
C0202
也就是三个向量:

A = [1, 1, 0, 1]
B = [0, 1, 1, 0]
C = [0, 2, 0, 2]


直观的看句子我们能感觉到A和C的意思比较接近,A和B说的意思差远了。这时如果我们使用曼哈顿距离来计算,则dist(A, B) = 3, dist(A, C) = 3。嗯……好像哪里不太对劲吧。这就是因为距离函数无法表征向量方向上的变化,解决这个问题的方法,就是使用余弦相似度来衡量向量的距离。

相关系数

余弦相似度(Cosine similarity)

公式:sim(x,y)=cos(θ)=x⋅y∥x∥∥y∥=∑ni=1xi×yi∑ni=1x2i−−−−−−−√×∑ni=1y2i−−−−−−−√

定义:就是求两个向量夹角的余弦值。给出的相似性范围从-1到1:-1意味着两个向量指向的方向正好截然相反,1表示它们的指向是完全相同的,0通常表示它们之间是独立的,而在这之间的值则表示中间的相似性或相异性。

#输入:x,y为维度相同的包含各特征值的list
#输出:输入值x,y的余弦相似度
def sim_cos(x, y):
num = sum([x[i]*y[i] for i in range(len(x))])
den = sum([x[i]**2 for i in range(len(x))])**0.5\
*sum([y[i]**2 for i in range(len(y))])**0.5
return num / den


把上述栗子二带入一下有:

print(sim_cos(A, B), sim_cos(A, C), sep='\n')

#0.40824829046386296
#0.8164965809277259


这下好像差不多了,A和C两个向量的方向比较接近,A和B差的有点远 : )

余弦相似度擅长处理稀疏向量,常用于文本分析比较两个文本向量的相似度。

直观的从二维空间的散点图中比较一下欧式距离与余弦相似度的差别:

欧几里得距离:



与红点(3, 3)的欧氏距离小于1的点为绿色,位于半径为1,圆心为(3, 3)的圆内,呈状。

余弦相似度:



与红点(3, 3)的欧氏距离大于0.99的点为绿色,呈状。

余弦相似度同样也存在类似欧式距离缺点1的问题:Grade Inflation-分数膨胀(i.e. 夸大分值)。举个栗子:

Max和Caroline分别对三个电影A, B, C的打分如下表:

UserABC
Max123
Caroline4.04.55.0
虽然Max的打分都在3分以下,Caroline的打分都在4分以上,好像两人的观影口味相去甚远,但其实两个人对这三个电影的评价基本一致,有图为证:



评价趋势完全一致嘛,出现评分差距过大的问题完全就是因为Max习惯差评,Caroline习惯好评而已,两人的口味是一样的(毕竟同住一个屋檐下 : P)

计算一下余弦相似度:

print(sim_cos([1, 2, 3], [4.0, 4.5, 5.0]))
# 0.9561828874675149


结果没能像我们预期的一样近似1。

解决分数膨胀问题的做法其实和标准化欧几里得距离的做法一致,就是修正余弦相似度(Adjusted Cosine Similarity)

修正余弦相似度(Adjusted Cosine Similarity)

公式:sim=∑c∈Iij(Ri,c−Ri¯¯¯¯)(Rj,c−Rj¯¯¯¯)∑c∈Ii(Ri,c−Ri¯¯¯¯)2−−−−−−−−−−−−−−√∑c∈Ij(Rj,c−Rj¯¯¯¯)2−−−−−−−−−−−−−−√

公式中的符号意义如下:

符号意义符号意义
Iij用户i和用户j的公共评分集,也就是均被两者评分的物品的集合Ri,c用户i对物品c的评分
Ii被用户i评分了的物品的集合Rj,c用户j 对物品c的评分
Ij被用户j评分了的物品的集合Ri¯¯¯¯用户 i所有评分的均值
Rj¯¯¯¯用户j所有评分的均值
换成类似上述公式的形式就是:

sim(x,y)=∑i∈Ixyxi−x¯sxyi−y¯sy∑j∈Ix(xj−x¯sx)2−−−−−−−−−−−√∑k∈Iy(yk−y¯sy)2−−−−−−−−−−−√=∑i∈Ixy(xi−x¯)(yi−y¯)∑j∈Ix(xj−x¯)2−−−−−−−−−−−−√∑k∈Iy(yk−y¯)2−−−−−−−−−−−−√

符号意义符号意义
Ixy用户x和用户y的公共评分集,也就是均被两者评分的物品的集合xj用户x对物品j的评分
Ix被用户x评分了的物品的集合yk用户y 对物品k的评分
Iy被用户y评分了的物品的集合用户 x所有评分的均值
用户y所有评分的均值
定义:跟标准化欧氏距离的做法基本一致。在此例中是求用户(user)x和用户y的相似度,分数膨胀的来源在于两个用户打分尺度的不同,所以是对样本自身进行标准化(横向)。如果是求两个物品(item)之间的相似度,表格如下:

ItemMaxCaroline
A14.0
B24.5
C35.0
分数膨胀的来源依旧在于两个人打分尺度的不同,所以是对样本各维度方向进行标准化(纵向),总的来说就是分数膨胀的问题出在哪就对哪进行标准化。

#输入:x,y为维度相同的包含各特征值的list
#输出:输入值x,y的修正余弦相似度
def sim_acs(x, y):
m_x = sum(x) / len(x)
m_y = sum(y) / len(y)
x = [i-m_x for i in x]
y = [j-m_y for j in y]
return sim_cos(x, y)


再来计算一下Max&Caroline的栗子:

print(sim_acs([1, 2, 3], [4.0, 4.5, 5.0]))
# 0.9999999999999998


没毛病。

皮尔逊相关系数(Pearson correlation coefficient)

还是以这个图为例:



皮尔逊相关系数是判断两组数据与某一直线拟合程度的一种度量。以A、B、C三点做最佳拟合线(best-fit line),发现三个点均落在该直线上,则可以得到一个结果为1的相关度评价。如果修改一下样本值:

ItemMaxCaroline
A14.0
B25.0
C34.5
再做三个点和其线性回归线如下:



这时三个点不都落在最佳拟合线上,但仍呈一定的正相关性,相关系数约为0.5。

公式:ρX,Y=cov(X,Y)σXσY=E[(X−μX)(Y−μY)]σXσY

其中,E是数学期望,cov表示协方差,σX和σY是标准差,μX和μY是均值。

因为μX=E(X),σ2X=E(X2)−E2(X),Y类似,所以有:

ρX,Y=E[(X−E(X))(Y−E(Y))]E(X2)−E2(X)−−−−−−−−−−−−−√ E(Y2)−E2(Y)−−−−−−−−−−−−−√=E(XY)−E(X)E(Y)E(X2)−E2(X)−−−−−−−−−−−−−√ E(Y2)−E2(Y)−−−−−−−−−−−−−√。

定义:两个变量之间的皮尔逊相关系数定义为两个变量之间的协方差和标准差的商。皮尔逊相关系数的变化范围为-1到1。 系数的值为1意味着X 和 Y可以很好的由直线方程来描述,所有的数据点都很好的落在一条 直线上,且 Y 随着 X 的增加而增加。系数的值为−1意味着所有的数据点都落在直线上,且 Y 随着 X 的增加而减少。系数的值为0意味着两个变量之间没有线性关系。

对于样本,公式可以写成:

sim(x,y)=1n∑i∈Ixy(xi−x¯)(yi−y¯)1n∑i∈Ixy(xi−x¯)2−−−−−−−−−−−−−−−√1n∑i∈Ixy(yi−y¯)2−−−−−−−−−−−−−−√=∑i∈Ixy(xi−x¯)(yi−y¯)∑i∈Ixy(xi−x¯)2−−−−−−−−−−−−−√∑i∈Ixy(yi−y¯)2−−−−−−−−−−−−−√

符号意义符号意义
Ixy用户x和用户y的公共评分集,也就是均被两者评分的物品的集合xi用户x对物品i的评分
Ix被用户x评分了的物品的集合yi用户y 对物品i的评分
Iy被用户j评分了的物品的集合用户 x所有评分的均值
nIxy的维数用户y所有评分的均值
嗯……是不是看着贼眼熟?没错,这个公式和上面的修正余弦相似度的公式非常像。两者的区别在于分母上,皮尔逊相关系数的分母采用的评分集是两个用户的共同评分集,而修正余弦相似度则采用两个用户各自的评分集。

b不难发现,皮尔逊相关系数也可以有效应对分数膨胀。与修正余弦相似度类似,它也是将样本数据进行中心化,通过移轴使各样本均值位于0,再计算余弦值。虽从概率论中推出,却与修正余弦相似度殊途同归。

杰卡德相似系数(Jaccard similarity coefficient)

公式:J(A,B)=|A∪B||A∩B|

定义:两个集合A和B的交集元素在A,B的并集中所占的比例,称为两个集合的杰卡德相似系数,用符号J(A,B)表示。杰卡德相似系数是衡量两个集合的相似度一种指标。

杰卡德系数主要用于计算符号度量或布尔值度量的个体间的相似度,无法衡量差异具体值的大小,只能获得“是否相同”这个结果,所以杰卡德系数只关心个体间共同具有的特征是否一致这个问题。

dist=1−J(A,B)被定义为杰卡德距离,它用两个集合中不同元素占所有元素的比例来衡量两个集合的区分度

def sim_jac(A, B):
A = set(A)
B = set(B)
return len(A.intersection(B)) / len(A.union(B))

A = ['a', 'b', 'c', 'e', 'g']
B = ['a', 'b', 'd', 'g', 'h']
print('Jaccard similarity: ', sim_jac(A, B))
print('Jaccard distance: ', 1 - sim_jac(A, B))

#Jaccard similarity:  0.42857142857142855
#Jaccard distance:  0.5714285714285714


总结

距离算法和相关系数种类繁多,还有很多没有提及。如此多的算法中该如何选择出最合适的呢?这里有一个简单的规则:

如果数据存在“分数膨胀”问题,就使用皮尔逊相关系数/修正余弦相似度。

如果数据是类似字符串或布尔值的符号度量不是具体数值,使用杰卡德距离。

如果数据比较“密集”,变量之间基本都存在公有值,且这些距离数据是非常重要的,那就使用欧几里得或曼哈顿距离。

如果数据是稀疏的,则使用余弦相似度。

参考资料

维基百科

《集体智慧编程》

《datamining guide》

http://www.cnblogs.com/arachis/p/Similarity.html

http://wulc.me/2016/02/22/%E3%80%8AProgramming%20Collective%20Intelligence%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0(2)/

https://www.zhihu.com/question/21824291

http://www.ramlinbird.com/knn%E7%AE%97%E6%B3%95%E5%92%8C%E8%B7%9D%E7%A6%BB%E6%88%96%E7%9B%B8%E4%BC%BC%E5%BA%A6%E5%BA%A6%E9%87%8F/

http://blog.csdn.net/wsywl/article/details/5727327

http://yuguangchuan.github.io/2015/11/17/Distance-measurements/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  机器学习 python 算法