在RAG中,文本切割(也称为分块)是一个关键步骤,它的主要作用是将文档划分成较小的块,以便更好地处理和检索。不同的分块策略会影响RAG系统的性能。以下是一些常见的文本分割方法及其优缺点。
1) 固定大小分块(Fixed Size Chunking) :这是最常见、最直接的分块方法。我们只需决定分块中的token数量,以及它们之间是否应该有任何重叠。优点:计算便宜且使用简单,不需要使用任何NLP库。缺点:刻板,不考虑文本的结构。
示例: 假设我们有一篇长篇文章,希望将其分块,每个块包含100个token。我们可以简单地按照每100个token进行切割,形成固定大小的块。
2) 递归分块(Recursive Chunking) :使用一组分隔符,以分层和迭代的方式将输入文本划分为更小的块。如果初始分割文本没有产生所需大小或结构的块,则该方法会使用不同的分隔符或标准递归地调用结果块,直至达到所需的块大小或结构。优点:生成的块具有相似的大小,可以利用固定大小块和重叠的优点。缺点:块的大小不会完全相同。
示例: 假设有一篇博客文章,其中包含许多小节和子标题。我们可以使用不同的分隔符(如“\n”)来划分文本。如果某个块不够大,我们可以递归地将其进一步划分,直到达到所需大小。
3) 基于文档逻辑的分块(Document Specific Chunking) :不使用一定数量的字符或递归过程,而是基于文档的逻辑部分(如段落或小节)来生成对齐的块。这样可以保持内容的组织,从而维持文本的连贯性,适用于特殊格式(如Markdown、HTML等)。
示例: 假设我们有一篇Markdown格式的文档,其中包含标题、段落和列表。我们可以根据Markdown标记(如“#”表示标题)来划分文本块。这样可以保持文档的结构和连贯性。
4) 语义分块(Semantic Chunking) :考虑文本内容之间的关系,将文本划分为有意义的、语义完整的块。优点:确保信息在检索过程中的完整性,获得更准确、更符合上下文的结果。缺点:速度较慢。
示例: 提取文档中每个句子的嵌入,比较所有句子之间的相似度,然后将嵌入最相似的句子分组在一起。
接下来我们将基于LangChain实现上述不同的策略。
固定大小分块是最简单的方法。这种方法根据字符数来确定分块的长度。
以下是一个简单的代码示例。输入内容如下:
一般来说, 固定大小分块 通常用于以下情况:
1) 简单粗暴的文本处理 :当需要将文本简单地按字符数量固定切割时,使用按字符切割的方法最为直接和简单。
2) 静态字符数据块 :这种方法适用于需要生成简单的静态字符数据块的场景。例如,在某些应用中,如数据传输或存储,可能需要将中文文本固定为特定长度的块,以便于后续处理。假设在一个数据传输协议中,规定每个数据包的大小为10个字符,如果传输的中文数据是“我爱中国,我爱北京”,那么它将被分块为:我爱中国,我爱北京。如果数据不足10个字符,可能需要使用特定的填充字符来补足,比如空格或其他约定的字符。
注意,通常中文字符在编码时占用的字节数可能与英文不同(如在UTF-8编码中,中文字符通常占用3个字节,而英文字符占用1个字节),在实际应用中需要根据具体编码和协议要求来确定分块的大小。
递归分块通过一个字符列表进行参数化。它尝试按照这些字符的顺序进行分割,直到分块足够小。默认的字符列表是["\n\n","\n"," ",""]。这样做的目的是尽可能保持段落(然后是句子,再然后是单词)在一起,因为这些通常是语义上最紧密相关的文本部分。
递归分块通常在需要考虑文本物理结构时使用。这种方法不是简单地按固定的字符数量切割文本,而是基于分隔符列表逐步递归地切割文本,从而更好地保留文本的结构和语义信息。例如,在处理包含换行符、段落等复杂结构的文档时,递归分块能够有效地将文本划分为更小、更有意义的块。
此外,递归分块还可以解决其他问题,例如,完全不考虑文档结构的简单固定大小分块方法可能导致信息丢失或上下文不完整,而递归分块方法可以确保每个分割块都具有一定的意义,并且能够更好地适应不同类型的文档。
综上,递归分块在需要保留文本结构和语义信息的情况下使用较多,特别是在处理复杂结构文档时效果显著。
许多聊天或问答应用程序在嵌入和向量存储之前,会先对输入文档进行分块处理,比如HTML和Markdown格式的文档。
Markdown文档可以按标题进行组织,在特定标题组中创建块是一种直观的方式。为了应对这一挑战,我们可以使用LangChain中的MarkdownHeaderTextSplitter。这种方法将按照指定的一组标题拆分Markdown文档。
HTMLHeader TextSplitter在概念上类似于MarkdownHeaderTextSplitter,是一个“结构感知”分块器,它在元素级别分割文本,并将每个“相关”标题的元数据添加到任何给定的块中。它可以逐个元素返回块,也可以将具有相同元数据的元素组合在一起。它的目标是:保持相关文本在语义上(或多或少)分组,以及保留文档结构中编码的上下文信息。它可以与其他文本拆分器一起使用,作为分块管道的一部分。
根据语义相似性拆分文本,首先将文本划分为句子,然后在嵌入空间中比较句子之间的相似度,最后将相似的句子合并在一起。
这里我们用的嵌入模型是OpenAI的text-embedding-3-small,我们先简单回顾一下。
综上所述,我们主要探讨了文本分割的四种常见策略。一般来说,我们需要根据不同的场景和任务选择不同的策略。语义分块是一种优雅的方式,同时也是优化RAG系统的关键。