RAG 中利用 Rerank 与两阶段检索提升检索质量
检索增强生成 (RAG) 是一个含义丰富的术语。它向世界许诺,但在开发出 RAG 管道后,我们中的许多人仍然在疑惑,为什么它的效果不如我们预期的那样好。
与大多数工具一样,RAG 易于使用但难以掌握。事实是,RAG 不仅仅是将文档放入矢量数据库并在上面添加 LLM。
这可以奏效,但并不总是如此。
本文中将介绍通常最简单、最快速地实施次优 RAG 管道的解决方案 — 我们将学习重新排序器(Rerank)。
回忆与上下文窗口
在开始讨论解决方案之前,我们先来谈谈这个问题。使用 RAG,我们可以对许多文本文档执行语义搜索— 这些文档可能有数万个,甚至数百亿个。
为了确保大规模搜索时间短,我们通常使用向量搜索 - 也就是说,我们将文本转换为向量,将它们全部放入向量空间,然后使用相似度度量(如余弦相似度)比较它们与查询向量的接近度。
要使向量搜索发挥作用,我们需要向量。这些向量本质上是将一些文本背后的'含义'压缩为(通常)768 或 1536 维向量。由于我们将这些信息压缩为单个向量,因此会有一些信息丢失。
由于这种信息丢失,我们经常看到前三个(例如)向量搜索文档会丢失相关信息。不幸的是,检索可能会返回低于我们的top_k截止值的相关信息。
如果较低位置的相关信息可以帮助我们的 LLM 制定更好的响应,我们该怎么办?最简单的方法是增加我们返回的文档数量(增加top_k)并将它们全部传递给 LLM。
我们在这里要衡量的指标是召回率— 即'我们检索了多少相关文档'。召回率不考虑检索到的文档总数 — 因此我们可以破解该指标,通过返回*所有内容来获得完美的召回率。
不幸的是,我们无法返回所有内容。LLM 对我们可以传递给它们的文本量有限制——我们称此限制为上下文窗口。一些 LLM 具有巨大的上下文窗口,例如 Anthropic 的 Claude,其上下文窗口有 100K 个标记。这样,我们可以容纳数十页的文本——那么我们是否可以返回许多文档(不是全部)并'填充'上下文窗口以提高召回率?
再次强调,不行。我们不能使用上下文填充,因为这会降低 LLM 的召回性能——请注意,这是 LLM 召回,与我们迄今为止讨论的检索召回不同。
当将信息存储在上下文窗口的中间时,LLM 回忆该信息的能力会变得比一开始没有提供该信息时更差。
LLM 回忆能力是指 LLM 从其上下文窗口内的文本中查找信息的能力。研究表明,随着我们在上下文窗口中放入更多标记,LLM 回忆能力会下降。当我们填充上下文窗口时,LLM 也不太可能遵循指令 — 因此上下文填充不是一个好主意。
我们可以增加向量数据库返回的文档数量以提高检索召回率,但如果不损害 LLM 召回率,我们就无法将这些文档传递给我们的 LLM。
解决此问题的方法是通过检索大量文档来最大化检索召回率,然后通过最小化进入 LLM 的文档数量来最大化 LLM 召回率。为此,我们对检索到的文档进行重新排序,只保留与我们的 LLM 最相关的文档 — 为此,我们使用重新排序。
强大的重排器
Rerank 模型(也称为交叉编码器)是一种模型,给定查询和文档对,它将输出相似度分数。我们使用此分数根据与查询的相关性对文档进行重新排序。
两阶段检索系统。向量 DB 步骤通常包括双编码器或稀疏嵌入模型。
搜索引擎工程师早已在两阶段检索系统中使用重新排序器。在这些两阶段系统中,第一阶段模型(嵌入模型/检索器)从较大的数据集中检索一组相关文档。然后,使用第二阶段模型(重新排序器)对第一阶段模型检索到的文档进行重新排序。
我们使用两个阶段,因为从大型数据集中检索一小组文档比对大型文档进行重新排序要快得多 - 我们将很快讨论为什么会出现这种情况 - 但 TL;DR,重新排序器很慢,而检索器很快。
为什么要使用 Rerankers?
如果重新排序器的速度如此之慢,为什么还要使用它们呢?答案是重新排序器比嵌入模型准确得多。
双编码器准确率低的原因在于,双编码器必须将文档的所有可能含义压缩为一个向量,这意味着我们会丢失信息。此外,双编码器没有查询上下文,因为我们在收到查询之前并不知道查询内容(我们在用户查询之前创建嵌入)。
另一方面,重新排序器可以将原始信息直接接收到大型转换器计算中,这意味着信息损失更少。由于我们在用户查询时运行重新排序器,因此我们还有一个额外的好处,那就是分析文档针对用户查询的含义 — 而不是试图产生一个通用的平均含义。
重新排序器避免了双编码器的信息丢失——但它们有不同的惩罚——。


