在机器学习中,会经常与各种距离打交道。于是,在这篇文章中,我们来简单总结一下常见的距离以及其计算方式。
首先定义两个向量 a ⃗ = ( x 1 , x 2 , … , x n ) , b ⃗ = ( y 1 , y 2 , … , y n ) \vec{a}=(x_1,x_2,\ldots,x_n),\vec{b}=(y_1,y_2,\ldots,y_n) a=(x1,x2,…,xn),b=(y1,y2,…,yn),然后计算它们之间的距离。
曼哈顿距离
又称“街区距离”。是向量各维度差值的绝对值之和。
d
=
∑
i
=
1
n
∣
x
i
−
y
i
∣
d=\sum_{i=1}^n\vert x_i-y_i\vert
d=i=1∑n∣xi−yi∣
欧氏距离
欧氏距离是大家最为熟悉的距离,早在高中甚至初中学习中就学习过。下面来回顾一下:
普通欧氏距离
普通欧氏距离是两个点在空间中的距离,对应向量就是各个分量差值的平方和再开方。
d
=
∑
i
=
1
n
(
x
i
−
y
i
)
2
d=\sqrt{\sum_{i=1}^n(x_i-y_i)^2}
d=i=1∑n(xi−yi)2
标准化欧氏距离
为什么需要标准化欧氏距离呢?因为原始的欧氏距离有缺点:
- 数据各维分量的分布不一样
- 各个维度之间的尺度不一样
方法:先将各个分量都“标准化”到均值、方差相等。标准化后的值=( 标准化前的值-分量的均值) /分量的标准差
d
=
∑
i
=
1
n
(
x
i
−
y
i
)
S
i
2
d=\sqrt{\sum_{i=1}^n\frac{(x_i-y_i)}{S_i}^2}
d=i=1∑nSi(xi−yi)2
切比雪夫距离
切比雪夫距离,来源于这样一个问题:国际象棋中的国王,每次可以沿八个方向行动,假设国王从A点到B点,那么国王至少需要移动几步?
对应我们的向量中,问题的答案就是向量各维的差值的最大值。
d
=
max
∣
x
i
−
y
i
∣
d=\max\vert x_i-y_i\vert
d=max∣xi−yi∣
闵可夫斯基距离
闵可夫斯基距离可能大家不太熟悉。其实它不是一个距离,而是一组距离。它的计算公式为:
d
=
∑
i
=
1
n
(
x
i
−
y
i
)
p
p
d=\sqrt[p]{\sum_{i=1}^n (x_i-y_i)^p}
d=pi=1∑n(xi−yi)p
当p取不同的值时,意义也会有所不同了。
- p=1,为曼哈顿距离
- p=2,为欧氏距离
- p= ∞ \infty ∞时,为切比雪夫距离
余弦距离
这个就不是度量向量之间的距离了,它是度量向量方向的差异。
d
i
s
t
(
a
,
b
)
=
1
−
A
⋅
B
∥
A
∥
⋅
∥
B
∥
dist(a,b)=1-\frac{A\cdot B}{\Vert A\Vert \cdot\Vert B\Vert}
dist(a,b)=1−∥A∥⋅∥B∥A⋅B
它的取值范围[0,2],值越大,说明两个向量方向差异也越大。余弦距离天然实现了归一化,不用考虑各个维度之间的尺度不一样的问题。
字符串距离
在计算机视觉中,是要经常跟字符串打交道的。字符串也有其相应的距离。
汉明距离
汉明距离很常见,它有两种表述:一是两个字符串对应位置的不同字符的个数,二是将其中一个变为另外一个所需要作的最小字符替换次数。
这从编程的角度上来说其实很简单。
例子:
-
1011111与1001001之间的汉明距离是3
-
happy 与puppy之间的汉明距离是2
int HanMing(const string&a,const string&b){
//字符串长度不一致时无法计算汉明距离
if (a.size()!=b.size())
return -1;
int ans=0;
for (int i = 0; i < a.size(); ++i) {
if (a[i]!=b[i])
ans++;
}
return ans;
}
编辑距离
编辑距离也是一个经典的字符串距离,它指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。
经典算法使用动态规划求解,设字符串A,B,计算它们的编辑距离。
状态转移方程:
下面附上代码实现(C++):
vector<vector<int>>dp;
int Distance(const string&a,const string&b){
//向量容器初始化(当其中一个字符串为0时的答案)
dp.assign(a.size()+1,vector<int>(b.size()+1));
for (int i = 0; i <= a.size(); ++i) {
dp[i][0]=i;
}
for (int i = 0; i <= b.size(); ++i) {
dp[0][i]=i;
}
//状态转移方程(核心部分)
for (int i = 1; i <= a.size(); ++i) {
for (int j = 1; j <= b.size(); ++j) {
if (a[i-1]==b[j-1])
dp[i][j]=dp[i-1][j-1];
else
dp[i][j]= min({dp[i-1][j],dp[i][j-1],dp[i-1][j-1]})+1;
}
}
return dp[a.size()][b.size()];
}