向量归一化是什么
归一化(L2 归一化)是把向量按自身欧氏范数缩放成单位长度,只保留方向信息、丢掉模长。做完这一步,两个向量的内积恒等于它们的余弦相似度,分数落在 -1 到 1 之间,排序与余弦完全一致,于是可以把索引库的度量设成内积(IP)换取更省算的检索。多数句向量模型(BGE、E5、text-embedding 系列)在模型卡里直接建议开启 normalize_embeddings,本质就是把这一步封装进编码函数。
基本信息
| 计算方式 | v ← v / ‖v‖,按欧氏范数缩放 |
|---|---|
| 效果 | 内积等价余弦,分数范围 -1~1 |
| Python 封装 | SentenceTransformer encode(normalize_embeddings=True) |
| 数组级调用 | faiss.normalize_L2(x),原地修改需先 copy |
| 一致性范围 | 入库向量、查询向量、索引度量三处对齐 |
典型使用场景
- 用 FAISS IndexFlatIP 做余弦检索:必须先自行归一化,库不管这件事
- 把零散来源的向量灌进同一个集合:统一尺度后不同模型产出的分数才有可比性
- 校验上游管道:入库前抽查范数是否为 1,能提前发现漏跑归一化
- 从 COSINE 度量切到 IP 度量做压测:归一化后两者结果应完全一致,可当回归手段
- 排查“同样的文本分数忽高忽低”:先确认两侧范数是否都被缩放
归一化与验证
归一化与验证
import numpy as np
v = np.array([0.3, 0.4, 0.5], dtype='float32')
q = np.array([0.1, 0.9, 0.2], dtype='float32')
vn = v / np.linalg.norm(v) # 单位化:范数变成 1
qn = q / np.linalg.norm(q)
print(float(np.linalg.norm(vn))) # 应输出 1.0 附近
print(float(qn @ vn)) # 归一化后的内积
print(float(q @ v / (np.linalg.norm(q) * np.linalg.norm(v)))) # 余弦相似度,两者相等各库归一化要求
| 库与度量 | 是否需自行归一化 | 说明 |
|---|---|---|
| FAISS IndexFlatIP | 需要 | 库不做缩放,模长会直接主导排序 |
| FAISS IndexFlatL2 | 不需要 | 欧氏距离本身含模长信息 |
| pgvector <=> | 不需要 | 余弦距离按夹角计算,内部处理范数 |
| pgvector <#> | 需要 | 负内积,两侧统一归一化后才等价余弦 |
| Milvus / Qdrant | 按度量配置 | 选余弦类度量时保持两侧处理一致,换度量要重灌 |
切换度量或更换编码方式后,库里已有的未归一化向量会造成分数漂移:要么整体重灌,要么批量补做归一化并校验范数,不能只改新写入的数据。
归一化只解决模长问题,不解决模型是否匹配:把两个不同模型产出的向量混在一个集合里,归一化之后依然不可比。