首页 / 文章

RAG检索工具包:BM25、Dense、HyDE、RRF、MMR与重排算法

RAG检索工具包:BM25、密集向量、HyDE、RRF、MMR以及重排序,各类工具的适用场景、各阶段的预估延迟,以及混合处理流程如何整合这些工具。

发布者:REACTAPP.TOP阅读约 8 分钟1736 词

来源: The Retrieval Toolkit That’s Quietly Deciding Whether Your RAG Pipeline Works.

经过几次RAG流程的尝试后,人们往往会遇到同样的瓶颈。嵌入质量不错,提示语也清晰,但生成的答案仍然不够准确。很多时候问题并不出在模型本身,而在于输入数据。信息检索才是隐藏的瓶颈,大多数失败都源于团队忽视或误用的一些常见技术。

以下就是这套工具包:每项技术的运作原理、存在意义、适用场景,以及它们如何在实际流程中结合使用,同时还包括它们在生成答案时所需的时间成本。

1. BM25:经久不衰的关键词评分算法

BM25(最佳匹配25)是一种稀疏的词汇排序函数,是对TF-IDF的改进版本。它根据文档与查询词之间的匹配数量进行排序,同时会考虑词频和文档长度的影响。

对于每个查询词,当该词在文档中出现频率较高时,文档就会获得得分,但收益会逐渐递减:出现20次远没有出现1次那么具有相关性。过长的文档会因此受到惩罚,无法仅凭篇幅冗长就获得大量匹配结果。

from rank_bm25 import BM25Okapi
corpus = [doc.split() for doc in documents]  # tokenized docs
bm25 = BM25Okapi(corpus)
query = "vector database indexing".split()
scores = bm25.get_scores(query)
top_k = sorted(range(len(scores)), key=lambda i: -scores[i])[:5]

BM25关注的是文本的字面标记而非含义,因此它在以下场景表现优异:

  • 产品代码、SKU、错误代码及标识符
  • 人名、公司名、药品名等命名实体
  • 那些容易因嵌入模型而混淆的罕见或专业术语

在需要精确识别特定术语而非容忍意译的场景中可使用它,比如法律、医疗、代码及日志搜索。它成本低、速度快,且无需训练或GPU支持。但其缺点是完全没有语义理解能力,因此“汽车”和“轿车”在它看来并无关联。

2. 密集向量检索:匹配含义

密集检索通过神经网络编码器将查询和文档转换为嵌入向量,这些向量具有数百到数千个维度且不含零值。相似度通过余弦值或点积来计算,而后通过HNSW或IVF等近似最近邻索引来找到最相似的文档。

from sentence_transformers import SentenceTransformer
import numpy as np
model = SentenceTransformer("BAAI/bge-small-en-v1.5")
doc_embeddings = model.encode(documents, normalize_embeddings=True)
query_embedding = model.encode([query], normalize_embeddings=True)
scores = np.dot(doc_embeddings, query_embedding.T).flatten()
top_k = np.argsort(-scores)[:5]

它能够捕捉语义相似性:即使“高管辞职”与“CEO卸任”之间没有共同的词汇,它也能将二者关联起来。人们通常将此称为语义搜索,但实际上其机制是密集检索,而语义匹配则是最终结果。

当用户使用自然语言查询、意译后的问题、跨语言检索,或不愿使用文档中的精确词汇时,就可以采用这种技术。由于嵌入空间会平滑处理BM25所保留的细节,因此它可能在处理生僻术语、精确代码和数字时表现不佳。

3. 查询重写与HyDE:先修复查询

有时检索系统本身没有问题,问题出在查询上。用户输入的往往是简短、模糊或表述奇怪的问题,而BM25和密集检索在处理结构完整、信息丰富的文本时效果最佳。

查询重写机制要求大型语言模型在检索之前扩展或明确问题,将“多快”这样的表述转化为关于X索引流程在峰值状态下每秒能处理多少文档的精确问题。

HyDE(假设性文档嵌入)则更进一步。它并非对查询本身进行嵌入,而是让大型语言模型生成一个合理的答案,再将这个虚构的答案进行嵌入,用以查找真实的文档。其原理在于,通常情况下,一个编造的答案在嵌入空间中与真实答案的距离比与原始问题的距离更近。

hypothetical_doc = llm.generate(f"Write a short passage answering: {query}")
hyde_embedding = model.encode([hypothetical_doc], normalize_embeddings=True)
scores = np.dot(doc_embeddings, hyde_embedding.T).flatten()

过短或含义模糊的查询是导致检索效果不佳的最常见原因,因为这类查询无法传递足够的有效信息。这在聊天式界面以及用户词汇与语料库差异较大的领域尤为明显。其代价是在开始检索之前需要多进行一次大语言模型调用,从而增加延迟和成本;此外,不准确的假设性答案还可能使检索方向出现偏差。

4. 混合搜索:同时使用两种方法

BM25与密集检索分别在两个极端表现不佳。混合搜索则同时运行这两种方法并合并结果列表,从而在一次处理中就能捕获精确匹配的词条及其同义表达。但问题在于BM25的评分与余弦相似度处于不同的量级,直接取平均值是行不通的,这就需要用到互反排名融合技术。

5. 互反排名融合:合并排名而非评分

RRF通过结合排名位置并忽略原始分数,完全避免了分数标准化的问题。

RRF_score(doc) = Σ  1 / (k + rank_r(doc))   for each ranker r

常数 k 通常取值为60,它能够减弱这种影响,从而避免某个排名第一的项占据绝对优势。

def reciprocal_rank_fusion(ranked_lists, k=60):
    scores = {}
    for ranked_list in ranked_lists:
        for rank, doc_id in enumerate(ranked_list):
            scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
    return sorted(scores.items(), key=lambda x: -x[1])
bm25_ranked = ["doc3", "doc1", "doc5"]
dense_ranked = ["doc1", "doc4", "doc3"]fused = reciprocal_rank_fusion([bm25_ranked, dense_ranked])

多个检索器给出高排名的文档会自然位居前列,无需对分数刻度进行校准。这是合并混合检索结果的常用方法,适用于将来自不同检索器的两个或更多排序列表结合的情况,例如 BM25 与密集向量模型,或是多个嵌入模型。

6. 最大边际相关性:去除冗余

即便排序十分完美,但如果前五个结果都表达相同内容,也毫无用处。MMR 会在保证相关性的同时,考虑结果的多样性来进行重新排序。

MMR = λ · Sim(doc, query) − (1 − λ) · max(Sim(doc, selected))

参数 λ 用于调整平衡关系。当 λ = 1 时,采用普通的 top-k 方法;而当 λ = 0 时,会忽略相关性,优先最大化多样性。

import numpy as np
def mmr(query_vec, doc_vecs, doc_ids, lambda_param=0.5, top_k=5):
    selected, remaining = [], list(range(len(doc_ids)))
    sim_to_query = doc_vecs @ query_vec
    while len(selected) < top_k and remaining:
        if not selected:
            idx = remaining[np.argmax(sim_to_query[remaining])]
        else:
            mmr_scores = []
            for i in remaining:
                sim_to_selected = max(doc_vecs[i] @ doc_vecs[j] for j in selected)
                mmr_scores.append(
                    lambda_param * sim_to_query[i] - (1 - lambda_param) * sim_to_selected
                )
            idx = remaining[np.argmax(mmr_scores)]
        selected.append(idx)
        remaining.remove(idx)
    return [doc_ids[i] for i in selected]

如果没有MMR,整个上下文窗口可能会被用于五个几乎相同的文本,而非五个涵盖答案不同方面的片段。在内容存在重叠或重复的语料库中,比如FAQ页面、包含固定模板的产品文档,或是任何一组几乎相同的段落,这种机制能发挥重要作用。

7. 交叉编码器重排序:提升准确率的利器

迄今为止所讨论的所有方法都属于双编码器框架,即分别对查询和文档进行嵌入后再进行比较。而交叉编码器则将查询和候选文本一起输入同一个变换器,从而使模型能够同时关注这两段文本。相比预计算向量后的比较,这种方式能做出更精细的相关性判断。

from sentence_transformers import CrossEncoder
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
pairs = [(query, doc) for doc in candidate_docs]
scores = reranker.predict(pairs)
reranked = [doc for _, doc in sorted(zip(scores, candidate_docs), reverse=True)]

交叉编码器在相关性排序方面始终优于双编码器,因为它们能够同时考虑查询和文档的信息。可将其中一种编码器用于对少量候选项进行最终优化,通常是融合处理或MMR算法后的前20到50个结果,绝不能用于整个语料库,因为每对文档都需要单独进行一次处理。在调用大语言模型之后,这是该流程中延迟最大的环节,正因如此它被安排在流程的末尾而非开头。

8. 回答时间:所有这些的代价

上述每种技术都能提升检索质量,但无一免费。它们都会增加延迟,而从提交查询到收到最终答案的端到端时间决定了经过精心调优的流程是否能在实际应用中使用。各阶段的典型成本如下:

  • 查询重写或HyDE(如使用):大约200毫秒至1秒,在检索开始前需额外调用一次大语言模型。
  • BM25查询:约5至20毫秒,基于成本较低的内存倒排索引完成。
  • 密集向量搜索(ANN):约10至50毫秒,具体时间取决于索引的大小与类型,以及是HNSW结构还是平面结构。
  • RRF融合:1至5毫秒,仅对排名列表进行简单算术运算。
  • MMR重排序:约20至100毫秒,由于采用迭代式逐对比较方式,候选项越多耗时越长。
  • 交叉编码器重排序(如使用):约50至300毫秒,通常是仅次于内容生成的第二大耗时环节。
  • 大语言模型生成:约500毫秒至数秒,通常占据总耗时的最大比例。

为控制总耗时:

  • 同时运行BM25和密集检索,二者互不依赖。
  • 由于成对相似度检测的复杂度很高,可将MMR应用于较小的数据集,比如RRF之后的前20到30条结果。
  • 为频繁或重复的查询缓存嵌入向量。
  • 只有当精度提升带来的好处足以弥补额外的延迟时,才添加交叉编码器。
  • 除了准确性指标(如忠实度或召回率@k)外,还应将响应时间作为重要衡量标准。一个精度高2%但速度慢3倍的系统可能并不值得投入使用。

各组件的协同方式

实际生产环境中的混合检索流程通常如下:

  • 在检索之前,可选地通过查询重写或HyDE方法对模糊的查询进行优化,但这会带来一定的延迟。
  • BM25用于捕捉精确的词汇。
  • 密集检索则用于理解查询的含义。
  • RRF直接合并两个列表,无需进行分数归一化处理。
  • MMR会筛选合并后的列表,得到一个多样且无重复的候选集。
  • 交叉编码器会对这个较小的候选集进行最终的高精度筛选。
  • 响应时间则是判断整个流程是否可行的重要指标。

快速决策指南

  • 查询内容简短、模糊或不在词汇表中?请重新撰写或尝试使用HyDE。
  • 需要代码、ID及稀有术语的精确匹配?选择BM25算法。
  • 能容忍同义表达的差异?使用密集检索技术。
  • 需要同时满足以上要求?采用RRF进行融合处理。
  • 上下文中存在冗余或重复内容?使用MMR处理。
  • 需要在较小的最终候选集中找到最相关的结果?使用交叉编码器进行重新排序。
  • 精度高但速度慢?应在盲目优化前先测量每个阶段的性能。

没有哪种技术能解决所有问题。最佳的生产系统会将多种技术结合起来,并根据真实的查询日志进行调整,而非依赖直觉;而具体的技术组合仍需根据数据集规模和延迟要求来决定。

相关阅读

  • 超越余弦相似度:五种RAG检索技术对比 — 通过代码详解余弦相似度、BM25、混合RRF、交叉编码器重排序和MMR算法,分析其优缺点,并提供一种以查询为优先的思路来决定RAG系统应采用哪种检索技术。
  • 基于 Firebase、Vertex AI 与 Firestore Vectors 的实用 RAG 架构 — 利用 Vertex AI 的嵌入功能及 Firestore 的向量搜索功能构建 RAG 系统:包括批量处理、元数据过滤、租户隔离、基于事实的提示词设计、缓存机制以及性能指标监控。