高级RAG 10纠正性检索增强生成CRAG

高级RAG 10纠正性检索增强生成CRAG

Barry Lv6

直观示例、原则、代码解析及关于CRAG的洞察

本文从一个常见场景开始:参加开卷考试。通常,我们有三种策略:

  • 方法1:对熟悉的话题快速作答。对不熟悉的话题,查阅参考书。迅速定位相关章节,在心中整理和总结,然后在试卷上写下答案。
  • 方法2:对每个话题,查阅书籍。找出相关章节,在心中总结,然后在试卷上写出回答。
  • 方法3:对每个话题,查阅书籍并找出相关章节。在形成观点前,将收集的信息分为三类:**正确****错误****模糊**。分别处理每类信息。然后,基于处理后的信息,在心中编排和总结。在试卷上写出回答。

方法1涉及自我RAG ,而方法2是经典RAG 过程。

最后,方法3,即校正检索增强生成(CRAG) ,是本文介绍的内容。

CRAG 的动机

图1 展示了大多数传统 RAG 方法并未考虑文档与问题的相关性,而是简单地合并检索到的文档。这可能会引入无关信息,阻碍模型获取准确知识,甚至可能导致模型产生幻觉问题。

此外,大多数传统 RAG 方法将检索到的整个文档作为输入。然而,这些检索到的文档中往往有很大一部分文本对于生成过程并不必要,不应同等程度地参与 RAG 过程。

CRAG 的核心理念

CRAG 设计了一个轻量级的检索评估器,用于评估针对特定查询检索到的文档的整体质量。同时,它还利用网络搜索作为辅助工具来提升检索结果。

CRAG 具有即插即用的特性,能够与基于 RAG 的各种方法无缝集成。整体架构如图 2 所示。

如图 2 所示,CRAG 通过引入检索评估器来评估检索到的文档与查询之间的关系,从而增强了传统的 RAG。

存在三种可能的判断结果:

  • 如果结果为 **正确**,这意味着检索到的文档包含了查询所需的必要内容,此时将采用知识精炼算法重写检索到的文档。
  • 如果检索到的文档为 **错误**,这表示查询与检索到的文档无关。因此,我们无法将该文档发送给 LLM。在 CRAG 中,使用网络搜索引擎来检索外部知识。
  • 对于 **模糊** 情况,这意味着检索到的文档可能接近但不足以提供答案。在这种情况下,需要通过网络搜索获取更多信息。因此,知识精炼算法和搜索引擎都会被采用。

最后,处理后的信息会被传递给 LLM 以生成响应。图 3 提供了这一过程的正式描述。

需要注意的是,网络搜索并不直接使用用户的输入查询进行搜索。相反,它会构建一个提示,并以少量样本的方式向 GPT-3.5 Turbo 展示,以获取搜索查询。

在对该方法有了大致了解后,让我们分别讨论 CRAG 的两个关键组件:检索评估器和知识精炼算法。

检索评估器

如图4所示,检索评估器对后续流程的结果影响显著,是决定系统整体性能的关键因素。

CRAG采用轻量级的T5-large模型 作为检索评估器并进行微调。值得一提的是,在大型语言模型时代,T5-large也被视为轻量级模型。

对于每个查询,通常会检索出十份文档。随后,将查询与每份文档分别拼接作为输入,预测其相关性。在微调过程中,对正样本赋予标签1,对负样本赋予标签-1。在推理阶段,评估器为每份文档分配一个从-1到1的相关性得分。

这些得分将根据阈值被划分为三个等级。显然,这一划分需要两个阈值。在CRAG中,阈值设置可能因实验数据而异:

触发三种行动之一的两个置信度阈值是根据经验设定的。具体而言,在PopQA中设定为(0.59, -0.99),在PubQA和ArcChallenge中为(0.5, -0.91),而在Biography中为(0.95, -0.91)。

知识精炼算法

对于检索到的相关文档,CRAG设计了一种先分解后重组的知识提取方法,以进一步提取最关键的知识陈述,如图4所示。

首先,应用启发式规则将每个文档分解为细粒度的知识条带,旨在获得细粒度的结果。如果检索到的文档仅包含一两句话,则视为独立单元。否则,文档将被分割成更小的单元,通常由几句话组成,具体取决于总长度。每个单元预期包含一条独立的信息。

接下来,使用检索评估器计算每个知识条带的相关性得分。过滤掉相关性得分低的条带。然后,将剩余的相关知识条带重新组合,以形成内部知识。

代码解释

CRAG 是 开源项目 Langchain LlamaIndex 都提供了各自的实现。我们将以 LlamaIndex 的实现 作为参考进行解释。

环境配置

1
2
3
4
5
6
7
(base) Florian:~ Florian$ conda create -n crag python=3.11

(base) Florian:~ Florian$ conda activate crag

(crag) Florian:~ Florian$ pip install llama-index llama-index-tools-tavily-research

(crag) Florian:~ Florian$ mkdir "YOUR_DOWNLOAD_DIR"

安装完成后,LlamaIndex 和 Tavily 的对应版本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(crag) Florian:~ Florian$ pip list | grep llama
llama-index 0.10.29
llama-index-agent-openai 0.2.2
llama-index-cli 0.1.11
llama-index-core 0.10.29
llama-index-embeddings-openai 0.1.7
llama-index-indices-managed-llama-cloud 0.1.5
llama-index-legacy 0.9.48
llama-index-llms-openai 0.1.15
llama-index-multi-modal-llms-openai 0.1.5
llama-index-packs-corrective-rag 0.1.1
llama-index-program-openai 0.1.5
llama-index-question-gen-openai 0.1.3
llama-index-readers-file 0.1.19
llama-index-readers-llama-parse 0.1.4
llama-index-tools-tavily-research 0.1.3
llama-parse 0.4.1
llamaindex-py-client 0.1.18

(crag) Florian:~ Florian$ pip list | grep tavily
llama-index-tools-tavily-research 0.1.3

测试代码

测试代码如下所示。首次执行需要下载 CorrectiveRAGPack。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import os
os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"

from llama_index.core import Document


# 选项:下载 CorrectiveRAGPack
# 首次执行需要下载 CorrectiveRAGPack
# 后续执行可注释掉此部分。
from llama_index.core.llama_pack import download_llama_pack
CorrectiveRAGPack = download_llama_pack(
"CorrectiveRAGPack", "YOUR_DOWNLOAD_DIR"
)


# 创建测试文档
documents = [
Document(
text="一群企鹅在陆地上被称为'摇摆队',它们在南极冰面上蹒跚前行,它们像燕尾服一样的羽毛在雪地上格外显眼。"
),
Document(
text="帝企鹅是所有企鹅种类中最高的,它们可以潜入比任何鸟类都深的地方,达到超过500米的深度。"
),
Document(
text="企鹅的黑白颜色是一种名为反荫蔽的伪装;从上方看,它们的黑色背部与海洋深处融为一体,而从下方看,它们的白色腹部与明亮的表面相匹配。"
),
Document(
text="尽管企鹅直立站立,但它们是不能飞行的鸟类;它们的翅膀已经进化成鳍状肢,使它们成为游泳高手。"
),
Document(
text="速度最快的种类,巴布亚企鹅,可以以每小时36公里的速度游泳,利用它们的鳍状肢和流线型身体在水中穿梭。"
),
Document(
text="企鹅是群居鸟类;许多种类形成大型繁殖群落,数量可达数万只。"
),
Document(
text="有趣的是,企鹅有出色的听力,并依靠独特的叫声在嘈杂的群落中识别它们的配偶和幼崽。"
),
Document(
text="最小的企鹅种类,小蓝企鹅,身高仅约40厘米,分布在澳大利亚和纽西兰的沿海地区。"
),
Document(
text="在繁殖季节,雄性帝企鹅会在严酷的南极冬季忍受数月,禁食并孵化它们的蛋,而雌性则在海上捕食。"
),
Document(
text="企鹅食用各种海鲜;它们的饮食主要由鱼、鱿鱼和磷虾组成,这些是它们在潜水探险中捕获的。"
),
]


from llama_index.packs.corrective_rag import CorrectiveRAGPack
corrective_rag = CorrectiveRAGPack(documents, "YOUR_TAVILYAI_API_KEY")


# 从此处开始,您可以使用该包,或在./corrective_rag_pack中检查和修改该包。
# run() 函数包含了 Corrective Retrieval Augmented Generation - CRAG 论文背后的逻辑。
query = "最小的企鹅有多高?"
print('-' * 100)
print("查询 " + query + " 的响应是:")
response = corrective_rag.run(query, similarity_top_k=2)
print(response)

其中 **YOUR_TAVILYAI_API_KEY** 可以通过此网站 申请。

测试代码产生了以下结果(大部分调试信息已被移除):

1
2
3
4
5
(crag) Florian:~ Florian$ python /Users/Florian/Documents/crag.py
----------------------------------------------------------------------------------------------------
查询 最小的企鹅有多高? 的响应是:
----------------------------------------------------------------------------------------------------
最小的企鹅大约有40厘米(16英寸)高。

理解测试代码的关键在于 **corrective_rag.run()** 的实现,让我们深入探讨。

CorrectiveRAGPack 类的构造函数

首先,我们来看构造函数,其源代码 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CorrectiveRAGPack(BaseLlamaPack):
def __init__(self, documents: List[Document], tavily_ai_apikey: str) -> None:
"""Init params."""
llm = OpenAI(model="gpt-4")
self.relevancy_pipeline = QueryPipeline(
chain=[DEFAULT_RELEVANCY_PROMPT_TEMPLATE, llm]
)
self.transform_query_pipeline = QueryPipeline(
chain=[DEFAULT_TRANSFORM_QUERY_TEMPLATE, llm]
)

self.llm = llm
self.index = VectorStoreIndex.from_documents(documents)
self.tavily_tool = TavilyToolSpec(api_key=tavily_ai_apikey)

请注意,默认设置为 **gpt-4**。如果您没有权限使用 **gpt-4**,可以手动切换至 **gpt-3.5-turbo**

class CorrectiveRAGPack:: run()

函数 run() 的源代码 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class CorrectiveRAGPack(BaseLlamaPack):
...
...
def run(self, query_str: str, **kwargs: Any) -> Any:
"""运行处理流程。"""
# 根据输入的查询字符串检索节点。
retrieved_nodes = self.retrieve_nodes(query_str, **kwargs)

# 评估每个检索到的文档与查询字符串的相关性。
relevancy_results = self.evaluate_relevancy(retrieved_nodes, query_str)
# 从评估为相关的文档中提取文本。
relevant_text = self.extract_relevant_texts(retrieved_nodes, relevancy_results)

# 初始化 search_text 变量以处理可能未定义的情况。
search_text = ""

# 如果发现任何文档不相关,则转换查询字符串以获得更好的搜索结果。
if "no" in relevancy_results:
transformed_query_str = self.transform_query_pipeline.run(
query_str=query_str
).message.content
# 使用转换后的查询字符串进行搜索并收集结果。
search_text = self.search_with_transformed_query(transformed_query_str)

# 编译最终结果。如果有来自转换查询的额外搜索文本,则包含它;
# 否则,仅返回初始检索的相关文本。
if search_text:
return self.get_result(relevant_text, search_text, query_str)
else:
return self.get_result(relevant_text, "", query_str)

上述代码与标准 CRAG 流程有三个主要区别:

  • 没有对 **ambiguous**(模糊)文档的判断或处理。
  • 不使用训练好的 T5-large 模型,而是利用 LLM 来评估检索到的信息。
  • 跳过了知识精炼的过程。

尽管存在这些差异,LlamaIndex 提供了一种替代的思考方式(langchain 也采用了这种方式)。

使用LLM评估检索信息

代码链接 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CorrectiveRAGPack(BaseLlamaPack):
...
...
def evaluate_relevancy(
self, retrieved_nodes: List[Document], query_str: str
) -> List[str]:
"""Evaluate relevancy of retrieved documents with the query."""
relevancy_results = []
for node in retrieved_nodes:
relevancy = self.relevancy_pipeline.run(
context_str=node.text, query_str=query_str
)
relevancy_results.append(relevancy.message.content.lower().strip())
return relevancy_results

调用LLM的提示模板 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DEFAULT_RELEVANCY_PROMPT_TEMPLATE = PromptTemplate(
template="""作为评分者,你的任务是评估一个文档对于用户问题的相关性。

检索到的文档:
-------------------
{context_str}

用户问题:
--------------
{query_str}

评估标准:
- 考虑文档是否包含与用户问题相关的关键词或主题。
- 评估不应过于严格;主要目标是识别并过滤掉明显不相关的检索结果。

决策:
- 分配一个二进制分数以指示文档的相关性。
- 如果文档与问题相关,使用'yes';如果不相关,使用'no'。

请在下方提供你的二进制分数('yes'或'no'),以指示文档与用户问题的相关性。"""
)

CRAG论文显示,ChatGPT在识别检索相关性方面的表现不如T5-Large。

此外,在实际项目中,我们当然可以使用原始的知识精炼算法。相关代码可以在这里 找到。

重写搜索查询

如前所述,网络搜索并不直接使用用户的输入查询。相反,它会构建一个提示,并采用少样本方法将该提示呈现给 GPT-3.5 Turbo 以获取搜索查询。该提示 如下:

1
2
3
4
5
6
7
8
9
10
DEFAULT_TRANSFORM_QUERY_TEMPLATE = PromptTemplate(
template="""你的任务是优化查询,确保其高效获取相关搜索结果。\n
分析给定的输入,把握核心语义意图或含义。\n
原始查询:
\n ------- \n
{query_str}
\n ------- \n
你的目标是重述或增强此查询,以提升其搜索性能。确保修订后的查询简洁并直接符合预期的搜索目标。\n
仅回复优化后的查询:"""
)

我对CRAG的见解与思考

CRAG与self-RAG的区别

  • 从流程角度看,self-RAG能利用LLM直接提供回复,无需检索步骤,而CRAG则必须在检索后加入评估层。
  • 从结构角度分析,self-RAG比CRAG更为复杂,它需要更精细的训练流程,并在生成阶段进行多次标签生成与评估,这无疑增加了推理成本。因此,CRAG相比self-RAG更为轻量级。
  • 就性能而言,如图5所示,CRAG在多数情况下通常优于self-RAG。

检索评估器的改进

检索评估器可视为一种评分分类模型。该模型用于判断查询与文档的相关性,类似于RAG中的重排序模型

通过整合更多与实际场景相符的特征,此类相关性判断模型可以得到改进。例如,科学论文问答RAG包含许多专业术语,而旅游领域的RAG则倾向于有更多口语化的用户查询。

通过在检索评估器的训练数据中加入场景特征,它能更好地评估检索到的文档的相关性。其他特征,如用户意图和编辑距离,也可以被整合进来,如图6所示:

此外,考虑到T5-Large获得的结果,轻量级模型似乎也能取得良好效果。这为CRAG在小规模团队或公司中的应用带来了希望。

检索评估器的评分与阈值

如前所述,不同类型的数据阈值有所不同。此外,我们发现**模糊****不正确**的阈值基本在-0.9左右,表明大部分检索到的知识与查询相关。完全摒弃这些检索到的知识而仅依赖网络搜索可能并不明智。

在实际应用中,我们需要根据实际问题和需求进行调整。

结论

本文从一个直观的例子出发,概述了CRAG的基本流程,并辅以代码解释,同时包含了我的见解和思考。

总的来说,CRAG作为一个即插即用的插件,能显著提升RAG的性能。它提供了一种轻量级的解决方案来改进RAG。

如果你对RAG技术感兴趣,欢迎查阅我的其他文章。

最后,如有任何错误或遗漏,或你有任何疑问,请随时在评论区讨论。

  • 标题: 高级RAG 10纠正性检索增强生成CRAG
  • 作者: Barry
  • 创建于 : 2024-04-18 03:18:15
  • 更新于 : 2024-08-31 06:59:45
  • 链接: https://wx.role.fun/2024/04/18/79a06904e595484c8aece23fd3b9a634/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。