前情回顾
到昨天为止,我们已经建立了一套相当完善的自动化检索流水线:从文档分块、向量化,再到使用向量数据库进行持久化存储和高效查询。我们的RAG系统现在已经可以针对用户问题,快速地从知识库中召回一批相关的文档片段了。
但我们不禁要问:这样就足够了吗?
从向量数据库里按相似度取出的Top 5结果,就真的是最好的5个吗?它们的顺序就是最佳顺序吗?
现实往往是:最相似 ≠ 最相关。
向量检索(我们称之为"召回"或"粗排")非常快,能迅速从海量数据中筛选出候选集。但它对语义的理解有时是"粗线条"的。比如,用户问"如何在Mac上安装Python?",它可能会召回:
- 一篇讲"如何在Windows上安装Python"的详细文章(因为"安装Python"相似度很高)。
- 一篇讲"Mac常用软件推荐"的文章(因为提到了"Mac")。
- 一篇真正讲"在Mac上安装Python步骤"的文章,但可能因为措辞原因,相似度排在了第三。
如果我们直接把前两个结果喂给大模型,大概率会得到一个错误的答案。我们需要一个"复试官",对召回的结果进行二次筛选和排序,把真正相关的结果(比如上面的第3个)提到最前面。
这个"复试官",就是Re-ranker(重排模型)。
两阶段检索:粗排(Recall) + 精排(Re-rank)
引入Re-ranker后,我们的检索流程就从一步变成了两步,这是一种在工业界非常成熟的搜索架构:
-
第一阶段:召回/粗排 (Recall)
- 目标:速度快,范围广(求全)。
- 工具:向量数据库(如ChromaDB)。
- 过程:从海量文档中,快速召回一个候选集(比如Top 20个可能相关的文档)。
-
第二阶段:精排 (Re-rank)
- 目标:精度高,排序准(求准)。
- 工具:一个更复杂、更精确的重排模型。
- 过程:对第一阶段召回的Top 20个文档,进行更细致的分析和计算,给出一个更精准的相关性分数,并按此分数重新排序。
这个过程就像招聘:HR助理(粗排)先从几千份简历里快速筛选出20份包含"关键词"的,然后招聘经理(精排)再仔细阅读这20份简历,挑出最匹配岗位的那几个人。
Re-ranker如何工作:Cross-Encoder简介
为什么Re-ranker更准?因为它通常使用一种叫做**Cross-Encoder(交叉编码器)**的模型结构。
- 我们之前用于Embedding的,叫Bi-Encoder(双塔编码器) 。它将问题和文档分开编码成向量,再计算相似度。速度快,但无法捕捉两者之间深层的交互信息。
- 而Cross-Encoder ,则是将问题和文档拼接在一起 (
[CLS] 问题 [SEP] 文档 [SEP]
)后,再输入给一个强大的预训练模型(如BERT)。这使得模型可以充分地、逐词地比较问题和文档的内部细节,从而给出极其精准的相关性判断。
缺点就是计算量大、速度慢。所以它不适合用于海量文档的初筛,但极其适合对小范围的候选集进行"精加工"。
上手实战:用 bge-reranker
精修结果
我们将使用 bge-reranker-base
这个目前非常流行的开源重排模型来演示。
首先,确保库已安装:
bash
pip install sentence-transformers
现在,我们来模拟一个场景。假设我们已经从向量数据库中召回了以下3个文档。
python
from sentence_transformers import CrossEncoder
# 加载一个预训练好的Cross-Encoder模型
reranker_model = CrossEncoder('bge-reranker-base')
# 模拟一个用户查询
query = "Mac电脑怎么安装Python?"
# 模拟从向量数据库召回的3个文档
# 注意它们的初始顺序
documents = [
"在Windows上安装Python的步骤非常简单,首先访问Python官网...", # 最不相关
"Python是一种强大的编程语言,适用于数据科学、Web开发和自动化。", # 有点相关,但不是教程
"要在macOS上安装Python,推荐使用Homebrew。首先打开终端,输入命令 'brew install python' 即可。" # 最相关
]
# Re-ranker需要的是[查询, 文档]对的列表
sentence_pairs = [[query, doc] for doc in documents]
# 使用predict方法计算相关性分数
# (注意:它不是0-1之间的相似度,而是一个可以排序的任意分数值)
scores = reranker_model.predict(sentence_pairs)
print("原始文档顺序:", documents)
print("Re-ranker打分:", scores)
print("-" * 20)
# 将分数和文档打包并排序
scored_documents = sorted(zip(scores, documents), reverse=True)
print("精排后的文档顺序:")
for score, doc in scored_documents:
print(f"分数: {score:.4f}\t文档: {doc}")
输出结果:
makefile
原始文档顺序: ['在Windows上安装Python的步骤非常简单,首先访问Python官网...', 'Python是一种强大的编程语言,适用于数据科学、Web开发和自动化。', "要在macOS上安装Python,推荐使用Homebrew。首先打开终端,输入命令 'brew install python' 即可。"]
Re-ranker打分: [-4.6853375 -1.370929 7.9545364]
--------------------
精排后的文档顺序:
分数: 7.9545 文档: 要在macOS上安装Python,推荐使用Homebrew。首先打开终端,输入命令 'brew install python' 即可。
分数: -1.3709 文档: Python是一种强大的编程语言,适用于数据科学、Web开发和自动化。
分数: -4.6853 文档: 在Windows上安装Python的步骤非常简单,首先访问Python官网...
结果一目了然! Re-ranker给出了一个非常明确的排序。最相关(直接回答了macOS安装问题)的文档获得了极高的正分(7.95) ,而那个讲Windows的文档则获得了很低的负分(-4.68)。
经过精排后,我们就可以胸有成竹地将排序第一的文档,作为最高质量的上下文,交给大模型去生成最终答案了。
总结与预告
今日小结:
- RAG的检索可以分为**粗排(Recall)和精排(Re-rank)**两个阶段,以平衡速度和精度。
- Re-ranker(重排模型)通常使用Cross-Encoder结构,能更精准地判断查询和文档的相关性。
- 在召回的候选集上增加一个精排步骤,可以显著提升送入大模型上下文的质量,从而改善最终生成答案的效果。
恭喜!到今天为止,我们已经把RAG系统中"R"(Retrieval)部分,从文本分块到向量检索再到重排序,完整地走了一遍。我们现在手上已经有了一套能够稳定产出高质量上下文信息的强大管道。
原材料已经精挑细选,接下来,就该轮到"G"(Generation)------我们的主角**大语言模型(LLM)**登场了。
有了高质量的上下文,我们该如何组织语言,向LLM提问,才能让它做出最精彩的回答呢?是简单地把上下文和问题丢给它吗?
明天预告:RAG 每日一技(八):连接大脑!为RAG定制强大的Prompt
明天,我们将正式打通RAG的最后一公里,学习如何设计和构造一个强大的提示词(Prompt),将我们辛苦检索到的上下文"喂"给大模型,并引导它生成我们想要的、忠实于原文且格式优美的答案!