常见距离度量方式
数值属性的距离度量
闵可夫斯基距离(MInkowski distance)
$\Large dist_{mk}(x_i,x_j)=(\sum_{u=1}^{n}|x_{iu}-x_{ju}|^p)^{\frac{1}{p}}$
欧式距离
当p=2时,闵可夫斯基距离即为欧式距离(Euclidean distance),欧氏距离是最易于理解的一种距离计算方法,源自欧氏空间中两点间的距离公式。
$\Large dist_{ed}(x_i,x_j)=||x_i-x_j||2=\sqrt{\sum{u=1}^{n}|x_{iu}-x_{ju}|^2}$
曼哈顿距离
当p=1时,闵可夫斯基距离即为曼哈顿距离(Manhattan distance),又名城市街区距离,计算两个地点的驾驶距离。
$\Large dist_{man}(x_i,x_j)=||x_i-x_j||1=\sum{u=1}^{n}|x_{iu}-x_{ju}|$
切比雪夫距离
当$p=\infty $时,闵可夫斯基距离即为切比雪夫距离( Chebyshev Distance)。 国际象棋玩过么?国王走一步能够移动到相邻的8个方格中的任意一个。那么国王从格子(x1,y1)走到格子(x2,y2)最少需要多少步?自己走走试试。你会发现最少步数总是max(| x2-x1 | , | y2-y1 | ) 步。有一种类似的一种距离度量方法叫切比雪夫距离。
$\Large dist_{cd}(x_i,x_j)=\lim_{p \to \infty}(\sum_{u=1}^{n}|x_{iu}-x_{ju}|^p)^{\frac{1}{p}}=\max\limits_{1\le u\le n}(|x_{iu}-x_{ju}|)$
马氏距离
有M个样本向量X1~Xm,协方差矩阵记为S,均值记为向量μ,则其中样本向量X到u的马氏距离表示为:
$\Large dist_{md}(X)=\sqrt{(X-\mu)^TS^{-1}(X-\mu)}$
而其中向量$X_i$与$X_j$之间的马氏距离定义为:
$\Large dist_{md}(X_i,X_j)=\sqrt{(X_i-X_j)^TS^{-1}(X_i-X_j)}$
马氏距离的优点:
马氏距离不受量纲的影响,两点之间的马氏距离与原始数据的测量单位无关;由标准化数据和中心化数据(即原始数据与均值之差)计算出的二点之间的马氏距离相同。马氏距离还可以排除变量之间的相关性的干扰。
马氏距离的缺点:
马氏距离夸大了变化微小的变量的作用。
余弦距离
在sklearn中有余弦距离和余弦相似度,余弦相似度即为下面的公式,余弦距离定义为1-余弦相似度。
$$\Large Cos_\theta=\frac{\sum_1^n(x_i y_i)}{\sqrt{\sum_1^n x_i^2}\sqrt{\sum_1^n y_i^2}}$$
余弦值的范围在[-1,1]之间,值越趋近于1,代表两个向量的方向越接近;越趋近于-1,他们的方向越相反;接近于0,表示两个向量近乎于正交。
当在做客户评分相似度比较时,不能直接比较两者的评分,需要比较两者对于均值的偏移值。如下例子:
虽然余弦相似度对个体间存在的偏见可以进行一定的修正,但是因为只能分辨个体在维之间的差异,没法衡量每个维数值的差异,会导致这样一个情况:比如用户对内容评分,5分制,X和Y两个用户对两个内容的评分分别为(1,2)和(4,5),使用余弦相似度得出的结果是0.98,两者极为相似,但从评分上看X似乎不喜欢这2个内容,而Y比较喜欢,余弦相似度对数值的不敏感导致了结果的误差,需要修正这种不合理性,就出现了调整余弦相似度,即所有维度上的数值都减去一个均值,比如X和Y的评分均值都是3,那么调整后为(-2,-1)和(1,2),再用余弦相似度计算,得到-0.8,相似度为负值并且差异不小,但显然更加符合现实。
文本属性的距离度量
编辑距离
编辑距离(Edit Distance),又称Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。一般来说,编辑距离越小,两个串的相似度越大。
import numpy as np
def Levenshtein_distance(str1,str2):
'''
计算编辑距离,用于学习算法,如果大规模计算建议改进或换c/matlab
参数
-----------------------
str1 字符串列表
str2 字符串列表
'''
if str1==None:
return len(str2)
elif str2==None:
return len(str1)
else:
l1=len(str1)
l2=len(str2)
dis =np.zeros([l1+1,l2+1])
dis[0,:]=range(l2+1)
dis[:,0]=range(l1+1)
for i in range(1,l1+1):
for j in range(1,l2+1):
if str1[i-1]==str2[j-1]:
dis[i,j]=dis[i-1,j-1]
else:
dis[i,j]=min(dis[i - 1,j] + 1, dis[i,j - 1] + 1, dis[i - 1,j - 1] + 1)
return dis[l1,l2]
Hamming 距离
汉明距离是使用在数据传输差错控制编码里面的,汉明距离是一个概念,它表示两个(相同长度)字对应位不同的数量,我们以d(x,y)表示两个字x,y之间的汉明距离。对两个字符串进行异或运算,并统计结果为1的个数,那么这个数就是汉明距离。
def hamming(s1, s2):
"""
返回两个等长的字符串的Hamming相似度
参数
--------------------------------
s1 字符串1
s2 字符串2
"""
if len(s1) != len(s2):
raise ValueError("Undefined for sequences of unequal length")
return sum(el1 != el2 for el1, el2 in zip(s1, s2))
Jaro-Winkler距离
在计算机科学和统计学中,Jaro-Winkler距离是测量两个序列之间编辑距离的字符串度量。它是由威廉E.温克勒于1990年提出的Jaro距离度量(1989,Matthew A.Jaro)的一个变体。非正式地说,两个单词之间的Jaro距离是将一个单词换成另一个单词所需的单个字符换位的最小数量。
$$
\Large d_j=
\left{
\begin{array}{lcl}
\frac{1}{3}(\frac{m}{|s_1|}+\frac{m}{|s_2|}+\frac{m-t}{m}) & &m\neq0 \
0&&m=0\
\end{array} \right.
$$
$|s_i|$是字符串$s_i$的长度
m 是两个字符串匹配字符的数量
t 是换位数目的一半
其中t换位数目表示:两个分别来自S1和S2的字符如果相距不超过
$$
\Large |\frac{max(|s_1|,|s_2|)}{2}-1|
$$
我们就认为这两个字符串是匹配的;而这些相互匹配的字符则决定了换位的数目t,简单来说就是不同顺序的匹配字符的数目的一半即为换位的数目t,举例来说,MARTHA与MARHTA的字符都是匹配的,但是这些匹配的字符中,T和H要换位才能把MARTHA变为MARHTA,那么T和H就是不同的顺序的匹配字符,t=2/2=1。
而Jaro-Winkler则给予了起始部分就相同的字符串更高的分数,他定义了一个前缀权重p,给予两个字符串,如果前缀部分有长度为$l$ 的部分相同,则Jaro-Winkler Distance为:
$$
\Large d_w=d_j+(lp(1-d_j))
$$
$d_j$是两个字符串的Jaro Distance
l是前缀的相同的长度,但是规定最大为4
$p$则是调整分数的常数,规定不能超过0.25,不然可能出现dw大于1的情况,Winkler将这个常数定义为0.1
def Jaro_Winkler(string1, string2,is_jaro=False):
"""
计算jaro或jaro winkler相似度
参数
-------------------------------------------------
string1 字符串1
string2 字符串2
is_jaro 是否使用jaro,默认为False,使用jaro_winkler
返回
--------------------------------------------------
string1和string2的jaro相似度
"""
len_str1 = len(string1)
len_str2 = len(string2)
if len_str1==0 or len_str2==0:
return 0
max_len = max(len_str1, len_str2)
search_range =(max_len // 2) - 1
if search_range < 0:
search_range = 0
#定义两个字符串的匹配标记
flags_s1 = np.zeros(len_str1, dtype=np.int32)
flags_s2 = np.zeros(len_str2, dtype=np.int32)
common_chars = 0
low = 0
high = 0
i = 0
j = 0
#找到两个字符串的共有字符
for i in range(0,len_str1):
low = i - search_range if i > search_range else 0
high = i + search_range if i + search_range < len_str2 else len_str2 - 1
for j in range(low,high+1):
if flags_s2[j]==0 and string2[j]==string1[i]:
flags_s1[i]=flags_s2[j]=1
common_chars=common_chars+1
break
if common_chars == 0:
return 0
#需要变换的位数
trans_count = 0
#已经匹配过的字符位数
k = 0
#得到需要变换的位数
for i in range(0,len_str1):
if flags_s1[i]==1:
for j in range(k,len_str2):
if flags_s2[j]==1:
k=j+1
break
if string1[i]!=string2[j]:
trans_count=trans_count+1
trans_count =trans_count // 2
sim = (common_chars / len_str1 + common_chars / len_str2 +
(common_chars - trans_count) / common_chars) / 3
if is_jaro==False:
ms1=''
for i in range(len_str1):
if flags_s1[i]==1:
ms1=''+string1[i]
ms2=''
for j in range(len_str2):
if flags_s2[j]==1:
ms2=''+string2[j]
#字符串共有前缀
prefix = 0
for mi in range(len(ms1)):
if ms1[mi]==ms2[mi]:
prefix=prefix+1
sim=sim+prefix*0.1*(1-sim)
return sim
Jaccard 相似度
用于比较有限样本集之间的相似性与差异性。Jaccard系数值越大,样本相似度越高。
$\Large J(A,B)=\frac{|A\bigcap B|}{|A\bigcup B|}$
jaccard距离
Jaccard distance (A, B) = 1 - Jaccard(A, B)
下面提供两种计算jaccard相似度的方法,一种不考虑重复项,一种考虑重复项。
不考虑重复项公式如上。
考虑重复项时将jaccard变为乘法:
$\Large J(A,B)=\frac{|\sum_{i\in A }dA_idB_i|}{|A| \times |B|}$
$dA_i,dB_i$分别表示集合A和集合B中的词i的出现次数
def jaccard(str1,str2):
'''
计算jaccard相似度,不考虑str中是否有重复项
参数
-----------------
str1 字符串列表
str2 字符串列表
'''
return len((set(a)&set(b)))/len((set(a)|set(b)))
from collections import Counter
def jaccard_times(str1,str2):
'''
计算jaccard相似度,考虑str中的重复项
参数
----------------
str1 字符串列表
str2 字符串列表
'''
c1=Counter(str1)
c2=Counter(str2)
s=0
for i in c1.keys():
s=s+c1.get(i,0)*c2.get(i,0)
return s/(len(c1)*len(c2))
TextDistance使用记录
一个计算文本距离的包
Algorithms
Edit based
Algorithm
Class
Functions
Hamming
hamming
Mlipns
mlipns
Levenshtein
levenshtein
DamerauLevenshtein
damerau_levenshtein
JaroWinkler
jaro_winkler, jaro
StrCmp95
strcmp95
NeedlemanWunsch
needleman_wunsch
Gotoh
gotoh
SmithWaterman
smith_waterman
Token based
Algorithm
Class
Functions
Jaccard
jaccard
Sorensen
sorensen, sorensen_dice, dice
Tversky
tversky
MongeElkan
monge_elkan
Sequence based
Compression based
Work in progress. Now all algorithms compare two strings as array of bits, not by chars.
NCD - normalized compression distance.
Functions:
bz2_ncd
lzma_ncd
arith_ncd
rle_ncd
bwtrle_ncd
zlib_ncd
Phonetic
Algorithm
Class
Functions
MRA
mra
Editex
editex
Simple
Algorithm
Class
Functions
Prefix similarity
Prefix
prefix
Postfix similarity
Postfix
postfix
Length distance
Length
length
Identity similarity
Identity
identity
Matrix similarity
Matrix
matrix
Installation
Stable:
pip install textdistance
Dev:
pip install -e git+https://github.com/orsinium/textdistance.git#egg=textdistance
Usage
All algorithms have 2 interfaces:
Class with algorithm-specific params for customizing.
Class instance with default params for quick and simple usage.
All algorithms have some common methods:
.distance(*sequences) -- calculate distance between sequences.
.similarity(*sequences) -- calculate similarity for sequences.
.maximum(*sequences) -- maximum possible value for distance and similarity. For any sequence: distance + similarity == maximum.
.normalized_distance(*sequences) -- normalized distance between sequences. The return value is a float between 0 and 1, where 0 means equal, and 1 totally different.
.normalized_similarity(*sequences) -- normalized similarity for sequences. The return value is a float between 0 and 1, where 0 means totally different, and 1 equal.
Most common init arguments:
qval-- q-value for split sequences into q-grams. Possible values:
1 (default) -- compare sequences by chars.
2 or more -- transform sequences to q-grams.
None -- split sequences by words.
as_set-- for token-based algorithms:
True -- t and ttt is equal.
False (default) -- t and ttt is different.
Example
import textdistance
textdistance.hamming('test', 'text')
# 1
textdistance.hamming.distance('test', 'text')
# 1
textdistance.hamming.similarity('test', 'text')
# 3
textdistance.hamming.normalized_distance('test', 'text')
# 0.25
textdistance.hamming.normalized_similarity('test', 'text')
# 0.75
textdistance.Hamming(qval=2).distance('test', 'text')
# 2
>>> textdistance.Hamming.similarity(['test'],['text'])
0
>>> textdistance.Hamming.similarity(['text','abc'],['text'])
1
Any other algorithms have same interface.