使用LlamaIndex评估RAG系统的理想分块大小
引言
检索增强生成(RAG)引入了一种创新方法,将搜索系统的强大检索能力与大型语言模型(LLM)相结合。在实现RAG系统时,一个关键参数是chunk_size
,它决定了系统的效率和性能。如何确定最佳的分块大小以实现无缝检索?这就是LlamaIndex响应评估模块发挥作用的地方。在本篇博客中,我们将指导您使用LlamaIndex的响应评估模块来确定最佳分块大小。如果您不熟悉响应评估模块,建议在继续之前查看其文档。
为什么分块大小很重要
选择合适的chunk_size
是一个关键决策,它可以通过以下几个方面影响RAG系统的效率和准确性:
相关性和粒度
- 小分块大小(如128):产生更细粒度的分块。然而,这种粒度存在风险:重要信息可能不在前几个检索到的分块中,特别是当
similarity_top_k
设置为2时。 - 大分块大小(如512):更有可能在顶部分块中包含所有必要信息,确保查询的答案随时可用。
为了解决这个问题,我们使用**忠实度(Faithfulness)和相关性(Relevancy)**指标。这些指标分别测量响应中“幻觉”的缺失和响应基于查询和检索上下文的相关性。
响应生成时间
随着chunk_size
的增加,进入LLM以生成答案的信息量也会增加。虽然这可以确保更全面的内容,但也可能减慢系统速度。确保增加的深度不会牺牲系统的响应速度至关重要。
本质上,确定最佳chunk_size
是关于平衡:捕捉所有必要信息而不牺牲速度。通过使用各种大小进行彻底测试,找到适合特定用例和数据集的配置至关重要。
设置
在开始实验之前,我们需要确保所有必需的模块都已导入:
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'
下载数据
我们将使用Uber 2021年的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_size
的平均响应时间、忠实度和相关性指标。DatasetGenerator
将帮助我们从文档中生成问题。
data_generator = DatasetGenerator.from_documents(documents)
eval_questions = data_generator.generate_questions_from_nodes()
设置评估器
我们将使用GPT-4模型作为实验中生成响应的评估基础。两个评估器,FaithfulnessEvaluator
和RelevancyEvaluator
,使用service_context
进行初始化。
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)
分块大小的响应评估
我们根据以下三个指标评估每个chunk_size
:
- 平均响应时间
- 平均忠实度
- 平均相关性
以下是一个函数evaluate_response_time_and_accuracy
,它实现了这些功能:
def evaluate_response_time_and_accuracy(chunk_size, eval_questions):
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)
# 使用GPT-4进行响应评估
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的响应评估模块,您可以尝试各种大小,并根据具体数据做出决策。在构建RAG系统时,请记住chunk_size
是一个关键参数。投入时间仔细评估和调整您的分块大小,以获得无与伦比的结果。