LlamaIndex 实测 RAG 系统的最佳 chunk size
Evaluating the Ideal Chunk Size for a RAG System using LlamaIndex
LlamaIndex 演示如何用 Response Evaluation 模块评测 RAG 系统的最佳 chunk size,以 Uber 2021 10K 文件为数据。
原文给出可复现的评测流程和具体数据,读者可以据此在自己的 RAG 场景中测试并选定合适的 chunk size。
引言
检索增强生成(RAG)引入了一种创新方法,将搜索系统的广泛检索能力与LLM融合在一起。在实施RAG系统时,一个关键参数决定了系统的效率和性能,那就是chunk_size。如何确定无缝检索的最佳块大小?这就是LlamaIndex Response Evaluation派上用场的地方。在这篇博客文章中,我们将指导您通过步骤,使用LlamaIndex的Response Evaluation模块来确定最佳的chunk size。如果您不熟悉Response评估模块,我们建议在继续之前查看其文档。
立即探索我们的免费和付费计划。
为什么块大小很重要
选择合适的chunk_size是一个关键决策,可以在多个方面影响RAG系统的效率和准确性:
- 相关性和粒度:较小的
chunk_size,如128,会产生更细粒度的块。然而,这种粒度带来风险:重要信息可能不在顶部检索到的块中,特别是如果similarity_top_k设置像2这样严格。相反,块大小为512很可能在顶部块中包含所有必要信息,确保查询答案随时可用。为了应对这一点,我们采用忠实度和相关性指标。这些分别衡量基于查询和检索上下文的响应中‘幻觉’的缺失和‘相关性’。 - 响应生成时间:随着
chunk_size增加,输入到LLM以生成答案的信息量也增加。虽然这可以确保更全面的上下文,但也可能减慢系统速度。确保增加的深度不损害系统的响应性至关重要。
本质上,确定最佳chunk_size就是寻求平衡:捕捉所有必要信息而不牺牲速度。使用各种大小进行彻底测试,以找到适合特定用例和数据集的配置至关重要。
为了在选择正确的chunk_size时进行实际评估,您可以访问并运行以下设置,在这个Google Colab Notebook上。
设置
在开始实验之前,我们需要确保所有必需的模块都已导入:
import nest_asyncio
nest_asyncio.apply()
from llama_index import (
SimpleDirectoryReader,
VectorStoreIndex,
ServiceContext,
)
from llama_index.evaluation import (
DatasetGenerator,
FaithfulnessEvaluator,
RelevancyEvaluator
)
from llama_index.llms import OpenAI
import openai
import time
openai.api_key = 'OPENAI-API-KEY'下载数据
我们将使用2021年的Uber 10K SEC文件进行这个实验。
!mkdir -p 'data/10k/'
!wget 'https://raw.githubusercontent.com/jerryjliu/llama_index/main/docs/examples/data/10k/uber_2021.pdf' -O 'data/10k/uber_2021.pdf'加载数据
让我们加载我们的文档。
documents = SimpleDirectoryReader("./data/10k/").load_data()问题生成
为了选择合适的chunk_size,我们将为各种chunk_sizes计算平均响应时间、忠实度和相关性等指标。DatasetGenerator将帮助我们生成文档中的问题。
data_generator = DatasetGenerator.from_documents(documents)
eval_questions = data_generator.generate_questions_from_nodes()设置评估器
我们正在设置GPT-4模型,作为评估实验中生成响应的骨干。两个评估器,FaithfulnessEvaluator和RelevancyEvaluator,使用service_context初始化。
- 忠实度评估器 — 它用于衡量响应是否幻觉,并衡量查询引擎的响应是否匹配任何源节点。
- 相关性评估器 — 它用于衡量查询是否实际由响应回答,并衡量响应+源节点是否匹配查询。
# We will use GPT-4 for evaluating the responses
gpt4 = OpenAI(temperature=0, model="gpt-4")
# Define service context for GPT-4 for evaluation
service_context_gpt4 = ServiceContext.from_defaults(llm=gpt4)
# Define Faithfulness and Relevancy Evaluators which are based on GPT-4
faithfulness_gpt4 = FaithfulnessEvaluator(service_context=service_context_gpt4)
relevancy_gpt4 = RelevancyEvaluator(service_context=service_context_gpt4)针对块大小的响应评估
我们基于3个指标评估每个chunk_size。
- 平均响应时间。
- 平均忠实度。
- 平均相关性。
这里有一个函数,evaluate_response_time_and_accuracy,它正好做到这一点,包括:
- VectorIndex创建。
- 构建查询引擎。
- 指标计算。
def evaluate_response_time_and_accuracy(chunk_size, eval_questions):
"""
Evaluate the average response time, faithfulness, and relevancy of responses generated by GPT-3.5-turbo for a given chunk size.
Parameters:
chunk_size (int): The size of data chunks being processed.
Returns:
tuple: A tuple containing the average response time, faithfulness, and relevancy metrics.
"""
total_response_time = 0
total_faithfulness = 0
total_relevancy = 0
llm = OpenAI(model="gpt-3.5-turbo")
service_context = ServiceContext.from_defaults(llm=llm, chunk_size=chunk_size)
vector_index = VectorStoreIndex.from_documents(
eval_documents, service_context=service_context
)
query_engine = vector_index.as_query_engine()
num_questions = len(eval_questions)
for question in eval_questions:
start_time = time.time()
response_vector = query_engine.query(question)
elapsed_time = time.time() - start_time
faithfulness_result = faithfulness_gpt4.evaluate_response(
response=response_vector
).passing
relevancy_result = relevancy_gpt4.evaluate_response(
query=question, response=response_vector
).passing
total_response_time += elapsed_time
total_faithfulness += faithfulness_result
total_relevancy += relevancy_result
average_response_time = total_response_time / num_questions
average_faithfulness = total_faithfulness / num_questions
average_relevancy = total_relevancy / num_questions
return average_response_time, average_faithfulness, average_relevancy在不同块大小上测试
我们将评估一系列分块大小,以确定哪种能提供最有前景的指标。
chunk_sizes = [128, 256, 512, 1024, 2048]
for chunk_size in chunk_sizes:
avg_response_time, avg_faithfulness, avg_relevancy = evaluate_response_time_and_accuracy(chunk_size, eval_questions)
print(f"Chunk size {chunk_size} - Average Response time: {avg_response_time:.2f}s, Average Faithfulness: {avg_faithfulness:.2f}, Average Relevancy: {avg_relevancy:.2f}")整合所有内容
让我们汇总这些流程:
import nest_asyncio
nest_asyncio.apply()
from llama_index import (
SimpleDirectoryReader,
VectorStoreIndex,
ServiceContext,
)
from llama_index.evaluation import (
DatasetGenerator,
FaithfulnessEvaluator,
RelevancyEvaluator
)
from llama_index.llms import OpenAI
import openai
import time
openai.api_key = 'OPENAI-API-KEY'
!mkdir -p 'data/10k/'
!wget 'https://raw.githubusercontent.com/jerryjliu/llama_index/main/docs/examples/data/10k/uber_2021.pdf' -O 'data/10k/uber_2021.pdf'
reader = SimpleDirectoryReader("./data/10k/")
documents = reader.load_data()
eval_documents = documents[:20]
data_generator = DatasetGenerator.from_documents()
eval_questions = data_generator.generate_questions_from_nodes(num = 20)
gpt4 = OpenAI(temperature=0, model="gpt-4")
service_context_gpt4 = ServiceContext.from_defaults(llm=gpt4)
faithfulness_gpt4 = FaithfulnessEvaluator(service_context=service_context_gpt4)
relevancy_gpt4 = RelevancyEvaluator(service_context=service_context_gpt4)
def evaluate_response_time_and_accuracy(chunk_size):
total_response_time = 0
total_faithfulness = 0
total_relevancy = 0
llm = OpenAI(model="gpt-3.5-turbo")
service_context = ServiceContext.from_defaults(llm=llm, chunk_size=chunk_size)
vector_index = VectorStoreIndex.from_documents(
eval_documents, service_context=service_context
)
query_engine = vector_index.as_query_engine()
num_questions = len(eval_questions)
for question in eval_questions:
start_time = time.time()
response_vector = query_engine.query(question)
elapsed_time = time.time() - start_time
faithfulness_result = faithfulness_gpt4.evaluate_response(
response=response_vector
).passing
relevancy_result = relevancy_gpt4.evaluate_response(
query=question, response=response_vector
).passing
total_response_time += elapsed_time
total_faithfulness += faithfulness_result
total_relevancy += relevancy_result
average_response_time = total_response_time / num_questions
average_faithfulness = total_faithfulness / num_questions
average_relevancy = total_relevancy / num_questions
return average_response_time, average_faithfulness, average_relevancy
for chunk_size in [128, 256, 512, 1024, 2048]
avg_time, avg_faithfulness, avg_relevancy = evaluate_response_time_and_accuracy(chunk_size)
print(f"Chunk size {chunk_size} - Average Response time: {avg_time:.2f}s, Average Faithfulness: {avg_faithfulness:.2f}, Average Relevancy: {avg_relevancy:.2f}")结果

上表表明,随着分块大小的增加,平均响应时间略有上升。有趣的是,平均忠实度似乎在chunk_size为1024时达到顶峰,而平均相关性则随着分块大小的增加持续改善,同样在1024时达到峰值。这表明,分块大小为1024可能在响应时间与响应质量(以忠实度和相关性衡量)之间达到最佳平衡。
结论
为RAG系统确定最佳分块大小,既依赖直觉,也依赖经验证据。借助LlamaIndex的Response Evaluation模块,你可以尝试各种大小,并基于具体数据做出决策。在构建RAG系统时,请始终记住chunk_size是一个关键参数。投入时间仔细评估并调整你的分块大小,以获得无与伦比的结果。
来源:LlamaIndex:产品、工程与评测 · llamaindex.ai