[控场AI]
· 8 分钟阅读· 4,076 字

BM25 vs Embeddings:为何向量搜索找不到错误码E4012?

BM25 vs Embeddings:为何向量搜索找不到错误码E4012?

混合检索用BM25与向量搜索融合,解决精确标识符检索失败问题。

文章探讨了向量搜索在精确标识符(如错误码E4012)检索上的失败原因:嵌入模型将文本压缩为语义向量,导致相邻错误码向量几乎重合,排序随机。BM25则依靠稀有token的IDF权重精准匹配。作者通过实验证明,采用保留标识符完整的分词器并结合RRF融合BM25与向量检索,在自建测试集上使38条标识符查询全部命中,且自然语言查询不受影响。文章还提供了分词器陷阱、重排序器局限性及低成本检查清单等实用建议。

在文档助手中输入 E4012,返回的却是 E4021 和 E4013 的页面,而真正解释 E4012 的内容排在第14位,远低于top-5截断线。这是嵌入模型与BM25在精确标识符检索上的典型冲突:语义向量擅长理解“错误码的含义”,却无法精准匹配“错误码本身”。

BM25 vs Embeddings检索对比示意

为什么向量搜索会错失 E4012?

嵌入模型编码的是段落的“主题”,而非精确字符。两个描述相邻错误码的页面主题几乎相同,向量自然高度相似。以 E4012 为例:

  • 分词:子词分词器将其拆为 E、40、12,这些片段没有特殊性;E4021 则拆为 E、40、21,大量重叠。
  • 上下文混合:Transformer将每个词元与周围句子融合,代码周围的“连接超时”等描述主导了语义。
  • 池化:均值池化将数百个词元压成一个384-1024维向量,几个数字词元对整体影响微乎其微。

结果,E4012 与 E4021 在向量空间中几乎重合,查询短句 E4012 的嵌入更接近“错误码类”的模糊向量,对每个错误页面的距离都差不多,排序近乎随机。

子词分词器(如 BPE、WordPiece)会将文本切分为高频子词单元,以平衡词汇量覆盖与未登录词处理。对于 E4012 这类标识符,分词器通常将其拆为 E、40、12 等常见片段,导致精确字符信息被稀释。这种设计对自然语言友好,却使标识符在嵌入空间中失去区分度,因为相邻错误码共享大量子词片段,向量表示高度相似。

BM25如何扭转局面?

BM25按精确词匹配计数,并依据词在整个语料中的稀有度加权。稀有词权重极高,包含该词的文档因此胜出。其评分包含三部分:

  1. IDF(逆文档频率):log((N - n + 0.5) / (n + 0.5) + 1)。在约2300个文本块中,“connection”出现数百次,而 e4012 仅出现3次,IDF约6.5,远超“connection”的1.5。
  2. 词频饱和:参数 k1(默认1.2-1.5)防止词频线性增长。
  3. 长度归一化:参数 b(默认0.75)避免长文本因长度占优。

BM25不懂错误码的含义,但它知道这个token稀有且查询包含它——这对标识符查找正是关键。

用 rank_bm25 实现的基础代码如下:

import re
from rank_bm25 import BM25Okapi

def tokenize(text: str) -> list[str]:
    # 保留 E4012、ERR_CONN_RESET、v2.3.1 等作为单一token
    return re.findall(r"[A-Za-z0-9_][A-Za-z0-9_.\-]*[A-Za-z0-9_]|[A-Za-z0-9]", text.lower())

corpus_tokens = [tokenize(c.text) for c in chunks]
bm25 = BM25Okapi(corpus_tokens, k1=1.2, b=0.75)

scores = bm25.get_scores(tokenize("E4012"))

IDF(逆文档频率) 衡量一个词在语料中的稀有程度:出现文档越少,IDF 越高,对评分的贡献越大。在约 2300 个文本块中,e4012 仅出现 3 次,其 IDF 远高于常见词如 connection,因此包含该稀有 token 的文档在 BM25 中得分显著提升。这正是 BM25 在精确标识符检索上优于语义向量的关键机制。

分词器陷阱:我的第一次BM25尝试为何失败?

首次尝试直接用 text.split(),导致 E4012,(带逗号)与 E4012 成为不同token,大量匹配丢失。改用“规范”分词器后,v2.3.1 被拆成 v2、3、1,ERR_CONN_RESET 变成三个常见词,稀有性荡然无存。

无论使用Elasticsearch、OpenSearch、Postgres全文检索还是 rank_bm25,都需要检查:

  • 分析器是否按 _、.、- 拆分?用真实标识符测试。
  • 词干提取是否破坏了代码?errors → error 可以,但 HTTP_403s 被扭曲则不行。
  • 查询与文档是否使用同一分词器?分析器不匹配是零命中经典bug。
  • 小写化对代码通常无害,除非产品中 E4012 和 e4012 是不同东西。

上面的正则虽然粗糙,但正好保留标识符完整。信任它之前,先用最棘手的ID跑一遍。

混合检索:用RRF融合BM25与向量

并行运行两种检索,再用倒数排名融合(RRF)合并有序列表。RRF忽略原始分数,只看排名位置,因为BM25分数与余弦相似度尺度完全不同。

公式:对每个文档,累加其出现在各列表中的 1/(k + rank),k=60 为原始论文常用默认值。

def rrf(result_lists: list[list[str]], k: int = 60, top_n: int = 5) -> list[str]:
    scores: dict[str, float] = {}
    for results in result_lists:
        for rank, doc_id in enumerate(results, start=1):
            scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (k + rank)
    return sorted(scores, key=scores.get, reverse=True)[:top_n]

dense_ids = vector_search(query, limit=50)
sparse_ids = bm25_search(query, limit=50)
context_ids = rrf([dense_ids, sparse_ids])

对 E4012 而言:BM25将正确页面排第1(得分 1/61),向量搜索排第14(1/74),总约0.030;E4021 页面向量排第1但未进入BM25列表,得分仅 1/61 约0.016。正确页面得分几乎翻倍。而对于“为什么上传卡住”这类模糊问题,向量搜索更强,RRF让每种信号自然胜出。

注意从每侧取约50个候选,而不是5个。若正确文档在一个列表中排第14,只取10个则融合永远看不到它。

倒数排名融合(RRF) 是一种无监督的排名融合方法,不依赖原始分数,仅利用各检索列表中的排名位置。对每个文档,累加其出现在各列表中的 1/(k + rank) 值,其中 k 为平滑常数(常取 60)。RRF 能有效平衡不同检索信号的贡献,避免因分数尺度差异导致某一路结果主导,适合融合 BM25 与向量检索这类异构结果。

测试集上的实际效果

在自建测试集上,混合检索找回了所有向量搜索漏掉的标识符查询,自然语言查询基本不受影响。以下是来自我的语料和嵌入模型的数字,并非通用基准。

设置:从支持工单抽取120条真实查询,每条标注可回答的文本块。其中38条包含标识符(错误码、配置键、CLI标志、版本字符串)。衡量标准为正确文本块是否进入top-5。

  • 纯向量:38条标识符查询命中21条。自然语言查询正常。
  • 纯BM25(朴素分词器):标识符表现比预期更差,原因即上述分词拆解问题。
  • 纯BM25(保留标识符分词器):38条命中36条,但在改写问题上表现不佳。
  • 混合+RRF:38条标识符查询全部命中,自然语言查询在此规模下无明显损失。

120条查询样本较小,应视为“方向性结论”而非“精确数字”。基于真实查询构建自己的评测集,才是唯一有意义的衡量。

是否该用重排序器替代?

交叉编码器重排序器能改进排序,但只能对检索返回的候选重排。如果向量搜索将正确文本块埋在14位而只取前10,重排序器永远看不到它。重排序器也读取相同的子词片段,对标识符虽优于双编码器但并非免疫。它应放在混合检索之后,而非替代混合检索。

我现在的低成本检查清单

  • 在查询日志中grep匹配 [A-Z]+\d{3,}、\w+_\w+ 或 v\d+\.\d+ 的模式,估算标识符流量占比。我的约为三分之一。
  • 对每个标识符查询,检查包含该精确字符串的文档是否进入top-5。未进入说明检索器在猜测。
  • 将5个真实ID输入BM25分析器,肉眼检查切分结果。
  • 融合前记录两个有序列表。答案错误时,可快速定位哪一侧掉了链子。

结论:BM25与嵌入模型应协同使用

嵌入模型找到语义相同的段落;BM25找到包含相同稀有token的段落。向量搜索错失 E4012 是因为嵌入模型将代码数字压缩进由周围语义主导的向量,使相邻错误码几乎重叠;BM25则赋予稀有精确token高IDF权重,将匹配文档排在第一。并行运行两种检索,各取约50个候选,用RRF在 k=60 下融合,并确保BM25分词器保持标识符完整。如此便可同时应对“这个错误是什么意思”和“查找错误E4012”两类查询。

#BM25#向量搜索#嵌入模型#混合检索#RRF#错误码检索#精确匹配
分享:

相关推荐