文档概要索引,简单提升检索性能的新选择 原创
今天介绍了一种全新的 LlamaIndex 数据结构:文档摘要索引。将描述它如何比传统语义搜索提供更好的检索性能,并通过一个示例进行了演示。
背景
大型语言模型 (LLM) 的核心用例之一是针对自己的数据进行问答。为此,我们将 LLM 与“检索”模型配对,该模型可以对知识语料库执行信息检索,并使用 LLM 对检索到的文本执行响应合成。这个整体框架称为检索增强生成(RAG)。
目前,大多数构建 LLM 驱动的 QA 系统的用户倾向于执行以下操作:
- 获取源文档,将每个文档拆分为文本块
- 将文本块存储在向量数据库中
- 在查询时,通过嵌入相似性和/或关键字过滤器来检索文本块。
- 执行响应合成
由于各种原因,这种方法的检索性能有限。
现有方法的局限性
使用文本块进行嵌入检索存在一些限制。
- 文本块缺乏全局上下文。问题通常需要超出特定块索引内容的上下文。
- 仔细调整 top-k/相似度得分阈值。如果值太小,将错过上下文。如果值太大,成本/延迟可能会随着不相关上下文的增加而增加。
- 嵌入并不总是会选择与问题最相关的上下文。嵌入本质上是在文本和上下文之间分别确定的。
添加关键字过滤器是增强检索结果的一种方法。但这也带来了一系列挑战。我们需要充分确定每个文档的正确关键字,无论是手动还是通过 NLP 关键字提取/主题标记模型。此外,我们还需要从查询中充分推断出正确的关键字。
文档概要索引
文档概要索引,它将提取/索引每个文档的非结构化文本摘要。此索引可以帮助增强现有检索方法之外的检索性能。它有助于索引比单个文本块更多的信息,并且比关键字标签具有更多的语义含义。它还允许更灵活的检索形式:我们可以进行 LLM 检索和基于嵌入的检索。
工作原理
在构建期间,我们会提取每个文档,并使用 LLM 从每个文档中提取摘要,还将文档拆分为文本块(节点)。摘要和节点都存储在我们的文档存储抽象中。我们维护从摘要到源文档/节点的映射。
在查询期间,我们根据摘要检索与查询相关的文档,使用以下方法:
- 基于 LLM 的检索:我们向 LLM 提供一系列文档摘要,并要求 LLM 确定哪些文档是相关的 + 它们的相关性分数。
- 基于嵌入的检索:我们根据摘要嵌入相似度(具有前 k 个截止值)检索相关文档。
注意,这种文档摘要检索方法(即使采用基于嵌入的方法)与基于嵌入的文本块检索不同。文档摘要索引的检索类会检索任何选定文档的所有节点,而不是返回节点级别的相关块。
存储文档摘要还可以实现基于 LLM 的检索。我们不必一开始就将整个文档提供给 LLM,而是先让 LLM 检查简明的文档摘要,看看它是否与查询相关。这利用了 LLM 的推理能力,这些能力比基于嵌入的查找更先进,但避免了将整个文档提供给 LLM 的成本/延迟。
更多
带摘要的文档检索可以看作是所有文档中的语义搜索和强力摘要之间的“中间地带”。我们根据给定查询的摘要相关性查找文档,然后返回与检索到的文档相对应的所有“节点”。
我们为什么要这样做?这种检索方法通过在文档级别检索上下文,为用户提供了比文本块上的 top-k 更多的上下文。但是,它也是一种比主题建模更灵活/自动化的方法;无需再担心您的文本是否具有正确的关键字标签!
代码示例
下面展示部分构建代码,完整代码地址:
https://docs.llamaindex.ai/en/stable/examples/index_structs/doc_summary/DocSummary/
from llama_index import (
SimpleDirectoryReader,
LLMPredictor,
ServiceContext,
ResponseSynthesizer
)
from llama_index.indices.document_summary import GPTDocumentSummaryIndex
from langchain.chat_models import ChatOpenAI
# load docs, define service context
...
# build the index
response_synthesizer = ResponseSynthesizer.from_args(response_mode="tree_summarize", use_async=True)
doc_summary_index = GPTDocumentSummaryIndex.from_documents(
city_docs,
service_cnotallow=service_context,
response_synthesizer=response_synthesizer
)
一旦索引建立,我们就可以获得任何给定文档的摘要:
summary = doc_summary_index.get_document_summary("Boston")
接下来,让我们看一个基于 LLM 的索引检索示例。
from llama_index.indices.document_summary import DocumentSummaryIndexRetriever
retriever = DocumentSummaryIndexRetriever(
doc_summary_index,
# choice_select_prompt=choice_select_prompt,
# choice_batch_size=choice_batch_size,
# format_node_batch_fn=format_node_batch_fn,
# parse_choice_select_answer_fn=parse_choice_select_answer_fn,
# service_cnotallow=service_context
)
retrieved_nodes = retriever.retrieve("What are the sports teams in Toronto?")
print(retrieved_nodes[0].score)
print(retrieved_nodes[0].node.get_text())The retriever will retrieve a set of relevant nodes for a given index.
请注意,LLM 除了返回文档文本之外,还返回相关性分数:
8.0
Toronto ( (listen) tə-RON-toh; locally [təˈɹɒɾ̃ə] or [ˈtɹɒɾ̃ə]) is the capital city of the Canadian province of Ontario. With a recorded population of 2,794,356 in 2021, it is the most populous city in Canada...
我们还可以将索引用作整体查询引擎的一部分,不仅可以检索相关上下文,还可以合成给定问题的答案。我们可以通过高级 API 和低级 API 来实现这一点。
高级 API
query_engine = doc_summary_index.as_query_engine(
response_mode="tree_summarize", use_async=True
)
response = query_engine.query("What are the sports teams in Toronto?")
print(response)
低级 API
# use retriever as part of a query engine
from llama_index.query_engine import RetrieverQueryEngine
# configure response synthesizer
response_synthesizer = ResponseSynthesizer.from_args()
# assemble query engine
query_engine = RetrieverQueryEngine(
retriever=retriever,
response_synthesizer=response_synthesizer,
)
# query
response = query_engine.query("What are the sports teams in Toronto?")
print(response)
本文转载自公众号哎呀AIYA
原文链接:https://mp.weixin.qq.com/s/USkhLo_qDhvZKoMTQAXquQ