682 字按 300 字硬切成 3 段,其中 67% 从句子中间断开; 按句子切成 24 段,最长 45 字、最短 13 字,差 3.5 倍;300 字带 80 字重叠多花 27% 存储。 清洗把 741 字压到 710 字。
上一节讲了检索。这一节往前退一步:喂进去的段落是怎么切出来的。切分质量是检索质量的地基。
一、为什么必须切
两个原因。一是上下文装不下:一份 5 万字的文档不可能整个塞进提示词。 二是精度:一整篇文档的向量是全文语义的平均,问其中一个小细节时,它和任何一段都「有点像」, 跟哪段都不够像。 切成小段之后,每段只讲一件事,向量就有明确方向了。检索才能精确命中。 但切太小也有问题:一段只有一句话,缺上下文,模型拿到也不知道这句在讲什么。 所以段长要在一个区间里,不是越小越好。 经验值:中文 200 到 500 字一段。 技术文档可以偏小(概念密集),叙述性文档可以偏大(需要连贯上下文)。
二、三种切法的取舍
固定长度硬切:每 N 个字切一刀。优点是段长均匀、实现简单;缺点是会把句子从中间切断。 按句子/段落切:在句号、换行处切。优点是语义完整; 缺点是段长极不均匀,长的可能上千字,短的可能只有一句。 带重叠切:固定长度切,但相邻两段重叠一部分。优点是切口处的句子至少在一段里是完整的; 缺点是存储变大、结果会重复。 实践里最常用的是第三种:先按段落切,段落太长再按固定长度切并带重叠。 这样既保住了语义边界,又控制了长度。
三、实测:67% 被切断,多花 27% 存储
Demo 用 12 段、682 字的资料试了三种切法。 按 300 字硬切:切成 3 段,平均 227 字, 其中 67% 的段落结尾不是句号——也就是从句子中间断开的。 按句子切:切成 24 段,平均只有 28 字。最长 45 字、最短 13 字,差 3.5 倍。 段太短会丢上下文。 300 字带 80 字重叠:切成 4 段,多花 27% 的存储,被切断比例降到 50%。 代价是同一句话可能被两段都包含,检索结果要去重。 注意重叠切法并没有把切断比例降到 0——它只是保证切口附近的句子至少在一段里完整。 衡量它的指标不该是「切断比例」,而是「关键信息丢失率」,后者要人工抽查。
四、清洗要清什么、不能清什么
Demo 里给正文加了些空白和缩进,清洗后从 741 字压到 710 字,少了 31 字、4%。 消掉的主要是连续空白和行首缩进。这些字符不影响人读,但会实实在在占 token——空白是最贵的垃圾。 该清的:连续空格、行首缩进、连续空行、页眉页脚、PDF 抽出来的断行连字符、重复的版权声明。 不该清的:Markdown 的空行(段落分隔)、表格里的空格(对齐)、代码块里的缩进(语义)。 全删了会把结构毁掉,模型反而读不懂。 所以清洗规则要按格式分支:正文用一种规则、表格用一种、代码块完全跳过。 一个全局正则扫全部,一定会伤到结构。
五、动手 Demo:分段实测:硬切七成段落断在句子中间
用 12 段共 682 字的资料,分别按「固定 300 字」「按句号切」「300 字带 80 字重叠」三种方式切分; 统计段数、平均长度、最长段、以及「结尾不是句号」的段落占比;最后对正文做一次清洗,比较前后字数。
def fixed(t, size):
return [t[i:i + size] for i in range(0, len(t), size)]
def overlap(t, size, ov):
return [t[i:i + size] for i in range(0, len(t), size - ov)]
def cut_ratio(ch):
return sum(1 for c in ch if c[-1] not in u"。!?") / float(len(ch))
实跑输出:
--------------------------------------------------------------------
文本分段:按 300 字硬切,67% 的段落被从句子中间切断
--------------------------------------------------------------------
原文 682 字,12 段。
切法 段数 平均长度 最长段 被切断比例
---------------------------------------------------------
按 300 字硬切 3 227 300 67%
按句子切 24 28 45 0%
300 字带 80 字重叠 4 216 300 50%
按句子切最干净,切断比例 0%,但段长很不均匀:最长 45 字、最短 13 字,差 3.5 倍。检索时一段太长会稀释相关性,太短又缺上下文。
硬切能保证每段等长,代价是 67% 的段落从句子中间断开。切断本身不致命,致命的是**关键信息正好落在切口两侧**,两段都不完整。
重叠切法是折中:多花 27% 的存储,换来「切口处的句子至少在一段里是完整的」。代价是同一句话被检索到两次,需要去重。
再看作清洗前后的差别:
清洗前字数 741
清洗后字数 710
压缩掉 31 字,4%
清洗消掉的主要是连续空白和行首缩进。这些字符不影响人读,但会实实在在占 token——**空白是最贵的垃圾**。
清洗时要注意别过头:Markdown 里的空行是段落分隔,表格里的空格是对齐,全删了会把结构毁掉。所以清洗规则要按格式分支处理,不是全局正则。
硬切只切出 3 段、平均 227 字,67% 断在句子中间。 样本小,所以段数少——换成长文档就是几十段,比例差不多。 按句子切成 24 段、平均 28 字,最长最短差 3.5 倍。 这个不均匀程度在检索时是麻烦:短段信息不足,长段又会稀释相关性。 重叠切法多花 27% 存储,切断比例从 67% 降到 50%。注意它没降到 0——最后一段可能仍然断在句中。 清洗那一步:741 字压到 710 字,只少 4%。 这个比例说明清洗省不下多少钱,它的价值主要在去掉页眉页脚这类噪声,而不是省 token。 这块没有真实 PDF 可解析,所以我用的是自己拼的 12 段文本。 切分的算法逻辑是真的,但真实 PDF 抽出来的文本还会有断行、乱码、 公式变成一串符号——那部分清洗工作量比这里大得多。
30 秒实验
- 把硬切的长度从 300 改成 120,看段数和切断比例怎么变。
- 把重叠从 80 改成 150,看存储涨多少、切断比例降多少。
- 想一想:为什么清洗规则要按格式分支,不能一个正则扫全文?
常见误区
- 「切得越小检索越准」——段太短会丢上下文,模型拿到也不知道在讲什么。
- 「重叠能完全避免切断」——它只保证切口附近有完整句子,不保证整段不断。
- 「清洗就是删空白」——Markdown 空行、表格空格、代码缩进都有语义,删了会毁结构。
自测三问(点开对答案)
1. 为什么必须把长文档切成小段? —— 上下文装不下,而且整篇的向量是全文语义的平均,检索精度很低。 2. 三种切法各自的缺点是什么? —— 硬切会断句,按句切段长不均,重叠切多花存储且结果重复。 3. 哪些空白不能清? —— Markdown 空行、表格对齐空格、代码块缩进,它们都带语义。资料准备好了。最后一步:把数据集挂上去,发布出去。
- ☐ (本节完,下一节 10.3.6 第六步:添加数据集并发布ChatGPT应用)