微信扫码
与创始人交个朋友
我要投稿
检索增强生成(Retrieval-Augmented Generation, RAG)可能是,现阶段大型语言模型在实际应用中落地最有效的方式。RAG技术通过结合检索和生成两种能力,为大模型LLM提供了外部知识源的支持,使其能够更准确、高效地生成符合上下文的答案,同时保持了模型的可扩展性、可控性和可解释性。
RAG可以总结成范式,分成四个主要阶段:预检索、检索、后检索和生成。本文我们探讨预检索阶段文档分块(Chunk)的策略。
RAG分块Chuck的策略大致可以分为以下几种:
1.固定大小分块:这是最常见的分块方法,通过设定块的大小和是否有重叠来决定分块。这种方法简单直接,不需要使用任何NLP库,因此计算成本低且易于使用。
2.基于结构的分块:常见的HTML、MARKDOWN格式,或者其他可以有明确结构格式的文档。这种可以借助“结构感知”对文档分块,充分利用文档文本意外的信息。
3.基于语义的分块:这种策略旨在确保每个分块包含尽可能多的语义独立信息。可以采用不同的方法,如标点符号、自然段落、或者NLTK、Spicy等工具包来实现语义分块,或者Embedding-based方法。
4.递归分块:递归分块使用一组分隔符,以分层和迭代的方式将输入文本划分为更小的块。如果最初分割文本没有产生所需大小或结构的块,则该方法会继续递归地分割直到满足条件。
这些策略各有优势和适用场景,选择合适的分块策略取决于具体的应用需求和数据特性。很遗憾,到目前为止还没有什么是最优的策略,但这也是很难有一个产品一统天下的原因。同时策略可以组合使用,并不是一类文档只能用一种策略。
固定大小分块
固定大小分块的具体实现方法主要涉及将文本划分为包含固定数量token的块。这种方法简单易用,且不需要消耗大量计算资源。在实施过程中,可以通过设定块中的字数并选择是否在不同块之间重复内容来进行。通常,为了保持语义上下文的一致性和连贯性,会在不同的块之间设置一定程度的重叠。
此外,固定大小分块还可以通过使用特定的工具或库来辅助实现,例如在LangChain中,可以使用CharacterTextSplitter工具来切分文档,将其分成小块,.from_tiktoken_encoder()方法将编码作为参数(例如cl100k_base)或模型名称(例如gpt-4)。所有其他参数,如chunk_size分块大小、chunk_overlap重叠大小和分隔符。
python
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
encoding="cl100k_base", chunk_size=100, chunk_overlap=0 )
texts = text_splitter.split_text(state_of_the_union)
还需要考虑使用的是哪种Embedding模型,它在多大的块大小上表现最佳?例如,sentence-transformer模型在单个句子上工作得很好,但像text-embedding-ada-002这样的模型在包含256或512个tokens的块上表现得更好。
基于结构分块
RAG模型需要对输入的Markdown或HTML文本进行解析和转换,以便于后续的处理。LangChain提供的MarkdownHeaderTextSplitter,HTMLHeaderTextSpliter是一个“结构感知”的分块器,在元素级别拆分文本,并为与任何给定块“相关”的每个标头添加元数据。它可以逐元素返回块,或者将元素与相同的元数据组合,目的是(a)保持相关文本在语义上分组,以及(b)保留文档结构中编码的上下文丰富的信息。
#python
from langchain_text_splitters import HTMLHeaderTextSplitter
html_string = """
<html>
<body>
<div>
<h1>Foo</h1>
<p>Some intro text about Foo.</p>
<div>
<h2>Bar main section</h2>
<p>Some intro text about Bar.</p>
<h3>Bar subsection 1</h3>
<p>Some text about the first subtopic of Bar.</p>
<h3>Bar subsection 2</h3>
<p>Some text about the second subtopic of Bar.</p>
</div>
<div>
<h2>Baz</h2>
<p>Some text about Baz</p>
</div>
<br>
<p>Some concluding text about Foo</p>
</div>
</body>
</html>
"""
headers_to_split_on = [
("h1", "Header 1"),
("h2", "Header 2"),
("h3", "Header 3"),
]
html_splitter = HTMLHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
html_header_splits = html_splitter.split_text(html_string)
html_header_splits
输出
#shell
page_content='Foo'
page_content='Some intro text about Foo. \nBar main section Bar subsection 1 Bar subsection 2' metadata={'Header 1': 'Foo'}
page_content='Some intro text about Bar.' metadata={'Header 1': 'Foo', 'Header 2': 'Bar main section'}
page_content='Some text about the first subtopic of Bar.' metadata={'Header 1': 'Foo', 'Header 2': 'Bar main section', 'Header 3': 'Bar subsection 1'}
page_content='Some text about the second subtopic of Bar.' metadata={'Header 1': 'Foo', 'Header 2': 'Bar main section', 'Header 3': 'Bar subsection 2'}
page_content='Baz' metadata={'Header 1': 'Foo'}
page_content='Some text about Baz' metadata={'Header 1': 'Foo', 'Header 2': 'Baz'}
page_content='Some concluding text about Foo' metadata={'Header 1': 'Foo'}
可以与其他文本拆分器一起使用,作为分块管道的一部分。在内部,当某一段落内容大于分块限制的大小是,使用RecursiveCharacterTextSplitter。
从一个HTML文档到另一个,可能会有相当多的结构变化,虽然HTMLHeaderTextSplitter会尝试将所有“相关”的头附加到任何给定的块,但有时可能会搞错。毕竟不是所有的文档都是格式严谨的,如果配合像apify-actor这样的爬虫工具,效果会比较好。
基于语义分块
利用文本自身的内容,作为分块的依据,也有把这种分块的策略称为“内容感知”分块。
SentenceTransformersTokenTextSplitter:用于句子转换器模型的文本拆分器。默认行为是将文本拆分为适合您想要使用的句子转换模型的标记窗口的块。
#python
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai.embeddings import OpenAIEmbeddings
text_splitter = SemanticChunker(OpenAIEmbeddings())
docs = text_splitter.create_documents([state_of_the_union], breakpoint_threshold_type="percentile")
print(docs[0].page_content)
递归分块
递归分块在处理长文本时的优势主要包括以下几点:
灵活性和适应性:递归分块通过使用一组分隔符以层级和迭代的方式将输入文本划分为更小的块,这种方法可以根据文本的内容和结构动态调整分块的大小和形状,从而更好地适应不同类型的文本数据。
信息流动性:由于采用了递归机制,递归分块能够使信息在不同的片段之间流动,这有助于模型捕捉到跨越多个片段的上下文关系,从而提高模型对文本的理解能力。
然而,递归分块也存在一些局限性:
计算复杂度:递归分块可能会导致计算过程变得更加复杂,因为它需要不断地评估和调整分隔符,以达到所需的分块效果。这可能会增加处理时间,尤其是在处理大规模文本数据时。
结果的不确定性:由于递归分块依赖于模型的决策来决定下一个片段的方向,这可能导致生成的块大小和结构具有较大的变异性。这种不确定性可能会影响最终模型输出的稳定性和可靠性。
LangChain的文本递归拆分器RecursiveCharacterTextSplitter,参考示例
#python
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
# Set a really small chunk size, just to show.
chunk_size=100,
chunk_overlap=20,
length_function=len,
is_separator_regex=False,
)
texts = text_splitter.create_documents([state_of_the_union])
print(texts[0])
print(texts[1])
参考
https://www.pinecone.io/learn/chunking-strategies/
https://luxiangdong.com/2023/09/20/chunk/
53AI,企业落地应用大模型首选服务商
产品:大模型应用平台+智能体定制开发+落地咨询服务
承诺:先做场景POC验证,看到效果再签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2024-03-30
2024-04-26
2024-05-10
2024-04-12
2024-05-28
2024-05-14
2024-04-25
2024-07-18
2024-04-26
2024-05-06
2024-12-22
2024-12-21
2024-12-21
2024-12-21
2024-12-21
2024-12-20
2024-12-20
2024-12-19