Unstructured专家分享RAG应用中文档分块(Chunking)的最佳实践
近日,Maria Khalusova在Unstructured官方博客分享了有关分块的最佳实践。
Unstructured成立于2022年9月,致力于解决自然语言处理(NLP)和大型语言模型(LLM)应用中的数据预处理问题。公司总部位于美国,专注于将非结构化数据转化为LLM可以处理的格式,当下流行的pdf解析库就来自于它们,它们在数据预处理方面拥有非常前沿的技术和经验。
为什么需要分块?
出于多种原因,在为 RAG 准备数据时,分块是必不可少的预处理步骤。
1)上下文窗口限制
首先从基础开始。检索到的块将直接作为上下文输入到提示中,以便LLM生成响应。这意味着所有检索到的块的总长度至少不能超过LLM的上下文窗口。尽管当下许多LLM有相当大的上下文窗口,但实际上并不希望填满上下文窗口,因为这些LLM会面临“大海捞针”的问题(延伸阅读:超长上下文窗口大模型的“照妖镜”——大海捞针实验,大模型“打假”必知必会)。另外,开发者可能还想以其他方式利用这个大上下文窗口,比如提供详细的指令、角色描述或一些少样本示例(few-shot)。
此外,如果打算使用相似性搜索并嵌入(embedding)文档,必须考虑到嵌入模型也有一个有限的上下文窗口。这些模型不能嵌入超过其上下文窗口最大长度的文本。这个限制因具体模型而异,但可以在模型的描述中找到这些信息,例如在Hugging Face Hub上的模型卡片上。一旦知道将使用哪种模型来生成嵌入,就能确定文本块的最大值(以token为单位,而不是字符或单词)。嵌入模型通常在上下文窗口大小上的最大值约为8K token或更少,这相当于英语中的大约6200个单词。为了直观理解有多大,比如,整个《指环王》系列,包括《霍比特人》,大约有576,459个单词,所以如果想利用这个语料库进行RAG与相似性搜索,需要将其分成至少93个块。
2)块大小对检索精度的影响
虽然嵌入模型对其可以嵌入的标记数量规定了硬性的最大限制,但这并不意味着分块必须达到这个长度。这只是意味着它们不能超过这个长度。事实上,在许多情况下,使用每个分块的最大长度(如 6200 字(8K 标记))可能会过长。这里有几个令人信服的理由来选择较小的语块。
回想一下当我们嵌入一段文本以获得嵌入向量时会发生什么。大多数嵌入模型都是编码器类型的转换器模型,输入文本的最大长度为 768。不管你给模型的是 10 个字的句子还是 1000 个字的段落,得到的嵌入向量的维度都是一样的,都是 768。其工作原理是,模型首先将文本转换为token,在预训练过程中为每个token学习了一个向量表征。然后,它将应用一个池化操作,将单个token表征平均为一个单向量表征。
常见的池化类型包括:
- CLS池化:特殊CLS token的向量表征成为整个序列的表征
- 平均池化:token向量表征的平均值作为整个序列的表征返回
- 最大池化:具有最大值的token向量表征成为整个序列的表征
其目标是将细粒度的token级表征压缩成单一的固定长度表征,其中包含整个输入序列的含义。这种压缩本身就是有损的。对于较大的块,表征可能会变得过于粗糙,可能会掩盖重要的细节。为确保精确检索,文本块必须拥有有意义且细致入微的表征。
现在,请考虑另一个潜在问题。一个大块可能包含多个主题,其中一些可能与用户查询相关,而另一些则不相关。在这种情况下,单个向量中每个主题的表示可能会变得模糊,这同样会影响检索精度。
另一方面,较小的片段可以保持重点突出的上下文,从而可以更精确地匹配和检索相关信息。通过将文档分解成有意义的片段,检索器可以更准确地找到特定段落或事实,从而最终提高 RAG 性能。那么,在保持上下文完整性的前提下,文件块可以有多小?这取决于文档的性质,可能需要进行一些试验。通常情况下,250 个 token 左右的块大小(相当于约 1000 个字符)是一个合理的实验起点。
分块的常见方法
1)字符级分块
将大文档分割成小块的最基本方法是将文本分成 N 个字符大小的块。通常在这种情况下,还会指定一定数量的字符,这些字符应在连续的文本块之间重叠。这在一定程度上降低了句子或观点在相邻两块之间的边界被突然切断的可能性。不过,可以想象,即使有重叠,每个块的固定字符数加上固定的重叠窗口,也不可避免地会导致信息流中断、不同主题混合,甚至句子在一个词的中间被分割。字符分割法完全不考虑文档结构。
2)句子级分块或递归分块
字符分割是一种简单化的方法,完全没有考虑到文档的结构。这种方法完全依赖于固定的字符数,经常会导致句子在中途甚至在词的中间被拆分,效果并不好。
解决这一问题的方法之一是使用递归分块法,这种方法有助于保留单个句子。使用这种方法,您可以指定一个有序的分隔符列表来指导分割过程。例如,以下是一些常用的分隔符:
- "\n\n" - 双换行符,通常表示段落断开
- "\n" - 单换行符
- "." - 句号
- " " - 空格
如果按照指定的顺序使用上述分隔符,过程将是这样的。首先,递归分块会在每次出现双新行("\n\n")时分解文档。然后,如果这些分段仍然超过了所需的分块大小,它将在新行处进一步分解它们("\n"),以此类推。
虽然这种方法大大降低了中途断句的可能性,但仍然无法捕捉到复杂的文档结构。文档通常包含多种元素,如段落、章节页眉、页脚、列表、表格等,所有这些元素都有助于文档的整体组织。然而,上述递归分块法主要考虑的是段落和句子,而忽略了其他结构上的细微差别。
此外,文档以多种本地格式存储,因此必须为每种不同的文档类型设计不同的分隔符。上面的列表可能对纯文本很有效,但对于标记符,你需要一个更细致、更有针对性的分隔符列表;如果是 HTML 或 XML 文档,还需要另一个列表,等等。将这种方法扩展到处理 PDF 和 PowerPoint 演示文稿等基于图像的文档,会带来更多复杂性。如果使用场景涉及各种非结构化文档,那么统一应用递归分块很快就会成为一项繁重的任务。
使用Unstructured智能分块
这就意味着,你不必再想办法区分文档的各个部分。Unstructured 已经完成了这些繁重的工作,直接展示不同的文档元素,这些元素封装了文档中的段落、表格、图片、代码片段和其他任何有意义的文本单元。在完成分区步骤后,文档已经被划分为更小的片段。这是否意味着文档已经分块?不完全是,但已经成功了一半!
分区后得到的某些文档元素可能仍会超出嵌入模型的上下文窗口或所需的块大小。这些需要进一步分割。相反,有些文档元素可能太小,无法包含足够的上下文。例如,一个列表被分割成单个的 ListItem元素,但你可以选择将这些元素合并成一个单一的块,只要它们仍然符合偏好设置的块大小。
从系统划分为离散元素的文档开始,Unstructured 提供的智能分块策略可以做到这一点:
- 确保信息流不中断,防止简单的字符分块造成的中途分词。
- 控制块的最大和最小尺寸。
- 保证不同的主题或想法,如不同主题的独立章节,不会被合并。
智能分块比递归分块更进一步,它实际上考虑到了文档的语义结构和内容。
智能分块提供了四种策略,它们在保证分块内容纯净度方面各有不同:
- 基本分块策略:这种方法可以在遵守最大分块大小限制的前提下,将连续元素组合起来,最大限度地填充每个分块。如果单个孤立的元素超过了最大硬限制,就会被分成两个或更多块。
- 按标题分块策略:该策略利用分区过程中识别的文档元素类型来理解文档结构,并保留章节边界。这就意味着,单个数据块永远不会包含出现在两个不同章节中的文本,从而确保主题保持自足,提高检索精度。
- 按页面分块策略(仅支持API调用):该策略专为每一页都能传递独特信息的文档而设计,可确保来自不同页面的内容绝不会混杂在同一个分块中。当检测到一个新页面时,即使下一个元素可以放在之前的内容块中,也会完成现有的内容块并开始一个新的内容块。
- 按相似性分块策略(仅支持API调用):当文档结构无法提供明确的主题边界时,可以使用 "通过相似性 "策略。该策略使用 "sentence-transformers/multi-qa-mpnet-base-dot-v1 "嵌入模型来识别在主题上相似的顺序元素,并将它们组合成块。
Unstructured 智能分块策略的另一个优势是可普遍适用于各种文档类型。不需要像递归分块那样,为每个文档硬编码和维护分隔符列表。可以轻松尝试分块大小和分块策略,为任何给定的使用场景找出最佳方案。
结论
分块是任何 RAG 系统中必不可少的预处理步骤之一。设置时的选择会影响检索质量,进而影响系统的整体性能。以下是设计分块步骤时需要注意的一些事项:
- 尝试不同的块大小:虽然大块可能包含更多上下文,但也会导致表述粗糙,对检索精度产生负面影响。最佳块大小取决于文档的性质,但要在不丢失重要上下文的情况下优化较小的块。
- 利用巧妙的分块策略:选择分块策略,在有语义意义的边界上分隔文本,避免信息流中断或内容混杂。
- 评估分块选择对 RAG 整体性能的影响:为您的特定用例设置评估集,并跟踪分块大小和分块策略实验对整体性能的影响。无论文档类型如何,您只需调整一两个参数,非结构化技术就能简化分块实验。
原文:https://unstructured.io/blog/chunking-for-rag-best-practices
本文转载自 AI工程化,作者:ully