文本分块策略:固定大小分块、语义分块、递归分块、分块大小对检索的影响

文本分块,说白了就是决定「一段话该切多碎」。

我刚开始做 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 模型做语义分块,结果差异很大。有的模型把两个紧密相关的段落切开了,有的又把无关内容粘在一起。

避坑指南:语义分块的阈值需要根据你的数据调优。我建议先用小样本跑一遍,人工检查分块质量,再决定阈值。不要直接用默认值。

递归分块

递归分块是我个人比较喜欢的方法。它先按文档的自然结构(如章节、段落)切分,如果某个块仍然太大,再递归地按更细的粒度切分。

举个例子:

  1. 先按 Markdown 的 ## 标题切
  2. 如果某个标题下的内容超过 1000 ,再按 ### 切
  3. 如果还大,按段落切
  4. 最后按句子切
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 模型效果更好。也见过有人用最简单的固定分块,配合好的检索策略,效果一样不错。

关键是多试、多测、多对比。你的数据会告诉你答案。