Appearance
向量点积、模长与余弦相似度详解(含代码演示)
这是一篇面向「不太了解」同学的学习笔记:用最直白的方式讲清楚点积(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°。
这说明:a 和 b 互相垂直(正交)!
点积的三种几何含义:
- 结果 > 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.05. 完整示例:为什么用余弦而不是点积
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 |