Skip to content

向量点积、模长与余弦相似度详解(含代码演示)

这是一篇面向「不太了解」同学的学习笔记:用最直白的方式讲清楚点积(Dot Product)、模长(Magnitude)、余弦相似度(Cosine Similarity) 三个概念,并用 Python 代码逐一演示。不发散,重在建立直觉。

从一个问题出发

假设我们用一个数字列表来表示一段文本、一张图片、或一个商品:

text
"苹果手机"  →  [0.21, -0.84, 0.55, ...]
"水果摊"    →  [0.19, -0.80, 0.61, ...]
"显卡"      →  [-0.77, 0.12, 0.03, ...]

这种「数字列表」在数学里叫向量(Vector)。列表里的每个数字叫一个维度(dimension)

那怎么判断「苹果手机」和「水果摊」更相似,还是和「显卡」更相似?答案就是比较这两个向量的方向——而比较方向最常用的工具,就是本文的三个主角。

1. 向量是什么(快速回顾)

向量就是带方向、带大小的有序数字列表

python
a = [3, 4]      # 二维向量:向右 3,向上 4
b = [4, -3]     # 另一个二维向量

它在平面上是一个从原点出发的箭头。[3, 4] 指向右上方,[4, -3] 指向右下方。

2. 模长(Magnitude / 范数)

模长就是向量的长度,也就是那个箭头从原点到端点的直线距离。

二维下用勾股定理:

text
|a| = √(a₁² + a₂²)

推广到 n 维:

text
|a| = √(a₁² + a₂² + ... + aₙ²)

代码实现:

python
import math

def magnitude(v):
    # 每个分量平方后求和,再开根号
    return math.sqrt(sum(x * x for x in v))

a = [3, 4]
print(magnitude(a))   # 5.0  → 3-4-5 直角三角形

几何直觉:模长衡量的是「这个向量有多大 / 多长」,和「指向哪里」无关。

3. 点积(Dot Product)

点积把两个等长向量算成一个标量(单个数字):对应位置相乘,再把所有乘积加起来。

text
a · b = a₁·b₁ + a₂·b₂ + ... + aₙ·bₙ

代码实现:

python
def dot(a, b):
    # 对应元素相乘后求和
    return sum(x * y for x, y in zip(a, b))

a = [3, 4]
b = [4, -3]
print(dot(a, b))     # 3*4 + 4*(-3) = 12 - 12 = 0

点积还有一个等价的几何公式,非常关键:

text
a · b = |a| · |b| · cosθ

其中 θ 是两个向量之间的夹角。把上面 a·b=0 代入:0 = |a|·|b|·cosθcosθ = 0θ = 90°

这说明:ab 互相垂直(正交)!

点积的三种几何含义:

  • 结果 > 0:两向量夹角小于 90°,方向「大致相同」。
  • 结果 = 0:两向量垂直,互不影响。
  • 结果 < 0:夹角大于 90°,方向「相反」。

但点积有个毛病:它会被向量长度带偏。 同一个方向、只是把向量拉长一倍,点积就会翻倍。所以我们往往不直接用点积衡量「相似度」。

4. 余弦相似度(Cosine Similarity)

既然点积受长度影响,那就把长度「除掉」:

text
cosine(a, b) = (a · b) / (|a| · |b|)

这正是上面几何公式里那个 cosθ!它只看方向,把两个向量的模长都归一化掉了。

取值范围固定在 [-1, 1]

  • 1:方向完全相同(最相似)。
  • 0:垂直(不相关)。
  • -1:方向完全相反。

代码实现(纯 Python):

python
def cosine(a, b):
    dot_ab = sum(x * y for x, y in zip(a, b))
    norm_a = math.sqrt(sum(x * x for x in a))
    norm_b = math.sqrt(sum(y * y for y in b))
    return dot_ab / (norm_a * norm_b)

a = [3, 4]
b = [4, -3]
print(cosine(a, b))   # 0.0  → 垂直,不相关

用 numpy 更简洁(实际项目里都这么写):

python
import numpy as np

a = np.array([3, 4])
b = np.array([4, -3])

cos = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
print(cos)            # 0.0

5. 完整示例:为什么用余弦而不是点积

python
import numpy as np

# 三个向量,方向差不多,但长度不同
v_apple   = np.array([3, 4])          # 长度 5
v_fruit   = np.array([6, 8])          # 和 v_apple 同方向,但长度 10(翻倍)
v_gpu     = np.array([-4, 3])         # 指向另一个方向

def cosine(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

print("点积  apple-fruit :", np.dot(v_apple, v_fruit))   # 50  (因为 fruit 更长,点积被放大)
print("点积  apple-gpu   :", np.dot(v_apple, v_gpu))     # -24
print("余弦  apple-fruit :", cosine(v_apple, v_fruit))   # 1.0  (方向一致,最相似)
print("余弦  apple-gpu   :", cosine(v_apple, v_gpu))     # -0.28(方向相反)

可以看到:

  • 点积apple-fruit=50 明显大于 apple-gpu=-24,但它高是因为 fruit 向量更长,而不是更「相似」。
  • 余弦apple-fruit=1.0(完全同向)清晰地表达了「这两者最像」,不受长度干扰。

这就是文本相似度、推荐系统、图像检索几乎都用余弦相似度而不是原始点积的原因。

6. 在 AI 中的应用

在现代 AI 里,文字、图片、音频都会被模型编码成嵌入向量(Embedding):语义越接近,向量方向越接近。于是:

  • 语义检索(RAG):把问题向量和知识库里每句话的向量算余弦相似度,取最高的若干条喂给大模型。
  • 推荐系统:用户向量和商品向量越「余弦相似」,越可能感兴趣。
  • 去重 / 聚类:余弦接近的归为一类。

一句话总结三者关系:

模长描述向量「有多大」,点积粗略描述「多同向(但受长度干扰)」,余弦相似度除以模长后,只描述「方向多一致」,最适合衡量相似度。

7. 小结与速查代码

python
import numpy as np

def magnitude(v):                 # 模长
    return np.linalg.norm(v)

def dot(a, b):                    # 点积
    return np.dot(a, b)

def cosine(a, b):                 # 余弦相似度
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
概念公式含义取值范围
模长√(Σaᵢ²)向量长度[0, ∞)
点积Σaᵢ·bᵢ同向程度(受长度影响)(-∞, ∞)
余弦相似度`(a·b)/(a
最近更新