各类算法中,距离是一个经常使用的量,经常会与各种相似性计算联系在一起。下面我们来总结一下各种距离与相似的计算。
1.欧式距离
欧式距离是我们最常用的距离度量之一,指在空间中两个点的真实距离长度,或者向量自身长度。
对于欧式距离,无须太多解释,直接看代码就可以。
import math
import numpy as np
from scipy.spatial.distance import pdist
def euclidean_distance():
x = np.array([1, 2, 3, 4])
y = np.array([5, 6, 7, 8])
distance = np.sqrt(sum(math.pow(a - b, 2) for a, b in zip(x, y)))
print(f'distance is: {distance}')
distance = np.sqrt(sum(np.square(x - y)))
print(f'distance is: {distance}')
xy = np.vstack([x, y])
distance = pdist(xy)
print(f'distance is: {distance[0]}')
输出结果为:
distance is: 8.0
distance is: 8.0
distance is: 8.0
2.标准化欧式距离
常规版的欧式距离没有考虑到向量各个维度的差异。如果向量各个维度的差异很大,会导致距离计算时候数值较大的分量占主导。比如两个向量a、b,a=(1,10), b=(2, 20),如果使用标准的欧式距离计算方式,第二维分量是第一维的十倍,此时整个距离计算第二维分量就占主要作用,第一维的影响很小。
为了解决上述问题,我们可以对每一维分量进行标准化处理。假设某一维分量为x, 均值为 μ \mu μ,标准差为s,则标准化公式为
x ∗ = x − μ s x^* = \frac{x - \mu}{s} x∗=sx−μ
有两个n维向量a, b, a = ( x 1 , x 2 , ⋯ , x n ) , b = ( y 1 , y 2 , ⋯ , y n ) a = (x_1,x_2,\cdots,x_n), b = (y_1,y_2,\cdots,y_n) a=(x1,x2,⋯,xn),b=(y1,y2,⋯,yn), 则ab的标准化欧式距离为
d ( a , b ) = ∑ k = 1 n ( x k − y k s k ) 2 d(a, b) =\sqrt { \sum_{k=1}^n \left(\frac{x_k - y_k}{s_k}\right)^2 } d(a,b)=k=1∑n(skxk−yk)2
def standard_euclidean_distance():
x = np.array([1, 2, 3, 4])
y = np.array([5, 6, 7, 8])
xy = np.vstack([x, y])
sk = np.var(xy, axis=0, ddof=1)
print(f'sk is: {sk}')
distance = np.sqrt(((x - y) ** 2 / sk).sum())
print(f'distance is: {distance}')
distance = pdist(xy, 'seuclidean')
print(f'distance is: {distance[0]}')
代码输出为:
sk is: [8. 8. 8. 8.]
distance is: 2.8284271247461903
distance is: 2.8284271247461903
3.曼哈顿距离
出租车几何或曼哈顿距离(Manhattan Distance)是由十九世纪的赫尔曼·闵可夫斯基所创词汇 ,是种使用在几何度量空间的几何学用语,用以标明两个点在标准坐标系上的绝对轴距总和。
在直角坐标系中,假设有两点 a = ( x 1 , y 1 ) a=(x_1, y_1) a=(x1,y1), b = ( x 2 , y 2 ) b = (x_2, y_2) b=(x2,y2),则两点的曼哈顿距离为
d ( a , b ) = ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ d(a, b) = |x_1 - x_2| + |y_1 - y_2| d(a,b)=∣x1−x2∣+∣y1−y2∣
代码实现如下
def manhattan_distance():
x = np.array([1, 2, 3, 4])
y = np.array([5, 6, 7, 8])
manhattan = np.sum(np.abs(x - y))
print(f'base on self calculate, manhattan is: {manhattan}')
xy = np.vstack([x, y])
manhattan = pdist(xy, 'cityblock')
print(f'base on pdist, cityblock is: {manhattan[0]}')
base on self calculate, manhattan is: 16
base on pdist, cityblock is: 16.0
4.切比雪夫(Chebyshev)距离
对于两个点,切比雪夫距离定义为各坐标值数值差绝对值中的最大值。如果从向量角度看,就是两个向量各维度差值绝对值的最大值。
对于两个n维向量a, b, a = ( x 1 , x 2 , ⋯ , x n ) , b = ( y 1 , y 2 , ⋯ , y n ) a = (x_1,x_2,\cdots,x_n), b = (y_1,y_2,\cdots,y_n) a=(x1,x2,⋯,xn),b=(y1,y2,⋯,yn),切比雪夫距离为
d ( a , b ) = max ( ∣ x 1 − y 1 ∣ , ∣ x 2 − y 2 ∣ , ⋯ , ∣ x n − y n ∣ ) = max i ( x i − y i ) d(a, b) = \max(|x_1 - y_1|, |x_2 - y_2|, \cdots, |x_n - y_n|) = \max_i (x_i - y_i) d(a,b)=max(∣x1−y1∣,∣x2−y2∣,⋯,∣xn−yn∣)=imax(xi−yi)
上面的表达式等价为
d ( a , b ) = lim p → ∞ ( ∑ k = 1 n ∣ x k − y k ∣ p ) 1 / p d(a, b) =\lim_{p \to \infty} \left( { \sum_{k=1}^n |x_k - y_k|^p } \right) ^ {1/p} d(a,b)=p→∞lim(k=1∑n∣xk−yk∣p)1/p
def chebyshev_distance():
x = np.array([1, 2, 3, 4])
y = np.array([5, 6, 7, 10])
distance = np.max(np.abs(x - y))
print(f'base on self calculate, chebyshev is: {distance}')
xy = np.vstack([x, y])
distance = pdist(xy, 'chebyshev')
print(f'base on pdist, chebyshev is: {distance[0]}')
base on self calculate, chebyshev is: 6
base on pdist, chebyshev is: 6.0
5.闵可夫斯基(Minkowski)距离
对于两个n维向量a, b, a = ( x 1 , x 2 , ⋯ , x n ) , b = ( y 1 , y 2 , ⋯ , y n ) a = (x_1,x_2,\cdots,x_n), b = (y_1,y_2,\cdots,y_n) a=(x1,x2,⋯,xn),b=(y1,y2,⋯,yn),p为一个变量,定义闵可夫斯基距离(闵氏距离)为:
d ( a , b ) = ∑ k = 1 n ∣ x k − y k ∣ p p = ( ∑ k = 1 n ∣ x k − y k ∣ p ) 1 / p d(a, b) =\sqrt [p]{ \sum_{k=1}^n |x_k - y_k|^p } = \left( { \sum_{k=1}^n |x_k - y_k|^p } \right) ^ {1/p} d(a,b)=pk=1∑n∣xk−yk∣p=(k=1∑n∣xk−yk∣p)1/p
需要注意的是,闵氏距离不是某一种具体的距离,而是一类距离的定义。
当p=1,此时就是曼哈顿距离。
当p=2,此时就是欧式距离。
当 p → ∞ p \to \infty p→∞,此时是切比雪夫距离。
def minkowski_distance():
x = np.array([1, 2, 3, 4])
y = np.array([5, 6, 7, 8])
xy = np.vstack([x, y])
manhattan = pdist(xy, 'minkowski', p=1)
print(f'when p=1, manhattan result is: {manhattan[0]}')
euclidean = pdist(xy, 'minkowski', p=2)
print(f'when p=2, euclidean result is: {euclidean[0]}')
when p=1, manhattan result is: 16.0
when p=2, euclidean result is: 8.0
6.杰卡德(Jaccard)距离
杰卡德相似系数(Jaccard similarity coefficient):是指两个集合A、B中,交集元素在A、B的并集中所占的比例,这个比例叫杰卡德相似系数,可以使用J(A,B)表示。
J ( A , B ) = A ∩ B A ∪ B J(A, B) = \frac{A \cap B}{A \cup B} J(A,B)=A∪BA∩B
杰卡德距离(Jaccard Distance)与杰卡德相似系数相反,用两个集合中不同元素的比例来表示两个集合的区分度。
J d ( A , B ) = 1 − J ( A , B ) = A ∪ B − A ∩ B A ∪ B J_d(A, B) = 1 - J(A, B) = \frac{A \cup B - A \cap B}{A \cup B} Jd(A,B)=1−J(A,B)=A∪BA∪B−A∩B
def jaccard_distance():
x = np.random.random(10) > 0.5
y = np.random.random(10) > 0.5
x = np.asarray(x, np.int32)
y = np.asarray(y, np.int32)
print("x = ", x)
print("y = ", y)
xy = np.vstack([x, y])
# 根据公式求解
up = np.double(np.bitwise_and((x != y), np.bitwise_or(x != 0, y != 0)).sum())
down = np.double(np.bitwise_or(x != 0, y != 0).sum())
dist1 = up / down
print(f'up is: {up}, down is: {down}')
print(f"base on pdist, dist1 is: : {dist1}")
dist2 = pdist(xy, 'jaccard')
x = [0 0 0 1 0 1 0 1 1 1]
y = [0 0 1 0 0 0 1 1 0 1]
up is: 5.0, down is: 7.0
base on pdist, dist1 is: : 0.7142857142857143
dist2 is: 0.7142857142857143
注意上面的计算,删除了(0,0)的数据对。
7.汉明(Hamming)距离
在信息论中,两个等长字符串之间的汉明距离(Hamming distance)是两个字符串对应位置的不同字符的个数。换句话说,它就是将一个字符串变换成另外一个字符串所需要替换的字符个数。
需要注意的是,汉明距离针对的是两个等长字符串。
def hamming_distance():
x = np.random.random(10) > 0.5
y = np.random.random(10) > 0.5
x = np.asarray(x, np.int32)
y = np.asarray(y, np.int32)
print(f'x is: {x}')
print(f'y is: {y}')
hamming = np.mean(x != y)
print(f'base on self calculate, hamming is: {hamming}')
xy = np.vstack([x, y])
hamming = pdist(xy, 'hamming')
print(f'base on pdist, hamming is: {hamming[0]}')
x is: [0 0 0 1 0 0 0 1 0 1]
y is: [1 1 1 0 1 1 1 1 1 0]
base on self calculate, hamming is: 0.9
base on pdist, hamming is: 0.9
8.编辑距离(levenshtein distance)
编辑距离是针对二个字符串(例如英文字)的差异程度的量化量测,量测方式是看至少需要多少次的处理才能将一个字符串变成另一个字符串。编辑距离可以用在自然语言处理中,例如拼写检查可以根据一个拼错的字和其他正确的字的编辑距离,判断哪一个(或哪几个)是比较可能的字。DNA也可以视为用A、C、G和T组成的字符串,因此编辑距离也用在生物信息学中,判断二个DNA的类似程度。Unix 下的 diff 及 patch 即是利用编辑距离来进行文本编辑对比的例子。
def levenshtein_distance(s1, s2):
n, m = len(s1), len(s2)
distance = [[0 for _ in range(m + 1)] for _ in range(n + 1)]
for i in range(n + 1):
distance[i][0] = i
for j in range(m + 1):
distance[0][j] = j
for i in range(1, n + 1):
for j in range(1, m + 1):
cost = 0 if s1[i-1] == s2[j-1] else 1
distance[i][j] = min(
distance[i - 1][j] + 1,
distance[i][j - 1] + 1,
distance[i - 1][j - 1] + cost
)
result = distance[n][m]
print(f'result is: {result}')
return result
s1, s2 = 'kitten', 'sitting'
levenshtein_distance(s1, s2)
result is: 3
9.余弦距离(cosine distance)
余弦是非常常用的概念。几何中夹角余弦可用来衡量两个向量方向的差异,机器学习中使用这一概念来衡量样本向量之间的差异。相比距离度量,余弦相似度更加注重两个向量在方向上的差异,而非距离或长度上。
def cosine_distance():
x = np.array([1, 2, 3, 4])
y = np.array([5, 6, 7, 8])
cosine = np.dot(x, y) / (np.linalg.norm(x) * np.linalg.norm(y))
print(f'base on self calculate, cosine is: {cosine}')
xy = np.vstack([x, y])
cosine = pdist(xy, 'cosine')
print(f'base on pdist, cosine is: {1 - cosine[0]}')
base on self calculate, cosine is: 0.9688639316269662
base on pdist, cosine is: 0.9688639316269662
10.皮尔逊相关系数(Pearson correlation coefficient)
皮尔逊相关系数是用来度量两个向量x,y之间的相关性,值位于[-1,1]之间。
两个变量之间的皮尔逊相关系数定义为两个变量的协方差除以它们标准差的乘积:
ρ X , Y = c o v ( X , Y ) σ X σ Y = E [ ( X − μ X ) ( Y − μ Y ) ] σ X σ Y {\displaystyle \rho _{X,Y}={\mathrm {cov} (X,Y) \over \sigma _{X}\sigma _{Y}}={E[(X-\mu _{X})(Y-\mu _{Y})] \over \sigma _{X}\sigma _{Y}}} ρX,Y=σXσYcov(X,Y)=σXσYE[(X−μX)(Y−μY)]
上式定义了总体相关系数,常用希腊小写字母 ρ (rho) 作为代表符号。估算样本的协方差和标准差,可得到样本相关系数(样本皮尔逊系数),常用英文小写字母 r 表示:
r = ∑ i = 1 n ( X i − X ‾ ) ( Y i − Y ‾ ) ∑ i = 1 n ( X i − X ‾ ) 2 ∑ i = 1 n ( Y i − Y ‾ ) 2 r={\frac {\sum \limits _{i=1}^{n}(X_{i}-{\overline {X}})(Y_{i}-{\overline {Y}})}{{\sqrt {\sum \limits _{i=1}^{n}(X_{i}-{\overline {X}})^{2}}} {\sqrt {\sum \limits _{i=1}^{n}(Y_{i}-{\overline {Y}})^{2}}}}} r=i=1∑n(Xi−X)2i=1∑n(Yi−Y)2i=1∑n(Xi−X)(Yi−Y)
r也可以从样本点的标准分数均值估算,等价的表达式为:
r = 1 n − 1 ∑ i = 1 n ( X i − X ‾ σ X ) ( Y i − Y ‾ σ Y ) r={\frac {1}{n-1}}\sum \limits _{i=1}^{n}\left({\frac {X_{i}-{\overline {X}}}{\sigma _{X}}}\right)\left({\frac {Y_{i}-{\overline {Y}}}{\sigma _{Y}}}\right) r=n−11i=1∑n(σXXi−X)(σYYi−Y)
def pearson_correlation_coefficient():
x = np.array([1, 2, 3, 4])
y = np.array([5, 6, 7, 8])
x_ = x - np.mean(x)
y_ = y - np.mean(y)
pearson = np.dot(x_, y_) / (np.linalg.norm(x_) * (np.linalg.norm(y_)))
print(f'pearson is: {pearson}')
xy = np.vstack([x, y])
pearson = np.corrcoef(xy)
print(f'pearson is: {pearson}')
输出结果为
pearson is: 0.9999999999999998
pearson is: [[1. 1.]
[1. 1.]]