文本分块策略:固定大小分块、语义分块、递归分块、分块大小对检索的影响
文本分块,说白了就是决定「一段话该切多碎」。
我刚开始做 RAG 时,觉得这步没什么技术含量。随便按 512 个字符切一下,跑起来再说。结果检索出来的内容,要么缺胳膊少腿,要么把两段无关的话硬凑在一起。嗯,后来我才明白——分块策略直接决定了检索质量的上限。
核心观点:没有完美的分块策略,只有适合你场景的分块策略。你需要理解每种方法的优缺点,然后根据你的文档类型和查询模式来做选择。
固定大小分块
这是最朴素的方法。按字符数或 数硬切,比如每 512 个字符一块,块与块之间可以重叠 128 个字符。
def fixed_size_chunk(text, chunk_size=512, overlap=128):
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunks.append(text[start:end])
start = end - overlap
return chunks
优点很明显:实现简单,计算快,适合批量处理。缺点也很致命:它完全无视语义边界。你想想看,一个句子可能被拦腰切断,一个概念可能被拆成两半。
我在项目中遇到过最典型的例子:切分一份技术文档时,把「请勿在潮湿环境下使用」切成了「请勿在潮湿环境」和「下使用」。检索时用户问「潮湿环境」,第二块根本匹配不到。
适用场景:日志文件、固定格式的报表、代码片段。这些内容本身就没有强语义结构,硬切反而稳定。
语义分块
语义分块试图解决固定分块的痛点。它利用 embedding 模型计算句子之间的语义相似度,当相似度低于某个阈值时,就认为这是一个自然的段落边界。
def semantic_chunk(sentences, model, threshold=0.7):
chunks = []
current_chunk = [sentences[0]]
for i in range(1, len(sentences)):
sim = cosine_similarity(
model.encode(sentences[i-1]),
model.encode(sentences[i])
)
if sim < threshold:
chunks.append(' '.join(current_chunk))
current_chunk = [sentences[i]]
else:
current_chunk.append(sentences[i])
chunks.append(' '.join(current_chunk))
return chunks
这种方法的好处是:每个块在语义上相对完整。检索时,用户的问题更容易与某个块整体匹配。
但要注意,embedding 模型本身有误差。我曾经试过用不同的 embedding 模型做语义分块,结果差异很大。有的模型把两个紧密相关的段落切开了,有的又把无关内容粘在一起。
避坑指南:语义分块的阈值需要根据你的数据调优。我建议先用小样本跑一遍,人工检查分块质量,再决定阈值。不要直接用默认值。
递归分块
递归分块是我个人比较喜欢的方法。它先按文档的自然结构(如章节、段落)切分,如果某个块仍然太大,再递归地按更细的粒度切分。
举个例子:
- 先按 Markdown 的
##标题切 - 如果某个标题下的内容超过 1000 ,再按
###切 - 如果还大,按段落切
- 最后按句子切
def recursive_chunk(text, max_tokens=1000):
# 按标题层级递归
sections = split_by_heading(text)
chunks = []
for section in sections:
if count_tokens(section) <= max_tokens:
chunks.append(section)
else:
# 递归切分
chunks.extend(recursive_chunk(section, max_tokens))
return chunks
这种方法的优势在于:它保留了文档的层级结构。检索时,你可以知道当前块属于哪个章节,上下文更丰富。
我记得有一次处理一份 200 页的产品手册,用递归分块后,每个块都带着章节标题。用户问「如何重置密码」,检索到的块不仅包含操作步骤,还附带了「安全设置」这个上下文,回答质量明显提升。
分块大小对检索的影响
分块大小是个需要权衡的参数。我整理了一张表,方便你对比:
| 分块大小 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 小(128-256 ) | 检索精度高,噪声少 | 上下文不足,容易丢失关联信息 | FAQ、短文本问答 |
| 中(512-1024 ) | 平衡精度和上下文 | 需要合理设计重叠 | 通用文档、技术手册 |
| 大(2048+ ) | 上下文丰富,适合长文本 | 检索精度下降,容易混入无关内容 | 、报告、法律文档 |
为什么会这样?
小分块就像用放大镜看地图,细节清晰但看不到全貌。大分块像用卫星图,能看到整体但细节模糊。你需要根据查询的粒度来选择。
我建议的做法是:先确定你的典型查询长度。如果用户的问题通常只有 10-20 个字,小分块可能更合适。如果用户会问「请总结第三章的内容」,那就需要大分块。
经验之谈:我一般从 512 开始试,然后分别测试 256 和 1024。用 100 个典型查询跑一遍,看哪个分块大小的检索命中率最高。这个过程不复杂,但效果立竿见影。
四种分块策略对比
我把四种策略放在一起对比,方便你选型:
| 策略 | 实现复杂度 | 语义完整性 | 计算开销 | 推荐场景 |
|---|---|---|---|---|
| 固定大小分块 | 低 | 低 | 低 | 日志、结构化数据 |
| 语义分块 | 中 | 高 | 中 | 新闻、博客、散文 |
| 递归分块 | 中高 | 高 | 中 | 技术文档、手册 |
| 混合策略 | 高 | 最高 | 高 | 复杂文档、多类型内容 |
混合策略是我现在常用的做法。比如:先用递归分块保留文档结构,再对每个块做语义分块,最后用固定大小兜底。虽然实现复杂,但效果确实好。
总结一下:分块不是越细越好,也不是越粗越好。关键是让你的分块策略与文档结构、查询模式对齐。花时间调分块参数,比花时间调 embedding 模型参数更值得。
最后说一句:分块策略没有银弹。我见过有人花两周调分块参数,最后发现换一个 embedding 模型效果更好。也见过有人用最简单的固定分块,配合好的检索策略,效果一样不错。
关键是多试、多测、多对比。你的数据会告诉你答案。