问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

RAG总结,分块Chuck的策略和实现

创作时间:
作者:
@小白创作中心

RAG总结,分块Chuck的策略和实现

引用
1
来源
1.
https://www.53ai.com/news/qianyanjishu/1849.html

检索增强生成(Retrieval-Augmented Generation, RAG)可能是,现阶段大型语言模型在实际应用中落地最有效的方式。RAG技术通过结合检索和生成两种能力,为大模型LLM提供了外部知识源的支持,使其能够更准确、高效地生成符合上下文的答案,同时保持了模型的可扩展性、可控性和可解释性。

RAG可以总结成范式,分成四个主要阶段:预检索、检索、后检索和生成。本文我们探讨预检索阶段文档分块(Chunk)的策略。

RAG分块Chuck的策略

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重叠大小和分隔符。

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)保留文档结构中编码的上下文丰富的信息。

from langchain_text_splitters import HTMLHeaderTextSplitter  
html_string = """  
  <!DOCTYPE html>  
  <html>  
  <body>  
      <div>  
          <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>  
              <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  

输出

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这样的爬虫工具,效果会比较好。

基于语义分块

利用文本自身的内容,作为分块的依据,也有把这种分块的策略称为“内容感知”分块。

这是一种传统的文本分块方法,依赖于显式的分隔符(如标点符号、空格字符或换行符)来划分文本为多个部分。例如,可以通过识别句子的结束符号(如句号、问号等)来自动将文本分割成句子,这种方法简单直接,但可能无法捕捉到更细微的语义信息。

使用各种工具包,LangChain提供了多种的Splitter,

  • NLTKTextSplitter:NLTK(The Natural Language Toolkit)是一套用Python编程语言编写的用于英语符号和统计自然语言处理(NLP)的库和程序。
  • SpacyTextSplitter:spaCy是一个用于高级自然语言处理的开源软件库,使用Python和Cython编程语言编写。
  • SentenceTransformersTokenTextSplitter:用于句子转换器模型的文本拆分器。默认行为是将文本拆分为适合您想要使用的句子转换模型的标记窗口的块。

Embedding-based方法,在这种方法中,首先需要将用户查询转换为嵌入向量,以保持语义信息。这通常通过使用预训练的词嵌入模型(如Word2Vec或BERT)来完成。这些模型能够捕捉到文本中的深层语义关系。接下来,将这些嵌入向量与数据库中的内容嵌入向量进行比较,计算它们之间的相似度,从而检索出最相关的内容。分块处理可以通过将文本分割成适当大小的小块来实现,这些小块既不丢失原有的含义,也便于处理和存储。

LangChain的SemanticChunker的工作原理是决定何时“分解”句子。这是通过寻找任何两个句子之间嵌入的差异来完成的。当这个差异超过某个阈值时,它们就会被分割。有几种方法可以确定阈值是多少。默认的分割方式是基于百分比。在这种方法中,计算句子之间的所有差异,然后拆分任何大于X百分位数的差异。还有四分位间距和标准差,这都是些统计指标。

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,参考示例

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/

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号