元数据替换 + 节点语句窗口
在本笔记本中,我们使用 SentenceWindowNodeParser 将文档解析为每个节点包含单个句子。每个节点还包含一个“窗口”,其中包含节点句子两侧的句子。
然后,在检索之后,将检索到的句子传递给LLM之前,使用MetadataReplacementNodePostProcessor将单个句子替换为包含周围句子的窗口。
这对于大型文档/索引最为有用,因为它有助于检索更精细的细节。
默认情况下,句子窗口在原句两侧各包含5个句子。
在这种情况下,未使用分块大小设置,而是遵循窗口设置。
%pip install llama-index-embeddings-openai%pip install llama-index-embeddings-huggingface%pip install llama-index-llms-openai%load_ext autoreload%autoreload 2如果您在 Colab 上打开这个笔记本,您可能需要安装 LlamaIndex 🦙。
!pip install llama-indeximport osimport openaios.environ["OPENAI_API_KEY"] = "sk-..."from llama_index.llms.openai import OpenAIfrom llama_index.embeddings.openai import OpenAIEmbeddingfrom llama_index.embeddings.huggingface import HuggingFaceEmbeddingfrom llama_index.core.node_parser import SentenceWindowNodeParserfrom llama_index.core.node_parser import SentenceSplitter
# create the sentence window node parser w/ default settingsnode_parser = SentenceWindowNodeParser.from_defaults( window_size=3, window_metadata_key="window", original_text_metadata_key="original_text",)
# base node parser is a sentence splittertext_splitter = SentenceSplitter()
llm = OpenAI(model="gpt-3.5-turbo", temperature=0.1)embed_model = HuggingFaceEmbedding( model_name="sentence-transformers/all-mpnet-base-v2", max_length=512)
from llama_index.core import Settings
Settings.llm = llmSettings.embed_model = embed_modelSettings.text_splitter = text_splitter在本节中,我们加载数据并构建向量索引。
这里,我们使用最近IPCC气候报告的第三章构建一个索引。
!curl https://www.ipcc.ch/report/ar6/wg2/downloads/report/IPCC_AR6_WGII_Chapter03.pdf --output IPCC_AR6_WGII_Chapter03.pdf % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0curl: (6) Could not resolve host: www..chfrom llama_index.core import SimpleDirectoryReader
documents = SimpleDirectoryReader( input_files=["./IPCC_AR6_WGII_Chapter03.pdf"]).load_data()我们将提取出要存储在向量索引中的节点集合。这包括使用句子窗口解析器的节点,以及使用标准解析器提取的“基础”节点。
nodes = node_parser.get_nodes_from_documents(documents)base_nodes = text_splitter.get_nodes_from_documents(documents)我们同时构建句子索引以及“基础”索引(使用默认的文本块大小)。
from llama_index.core import VectorStoreIndex
sentence_index = VectorStoreIndex(nodes)base_index = VectorStoreIndex(base_nodes)在这里,我们现在使用 MetadataReplacementPostProcessor 将每个节点中的句子替换为其上下文环境。
from llama_index.core.postprocessor import MetadataReplacementPostProcessor
query_engine = sentence_index.as_query_engine( similarity_top_k=2, # the target key defaults to `window` to match the node_parser's default node_postprocessors=[ MetadataReplacementPostProcessor(target_metadata_key="window") ],)window_response = query_engine.query( "What are the concerns surrounding the AMOC?")print(window_response)There is low confidence in the quantification of Atlantic Meridional Overturning Circulation (AMOC) changes in the 20th century due to low agreement in quantitative reconstructed and simulated trends. Additionally, direct observational records since the mid-2000s remain too short to determine the relative contributions of internal variability, natural forcing, and anthropogenic forcing to AMOC change. However, it is very likely that AMOC will decline for all SSP scenarios over the 21st century, but it will not involve an abrupt collapse before 2100.我们还可以检查为每个节点检索到的原始句子,以及实际发送给LLM的句子窗口。
window = window_response.source_nodes[0].node.metadata["window"]sentence = window_response.source_nodes[0].node.metadata["original_text"]
print(f"Window: {window}")print("------------------")print(f"Original Sentence: {sentence}")Window: Nevertheless, projected future annual cumulative upwelling windchanges at most locations and seasons remain within ±10–20% ofpresent-day values (medium confidence) (WGI AR6 Section 9.2.3.5;Fox-Kemper et al., 2021). Continuous observation of the Atlantic meridional overturningcirculation (AMOC) has improved the understanding of its variability(Frajka-Williams et al., 2019), but there is low confidence in thequantification of AMOC changes in the 20th century because of lowagreement in quantitative reconstructed and simulated trends (WGIAR6 Sections 2.3.3, 9.2.3.1; Fox-Kemper et al., 2021; Gulev et al., 2021). Direct observational records since the mid-2000s remain too short todetermine the relative contributions of internal variability, naturalforcing and anthropogenic forcing to AMOC change (high confidence)(WGI AR6 Sections 2.3.3, 9.2.3.1; Fox-Kemper et al., 2021; Gulev et al.,2021). Over the 21st century, AMOC will very likely decline for all SSPscenarios but will not involve an abrupt collapse before 2100 (WGIAR6 Sections 4.3.2, 9.2.3.1; Fox-Kemper et al., 2021; Lee et al., 2021). 3.2.2.4 Sea Ice ChangesSea ice is a key driver of polar marine life, hosting unique ecosystemsand affecting diverse marine organisms and food webs through itsimpact on light penetration and supplies of nutrients and organicmatter (Arrigo, 2014). Since the late 1970s, Arctic sea ice area hasdecreased for all months, with an estimated decrease of 2 million km2(or 25%) for summer sea ice (averaged for August, September andOctober) in 2010–2019 as compared with 1979–1988 (WGI AR6Section 9.3.1.1; Fox-Kemper et al., 2021).------------------Original Sentence: Over the 21st century, AMOC will very likely decline for all SSPscenarios but will not involve an abrupt collapse before 2100 (WGIAR6 Sections 4.3.2, 9.2.3.1; Fox-Kemper et al., 2021; Lee et al., 2021).query_engine = base_index.as_query_engine(similarity_top_k=2)vector_response = query_engine.query( "What are the concerns surrounding the AMOC?")print(vector_response)The concerns surrounding the AMOC are not provided in the given context information.好吧,这个方法没奏效。让我们提高 top k 值!与句子窗口索引相比,这会运行得更慢并消耗更多令牌。
query_engine = base_index.as_query_engine(similarity_top_k=5)vector_response = query_engine.query( "What are the concerns surrounding the AMOC?")print(vector_response)There are concerns surrounding the AMOC (Atlantic Meridional Overturning Circulation). The context information mentions that the AMOC will decline over the 21st century, with high confidence but low confidence for quantitative projections.所以 SentenceWindowNodeParser + MetadataReplacementNodePostProcessor 组合在这里明显胜出。但为什么呢?
句子级别的嵌入似乎能捕捉更精细的细节,比如词语 AMOC。
我们还可以比较每个索引检索到的文本块!
for source_node in window_response.source_nodes: print(source_node.node.metadata["original_text"]) print("--------")Over the 21st century, AMOC will very likely decline for all SSPscenarios but will not involve an abrupt collapse before 2100 (WGIAR6 Sections 4.3.2, 9.2.3.1; Fox-Kemper et al., 2021; Lee et al., 2021).
--------Direct observational records since the mid-2000s remain too short todetermine the relative contributions of internal variability, naturalforcing and anthropogenic forcing to AMOC change (high confidence)(WGI AR6 Sections 2.3.3, 9.2.3.1; Fox-Kemper et al., 2021; Gulev et al.,2021).--------在这里,我们可以看到句子窗口索引轻松检索到了两个讨论AMOC的节点。请记住,这里的嵌入完全基于原始句子,但LLM实际上最终也会读取周围的上下文!
现在,让我们尝试分析为什么简单的向量索引失败了。
for node in vector_response.source_nodes: print("AMOC mentioned?", "AMOC" in node.node.text) print("--------")AMOC mentioned? False--------AMOC mentioned? False--------AMOC mentioned? True--------AMOC mentioned? False--------AMOC mentioned? False--------所以索引 [2] 处的源节点提到了AMOC,但这段文本实际上是什么样子的?
print(vector_response.source_nodes[2].node.text)2021; Gulev et al.2021)The AMOC will decline over the 21st century(high confidence, but low confidence forquantitative projections).4.3.2.3, 9.2.3 (Fox-Kemperet al. 2021; Lee et al.2021)Sea iceArctic sea icechanges‘Current Arctic sea ice coverage levels are thelowest since at least 1850 for both annual meanand late-summer values (high confidence).’2.3.2.1, 9.3.1 (Fox-Kemperet al. 2021; Gulev et al.2021)‘The Arctic will become practically ice-free inSeptember by the end of the 21st century underSSP2-4.5, SSP3-7.0 and SSP5-8.5[…](highconfidence).’4.3.2.1, 9.3.1 (Fox-Kemperet al. 2021; Lee et al.2021)Antarctic sea icechangesThere is no global significant trend inAntarctic sea ice area from 1979 to 2020 (highconfidence).2.3.2.1, 9.3.2 (Fox-Kemperet al. 2021; Gulev et al.2021)There is low confidence in model simulations offuture Antarctic sea ice.9.3.2 (Fox-Kemper et al.2021)Ocean chemistryChanges in salinityThe ‘large-scale, near-surface salinity contrastshave intensified since at least 1950 […](virtually certain).’2.3.3.2, 9.2.2.2(Fox-Kemper et al. 2021;Gulev et al. 2021)‘Fresh ocean regions will continue to get fresherand salty ocean regions will continue to getsaltier in the 21st century (medium confidence).’9.2.2.2 (Fox-Kemper et al.2021)Ocean acidificationOcean surface pH has declined globally over thepast four decades (virtually certain).2.3.3.5, 5.3.2.2 (Canadellet al. 2021; Gulev et al.2021)Ocean surface pH will continue to decrease‘through the 21st century, except for thelower-emission scenarios SSP1-1.9 and SSP1-2.6[…] (high confidence).’4.3.2.5, 4.5.2.2, 5.3.4.1(Lee et al. 2021; Canadellet al. 2021)OceandeoxygenationDeoxygenation has occurred in most openocean regions since the mid-20th century (highconfidence).2.3.3.6, 5.3.3.2 (Canadellet al. 2021; Gulev et al.2021)Subsurface oxygen content ‘is projected totransition to historically unprecedented conditionwith decline over the 21st century (mediumconfidence).’5.3.3.2 (Canadell et al.2021)Changes in nutrientconcentrationsNot assessed in WGI Not assessed in WGI所以AMOC被讨论了,但遗憾的是它位于中间段落。在使用大语言模型时,经常观察到检索上下文中间的文本往往被忽略或效果较差。最近一篇论文《迷失在中间》在此处讨论了这个问题。
我们更严格地评估了句子窗口检索器相较于基础检索器的性能表现。
我们定义/加载一个评估基准数据集,然后在其上运行不同的评估。
警告:这可能成本高昂,特别是使用 GPT-4 时。请谨慎使用,并根据您的预算调整样本大小。
from llama_index.core.evaluation import DatasetGenerator, QueryResponseDataset
from llama_index.llms.openai import OpenAIimport nest_asyncioimport random
nest_asyncio.apply()len(base_nodes)428num_nodes_eval = 30# there are 428 nodes total. Take the first 200 to generate questions (the back half of the doc is all references)sample_eval_nodes = random.sample(base_nodes[:200], num_nodes_eval)# NOTE: run this if the dataset isn't already saved# generate questions from the largest chunks (1024)dataset_generator = DatasetGenerator( sample_eval_nodes, llm=OpenAI(model="gpt-4"), show_progress=True, num_questions_per_chunk=2,)eval_dataset = await dataset_generator.agenerate_dataset_from_nodes()eval_dataset.save_json("data/ipcc_eval_qr_dataset.json")# optionaleval_dataset = QueryResponseDataset.from_json("data/ipcc_eval_qr_dataset.json")import asyncioimport nest_asyncio
nest_asyncio.apply()from llama_index.core.evaluation import ( CorrectnessEvaluator, SemanticSimilarityEvaluator, RelevancyEvaluator, FaithfulnessEvaluator, PairwiseComparisonEvaluator,)
from collections import defaultdictimport pandas as pd
# NOTE: can uncomment other evaluatorsevaluator_c = CorrectnessEvaluator(llm=OpenAI(model="gpt-4"))evaluator_s = SemanticSimilarityEvaluator()evaluator_r = RelevancyEvaluator(llm=OpenAI(model="gpt-4"))evaluator_f = FaithfulnessEvaluator(llm=OpenAI(model="gpt-4"))# pairwise_evaluator = PairwiseComparisonEvaluator(llm=OpenAI(model="gpt-4"))from llama_index.core.evaluation.eval_utils import ( get_responses, get_results_df,)from llama_index.core.evaluation import BatchEvalRunner
max_samples = 30
eval_qs = eval_dataset.questionsref_response_strs = [r for (_, r) in eval_dataset.qr_pairs]
# resetup base query engine and sentence window query engine# base query enginebase_query_engine = base_index.as_query_engine(similarity_top_k=2)# sentence window query enginequery_engine = sentence_index.as_query_engine( similarity_top_k=2, # the target key defaults to `window` to match the node_parser's default node_postprocessors=[ MetadataReplacementPostProcessor(target_metadata_key="window") ],)import numpy as np
base_pred_responses = get_responses( eval_qs[:max_samples], base_query_engine, show_progress=True)pred_responses = get_responses( eval_qs[:max_samples], query_engine, show_progress=True)
pred_response_strs = [str(p) for p in pred_responses]base_pred_response_strs = [str(p) for p in base_pred_responses]evaluator_dict = { "correctness": evaluator_c, "faithfulness": evaluator_f, "relevancy": evaluator_r, "semantic_similarity": evaluator_s,}batch_runner = BatchEvalRunner(evaluator_dict, workers=2, show_progress=True)在忠实度/语义相似性上运行评估。
eval_results = await batch_runner.aevaluate_responses( queries=eval_qs[:max_samples], responses=pred_responses[:max_samples], reference=ref_response_strs[:max_samples],)base_eval_results = await batch_runner.aevaluate_responses( queries=eval_qs[:max_samples], responses=base_pred_responses[:max_samples], reference=ref_response_strs[:max_samples],)results_df = get_results_df( [eval_results, base_eval_results], ["Sentence Window Retriever", "Base Retriever"], ["correctness", "relevancy", "faithfulness", "semantic_similarity"],)display(results_df).dataframe tbody tr th { vertical-align: top;}
.dataframe thead th { text-align: right;}| 名称 | 正确性 | 相关性 | 忠实度 | semantic_similarity | |
|---|---|---|---|---|---|
| 0 | 句子窗口检索器 | 4.366667 | 0.933333 | 0.933333 | 0.959583 |
| 1 | 基础检索器 | 4.216667 | 0.900000 | 0.933333 | 0.958664 |