Type something to search...
可视化你的 RAG 数据——使用 Ragas 评估你的检索增强生成系统

可视化你的 RAG 数据——使用 Ragas 评估你的检索增强生成系统

如何使用 UMAP 降维将嵌入可视化以展示多个评估问题及其与源文档的关系,结合 Ragas、OpenAI、Langchain 和 ChromaDB

检索增强生成(RAG)在 LLM 的工作流程中增加了一个检索步骤,使其能够在回答问题和查询时,从私人文档等额外来源查询相关数据 [1]。该工作流程不需要对额外文档进行昂贵的训练或微调。文档被拆分成片段,然后进行索引,通常使用紧凑的 ML 生成的向量表示(嵌入)。内容相似的片段在这个嵌入空间中会彼此靠近。

RAG 应用将用户提供的问题投影到嵌入空间,以根据与问题的距离检索相关文档片段。LLM 可以使用检索到的信息来回答查询,并通过呈现片段作为参考来证明其结论。

评估 RAG 应用是具有挑战性的 [2]。存在不同的方法:一方面,有些方法要求开发者提供答案作为真实值;另一方面,答案(和问题)也可以由另一个 LLM 生成。最大的开源 LLM 支持回答系统之一是 Ragas [4](检索增强生成评估),它提供

  • 基于文档生成测试数据的方法,以及
  • 基于不同指标逐步和端到端评估检索和生成步骤的评估。

在本文中,您将学习

代码可在 Github 上获取。

准备你的环境

启动一个笔记本并安装所需的 python 包

!pip install langchain langchain-openai chromadb renumics-spotlight
%env OPENAI_API_KEY=<your-api-key>

本教程使用以下 python 包:

  • Langchain: 一个集成语言模型和 RAG 组件的框架,使设置过程更加顺畅。
  • Renumics-Spotlight: 一个可视化工具,用于交互式探索非结构化的机器学习数据集。
  • Ragas: 一个帮助你评估 RAG 管道的框架

免责声明:本文作者也是 Spotlight 的开发者之一。

为数据集准备文档和嵌入

您可以使用自己的 RAG 应用程序,跳到下一部分了解如何评估、提取和可视化。

或者您可以使用来自上一篇文章的 RAG 应用程序,配合我们准备的所有维基百科 Formula One 文章的数据集。您还可以将自己的文档插入到“docs/”子文件夹中。

此数据集基于来自维基百科的文章,并根据知识共享署名-相同方式共享许可协议进行许可。原始文章及作者列表可以在相应的维基百科页面中找到。

现在您可以使用 Langchain 的 DirectoryLoader 从 docs 子目录加载所有文件,并使用 RecursiveCharacterTextSpliter 将文档拆分为片段。通过 OpenAIEmbeddings,您可以创建嵌入并将其存储在 ChromaDB 中作为向量存储。对于 Chain 本身,您可以使用 LangChains 的 ChatOpenAIChatPromptTemplate

本文的链接代码包含所有必要步骤,您可以在上一篇文章中找到上述所有步骤的详细描述。

一个重要的点是,您应该使用哈希函数为 ChromaDB 中的片段创建 ID。这允许在仅拥有文档及其内容和元数据的情况下找到数据库中的嵌入。这使得可以跳过已经存在于数据库中的文档。

import hashlib
import json
from langchain_core.documents import Document

def stable_hash_meta(doc: Document) -> str:
    """
    Stable hash document based on its metadata.
    """
    return hashlib.sha1(json.dumps(doc.metadata, sort_keys=True).encode()).hexdigest()

...
splits = text_splitter.split_documents(docs)
splits_ids = [
    {"doc": split, "id": stable_hash_meta(split.metadata)} for split in splits
]

existing_ids = docs_vectorstore.get()["ids"]
new_splits_ids = [split for split in splits_ids if split["id"] not in existing_ids]

docs_vectorstore.add_documents(
    documents=[split["doc"] for split in new_splits_ids],
    ids=[split["id"] for split in new_splits_ids],
)
docs_vectorstore.persist()

评估问题

对于像一级方程式这样的常见主题,可以直接使用 ChatGPT 生成一般性问题。本文使用了四种问题生成方法:

  • GPT4: 使用 ChatGPT 4 生成了 30 个问题,提示为“写 30 个关于一级方程式的问题” – 随机示例:“哪个一级方程式车队以其跃马标志而闻名?”
  • GPT3.5: 使用 ChatGPT 3.5 生成了另外 199 个问题,提示为“写 100 个关于一级方程式的问题”,并重复“谢谢,再写 100 个吧” – 示例:“哪位车手在 1950 年赢得了首届一级方程式世界锦标赛?”
  • Ragas_GPT4: 使用 Ragas 生成了 113 个问题。Ragas 再次利用文档及其自身的嵌入模型构建一个向量数据库,然后用 GPT4 生成问题。 – 示例:“你能告诉我更多关于乔丹 198 一级方程式赛车在 1998 年世界锦标赛中的表现吗?”
  • Rags_GPT3.5: 使用 Ragas 生成了 226 个额外问题——这里我们使用 GPT3.5 – 示例:“在 2014 年比利时大奖赛上发生了什么事件导致汉密尔顿退赛?”
from ragas.testset import TestsetGenerator

generator = TestsetGenerator.from_default(
    openai_generator_llm="gpt-3.5-turbo-16k", 
    openai_filter_llm="gpt-3.5-turbo-16k"
)

testset_ragas_gpt35 = generator.generate(docs, 100)

问题和答案没有经过审核或修改。所有问题都合并在一个单一的数据框中,包含 idquestionground_truthquestion_byanswer 列。

接下来,问题将被提出给 RAG 系统。对于超过 500 个问题,这可能需要一些时间并产生费用。如果逐行询问问题,可以暂停并继续该过程,或者在崩溃后恢复,而不会丢失到目前为止的结果:

for i, row in df_questions_answers.iterrows():
    if row["answer"] is None or pd.isnull(row["answer"]):
        response = rag_chain.invoke(row["question"])

        df_questions_answers.loc[df_questions_answers.index[i], "answer"] = response[
            "answer"
        ]
        df_questions_answers.loc[df_questions_answers.index[i], "source_documents"] = [
            stable_hash_meta(source_document.metadata)
            for source_document in response["source_documents"]
        ]

不仅存储了答案,还存储了检索到的文档片段的源 ID 及其文本内容作为上下文:

此外,还生成并存储了所有问题的嵌入,并将其存储在数据框中。这使得可以将它们与文档一起可视化。

使用 Ragas 进行评估

Ragas 提供了评估您的 RAG 流水线中每个组件的指标,以及整体性能的端到端指标:

  1. 上下文精确度: 使用 question 和检索到的 contexts 来测量信号与噪声的比率。
  2. 上下文相关性: 测量检索到的上下文与问题的相关性,使用 questioncontexts 计算。
  3. 上下文召回率: 基于 ground truthcontexts 检查是否检索到所有与答案相关的信息。
  4. 忠实度: 利用 contextsanswer 来衡量生成答案的事实准确性。
  5. 答案相关性: 使用 questionanswer 计算,评估生成的答案与问题的相关性(不考虑事实性)。
  6. 答案语义相似度: 使用 ground truthanswer 进行评估,以判断生成答案与正确答案之间的语义相似性。
  7. 答案正确性: 依赖于 ground truthanswer 来衡量生成答案的准确性和与正确答案的一致性。
  8. 方面评估: 涉及分析 answer 以根据预定义或自定义方面(如正确性或有害性)评估提交结果。

目前,我们专注于答案正确性的端到端指标。数据框中的列名和内容已复制并调整,以符合 Ragas API 的命名和格式要求:

## prepare the dataframe for evaluation
df_qa_eval = df_questions_answers.copy()


## adapt the ground truth to the ragas naming and format
df_qa_eval.rename(columns={"ground_truth": "ground_truths"}, inplace=True)
df_qa_eval["ground_truths"] = [
    [gt] if not isinstance(gt, list) else gt for gt in df_qa_eval["ground_truths"]
]

这可能需要一些时间,甚至比仅查询您的 RAG 系统花费更多的金钱。让我们逐行应用评估,以便在崩溃后能够恢复而不丢失到目前为止的结果:

## evaluate the answer correctness if not already done
fields = ["question", "answer", "contexts", "ground_truths"]
for i, row in df_qa_eval.iterrows():
    if row["answer_correctness"] is None or pd.isnull(row["answer_correctness"]):
        evaluation_result = evaluate(
            Dataset.from_pandas(df_qa_eval.iloc[i : i + 1][fields]),
            [answer_correctness],
        )
        df_qa_eval.loc[i, "answer_correctness"] = evaluation_result[
            "answer_correctness"
        ]

之后,您可以将结果存储在 df_questions_answer 数据框中:

df_questions_answers["answer_correctness"] = df_qa_eval["answer_correctness"]

准备可视化

为了在可视化中包含文档片段,我们添加了从文档到使用该文档作为来源的问题的引用。此外,引用文档的问题数量也被存储:

## Explode 'source_documents' so each document ID is in its own row alongside the question ID
df_questions_exploded = df_qa_eval.explode("source_documents")

## Group by exploded 'source_documents' (document IDs) and aggregate
agg = (
    df_questions_exploded.groupby("source_documents")
    .agg(
        num_questions=("id", "count"),  # Count of questions referencing the document
        question_ids=(
            "id",
            lambda x: list(x),
        ),  # List of question IDs referencing the document
    )
    .reset_index()
    .rename(columns={"source_documents": "id"})
)

## Merge the aggregated information back into df_documents
df_documents_agg = pd.merge(df_docs, agg, on="id", how="left")

## Use apply to replace NaN values with empty lists for 'question_ids'
df_documents_agg["question_ids"] = df_documents_agg["question_ids"].apply(
    lambda x: x if isinstance(x, list) else []
)
## Replace NaN values in 'num_questions' with 0
df_documents_agg["num_questions"] = df_documents_agg["num_questions"].fillna(0)

现在将问题的数据框与文档的数据框连接起来

df = pd.concat([df_qa_eval, df_documents_agg], axis=0)

此外,让我们准备一些不同的 UMAP [3] 映射。您可以稍后在 Spotlight GUI 中做类似的事情,但提前做好可以节省时间。

  • umap_all: 对所有文档和问题嵌入应用 fit 和 transform 的 UMAP
  • umap_questions: 仅对问题嵌入应用 fit,并对两者应用 transform 的 UMAP
  • umap_docs: 仅对文档嵌入应用 fit,并对两者应用 transform 的 UMAP

我们像这样准备每个 UMAP 转换:

umap = UMAP(n_neighbors=20, min_dist=0.15, metric="cosine", random_state=42).fit
umap_all = umap.transform(df["embedding"].values.tolist())
df["umap"] = umap_all.tolist()

每个文档片段的另一个有趣指标是其嵌入与最近问题的嵌入之间的距离:

question_embeddings = np.array(df[df["question"].notna()]["embedding"].tolist())
df["nearest_question_dist"] = [  # brute force, could be optimized using ChromaDB
    np.min([np.linalg.norm(np.array(doc_emb) - question_embeddings)])
    for doc_emb in df["embedding"].values
]

这个指标可以帮助找到未被问题引用的文档。

可视化结果

如果您跳过了之前的步骤,您可以下载数据框并使用以下代码加载它:

import pandas as pd
df = pd.read_parquet("df_f1_rag_docs_and_questions.parquet")

然后启动 Renumics Spotlight 以可视化它:

from renumics import spotlight

spotlight.show(df)
spotlight.show(
    df,
    layout="/home/markus/Downloads/layout_rag_1.json",
    dtype={x: Embedding for x in df.keys() if "umap" in x},
)

这将打开一个新的浏览器窗口:

在左上角,您可以看到一个所有问题和所有文档片段的表格。您可以使用“可见列”按钮来控制表格中显示哪些数据框列。直接创建一个选择仅问题的过滤器是很有用的,以便能够在可视化中打开和关闭问题:选择所有问题,然后使用“从选定行创建过滤器”按钮创建过滤器。

在表格的右侧,answer correctness 作为一个指标显示在所有问题中。下面有两个直方图;左侧显示了根据不同问题生成方法划分的answer correctness的分布。右侧显示了问题生成方法的分布。在这里,如果需要,建议使用过滤按钮为问题创建过滤器,以仅显示选定的行(问题)。

右侧有两个相似性图。第一个使用umap_questions列,基于仅对问题应用的转换显示问题和文档。这对于独立于相关文档查看问题的分布很有帮助,因为这种方法允许分析师识别问题本身的模式或簇。

第二个相似性图基于仅对文档应用的转换(umap_docs)显示问题和文档。它对于在其相关文档的上下文中查看问题很有用。一个同时对问题和文档进行转换的相似性图在问题数量较多时被证明不太有用,因为更多或更少的问题会聚集在一起并倾向于与文档分开。因此,这种表示在这里被省略。

文档嵌入相似性图:观察

在相似性图 umap_docs 中,您可以识别出文档嵌入空间中没有邻近问题的区域。当选择 nearest_question_dist 进行着色时,这一点更加明显。

可以识别出一些簇,包括仅包含标题或逐页包含仅数字的表格数据的片段,这些在拆分过程中其意义丧失。此外,许多不包含相关信息的维基百科特定文本添加,例如指向其他语言的链接或编辑注释,形成了没有邻近问题的簇。

使用维基百科 API 删除维基百科相关文本形式的噪声非常简单。这可能并不是特别必要,因为它主要占用一些空间——预计 RAG 结果不会因此特别恶化。然而,包含在大表格中的数据很难被 RAG 系统捕获,使用先进的预处理方法进行表格提取并将其连接到 RAG 系统可能是有益的。

您可以在 umap_docs 相似性图中观察到的另一个点是来自不同来源的问题的分布。

由 ChatGPT(GPT-3.5、GPT-4)直接生成的问题位于中心的一个更为封闭的区域,而基于文档生成的 ragas 生成的问题覆盖了更大的区域。

答案正确性直方图

直方图可以作为了解数据全球统计的起点。总体而言,在所有问题中,答案正确性为0.45。对于没有使用ragas创建的问题,该值为0.36,而使用ragas的问题则为0.52。预计系统在生成使用ragas的问题时表现会更好,因为这些问题是基于可用数据生成的,而ChatGPT直接生成的问题可能来自于ChatGPT训练时使用的所有数据。

对一些问题/答案和真实情况进行快速随机手动审核显示,在答案正确性为0.3–0.4的区间,大多数问题仍然根据真实情况得到了正确回答。在0.2–0.3的区间,存在许多错误答案。在0.1–0.2的区间,大多数答案都是错误的。值得注意的是,这个范围内几乎所有的问题都来自GPT-3.5。尽管在这个区间内生成的两个问题使用的是GPT-4,但它们仍然得到了正确的回答,尽管其答案正确性低于0.2。

问题嵌入相似性图:观察

问题嵌入相似性图可以通过检查可能导致类似问题的相似问题集群,帮助深入挖掘 答案正确性

  • 集群“驱动程序/过程/汽车的术语”: 平均 答案正确性 0.23:答案通常不够精确。例如,底盘调校与底盘弯曲或刹车调校与刹车偏差调整。是否适合用这些类型的问题来评估系统是值得怀疑的,因为判断答案似乎非常困难。
  • 集群“燃料策略的术语”: 平均 答案正确性 0.44,类似于全球 答案正确性
  • 集群“赛道名称”: 平均 答案正确性 0.49,类似于全球 答案正确性
  • 集群“谁保持了…的记录”: 平均 答案正确性 0.44,类似于全球 答案正确性
  • 集群“赢得…锦标赛”: 平均 答案正确性 0.26 — 看起来很具挑战性。带有许多条件的问题,例如:“谁是唯一一位凭借英国赛车执照、为意大利车队驾驶美国引擎赢得一级方程式世界锦标赛的车手。” 扩展的RAG方法如多查询可能有助于改善这一点。
  • 集群“谁是唯一一位赢得…的车手,驾驶编号为<number>的汽车”: 平均 答案正确性 0.23 — 看起来GPT-3.5在这里懒惰,重复了相同的问题,只是换了不同的数字,尽管大多数真实答案都是错误的!

结论

总之,利用基于 UMAP 的可视化提供了一种有趣的方法,可以深入分析全球指标之外的内容。文档嵌入相似性地图提供了一个良好的概述,展示了相似文档的聚类及其与评估问题的关系。问题相似性地图揭示了模式,使得可以结合质量指标对问题进行区分和分析,从而生成洞察。请参阅可视化结果部分,将可视化应用于您的评估策略——您将发现什么洞察?

I am a professional with expertise in creating advanced software solutions for the interactive exploration of unstructured data. I write about unstructured data and use powerful visualization tools to analyze and make informed decisions.

参考文献

[1] Yunfan Gao, Yun Xiong, Xinyu Gao, Kangxiang Jia, Jinliu Pan, Yuxi Bi, Yi Dai, Jiawei Sun, Qianyu Guo, Meng Wang, Haofen Wang: Retrieval-Augmented Generation for Large Language Models: A Survey (2024), arxiv

[2] Yixuan Tang, Yi Yang: MultiHop-RAG: Benchmarking Retrieval-Augmented Generation for Multi-Hop Queries (2021), arXiv

[3] Leland McInnes, John Healy, James Melville: UMAP: Uniform Manifold Approximation and Projection for Dimension Reduction (2018), arXiv

[4] Shahul Es, Jithin James, Luis Espinosa-Anke, Steven Schockaert: RAGAS: Automated Evaluation of Retrieval Augmented Generation (2023), arXiv

Related Posts

使用 ChatGPT 搜索网络功能的 10 种创意方法

使用 ChatGPT 搜索网络功能的 10 种创意方法

例如,提示和输出 你知道可以使用 ChatGPT 的“搜索网络”功能来完成许多任务,而不仅仅是基本的网络搜索吗? 对于那些不知道的人,ChatGPT 新的“搜索网络”功能提供实时信息。 截至撰写此帖时,该功能仅对使用 ChatGPT 4o 和 4o-mini 的付费会员开放。 ![](https://images.weserv.nl/?url=https://cdn-im

阅读更多
在人工智能和技术领域保持领先地位的 10 项必学技能 📚

在人工智能和技术领域保持领先地位的 10 项必学技能 📚

在人工智能和科技这样一个动态的行业中,保持领先意味着不断提升你的技能。无论你是希望深入了解人工智能模型性能、掌握数据分析,还是希望通过人工智能转变传统领域如法律,这些课程都是你成功的捷径。以下是一个精心策划的高价值课程列表,可以助力你的职业发展,并让你始终处于创新的前沿。 1. 生成性人工智能简介课程: [生成性人工智能简介](https://genai.works

阅读更多
10 个强大的 Perplexity AI 提示,让您的营销任务自动化

10 个强大的 Perplexity AI 提示,让您的营销任务自动化

在当今快速变化的数字世界中,营销人员总是在寻找更智能的方法来简化他们的工作。想象一下,有一个个人助理可以为您创建受众档案,建议营销策略,甚至为您撰写广告文案。这听起来像是一个梦想? 多亏了像 Perplexity 这样的 AI 工具,这个梦想现在成为现实。通过正确的提示,您可以将 AI 转变为您的 个人营销助理。在本文中,我将分享 10 个强大的提示,帮助您自动

阅读更多
10+ 面向 UI/UX 设计师的顶级 ChatGPT 提示

10+ 面向 UI/UX 设计师的顶级 ChatGPT 提示

人工智能技术,如机器学习、自然语言处理和数据分析,正在重新定义传统设计方法。从自动化重复任务到实现个性化用户体验,人工智能使设计师能够更加专注于战略思维和创造力。随着这一趋势的不断增长,UI/UX 设计师越来越多地采用 AI 驱动的工具来促进他们的工作。利用人工智能不仅能提供基于数据的洞察,还为满足多样化用户需求的创新设计解决方案开辟了机会。 1. 用户角色开发 目的

阅读更多
在几分钟内完成数月工作的 100 种人工智能工具

在几分钟内完成数月工作的 100 种人工智能工具

人工智能(AI)的快速发展改变了企业的运作方式,使人们能够在短短几分钟内完成曾经需要几周或几个月的任务。从内容创作到网站设计,AI工具帮助专业人士节省时间,提高生产力,专注于创造力。以下是按功能分类的100个AI工具的全面列表,以及它们在现实世界中的使用实例。 1. 研究工具 研究可能耗时,但人工智能工具使查找、分析和组织数据变得更加容易。**ChatGPT, Cop

阅读更多
你从未知道的 17 个令人惊叹的 GitHub 仓库

你从未知道的 17 个令人惊叹的 GitHub 仓库

Github 隐藏的宝石!! 立即收藏的代码库 学习编程相对简单,但掌握编写更好代码的艺术要困难得多。GitHub 是开发者的宝藏,那里“金子”是其他人分享的精心编写的代码。通过探索 GitHub,您可以发现如何编写更清晰的代码,理解高质量代码的样子,并学习成为更熟练开发者的基本步骤。 1. notwaldorf/emoji-translate *谁需

阅读更多