首页/AI/RAG 与向量库速查/余弦检索实现细节与坑
Embedding 与检索50 条

余弦检索实现细节与坑

工程实现余弦检索时的细节:归一化必须用 float32 避免精度漂移;接近零的向量(全零 embedding)会让除法产生 NaN,需要在入库前过滤或兜底处理。

RAG 与向量库速查AI更新于 2026-10-13

余弦检索实现细节与坑

用 NumPy 实现余弦检索时有几个容易踩的实现细节:归一化要用 float32 以避免精度漂移;接近零的向量(例如空文本编码出的全零 embedding)会让除法产生 NaN,必须在入库前过滤或兜底。NaN 一旦写进向量库,排序会整体异常——比较结果全部为假,返回顺序看起来像随机的,而且极难定位。

基本信息

归一步骤逐行计算模长后相除
精度要求全链路统一 float32
除零风险全零向量做除法会得到 NaN
兜底写法分母加一个小量 eps,或直接过滤
入库校验检查 NaN 与 Inf
常见根因空文本或纯符号文本的 embedding

典型使用场景

  • 自实现余弦检索的原型代码
  • 离线批量计算相似度
  • 校验向量库中已有向量的健康度
  • 排查检索结果完全随机的诡异问题
  • 数据清洗阶段过滤脏向量

安全归一化

安全归一化
import numpy as np

def safe_l2norm(x, eps=1e-12):
    norms = np.linalg.norm(x, axis=1, keepdims=True)
    return x / np.maximum(norms, eps)      # 全零向量保持全零,不产生 NaN

vecs = safe_l2norm(np.random.rand(100, 384).astype("float32"))
assert not np.isnan(vecs).any()            # 入库前统一校验一次

异常值读法

现象原因处置
结果为 NaN全零向量做除法分母加 eps,或直接过滤该向量
排序完全错乱库中已混入 NaN入库前统一校验并清理脏数据
分数微小不一致float32 与 float64 混用全链路统一 dtype
模长异常偏大归一化步骤未执行确认归一化是否在入库与查询两侧都做了
对全零向量做除法会得到 NaN;NaN 一旦入库,检索排序整体异常且极难定位,所以校验必须放在写入之前。
全零向量的常见来源是空文本或纯符号文本:切分阶段就应丢弃这类片段,或为它们指定一个兜底向量。
余弦与内积在归一化之后等价,但归一化必须两侧都做:只归一化查询而不归一化文档,分数就失去可比性。
校验脚本值得定期跑一次:抽查全量向量是否存在 NaN 或零模长,把问题挡在检索之前,而不是等用户反馈结果奇怪。

易错点

  • 用 float64 重算入库向量:与线上 float32 检索结果出现微小不一致,难以解释
  • 空文本编码出的全零向量直接入库:后续相似度全部为 NaN,排序彻底失效
  • 只归一化查询不归一化文档:两侧尺度不一致,分数失去可比性
  • 没有入库校验:脏向量混入后发现太晚,只能重建索引来清理

数据来源与参考

  • NumPy 官方文档(浮点与除法)
  • Qdrant 官方文档(向量校验)
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌