每日推荐一篇专注于解决实际问题的外文,精准翻译并深入解读其要点,助力读者培养实际问题解决和代码动手的能力。
原文标题:Chunking Strategies for LLM Applications
原文地址:https://www.pinecone.io/learn/chunking-strategies/
在构建与LLM相关的应用程序的背景下,分块是将大段文本分解为较小段的过程。这是一种关键的技术,有助于在使用LLM嵌入内容后,优化从矢量数据库获取的内容的相关性。在本博客文章中,我们将探讨分块是否以及如何提高LLM相关应用程序的效率和准确性。
正如我们所知,我们在Pinecone中索引的任何内容都需要首先进行嵌入。分块的主要原因是确保我们嵌入的内容尽可能少地包含噪音,同时仍然在语义上相关。
例如,在语义搜索中,我们索引一系列文档,每个文档都包含特定主题的有价值信息。通过应用有效的分块策略,我们可以确保我们的搜索结果准确捕捉用户查询的本质。如果我们的文本块太小或太大,可能会导致不精确的搜索结果或错过展示相关内容的机会。通常来说,如果文本块在不考虑周围上下文的情况下对人类来说是有意义的,那么对语言模型来说也是有意义的。因此,找到语料库中文档的最佳分块大小对确保搜索结果准确和相关至关重要。
另一个例子是对话代理(我们之前使用Python和Javascript进行了介绍)。我们使用嵌入的块来构建基于知识库的对话代理的上下文,该知识库将代理置于可信的信息中。在这种情况下,选择正确的分块策略非常重要,原因有两个:首先,它将决定上下文是否真正与我们的提示相关。其次,它将决定我们是否能够将检索到的文本适应到上下文中,然后发送给外部模型提供商(例如,OpenAI),考虑到我们每次请求可以发送的令牌数量的限制。在某些情况下,比如在使用具有32k上下文窗口的GPT-4时,适应文本块可能不是问题。然而,我们需要注意,当我们使用非常大的文本块时,这可能会对我们从Pinecone得到的结果的相关性产生负面影响。
在这篇文章中,我们将探讨几种分块方法,并讨论在选择分块大小和方法时应考虑的权衡。最后,我们将给出一些关于确定最佳分块大小和方法的建议,这将适用于您的应用程序。
当我们嵌入我们的内容时,我们可以预见到根据内容的长度(如句子或段落,甚至整篇文章)的不同,会有不同的行为。
当一个句子被嵌入时,生成的向量专注于句子的特定含义。与其他句子嵌入比较时,比较通常会在这个层面上进行。这也意味着嵌入可能会错过在段落或文档中发现的更广泛的上下文信息。
当整个段落或文章被嵌入时,嵌入过程会同时考虑整体上下文和文本内部句子和短语之间的关系。这可能导致一个更全面的向量表示,捕捉文本的广泛含义和主题。另一方面,更大的输入文本大小可能会引入噪音或淡化单个句子或短语的重要性,使得在查询索引时找到精确匹配更困难。
查询的长度也会影响嵌入之间的关系。更短的查询,如单个句子或短语,会集中在特定的细节上,可能更适合与句子级别的嵌入进行匹配。超过一句话或一段落的更长查询可能会更符合段落或文档级别的嵌入,因为它可能在寻找更广泛的上下文或主题。
索引也可能是非均匀的,包含各种大小的块嵌入。这可能在查询结果的相关性方面带来挑战,但也可能带来一些积极的影响。一方面,由于长、短内容的语义表示之间的差异,查询结果的相关性可能会波动。另一方面,非均匀索引可能捕获更广泛的上下文和信息,因为不同大小的块代表了文本中不同级别的粒度。这可能更灵活地适应不同类型的查询。
确定最佳分块策略的几个变量会根据使用场景的不同而变化。以下是一些需要考虑的关键因素:
(1)被索引的内容是什么性质?你是在处理长的文档,如文章或书籍,还是较短的内容,如推特或即时信息?答案将决定哪个模型更适合你的目标,因此,也会决定应用什么样的分块策略。
(2)你正在使用哪种嵌入模型,以及它对哪种大小的块有最佳的性能?例如,句子-转换器模型在处理单个句子时表现良好,但像text-embedding-ada-002这样的模型在处理包含256或512个标记的块时表现更好。
(3)你对用户查询的长度和复杂性有什么期望?他们会是短而具体,还是长且复杂?这可能会影响你选择如何分块内容,以便嵌入查询和嵌入块之间有更紧密的关联。
(4)在你的特定应用中如何使用检索到的结果?例如,他们会被用于语义搜索,问答,总结,还是其他目的?例如,如果你的结果需要被输入到另一个具有标记限制的LLM中,你就必须考虑这个因素,并根据你希望适应到LLM请求的块数量来限制块的大小。
回答这些问题将使你能够制定出一个平衡性能和精度的分块策略,这反过来也将确保查询结果的相关性更高。
有多种不同的分块方法,每种方法可能适合不同的情况。通过分析每种方法的优点和缺点,我们的目标是确定适合应用它们的正确场景。
这是最常见和最直接的分块方法:我们只需决定我们的块中的token数,并选择性地决定它们之间是否应存在任何重叠。一般来说,我们会希望块之间保持一些重叠,以确保语义上下文不会在块之间丢失。固定大小的分块在大多数常见情况下将是最好的路径。与其他形式的分块相比,固定大小的分块在计算上便宜且使用简单,因为它不需要使用任何NLP库。
以下是使用LangChain执行固定大小分块的示例:
text = "..." # your text
from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(
separator = "\n\n",
chunk_size = 256,
chunk_overlap = 20
)
docs = text_splitter.create_documents([text])
这是一组方法,用于利用我们正在分块的内容的性质,并应用更复杂的分块方法。以下是一些例子:
(1)句子分割
如我们之前提到的,许多模型都是针对嵌入句子级内容进行优化的。自然地,我们会使用句子分块,有几种方法和工具可以执行此操作,包括:
text = "..." # your text
docs = text.split(".")
text = "..." # your text
from langchain.text_splitter import NLTKTextSplitter
text_splitter = NLTKTextSplitter()
docs = text_splitter.split_text(text)
text = "..." # your text
from langchain.text_splitter import SpacyTextSplitter
text_splitter = SpaCyTextSplitter()
docs = text_splitter.split_text(text)
(2)递归分块
递归分块使用一组分隔符以分层和迭代的方式将输入文本划分为更小的块。如果首次尝试分割文本未能产生所需大小或结构的块,该方法会递归地在结果块上调用自身,并使用不同的分隔符或标准,直到达到所需的块大小或结构。这意味着,虽然块不会完全相同大小,但它们仍会“渴望”达到类似的大小。
以下是如何使用LangChain进行递归分块的示例:
text = "..." # your text
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
# Set a really small chunk size, just to show.
chunk_size = 256,
chunk_overlap = 20
)
docs = text_splitter.create_documents([text])
(3)专用分块
Markdown和LaTeX是您可能会遇到的两个结构化和格式化内容的示例。在这些情况下,您可以使用专用的分块方法,在分块过程中保留内容的原始结构。
from langchain.text_splitter import MarkdownTextSplitter
markdown_text = "..."
markdown_splitter = MarkdownTextSplitter(chunk_size=100, chunk_overlap=0)
docs = markdown_splitter.create_documents([markdown_text])
from langchain.text_splitter import LatexTextSplitter
latex_text = "..."
latex_splitter = LatexTextSplitter(chunk_size=100, chunk_overlap=0)
docs = latex_splitter.create_documents([latex_text])
如果常见的分块方法(如固定分块)不能轻松适用于您的用例,以下是一些提示,帮助您找出最优的块大小。
预处理你的数据 - 在确定应用程序的最佳块大小之前,你首先需要预处理你的数据以确保质量。例如,如果你的数据是从网页获取的,你可能需要删除HTML标签或特定只增加噪音的元素。
选择一定范围的块大小 - 数据预处理完成后,下一步是选择一定范围的潜在块大小进行测试。如前所述,这个选择应考虑到内容的性质(例如,短消息或长篇文档)、你将使用的嵌入模型及其功能(例如,token限制)。目标是在保留上下文和维持准确性之间找到平衡。可以通过探索不同的块大小开始,包括较小的块(例如,128或256 token)用于捕获更细粒度的语义信息,和较大的块(例如,512或1024 token)用于保留更多的上下文。
评估每个块大小的性能 - 为了测试各种块大小,你可以使用多个索引或单个索引与多个命名空间。有了代表性的数据集,创建你想要测试的块大小的嵌入,然后保存在你的索引中。然后你可以运行一系列可以评估质量的查询,并比较各种块大小的性能。这很可能是一个迭代的过程,在这个过程中你要测试各种块大小与不同的查询,直到你能确定最适合你的内容和预期查询的最佳性能的块大小。
在大多数情况下,对您的内容进行分块是相当简单的 - 但当您开始走出常规思维时,它可能会带来一些挑战。分块没有一种适合所有情况的解决方案,所以对一个用例有效的方法可能对另一个用例无效。希望这篇文章可以帮助您更好地理解如何为您的应用程序进行分块的方法。