我们在很多地方都会遇到同一个问题。推荐系统里要判断两个商品像不像,聚类里要看两个样本离多远,向量检索里要从百万条候选里挑出最接近的几条。说到底,都是要把两个向量的相似程度量化成一个数。这个数怎么算?方法有一大族,各自看重的东西不一样,选错了结论可能完全反过来。
接下来,我们把七种常见的度量一次过一遍,它们是:曼哈顿、欧几里得、切比雪夫、闵可夫斯基、标准化欧氏、余弦、点积。每一种我们都会先给定义、讲直观意思,再拿具体向量代进去算出一个数,最后放一张对照表帮你按场景挑。读这篇之前,你需要知道向量是什么,以及点积和模长怎么算。
1. 曼哈顿距离
我们先从最好懂的一种说起。曼哈顿距离就是两个向量在每一维上的差取绝对值,再全部加起来,公式写成 d = 各维 |xi – yi| 之和。它的名字来自城市里开车,你只能沿横纵街道走,从这头到那头要拐几个直角、走多少个街区,而不能斜着穿楼过去。
我们代一组具体数进去。取 x = (3, 5),y = (4, 9),第一维差 |3-4| = 1,第二维差 |5-9| = 4,加起来距离就是 5。写成代码就是:
def manhattan_distance(x, y):
distance = 0.0
for v1, v2 in zip(x, y):
distance += abs(v1 - v2)
return distance
if __name__ == '__main__':
x = [3, 5]
y = [4, 9]
print('distance:', manhattan_distance(x, y))
跑出来是 5,和手算的一致。它对每一维一视同仁地累加,缺点是受量纲影响大,数字大的那一维在求和里天然占大头。
2. 欧氏距离
曼哈顿走的是街区折线,而两点之间的直线距离就是欧氏距离,也叫欧几里得度量。在二维三维空间里,它就是中学几何里两点连线的长度,公式写成 d = 各维 (xi – yi) 平方之和再开根号。
我们还用刚才那对向量 x = (3, 5),y = (4, 9)。差的平方是 (3-4)² = 1 和 (5-9)² = 16,加起来 17,开根号约等于 4.1231。它比曼哈顿的 5 小,正好符合直线比折线短的直觉。代码如下:
import math
def euclidean_distance(x, y):
distance = 0.0
for v1, v2 in zip(x, y):
distance += math.pow(v1 - v2, 2)
return math.sqrt(distance)
if __name__ == '__main__':
x = [3, 5]
y = [4, 9]
print('distance:', euclidean_distance(x, y))
因为差值先平方再求和,大的那一项会被进一步放大,所以欧氏距离对离群维度比曼哈顿更敏感。它也是日常用得最多的一种距离。
3. 切比雪夫距离
前两种都把各维的差合起来算,切比雪夫距离换了个思路,它只盯着最大的那一维。两个向量各维差的绝对值是一串数,切比雪夫距离就取里面最大的那个。
这个名字来自国际象棋。王一步可以横走、竖走、斜走一格,从一个格子走到另一个格子最少要几步,那就是切比雪夫距离。因为只要把最长那一段走完,短的几段在斜走里顺带就覆盖了。我们代 x = (3, 5, 9),y = (4, 9, 1),三个差的绝对值是 1、4、8,取最大的 8:
import math
def chebyshev_distance(x, y):
max_distance = 0.0
for v1, v2 in zip(x, y):
if abs(v1 - v2) > max_distance:
max_distance = abs(v1 - v2)
return max_distance
if __name__ == '__main__':
x = [3, 5, 9]
y = [4, 9, 1]
print('distance:', chebyshev_distance(x, y))
4. 闵氏距离
讲到这里你可能发现了,上面三种其实是同一个公式的三个特例。把它们统一起来的就是闵可夫斯基距离,简称闵氏距离。公式写成 d = 各维 |xi – yi| 的 p 次方之和,再开 p 次方,里面的 p 是一个可调的常数。
p 取不同值,就退化成前面讲过的某一种。p = 1 时,开一次方就是把绝对值差直接相加,正好是曼哈顿距离。p = 2 时,平方和开根号,就是欧氏距离。p 趋向无穷大时,高次幂会让最大那一项在求和里占绝对主导,结果就收敛到切比雪夫距离。我们拿一对新向量 x = (1, 2),y = (4, 6) 试 p = 3,差是 3 和 4,(3³ + 4³) 开三次方等于 91 开三次方,约等于 4.50。可以看到,p 越大,距离越偏向最大那一维的差。
5. 标准化欧氏
普通欧氏距离有个毛病,它对各维一视同仁,可现实里各维的量纲往往差得很远。举个例子,身高用米记录,数值在 1 到 2 之间,体重用公斤记录,动辄五六十,欧氏距离算出来几乎被体重那一维的差异主导,身高那点差别直接被淹没了。
标准化欧氏距离就是针对这个缺点的改进。它先把每一维除以这一维在整个数据集上的标准差 σi,让各维的方差拉到同一个量级,再算欧氏距离,公式写成 d = 各维 ((xi – yi) 除以 σi) 的平方和再开根号。这样量纲大的维度不会因为数字大就占便宜,量纲小的维度也不会被埋没。这里我们直接用文字公式说明,思路和配图是一致的。
6. 余弦相似度
前面几种都是距离,越小代表越近、越像。余弦相似度换了个角度,它不管向量有多长,只看两个向量的方向一不一致。公式写成两个向量的点积除以两个模长的乘积,即:cos = (x·y) 除以 (|x|·|y|),取值范围在 -1 到 1 之间。越接近 1,夹角越小、方向越一致,越接近 -1,方向相反,等于 0 就是互相垂直,扯不上关系。
import numpy as np
def cosine_similarity(x, y):
x = np.array(x)
y = np.array(y)
a = x @ y
b = np.sqrt(x @ x) * np.sqrt(y @ y)
return a / b
if __name__ == '__main__':
x = [0, 1]
y = [0, 2]
print('distance:', cosine_similarity(x, y))
x = [0, 1]
y = [0, 5]
print('distance:', cosine_similarity(x, y))
x = [0, 1]
y = [0, -1]
print('distance:', cosine_similarity(x, y))
x = [0, 1]
y = [0, -5]
print('distance:', cosine_similarity(x, y))
输出结果:
distance: 1.0 distance: 1.0 distance: -1.0 distance: -1.0
这四组例子里 x 都是 [0, 1],y 只是在第二个坐标上取 2、5、-1、-5。前两组方向完全一致,不管 y 拉长到 2 还是 5,余弦都是 1。后两组方向反过来,都是 -1。这就验证了,余弦只和两个向量的夹角有关,和各自模长多大没关系。做文本相似度时我们常选它,长文档和短文档只要用词方向一致就算相似,不会因为文档长、点积大就被误判成更像。
7. 点积相似度
余弦在分母上把模长除掉了,点积则把模长保留下来。点积公式写成 x·y = |x|·|y|·cosθ,它既看夹角,又乘上两个向量各自的长度。两个向量方向越一致、本身越长,点积就越大。
我们拿统一的例子 x = (1, 2),y = (4, 6) 算一遍,点积是 1×4 + 2×6 = 16。换一个和 y 同向但长一倍的向量 (8, 12),点积会翻倍到 32,可夹角其实一点没变。这就是点积的特点,它把长度也算进了相似度里。好处是算起来比余弦省事,少两次开方和一次除法,向量检索里常拿它先粗排一遍,再交给精排模型细排。要注意的坑也在这里,向量没归一化时,长向量天然点积偏大,可能并不是它真的更像。
8. 对照与选择
七种方法摆在一起,我们挑一张表对照最清楚。用同一对向量 x = (1, 2),y = (4, 6) 把各种度量都算出来,横向一比,差别就显出来了:
| 度量 | 看重什么 | 取值方向 | 本例数值 |
|---|---|---|---|
| 曼哈顿 | 各维绝对差之和 | 越小越近 | 7 |
| 欧氏 | 两点直线距离 | 越小越近 | 5 |
| 切比雪夫 | 最大那一维的差 | 越小越近 | 4 |
| 闵氏 p = 3 | 大差值权重更高 | 越小越近 | 4.50 |
| 余弦 | 夹角方向,不管长度 | 越接近 1 越像 | 0.9923 |
| 点积 | 方向和长度一起算 | 越大越像 | 16 |
那怎么选呢?我们先问自己在不在乎向量长度。只关心方向,比如文本、标签的相似度,用余弦。要算真实坐标点之间的距离,用欧氏。各维量纲不齐、数字大小差很多,先做标准化再算欧氏。向量已经归一化过、又追求速度,点积和余弦结果一致,用点积更省一次除法。
我们回到开头的问题。衡量两个向量像不像,本质上是在量两件事,方向和长度。距离类的曼哈顿、欧氏、切比雪夫、闵氏、标准化欧氏,关心两个点隔多远,越小越像,其中闵氏用一个可调的 p 把前三种串在一条线上。相似度类的余弦和点积,关心方向是否一致,余弦剔除长度只看夹角,点积把长度一起算进去。按你在不在乎长度和量纲去挑,基本就不会选错了。




冀公网安备13050302001966号