向量相似度怎么算?一文讲透!
在今天的 AI 世界里,「向量」成了我们理解语义、匹配兴趣、检索信息的核心工具。不理解这个就不能称之为合格的AI工程师。
但问题来了:两个向量到底有多像? 这可不是靠肉眼比划能解决的------我们需要一套靠谱的"相似度打分系统"。
这次我们就来聊聊最常用的几种向量相似度判断方法,尤其聚焦于两类主流场景:
- 浮点稠密向量(比如 BERT 嵌入):用 L2、IP、余弦
- 稀疏向量(比如 TF-IDF、关键词集合):用 IP 和 BM25
咱们不整那些云里雾里的数学黑话,而是用大白话+类比+代码,让你真正搞明白它们的区别和适用场景。
一、浮点向量三大金刚:L2、IP、余弦
假设你有两个高维向量 A 和 B,比如是两篇文档经 Sentence-BERT 编码后的 768 维嵌入。怎么判断它们像不像?
1. L2 距离(欧几里得距离)------"直线距离派"
公式 : L2(A,B)=∑i=1n(Ai−Bi)2 \text{L2}(A, B) = \sqrt{\sum_{i=1}^n (A_i - B_i)^2} L2(A,B)=i=1∑n(Ai−Bi)2
通俗解释 :
想象你在城市里走路,A 和 B 是两个坐标点。L2 就是你掏出无人机,直接飞过去测的"直线距离"。越近越像,越远越不像。
特点:
- 考虑了方向 + 大小(模长)
- 对特征尺度敏感(比如一个维度是年龄[0-100],另一个是收入[0-1e6],收入会主导结果)
- 高维下容易"大家都差不多远"(维度灾难)
适用场景:图像特征匹配、低维用户画像聚类(如 K-Means)。
python
import numpy as np
A = np.array([1.0, 2.0, 3.0])
B = np.array([1.1, 2.1, 3.1])
l2_dist = np.linalg.norm(A - B)
print("L2距离:", l2_dist) # 越小越相似
⚠️ 注意:如果不用标准化(如 StandardScaler),L2 很容易被某些大数值维度"带偏节奏"。
2. 内积(IP, Inner Product)------"对齐强度派"
公式 : IP(A,B)=A⋅B=∑i=1nAi×Bi \text{IP}(A, B) = A \cdot B = \sum_{i=1}^n A_i \times B_i IP(A,B)=A⋅B=i=1∑nAi×Bi
通俗解释 :
IP 不关心你俩站多远,只看你俩"是不是朝着同一个方向使劲"。比如用户喜欢科幻(向量方向),电影也是纯科幻(同方向),哪怕一个向量很长(重度用户)、一个很短(冷门片),只要方向一致,IP 就高!
特点:
- 只看方向 + 各自强度,不强制归一化
- 计算快(只有乘加,无开方)
- 如果向量已归一化(L2 norm = 1),IP 就等于余弦相似度!
适用场景:推荐系统(用户×物品匹配)、未归一化的嵌入向量召回。
python
ip_score = np.dot(A, B)
print("内积得分:", ip_score) # 越大越相似
💡 工业级推荐系统最爱 IP,因为:1)符合"偏好投影"直觉;2)计算快,适合亿级召回。
3. 余弦相似度(Cosine Similarity)------"纯方向派"
公式 : cos(θ)=A⋅B∣A∣2⋅∣B∣2 \cos(\theta) = \frac{A \cdot B}{|A|_2 \cdot |B|_2} cos(θ)=∣A∣2⋅∣B∣2A⋅B
通俗解释 :
把 A 和 B 都"缩"成单位长度(就像把不同长短的手电筒光束都调成一样亮),然后看它们照的方向夹角多大。夹角越小(余弦越接近 1),越像。
特点:
- 完全忽略向量长度,只比方向
- 结果在 [-1, 1],通常文本场景中为 [0, 1]
- 特别适合高维稀疏或长度差异大的场景(比如一篇长论文 vs 一条短评论)
适用场景:文本语义搜索、文档去重、NLP 嵌入比较。
python
from sklearn.metrics.pairwise import cosine_similarity
cos_sim = cosine_similarity([A], [B])[0][0]
print("余弦相似度:", cos_sim) # 越接近1越相似
✅ 小技巧:如果你用 FAISS 或 Milvus,把向量先
normalize_L2(),再用 IP 索引,效果 = 余弦,但速度更快!
二、稀疏向量:IP 和 BM25 的天下
当向量不是稠密浮点数,而是像 TF-IDF 那样------大部分是 0,少数维度有值(比如词频),就得换思路了。
1. 稀疏向量的 IP(内积)
其实和上面一样,还是 A·B。但因为稀疏,大量 0 相乘自动跳过,计算效率极高。
比如:
doc1 = [0, 0.5, 0, 0.8, 0](含"AI"和"模型")doc2 = [0, 0.4, 0, 0.7, 0]
IP = 0.5*0.4 + 0.8*0.7 = 0.76,直接反映共同关键词的加权重合度。
优势:天然适配稀疏结构,快且有效。
2. BM25 ------ 搜索引擎的老炮儿
BM25 不是向量运算,而是一种基于词频的排序函数,专为信息检索设计。
核心思想:
- 一个词在查询中出现 → 加分
- 该词在文档中出现次数多 → 加分,但有饱和效应(避免堆词作弊)
- 该词在整个语料库中太常见(如"的"、"是")→ 降权
- 文档越长 → 适当惩罚(避免长文天然占优)
为什么比 TF-IDF + 余弦更优 ?
因为 BM25 考虑了文档长度归一化 和词频饱和,更适合搜索排序。
使用示例(用 rank_bm25 库):
python
from rank_bm25 import BM25Okapi
corpus = [
"人工智能是未来的趋势",
"机器学习需要大量数据",
"今天的天气真好"
]
tokenized_corpus = [doc.split() for doc in corpus]
bm25 = BM25Okapi(tokenized_corpus)
query = "人工智能 数据"
tokenized_query = query.split()
scores = bm25.get_scores(tokenized_query)
print("BM25得分:", scores) # 越高越相关
📌 场景建议:做关键词搜索、电商商品召回、传统搜索引擎------优先考虑 BM25。
三、怎么选?一张表说清楚
| 方法 | 是否考虑长度 | 适合数据类型 | 典型场景 | 相似性判断 |
|---|---|---|---|---|
| L2 | ✅ | 稠密浮点 | 图像、低维聚类 | 距离越小越相似 |
| IP | ✅(隐式) | 稠密 or 稀疏 | 推荐系统、向量召回 | 分数越大越相似 |
| 余弦 | ❌ | 稠密(常归一化) | 文本语义、NLP | 越接近1越相似 |
| BM25 | N/A | 文本关键词 | 搜索引擎、关键词匹配 | 分数越高越相关 |
结语
- 要物理距离?用 L2(记得标准化!)
- 要兴趣对齐?用 IP(推荐系统首选)
- 要语义方向?用余弦(文本标配)
- 要关键词匹配?上 BM25(搜索老将)
记住:没有"最好"的指标,只有"最合适"的选择。理解你的数据、你的业务、你的向量是怎么来的,比死记公式重要一百倍!
下次你在 Milvus 里建索引时,看到 metric_type="IP" 还是 "COSINE",就不会再懵了------因为你已经是个懂行的"向量侦探"了 🔍!