关于在RAG中引入上下文检索的学习记录
前言
最近在学习RAG(Retrieval-Augmented Generation)相关技术。今天看到Anthropic的一篇文章,提出了一种叫“上下文检索”(Contextual Retrieval)的方法,感觉很有意思。于是记录一下
传统RAG的问题
我们都知道,RAG的核心思想是“检索+生成”。先把大量的文档切分成小块(chunks),然后用embedding模型把这些小块转换成向量存起来。当用户提问时,再把问题也转换成向量,去向量数据库里找最相似的几个文本块,最后把这些文本块和原始问题一起丢给大模型,让它生成答案。
但问题就出在这个“切块”上。一个长文档被切开后,很多小块就失去了上下文。比如,一句话“他这么做了”,脱离了前面的内容,我们根本不知道“他”是谁,“这么做”又是做了什么。模型在编码这些信息的时候,上下文的缺失会导致信息不完整,等到检索的时候,自然就容易“失忆”,找不到最相关的答案。这就导致了检索失败。
Anthropic的解决方案:上下文检索
为了解决这个问题,Anthropic提出了“上下文检索”(Contextual Retrieval)技术,主要包含两个子技术:上下文嵌入(Contextual Embeddings) 和 上下文BM25(Contextual BM25)。
上下文嵌入:是指的用自然语言“补全语义”,就是插入一段解释型语言,从而让embedding模型更好的理解,提高rank成功率
上下文BM25:直接根据词汇出现频率排出待选文档,后续再通过rerank增加命中率(直接使用embedding)
核心思想其实很简单:在处理每个文本块之前,先给它补上上下文。
具体怎么做呢?他们用了一个大模型(比如Claude 3 Haiku)来为每个文本块生成一段简短的、针对性的上下文解释。然后,在创建向量嵌入和BM25索引的时候,不是直接用原文,而是把 “生成的上下文 + 原始文本块” 拼在一起再进行处理。
这样一来,每个文本块都带上了自己的“背景故事”,信息变得更加完整和明确。模型在检索的时候,就能更好地理解每个文本块的真实含义,从而大大提高了检索的准确率。
实施注意事项
在实现上下文检索时,需要注意以下几点:
- 数据块边界: 考虑如何将文档分割成数据块。数据块大小、数据块边界和数据块重叠的选择会影响检索性能 ¹ 。
- 嵌入模型: 虽然上下文检索提升了我们测试的所有嵌入模型的性能,但某些模型可能受益更多。我们发现 Gemini 和 Voyage 嵌入模型尤其有效。
- 自定义上下文提示: 虽然我们提供的通用提示效果不错,但如果您使用针对特定领域或用例量身定制的提示(例如,包含可能仅在知识库的其他文档中定义的关键术语词汇表),则可能会获得更好的结果。
- 信息块数量: 在上下文窗口中添加更多信息块可以提高包含相关信息的概率。但是,过多的信息可能会分散模型的注意力,因此信息块的数量应该有所限制。我们尝试了 5 个、10 个和 20 个信息块,发现 20 个信息块的性能最佳(详见附录中的对比),但建议您根据实际使用情况进行实验。
始终运行 evals: 通过向其传递上下文块并区分什么是上下文什么是块,可以改进响应生成。
通过重新排名进一步提升性能
文中还提到可以把上下文检索与重排列结合起来,这里顺便也说一下重排列:
- 执行初始检索,获取最有可能相关的块(我们使用了前 150 个);
- 将前 N 个数据块连同用户的查询一起传递给重排序模型;
- 使用重排序模型,根据每个块与提示的相关性和重要性给每个块打分,然后选择前 K 个块(我们使用了前 20 个块);
- 将前 K 个数据块作为上下文传递给模型,以生成最终结果。
就是多做一次取出向量排序的过程,增加一次筛选来提高上下文的准确性
总结
总的来说,上下文检索机制就是在做embedding之前,往拆分好的文档片段中添加一段解释性语言,然后通过BM25技术,“笨拙”地通过词汇出现的的频率准确的找到需要地文档片段
参考文章:https://www.anthropic.com/engineering/contextual-retrieval
