RAG系统文本切分算法选型指南
简介
在构建企业级检索增强生成(RAG)系统时,文本切分算法的选择至关重要。切分策略直接影响检索的质量和生成结果的准确性。本文档将介绍常用的文本切分方法,分析其优缺点,并结合不同检索器给出最佳实践方案。
RAG系统通常包含两个主要组件:
-
1. 索引阶段:加载数据、切分文档、存储向量 -
2. 检索和生成阶段:基于用户输入检索相关内容,利用LLM生成回答
其中,文本切分是索引阶段的关键步骤,它将大型文档分割成更小的块,以便于检索和处理。
文本切分算法概述
文本切分算法的主要目标是将长文本分割成语义连贯的小块,使其既能保持上下文完整性,又能有效地用于检索和传递给模型。理想的切分算法应该能够:
-
• 保持语义完整性 -
• 适应不同类型的文档 -
• 产生大小适中的文本块 -
• 确保检索效率 -
• 减少冗余信息
常用文本切分方法
基于字符的切分
描述
基于字符的切分是最简单的切分方法,它根据字符数量将文本切分成固定大小的块。
LangChain示例
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # 每个块的字符数
chunk_overlap=200, # 相邻块之间的重叠字符数
separators=["nn", "n", " ", ""] # 优先按照这些分隔符切分
)
docs = text_splitter.split_documents(documents)
优点
-
• 实现简单,易于理解 -
• 处理速度快 -
• 适用于各种类型的文本 -
• 可以通过分隔符列表优化切分位置
缺点
-
• 可能在语义边界处不精确切分 -
• 不考虑内容的语义结构 -
• 固定大小的块可能会分割重要的上下文信息
最佳实践
-
• 结合使用 BM25Retriever
或简单的向量检索 -
• 增加块重叠以减少信息丢失 -
• 使用更有意义的分隔符(如段落分隔符) -
• 对于较短的文档,建议使用较小的 chunk_size
(500-1000) -
• 对于长文档,考虑分层检索策略
基于令牌的切分
描述
基于令牌的切分根据LLM的令牌(token)计数来切分文本,这更符合模型处理文本的方式。
LangChain示例
from langchain_text_splitters import TokenTextSplitter
text_splitter = TokenTextSplitter(
chunk_size=500, # 每个块的最大令牌数
chunk_overlap=50 # 相邻块之间的重叠令牌数
)
docs = text_splitter.split_documents(documents)
优点
-
• 直接与LLM的上下文窗口大小相关联 -
• 可以精确控制每个块的令牌数 -
• 减少模型处理时的截断问题
缺点
-
• 计算令牌数需要额外处理 -
• 可能在语义不完整处切分 -
• 不同模型的分词器可能导致不同结果
最佳实践
-
• 结合 VectorStoreRetriever
使用 -
• 令牌大小设置应考虑目标LLM的上下文窗口 -
• 对于GPT模型,可以设置chunk_size为1024-2048 -
• 重叠部分建议为块大小的10%-20% -
• 适合需要精确控制令牌用量的场景
基于语义的切分
描述
基于语义的切分通过计算文本嵌入并识别语义边界来切分文本,确保每个块的语义连贯性。
不同breakpoint_threshold_type的功能与作用
SemanticChunker提供了四种不同的阈值类型(breakpoint_threshold_type),用于确定文本的切分点:
1. 百分位数方法(percentile)
-
• 功能:基于文本嵌入向量之间的距离分布,使用百分位数作为划分边界的阈值 -
• 原理:计算相邻句子嵌入向量之间的距离,找出距离分布中特定百分位(如95%)的值作为阈值,超过该阈值的位置被视为语义边界 -
• 适用场景:适合结构相对规律、语义转换明显的一般文本 -
• 参数调整:通过调整 breakpoint_threshold_amount
(0-100之间的值)控制切分的粒度,值越高切分越少,默认为95.0 -
• 特点:能够有效识别主题转换点,保持语义连贯性
2. 标准差方法(standard_deviation)
-
• 功能:使用距离分布的平均值加上一定倍数的标准差作为切分阈值 -
• 原理:计算所有相邻句子嵌入向量距离的平均值和标准差,将"平均值+n倍标准差"作为阈值 -
• 适用场景:适合语义变化程度差异较大的文档,如包含多个不同主题的论文或报告 -
• 参数调整:通过 breakpoint_threshold_amount
设置标准差的倍数,默认为1.0 -
• 特点:对异常值(语义突变点)更敏感,能够适应文档内部语义变化程度的差异
3. 四分位距方法(interquartile)
-
• 功能:使用四分位距(IQR)来确定语义边界的阈值 -
• 原理:计算距离分布的第一四分位数(Q1)和第三四分位数(Q3),将"Q3 + n倍IQR"作为阈值,其中IQR = Q3 – Q1 -
• 适用场景:适合语义高度相关但需要在自然边界处切分的文档,如专业论文、技术文档 -
• 参数调整:通过 breakpoint_threshold_amount
调整IQR的倍数,默认为1.5 -
• 特点:对极端值不敏感,提供更稳健的切分方式,减少噪声影响
4. 梯度方法(gradient)
-
• 功能:结合梯度分析和百分位数方法,特别适用于语义高度相关的专业领域文档 -
• 原理:计算相邻向量距离的变化率(梯度),然后对梯度分布应用异常检测,找出语义变化显著的点 -
• 适用场景:特别适合法律、医学等专业领域文档,这类文档往往语义高度相关、术语密集 -
• 参数调整:与百分位数方法类似,通过 breakpoint_threshold_amount
设置百分位阈值,默认为95.0 -
• 特点:能够在语义高度相关的文本中识别出细微的主题转换,使分布更宽,边界更容易识别
LangChain示例
百分位数方法:
from langchain_text_splitters import SemanticChunker
from langchain_openai import OpenAIEmbeddings
text_splitter = SemanticChunker(
OpenAIEmbeddings(),
breakpoint_threshold_type="percentile", # 使用百分位数方法
breakpoint_threshold_amount=95.0 # 95百分位数
)
docs = text_splitter.create_documents([text])
标准差方法:
text_splitter = SemanticChunker(
OpenAIEmbeddings(),
breakpoint_threshold_type="standard_deviation", # 使用标准差方法
breakpoint_threshold_amount=1.5 # 1.5倍标准差
)
docs = text_splitter.create_documents([text])
四分位距方法:
text_splitter = SemanticChunker(
OpenAIEmbeddings(),
breakpoint_threshold_type="interquartile", # 使用四分位距方法
breakpoint_threshold_amount=1.5 # 1.5倍四分位距
)
docs = text_splitter.create_documents([text])
梯度方法:
text_splitter = SemanticChunker(
OpenAIEmbeddings(),
breakpoint_threshold_type="gradient", # 使用梯度方法
breakpoint_threshold_amount=95.0 # 95百分位数
)
docs = text_splitter.create_documents([text])
优点
-
• 保持语义的完整性和连贯性 -
• 更符合人类理解文本的方式 -
• 可以适应不同类型和结构的文档 -
• 能够识别自然的主题转换点 -
• 提高检索相关性和准确性
缺点
-
• 计算成本高,处理速度慢 -
• 需要嵌入模型支持 -
• 参数调整可能需要实验 -
• 对于某些特定领域文本效果可能不一致 -
• 对嵌入模型的质量依赖较高
最佳实践
-
• 与 MMR
(最大边际相关性)检索策略结合使用 -
• 对于长文档或结构化文档效果更好 -
• 百分位数方法适合一般文档和初始尝试 -
• 标准差方法适合语义变化较大的混合文档 -
• 四分位距方法适合需要稳健切分的专业文档 -
• 梯度方法适合专业领域文档(如法律、医学、学术论文) -
• 考虑使用 HyperRetriever
或SelfQueryRetriever
增强语义检索能力 -
• 为不同类型的文档集合选择不同的阈值类型 -
• 进行A/B测试比较不同方法的检索效果
特定格式文档切分
描述
针对特定格式的文档(如Markdown、HTML、代码等)的专用切分器,能够理解文档结构。
LangChain示例
Markdown切分:
from langchain_text_splitters import MarkdownHeaderTextSplitter
headers_to_split_on = [
("#", "Header 1"),
("##", "Header 2"),
("###", "Header 3"),
]
markdown_splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=headers_to_split_on
)
md_docs = markdown_splitter.split_text(markdown_text)
HTML切分:
from langchain_text_splitters import HTMLHeaderTextSplitter
html_splitter = HTMLHeaderTextSplitter(tags_to_split_on=["h1", "h2", "h3"])
html_docs = html_splitter.split_text(html_text)
代码切分:
from langchain_text_splitters import RecursiveCharacterTextSplitter
python_splitter = RecursiveCharacterTextSplitter.from_language(
language="python",
chunk_size=1000,
chunk_overlap=200
)
code_docs = python_splitter.split_text(code_text)
优点
-
• 尊重文档的结构和层次 -
• 保留标题和内容的关系 -
• 适合结构化文档的处理 -
• 能够根据文档特性进行优化
缺点
-
• 仅适用于特定格式的文档 -
• 可能需要额外的预处理 -
• 对格式错误的文档处理效果不佳 -
• 需要针对不同文档类型选择不同分割器
最佳实践
-
• 结合使用 ParentDocumentRetriever
保持文档层次结构 -
• 对于Markdown文档,按标题分割可提高检索精度 -
• 对于HTML文档,可以结合CSS选择器进行更精细的控制 -
• 对于代码文档,考虑按函数或类进行分割 -
• 使用元数据存储文档结构信息,便于检索时重建上下文
最佳实践方案
混合策略
在企业级RAG系统中,可以考虑采用混合策略,结合多种切分方法的优势:
-
1. 分层切分:先使用结构化切分(如按标题、段落),再使用细粒度切分(如基于令牌) -
2. 动态切分:根据文档类型和长度自动选择适合的切分算法 -
3. 并行切分:同时使用多种切分方法,在检索时合并结果
文档类型与切分方法匹配
-
• 长文本文档(如论文、报告):语义切分 + 分层检索 -
• 结构化文档(如Markdown、HTML):特定格式切分器 + 层次检索 -
• 代码库:代码专用切分器 + 函数级别切分 -
• 对话记录:基于对话轮次切分 + 上下文感知检索 -
• 表格数据:专用表格处理 + 结构化检索
检索器选择与优化
-
• 向量存储检索器:适合语义相似性查询,与语义切分配合使用 -
• BM25检索器:适合关键词查询,与基于字符的切分配合使用 -
• 混合检索器:结合关键词和语义检索,提高召回率 -
• 自查询检索器:允许文档自描述,与语义切分配合良好 -
• 父文档检索器:保持文档结构,与特定格式切分器配合使用
结论
文本切分是RAG系统的关键环节,直接影响检索质量和生成结果。在企业级应用中,应根据具体需求、文档特性和资源限制选择合适的切分策略。
理想的切分方案应该:
-
• 保持语义完整性 -
• 适应不同类型的文档 -
• 与检索器协同工作 -
• 根据需求动态调整 -
• 考虑计算资源与性能平衡
建议在实际部署前进行充分测试,并根据反馈持续优化切分策略。
额外内容
文本切分预处理
文本切分可以先通过一些预处理来获得更好的检索效果
-
1. 预设问题:通过大模型对切分后的文本进行扩充,补充1-2个预设问题来增加chunk召回概率,例如text切分成10个chunk,每个chunk通过大模型生成1-2个问题,然后更新chunk为问题+chunk文本 -
2. 保存chunk位置信息,检索到chunk后取出上下n个chunk一起参与模型问答,例如搜索到chunk3,将chunk1,chunk2,chunk4,chunk5一起填入context -
3. 存储chunk与原文关系,检索到chunk后使用原文,剔除chunk -
4. context拼接:由于大模型注意力机制,prompt的头尾两部分数据被关注权重比中间高,因此检索到的chunk按分数从高到低排序后,使用头尾交替法插入prompt,例如排序12345,插入prompt后顺序13542