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