索引 · 2023
切块策略
不再只按固定字数切开,而是让一块内部尽量是同一个意思。
要解决的问题
固定长度会把一句话切成两块,或把两件无关的事放进同一块。检索命中的是块,不是文档本身。
相对前一代的变化
朴素 RAG 把切块当成预处理细节。语义切块按相邻句子的向量差异找边界;后期切块则先用长上下文编码整篇,再把词向量切回各个块。
做法
一种做法是句子滑动窗口,向量突然变远时就断开。也可以按标题和段落递归切开,先保住文档结构,再限制每块长度。Günther 等人的后期切块不先切再嵌入,而是整篇编码后再按块取向量,使每块仍带着全文语境。
局限
块仍是近似单位。过长的块召不精,过短的块缺上下文。语义边界也会因嵌入模型而变。
在链路中的位置
发生在入库时,决定后面所有检索能看到的最小单位。
详解
「详解」根据 Datawhale《All-in-RAG》 的 文本分块 改写,有删节和重组,未包含原教程的代码、图片和练习。原教程与本节详解均按 CC BY-NC-SA 4.0 许可提供:使用时需署名并标明修改,不得用于商业目的,再次改编须以相同许可共享。
块是检索和阅读的基本单位
切块把长文档分成后续可以编码、召回和放进提示词的片段。嵌入模型有输入上限,例如一些中文嵌入模型只接收 512 个 token,超长内容会被截断,向量也就不再代表全文。生成模型的窗口更大,但问题、提示词和所有命中的块必须一起放进去。
块并不是越大越好
多数嵌入模型先给每个 token 一个向量,再通过 CLS 位或平均池化压成一个向量。块越长,这个向量要概括的语义点越多,细节就被稀释,检索变得笼统。
生成阶段也受影响。很长且混杂的上下文里,模型更容易利用开头和结尾,忽略中间证据。一个块同时装着技能、装备和背景故事时,查询“怎么出装”得到的向量会被无关主题拉偏;把三个主题切开后,出装那一块才能获得明确的高分。
四种常用切法
固定大小切分实现快,但容易切断句子。实际工具通常先按段落合并,接近目标长度才断开,并用一小段重叠保住上下文。单个超长段落可能被原样保留,所以它并不真是严格等长。
递归切分按分隔符层级处理:先空行,再换行、句号、逗号、空格,最后才退到字符。没超过长度的片段先合并;超过长度的片段用下一级分隔符继续切。中文没有自然的词间空格,分隔符要显式加入句读。代码文档则适合按类、函数和空行分层。
语义切分比较相邻句子的向量。差异突然变大时认为主题变了,就在那里断开。它更贴合意思,也更依赖嵌入模型,并且速度慢于字符切分。
结构切分直接利用 Markdown 标题、HTML 标签或文档元素。标题层级能保住章节关系,但作者的标题必须可靠。标题缺失或一层里面塞了多个主题时,仍要再用长度规则收一次。
切完还要决定阅读哪一层
Unstructured 可以按标题、表格、列表等元素聚合,而不是只按字符数。LlamaIndex 把切分结果视为节点,之后还能做句子窗口:用句子检索,命中后把周围句子送去阅读。父子索引也是同一想法,小块负责被找到,大块负责被阅读。
选择时先看嵌入模型的输入上限,再拿几十个真实问题检查召回。重叠通常只需要盖住被切断的一句,不必大到让相邻块几乎重复。