Hi,我是 Hyde,今天的主题与 RAG 系统的评估有关。
在本系列的前几篇文章中,已经介绍了 RAG 以及一些 RAG 的优化方法。接下来,为了了解我们对 RAG 的调整是否真正起到了效果,我们需要掌握评估 RAG 系统性能的方法和一些评估框架,例如 Ragas。
Ragas 是一个很实用的开源框架,能够帮助我们自动化地生成测试集并对 RAG 系统进行性能评估。有兴趣的同学可以访问 Ragas 的文档或 Repo,来获得更多的信息:
- Ragas 文档:docs.ragas.io/en/stable/i…
- Ragas Github 仓库:github.com/explodinggr…
正好前段时间微软开源了一种新的基于图的 RAG 方法 GraphRAG,这种方法使用大模型从文本构建并增强知识图,目的是解决 Baseline RAG 系统在全局理解上的缺陷。除了 GraphRAG,学界和工业界也提出了一些非常巧妙的方法,例如 RAPTOR。RAPTOR 使用了聚类算法将 chunk 进行递归聚类,并使用 LLM 对簇进行总结,以此获得对原始资料不同层次的理解。
因此本文将介绍一下如何使用 Ragas 评估 RAG 系统的性能,并使用一个简单的测试集来评估 GraphRAG 及 RAPTOR 相较于 NaiveRAG 的性能提升。
如果对于这些方法还不了解的话,欢迎参考以下文章:
评估 RAG 系统的方法
创建一个 RAG 很简单,但在此基础上做优化则很复杂。其中最重要的一件事情就是不断对系统的性能进行测试和评估。就像没有清晰的海图和六分仪,经验再丰富地水手也难以穿越太平洋。如果没有对系统的持续评估,我们很难对其进行持续的维护和改进。
这是一种指标驱动的理念,通过数据指标来指导开发决策,而不是依赖于直觉或假设。
评估指标
为了使评估有效,我们需要建立合适的评估指标体系。
好的评估指标应该与产品的目标对齐,而这取决于业务需求。例如,适用于“智能客服”和“情感对话”这两个场景的指标有很大不同。智能客服场景倾向于解决用户的问题和抱怨,而情感对话更倾向于促进用户的情感交流和理解,因此需要关注不同的指标来衡量系统的效果。
为了确保评估指标的有效性,还需定期审查和调整指标体系,确保其始终与业务目标保持一致。此外,定期分析和评估指标数据,根据结果进行系统优化,是实现持续改进的重要步骤。
建立基准
建立了指标体系以后,就需要测量基准值。
基准指的是系统在初始状态下的指标评分,其意义在于量化和对比。在每一次对系统进行改进后,需要测量其指标并与基准值进行对比。这能告诉我们我们操作对性能有多少的提升,并为我们的行动指明方向。例如,如果“Context Precision(上下文精确度)”指标较差,那么就意味着我们应该对检索器进行进一步改进,以提升检索结果的精准度。
在本文的案例中,基于一个 Naive RAG 系统建立了 Baseline,并基于此 Baseline 我们就能够量化地衡量 GraphRAG 或 RAPTOR 在评估指标上有多少改进。
持续评估
评估不是一个一次性的过程,而是「构建 - 测量 - 改进」的循环,通过不断监测和分析系统的性能和效果,确保系统始终符合业务目标和用户需求,从而实现持续改进和优化。
Ragas 框架的使用
Ragas 框架提供了一系列工具和技术,帮助开发者从数据中获得见解,并迭代和改进 RAG 管道中的组件。
借助 Ragas,我们可以:
- 合成生成一个多样化的测试数据集。
- 使用 LLM 辅助的评估指标,客观衡量 LLM 或 RAG 系统性能。
Ragas 框架安装
1. 安装 Ragas 框架
使用 pip
命令安装 Ragas 框架。
pip install ragas
生成测试集
为了评估 LLM 或 RAG 系统的性能,我们需要准备一系列测试数据集。
Ragas 提供了一种借助 LLM 从原始资料创建测试集的方法。在官方文档中使用了 OpenAI 的 LLM 和 Embedding 模型,为了便于使用,在本文档中使用 Deepseek 的 LLM 和 ZhipuAI 的 Embedding 模型替代。
由于 Ragas 基于 LangChain 构建,因此也可以使用其他的 LangChain LLM 或 Embedding 模型。
1. 加载文档
为了构建测试数据集,首先需要加载原始文档,此处使用了 LangChian 文档加载器。
from langchain_community.document_loaders import DirectoryLoader
loader = DirectoryLoader("your-directory")
documents = loader.load()
2. 为文档创建 filename 元数据
每个文档对象都包含一个元数据字典,用来存储关于文档的附加信息,这些信息可以通过 Document.metadata
访问。
此步骤将为metadata
添加一个filename
的键,用于识别属于同一文档的块。例如,属于同一研究论文的页面可以通过文件名来识别。
for document in documents:
document.metadata['filename'] = document.metadata['source']
3. 数据生成
理想的评估数据集应包含生产中遇到的各种类型的问题,包括不同难度级别的问题。
Ragas 通过采用进化生成范式来实现这一点,从提供的原始文档中构建具有不同特征的问题,如推理、条件、多上下文等。这种方法确保了对您管道中各种组件性能的全面覆盖,从而实现了更稳健的评估过程。
以下是关于这些问题的介绍:
- simple:可以直接从 context 中获得答案的问题。
- reasoning:问题需要根据 context 进行推理。
- conditional:引入了条件元素的问题,例如:“当摘要异步生成时,基于嵌入的搜索如何提高当前查询的相关性?”。
- multi_context:需要结合多个上下文来进行回答的问题。
我们可以通过修改 generator.generate_with_langchain_docs
中的可配置参数,来控制最终测试集的生成:
test_size
:最终测试集的大小;{simple: 0.25, reasoning: 0.25, multi_context: 0.25, conditional:0.25}
:配置测试集中不同类型问题的数量比例;
from ragas.testset.generator import TestsetGenerator
from ragas.testset.evolutions import simple, reasoning, multi_context, conditional
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
llm_api_key = "<your_llm_api_key>"
llm_base_url = "<your_llm_base_url>"
embd_api_key = "<your_embd_api_key>"
embd_base_url = "<your_embd_base_url>"
# generator with openai models
generator_llm = ChatOpenAI(model="Deepseek-chat", api_key=llm_api_key, base_url=llm_base_url)
critic_llm = ChatOpenAI(model="Deepseek-chat", api_key=llm_api_key, base_url=llm_base_url)
embeddings = OpenAIEmbeddings(model="embedding-2", api_key=llm_api_key, base_url=llm_base_url)
generator = TestsetGenerator.from_langchain(
generator_llm,
critic_llm,
embeddings
)
# generate testset
testset = generator.generate_with_langchain_docs(
documents,
test_size=16,
distributions=distributions={
simple: 0.25,
reasoning: 0.25,
conditional:0.25,
multi_context: 0.25,
})
4. 查看数据集
生成后的数据包含了许多 column,其中较为重要的是 question
、contexts
和 ground_truth
,这三列将用于进行最终的评估。
- question:此步骤生成的测试问题,用来向 LLM 或 RAG 提问。
- context:此步骤中生成问题时采用的上下文内容。
- ground_truth:针对于问题的可信事实,可以理解为此步骤中生成问题的正确答案。
需要说明的是,如果评估的对象是 LLM,则可以使用 contexts
作为上下文传递给模型。而评估对象是 RAG 系统时,则需要将 contexts
替换为 RAG 系统中检索器的检索结果,以评估检索相关的性能指标。
testset.to_pandas()
评估 RAG 系统
1. 构建评估数据集
评估数据集通常应包含 4 个 column,分别是:question
、ground_truth
、answer
和 contexts
。但这并不绝对,具体需要哪些 column 与后续使用的 Ragas 评估指标有关。
与测试数据集的内容相比,评估数据集发生了一些变化:
- answer:LLM 或 RAG 系统生成的回答。
- context:RAG 系统中检索器的检索结果。
3. 引入评价指标
首先我们需要引入评测指标,此处以 Ragas 文档中使用的四个评估指标为例。
这四个指标用于评测 RAG 系统的检索器和生成器的性能,更详细的内容将在下一个小节做详细说明,这里暂不展开:
-
检索器:使用
context_precision
和context_recall
来衡量 RAG 中检索组件的性能。 -
生成器:使用
faithfulness
来衡量幻觉,answer_relevancy
来衡量答案与问题的相关性。
from ragas.metrics import (
answer_relevancy,
faithfulness,
context_recall,
context_precision,
)
4. 评估系统性能
与生成测试集一样,这里也使用了自定义(LangChain)的 LLM 和 Embedding Model。
from ragas import evaluate
eval_result = evaluate(
dataset,
metrics=[
context_precision,
faithfulness,
answer_relevancy,
context_recall,
],
llm = <your_langchain_llm>,
embeddings=<your_langchain_embeddings>
run_config = RunConfig(timeout=600, thread_timeout=600),
)
print(eval_result)
然后我们就获得了最终的评估结果,如下图所示。
5. 进一步数据分析
数据测试结果也可以转换为 pandas DataFrame 进行更进一步的数据分析。
df = eval_result.to_pandas()
df
Ragas 的评估指标
在上一小节,我们提到了四个评估指标,如下图所示:
1. Faithfulness
此指标通过使用 context
和 answer
来计算。
Faithfulness 指标用于衡量答案的可信程度,即答案中的主张有多少是可以被 contexts 证实的。
在如下案例中,低忠实度答案关于爱因斯坦出生日期的主张错误,因此忠实度较低。
问题:爱因斯坦何时在何地出生?
上下文:阿尔伯特·爱因斯坦(1879 年 3 月 14 日出生)是一位出生于德国的理论物理学家,被广泛认为是历史上最伟大和最有影响力的科学家之一.
高忠实度回答:爱因斯坦于 1879 年 3 月 14 日出生于德国。
低忠实度回答:爱因斯坦于 1879 年 3 月 20 日出生于德国。
计算过程
- 使用 LLM 从答案中抽取主张。
- 使用 LLM 验证每个主张是否可以从上下文中推断出来。
- 使用公式计算 Faithfulness score。
计算公式
2. Answer Relevance
此指标通过使用 question
、 context
和 answer
来计算。
Answer Relevance 指标用于衡量 RAG 生成的回答与问题之间的相关程度。此分数通过从答案中逆向推理变体问题,并计算与原始问题的余弦相似度,来衡量原始答案中的信息是否都与问题相关。
如果原始答案不完整或其中包含了与问题不相关的冗余信息,会导致逆向生成的问题缺失信息或包含冗余信息,最终的 Answer Relevance 分数将下降。
在下面的案例中,由于原始问题包含了冗余信息,逆向推理的变体问题就包含了更多的内容,因此其与原始问题的余弦相似度较低,因此可以计算出答案的 Answer Relevance 指标也较低。
原始问题:爱因斯坦出生在哪个国家?
答案:爱因斯坦于 1879 年 3 月 14 日出生于德国。
可能的变体问题:爱因斯坦于何时出生在哪个国家?
得分较低表示答案不完整或包含冗余信息,而得分较高则表示相关性更好。
计算过程
-
使用 LLM 根据原始答案逆向生成出 N 个变体问题。
-
计算变体问题的嵌入向量与原始问题的嵌入向量间的平均余弦相似度。
- 其中:
- 是通过答案逆向生成的变体问题的嵌入向量。
- 是原始问题的嵌入向量
3. Content Precision
该指标通过 question
、 ground_truth
和 contexts
计算得出。
在理想情况下,检索器的检索结果中,与问题相关的上下文片段应该放在检索结果的顶部。这个描述中包含了两个关键内容:
- 与问题相关的上下文片段。
- 放在检索结果的顶部。
Content Precision 用于衡量检索出的上下文与问题的相关程度,即同时衡量上下文与问题的相关性及排序情况。
计算过程
-
使用 LLM 检查每一个上下文片段,判断是否与问题相关。
-
计算 ,其意义是前 k 个片段中与问题有关的片段的比例,计算公式如下:
- 其中 为前 k 个检索结果片段中与问题相关的数量,若前 5 条上下文片段中,有 3 条与问题相关,则 ,,。
-
遍历 1~k,计算 的均值,得出最终的 context precisio 分数。计算公式如下:
4. Context Recall
该指标基于 ground truth
, 和 contexts
计算得出。
在理想情况下,ground truth
的每一个主张都可以完全归因于检索器的检索结果。若存在无法归因的主张,则说明检索器的召回性能差,无法检索出全部有用信息。
计算过程
- 使用 LLM 将
ground truth
分解为单个陈述。 - 使用 LLM 验证每一个陈述是否可以归因于上下文。
- 计算召回率:
除了这四个指标外,Rages 也支持一些其他的指标,限于篇幅,这里不做展开介绍,有兴趣的同学可以参考 Ragas 文档中关于这些评估指标的介绍:docs.ragas.io/en/stable/c…
评估 GraphRAG、RAPTOR 的性能指标
在本次评估实验中,我们使用了 Rages 框架中的 context_precision
、 context_recall
、 faithfulness
和 answer_relevancy
指标,针对 NaiveRAG、RAPTOR RAG 及 GraphRAG 的 Global Query 和 Local Query 的性能进行了评估。
RAG 数据源采用了 OpenAI 官方文档中的 Prompt Engineering 和 Fine-tuning 两篇文档,并使用 Ragas 框架自动生成了包含 12 个问题的测试集,其中简单、推理、条件和多上下文问题各 3 个。
序号 | 类型 | 问题 |
---|---|---|
0 | simple | How can one evaluate the quality of a fine-tuned model using metrics and sample comparisons? |
1 | simple | What is the format for creating prompt completion pairs for fine-tuning models like babbage-002 and davinci-002? |
2 | simple | How does the recursive summarization process help in summarizing very long documents like books? |
3 | reasoning | What training data imbalance could lead a model to frequently avoid answering? |
4 | reasoning | What comes before training a fine-tuned model after prompt optimization? |
5 | reasoning | What's the anticipated cost for fine-tuning gpt-3.5-turbo-0125 after uploading a 100k token file? |
6 | multi_context | How does increasing edge case examples affect the model's accuracy in referencing specific facts? |
7 | multi_context | How does summarizing dialogue, adopting personas, and specifying tasks help optimize long-term interactions with models limited by context length? |
8 | multi_context | What are the ideal example counts and data splits for fine-tuning gpt-4o-mini and gpt-3.5-turbo, considering token limits and overfitting risks? |
9 | conditional | How do the last 3 epochs' checkpoints help in evaluating fine-tuning performance? |
10 | conditional | How might easing time constraints improve GPT-4o's performance in prompt engineering? |
11 | conditional | How does structured inner monologue format ensure correct parts visibility during parsing errors? |
需要额外说明的是,由于 GraphRAG Global Query 并未对全部问题给出有效回答(例如:I am sorry but I am unable to answer this question given the provided data,占比约 25%),因此在对比时额外为 Global Query 增加了去除无效回答的指标统计(即 GraphRAG Global Query (Correct the data)项)。
产生这种情况的原因主要是由于 Global Query 模式的检索范围在于 community reports,适合于回答较为全局性的问题。但对于更细节的一些问题,检索效果较差。在无法检索到有效上下文的情况下,模型给出了:“I am sorry but I am unable to answer this question given the provided data” 的回答。
总体对比
从数据中可以发现,在我们的测试数据集中,NaiveRAG 性能相当不错,在各个指标上都有不错的表现。
RAPTOR
RAPTOR 在与生成器相关的 faithfulness 和 answer_relvancy 指标上优于 baseline,从侧面反映了更丰富的层次总结有助于提升模型的输出效果。但更多的内容也对检索器提出了更高的要求,由于实验中使用的 NaiveRAG 和 RAPROT 均使用了 Chroma 和向量检索,因此还有很大的优化空间。
GraphRAG
从数据上看,GraphRAG 的指标几乎全面落后于 baseline 和 RAPTOR,这与实际使用 GraphRAG 时的体验相有较大出入。
Local Query 的 context precision 指标很低,可能是由于其检索策略的影响。检索器会返回许多潜在相关的图数据,而这些图数据可能在评估时被认为是与问题无关,因此拉低了 context precision 分数。
例如,图数据:/v1/fine-tunes is an endpoint for fine-tuning models, which has been deprecated in favor of a new endpoint.
是针对问题: How can one evaluate the quality of a fine-tuned model using metrics and sample comparisons?
的一条检索结果。
通过绘制直方图我们可以了解到,GraphRAG 指标偏低的直接原因是存在一些得分很低的 badcase。
GraphRAG Global 直方图:
GraphRAG Local 直方图:
NaiveRAG 直方图:
一些详细测试数据
最后再放一些详细的测试数据。