如何评估 RAG 的分块Chunking策略

article/2025/6/24 18:47:17

如何评估 RAG 的分块策略

我对 RAG(检索增强生成模型)进行了深入研究,深知分块在任何 RAG 流水线中都至关重要。

我接触过的许多人坚信更好的模型能够提升 RAG 的性能。有些人则对向量数据库寄予厚望。即便那些认同分块重要性的人,也认为它对系统的改进幅度有限。

大多数人认为,大上下文窗口会取代分块策略的需求。

但分块技术是长期存在的,它非常有效,是任何 RAG 项目的必备要素。

然而,一个关键问题仍未得到解答:如何为项目挑选最佳的分块策略呢?

常用的分块策略有很多:递归字符分割、语义分块以及代理分块,llm分块等等,甚至还主张将聚类作为代理分块的快速且廉价的替代方案。

但残酷的事实是,没有任何一种策略总是表现良好。你无法仅凭项目的性质来猜测哪种策略会奏效。

唯一的办法就是将它们都试一遍,找出最适合的那一个。

所以,这就是我的方法。

首先对文档进行抽样

如果你正在开发一个生产级应用,可能需要处理数百 GB 的数据。

但你不可能用所有这些数据来试验分块策略。

成本是一个显而易见的原因。评估过程会使用 LLM(大型语言模型)推理,无论是通过 API 调用还是本地托管的 LLM 推理,都需要花费一定的成本。

另一个问题是评估的耗时。你的项目通常都有时间限制,你可不想浪费数周时间来评估分块技术。

想低成本且快速地推进?那就抽样吧。

但一定要确保你的样本能够准确地代表总体。

说起来容易做起来难。但鉴于项目的性质,分层抽样可能是个不错的选择。

例如,如果你有 100 份客户交付的 PPT、50 份案例研究 PDF 文档以及 300 份会议记录,你可以从每个类别中各抽取 10% 作为样本。这样就能确保每个群体都在样本中有所体现。

一旦有了样本,就可以进入下一步了。

创建测试集

进行评估需要问题,LLM 将根据这些问题来评估答案。

最好是由人类专家手动创建这些问题集。然而,并非每个组织都有这样的条件。领域专家的时间宝贵,通常无法用于研发工作。

在这种情况下,你可以借助 LLM 来帮忙。

LLM 可以根据样本文档生成问题。尽管我一直说 RAG 中的增强功能不需要强大的 LLM,但这个步骤却需要。选择一个推理能力出色的模型来创建高质量的问题集。

下面的代码可以帮助你开始,根据具体需求进行调整。

def create_test_questions(documents: List[Document], num_questions: int = 10) -> List[Dict[str, str]]:"""从文档中生成测试问题"""questions = []# 为问题生成采样文本sample_texts = []for doc in documents[:3]:  # 使用前 3 份文档words = doc.page_content.split()[:500]  # 前 500 个单词sample_texts.append(" ".join(words))combined_text = "\n\n".join(sample_texts)prompt = f"""根据以下文本,生成 {num_questions} 个多样化的问题,这些问题可以通过提供的信息来回答。文本:{combined_text}只返回问题,每个问题占一行,不要编号或添加额外文本。"""response = llm.invoke(prompt)question_lines = [q.strip() for q in response.content.split('\n') if q.strip()]for i, question in enumerate(question_lines[:num_questions]):questions.append({"question": question,"question_id": f"q_{i+1}"})return questions

上面的代码使用了 Langchain 框架。该函数将文档分成每段 500 个单词的部分,并提示 LLM 生成问题。

准备评估

LLM 评估是一个广泛的话题,我们在这里只是浅尝辄止。

我将使用 RAGAS,这是一个用于评估 RAG 的框架。它功能丰富,但我们只需要其中一小部分指标来完成这项任务。

你可以使用以下命令安装 RAGAS。

pip install ragas
# uv add ragas

如果遇到错误,尝试将 RAGAs 降级到 ragas==0.1.9

注意:与本文相关的所有代码都在 这个笔记本 中。我将在每个部分中仅讨论该代码库的一部分。

下面的代码创建并评估了一个简单的 RAG,使用了分块策略。它使用了 Langchain 框架和 Chroma DB 作为向量存储。但如果你使用的是其他向量存储,最好用你选择的向量存储来测试代码。

from typing import List, Dict
import time
import numpy as np
from tqdm import tqdm
from datasets import Dataset
from langchain_chroma import Chroma
from ragas import evaluate
from ragas.metrics import (answer_relevancy,faithfulness,context_precision,context_recall,answer_correctness
)def evaluate_strategy(strategy_name: str,strategy,documents: List,test_questions: List[Dict[str, str]],embeddings,llm,chroma_client,top_k: int = 5) -> 'EvaluationResult':"""评估单个分块策略参数:strategy_name:正在评估的策略名称strategy:具有 chunk_documents 方法的分块策略对象documents:要分块的文档列表test_questions:问题字典列表embeddings:向量存储的嵌入函数llm:用于答案生成的语言模型chroma_client:ChromaDB 客户端实例top_k:要检索的文档数量返回:包含指标和性能数据的 EvaluationResult 对象"""print(f"\n正在评估策略:{strategy_name}")# 1. 对文档进行分块chunks = strategy.chunk_documents(documents)print(f"创建了 {len(chunks)} 个分块")# 2. 创建向量存储collection_name = f"eval_{strategy_name}_{hash(str(time.time()))}"collection_name = collection_name.replace("-", "_").replace(" ", "_")vectorstore = Chroma(collection_name=collection_name,embedding_function=embeddings,client=chroma_client)# 将分块添加到向量存储vectorstore.add_documents(chunks)# 3. 使用测试问题进行评估evaluation_data = []for q_data in tqdm(test_questions, desc="处理问题"):question = q_data["question"]# 检索上下文start_time = time.time()retrieved_docs = vectorstore.similarity_search(question, k=top_k)retrieval_time = time.time() - start_timecontexts = [doc.page_content for doc in retrieved_docs]# 生成答案start_time = time.time()context_text = "\n\n".join(contexts)prompt = f"上下文:
{context_text}问题:{question}答案:"response = llm.invoke(prompt)answer = response.contentgeneration_time = time.time() - start_time# 为 RAGAS 准备数据evaluation_data.append({"question": question,"answer": answer,"contexts": contexts,"ground_truth": answer,  # 使用生成的答案作为代理"retrieval_time": retrieval_time,"generation_time": generation_time})# 4. 运行 RAGAS 评估dataset = Dataset.from_list(evaluation_data)metrics = [answer_relevancy,faithfulness,context_precision,context_recall,answer_correctness]ragas_results = evaluate(dataset, metrics=metrics)# 5. 计算统计信息avg_chunk_size = np.mean([len(chunk.page_content) for chunk in chunks])avg_retrieval_time = np.mean([d["retrieval_time"] for d in evaluation_data])avg_generation_time = np.mean([d["generation_time"] for d in evaluation_data])# 计算总体得分(加权平均值)overall_score = (ragas_results["answer_relevancy"] * 0.25 +ragas_results["faithfulness"] * 0.25 +ragas_results["context_precision"] * 0.2 +ragas_results["context_recall"] * 0.2 +ragas_results["answer_correctness"] * 0.1)result = EvaluationResult(strategy_name=strategy_name,chunk_count=len(chunks),avg_chunk_size=avg_chunk_size,retrieval_time=avg_retrieval_time,generation_time=avg_generation_time,answer_relevancy=ragas_results["answer_relevancy"],faithfulness=ragas_results["faithfulness"],context_precision=ragas_results["context_precision"],context_recall=ragas_results["context_recall"],answer_correctness=ragas_results["answer_correctness"],overall_score=overall_score)# 清理try:chroma_client.delete_collection(collection_name)except:passreturn result

我们可以用几种分块技术运行这段代码,并收集结果。每个结果包含五个评估指标。我们还计算了这些指标的加权平均值作为总体得分。结果还会包含其他重要数据,如分块大小、分块数量、检索时间和生成时间。

在返回结果之前,我们还会清理 Chroma DB,以免剩余数据影响后续策略的评估。

现在我们已经得到了评估数据,接下来可以对它们进行可视化,以便做出明智的决策,选择最佳的分块技术。

可视化评估结果

我至少会创建四种结果可视化的图表,以找到最佳的分块技术。

你可以对数据进行切片和分析,创建更有帮助的图表,但这些可以作为一个很好的起点。

在共享的笔记本中,我使用 Plotly 创建了这些图表。我也喜欢用它来创建仪表板。

以下是我要创建的图表及其用途。

性能排名

None

由于我们已经计算了总体得分以及五个单独评估指标的加权平均值,最合理的第一步就是将它们绘制在条形图中。

这张图表可以快速显示哪种策略表现最佳以及与其他策略的比较情况。

在上面的例子中,我们可以看到 fixed-1000 策略超过了其他三种策略。不过,除了 Semantic-1000 表现不佳外,差距似乎并不大。

分块分布与性能

None

这个图表在很多方面都非常有用。

在一个图表中,我们可以看到总体性能、分块大小以及分块数量。

在 RAG 中,分块大小和数量至关重要。我们必须决定为用户输入检索多少个分块作为上下文。如果分块太大,检索到的内容可能会迅速填满 LLM 的上下文窗口。

过多的噪声可能会降低响应质量,即使上下文窗口足够大,能够容纳所有内容。

在我们的例子中,尽管 fixed-1000 策略的总体得分很高,但它创建的分块较少且较大。

在另一个极端,recursive-500 创建了过多的分块。但它们很小。然而,这种策略的表现也很好。如果我需要抽取许多文档并在生成最终答案之前对它们进行重新排序,我会选择这个策略。

指标雷达图

None

到目前为止,我们只讨论了总体得分。我们还没有关注细节。

在这个图表中,我们会关注细节。

雷达图可以揭示很多信息。对于特定应用来说,真实性可能是最重要的。然而,对于更随意的应用,高相关性得分就足够了。

你必须自己决定什么是最好的。但你需要在一个地方看到它们,而这是最好的方式。

处理时间分解

None

我接下来想看到的图表显示了每种策略完成所需的时间。这是一个我们需要做出的操作决策。

对于需要低延迟的应用程序来说,这个视图是必不可少的。

在我们的例子中,语义分块比其他两种策略花费的时间要长得多。递归-500 是最快的。

我们既有生成时间,也有检索时间。生成时间是创建分块所花费的时间。在实际应用中,这个数字可能会很大。因此,时间差可以为公司节省大量资金。

检索时间是导致延迟的因素。

再次强调,优先选择哪一个取决于你的具体应用。

我如何做决定(我的思考过程)

到目前为止,我们已经做了很多工作。

我们选择了样本以评估分块技术,创建了问题以评估系统,创建了一个可以改变分块技术并使用 RAG 进行评估的 RAG 流水线,最后用收集到的结果创建了图表。

现在怎么办?

让我们回顾一下我们的例子。

语义分块被排除在外。它的性能很差。它生成分块的时间也长得多。

固定-1000 的总体得分最高,但它的相关性得分主要提升了。如果这是我们最看重的,那它仍然是一个不错的选择。然而,考虑到较长的检索时间和较大的分块大小,我仍然会避免使用它。

两种递归方法在评估得分上都表现良好,但它们的分块大小、生成时间和检索时间差异很大。

我已经说过,如果我使用重新排序模型,我会选择递归-500。然而,有了处理时间的额外信息,我现在认为这是最佳选择。

最后

分块成就了 RAG,它是 RAG 的核心。

我们在分块技术上投入的时间越多,我们的 RAG 应用就会越有帮助。

然而,不同的应用需要优先考虑不同的方面。因此,我们需要系统地评估分块技术。


http://www.hkcw.cn/article/FQqcGdIKkq.shtml

相关文章

贪心算法应用:顶点覆盖问题详解

贪心算法应用:顶点覆盖问题详解 贪心算法是解决顶点覆盖问题的经典方法之一。下面我将从基础概念到高级优化,全面详细地讲解顶点覆盖问题及其贪心算法解决方案。 一、顶点覆盖问题基础 1. 问题定义 顶点覆盖问题(Vertex Cover Problem&am…

代码随想录算法训练营第十一天 | 150. 逆波兰表达式求值、239. 滑动窗口最大值、347.前 K 个高频元素、栈与队列总结

150. 逆波兰表达式求值--后缀表达式 力扣题目链接(opens new window) 根据 逆波兰表示法,求表达式的值。 有效的运算符包括 , - , * , / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。 说明: 整数除法只保留整数部分。 给…

女童玩的气球突然爆炸 静电引发惊魂一刻

一位江苏宝妈在六一儿童节前发布了一段视频,提醒家长们不要给孩子买氢气球玩。她描述了自己孩子遇到的一次危险情况:气球突然爆炸,孩子的后脑勺头发被烧焦了一层,手臂上的汗毛也被烧掉了。事发时,这位宝妈正在给孩子喂食鸡腿,突然听到“嘭”的一声,气球在孩子身后爆裂,…

Ubuntu安装CH340驱动教程

Ubuntu22.04安装CH340驱动 3.1 用lsusb查看USB 插上CH340之前 插上CH340之后 输出中包含ID 1a86:7523 QinHeng Electronics CH340 serial converter的信息,这表明CH340设备已经被系统识别。 3.2 查看USB转串口 ls -l /dev/ttyUSB0/dev下没有该设备节点。 用dme…

新人雨中骑“鸡动车”去结婚 创意婚礼引关注

6月1日,在合肥渡仙桥路上,一对新人以独特的方式前往婚礼现场。他们没有选择豪华婚车,甚至没有开车,而是骑着小鸡造型的两轮电动车赶往婚礼现场。这对新人表示,看到这种小鸡造型的电动车觉得很漂亮,便决定用它作为婚车。视频中,这支婚车车队虽然数量不多,但在路上格外显…

上海迪士尼打架事件后续 因拍照引发冲突

5月31日,有网友发布视频称,在上海迪士尼乐园内一对情侣和一家三口发生了冲突。视频中可以看到双方在现场扭打,周围游客纷纷上前劝阻。6月1日,当地相关部门透露,该事件发生在5月31日下午,在迪士尼疯狂动物城的一处拍照打卡点,双方因拍照问题发生争执,随后演变成肢体冲突…

【Python连接数据库基础 02】SQLAlchemy核心实战:SQL表达式构建与执行完全指南

【Python连接数据库基础 02】SQLAlchemy核心实战:SQL表达式构建与执行完全指南 关键词:SQLAlchemy Core、SQL表达式、数据库查询、Python ORM、表达式语言、数据库操作、查询构建、SQLAlchemy教程 摘要:本文深入讲解SQLAlchemy Core的核心功能…

台湾一大学生暗网贩毒被捕 涉案金额超7亿

美国联邦调查局近日破获了暗网毒品交易平台“隐身市场”,该平台的经营者“法老”被发现是24岁的台湾大学资管系学生林睿庠。林睿庠通过贩毒积累了大量财富,三年多时间里非法所得超过1亿美元。林睿庠于2024年5月18日在美国被捕,并面临四项罪名指控,一旦定罪将面临至少终身监…

数据库系统概论(十四)详细讲解SQL中空值的处理

数据库系统概论(十四)详细讲解SQL中空值的处理 前言一、什么是空值?二、空值是怎么产生的?1. 插入数据时主动留空2. 更新数据时设置为空3. 外连接查询时自然出现 三、如何判断空值?例子:查“漏填数据的学生…

我国代表反驳对华无端指责:无中生有 颠倒黑白 贼喊捉贼

我国代表反驳对华无端指责。5月31日,参加第22届香格里拉对话会的中国代表团团长、中国人民解放军国防大学副校长兼教育长胡钢锋出席《亚太地区海事安全合作》特别论坛,并阐述中方观点。他首先反驳了当天上午大会发言中涉及中方的内容。他表示,我们不接受对中方的无端指责,有…

喊话万科,74岁的王石难有“真还传” 万科面临转型挑战

喊话万科,74岁的王石难有“真还传” 万科面临转型挑战。对于如今的万科来说,他们需要的是“化腐朽为神奇”的钞能力,而不是“剪刀手”。近期地产圈和网络上热议的话题是,如果王石重新回归万科,是否会有奇迹发生。5月27日,万科创始人王石在朋友圈发布了一则长文,表示正在…

国际组织:美债务飙升不仅危及自身更可能引发全球债券市场危机

国际组织:美债务飙升不仅危及自身。5月31日报道,国际金融协会最近的一份报告警告,美国债务飙升的影响不仅限于国内经济,更可能引发全球债券市场危机。报告显示,一些国家的借贷成本往往与美国国债同步变动,这意味着美国国债的波动将对其它债务产生连锁反应。报告写道,“由…

一句古语看懂中方如何应对抹黑!

一句古语看懂中方如何应对抹黑。第22届香格里拉对话会闭幕了这期间,谭主在香会现场看到了美菲防长的无理挑衅甚至连中国记者都变成了菲防长口中的“特工”这说明他们急了,为什么?一句古语看懂中方如何应对抹黑责任编辑:0882

005-C/C++内存管理

C/C内存管理 1. C/C内存分布 栈–存储非静态局部变量/函数参数/返回值等等,栈是向下增长的。内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信(这是系统部分的知识…

tex中的表格4:自动表格宽度自动换行tabularx宏包

文章目录 介绍语法规则示例 自定义 X 列的对齐方式多行与多列处理长表格 介绍 在 LaTeX 里,tabularx 是一个很实用的包,它能够创建宽度固定的表格,而且可以自动对列宽进行调整。 语法规则 \usepackage{tabularx} \begin{tabularx}{总宽度}…

数据结构之排序

正值数据结构期末复习之际,遂将排序相关的复习内容记录在此,也算是对知识的一种整理归纳和复习。 常见的排序方法:插入排序、交换排序、选择排序、归并排序、基数排序 其中插入排序又包含直接插入排序、折半插入排序、希尔排序。 交换排序有包括冒泡排…

【Hot 100】118. 杨辉三角

目录 引言杨辉三角我的解题代码优化优化说明 🙋‍♂️ 作者:海码007📜 专栏:算法专栏💥 标题:【Hot 100】118. 杨辉三角❣️ 寄语:书到用时方恨少,事非经过不知难! 引言 …

【Linux网络】传输层TCP协议

🌈个人主页:秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343 🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12891150.html 目录 TCP 协议 TCP 协议段格式 确认应答(ACK)机制 超时重传机制 连接管理机制 …

【C语言】C语言经典小游戏:贪吃蛇(上)

文章目录 一、游戏背景及其功能二、Win32 API介绍1、Win32 API2、控制台程序3、定位坐标(COORD)4、获得句柄(GetStdHandle)5、获得光标属性(GetConsoleCursorInfo)1)描述光标属性(CO…

【Python 算法零基础 4.排序 ⑥ 快速排序】

既有锦绣前程可奔赴,亦有往日岁月可回首 —— 25.5.25 选择排序回顾 ① 遍历数组:从索引 0 到 n-1(n 为数组长度)。 ② 每轮确定最小值:假设当前索引 i 为最小值索引 min_index。从 i1 到 n-1 遍历,若找到…