一、任务概述
RAG(检索增强生成)中的文本数据切割是将原始文本按照特定策略分解为更小、更易处理的片段(chunk)的过程。其核心目标是优化检索效率与问答准确性,使大语言模型能更精准地获取相关上下文信息。
二、背景
随着自然语言处理技术的发展,RAG系统被广泛应用于知识问答、信息检索等场景。原始文本通常具有长度长、结构复杂的特点,直接输入模型会导致:
- 超出模型上下文窗口限制
- 关键信息被稀释,检索精度下降
- 生成回答时上下文关联断裂
因此,合理的文本切割成为RAG系统性能优化的关键环节。
三、在RAG流程中的定位
在RAG系统完整流程中,文本数据切割属于知识预处理阶段的核心环节,具体位置如下:
- 数据采集(获取原始文本/文档)→
- 文本切割(当前任务)→
- 文本嵌入(将切割后的片段转换为向量)→
- 向量存储(存入向量数据库)→
- 检索阶段(根据用户查询匹配相关片段)→
- 生成阶段(结合检索结果生成回答)
文本切割是连接原始数据与后续向量处理的桥梁,直接影响整个RAG系统的效果。
四、作用
- 提升检索效率:更小的片段减少检索比对的数据量,加快响应速度
- 增强匹配精度:主题更聚焦的片段能提高与查询的相关性匹配
- 优化上下文利用:符合模型窗口限制的片段能被更充分地用于生成回答
- 保留语义完整性:合理的切割策略可最大程度保留文本内在逻辑关系
五、主要切割方式及实现
1. 通用递归字符切割
基于标点符号和换行符等自然分隔符进行递归分割,适用于无结构化文本。
from langchain_text_splitters import RecursiveCharacterTextSplitter
with open(r'F:\python测试\智谱-langchain\测试数据\test.txt', encoding='utf8') as f:
text_data = f.read()
'''
chunk_size:块的最大大小,其中大小由length_function决定
chunk_overlap:数据块之间的目标重叠。重叠数据块有助于在数据块之间划分上下文时减少信息丢失。
length_function:确定块大小的函数。
is_separator_regex:分隔符列表是否应解释为正则表达式。
'''
# 递归切割器
# 默认的分隔符:["\n\n", "\n", " ", ""]
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=100,
chunk_overlap=20,
length_function=len,
is_separator_regex=False,
separators=[
"\n\n",
"\n",
".",
"?",
"!",
"。",
"!",
"?",
",",
",",
" "
]
)
# chunks_list = text_splitter.split_documents([text_data])
chunks_list = text_splitter.create_documents([text_data])
print(len(chunks_list))
print(chunks_list[0])
print('--------------------------')
print(chunks_list[1])
特点:
- 实现简单,处理速度快
- 支持多语言标点符号
- 可通过调整
chunk_size和chunk_overlap控制切割粒度 - 适合处理无明显结构的纯文本
2. 基于语义的智能切割
利用文本嵌入计算句子间语义相似度,在语义断点处分割,保留语义完整性。
import os
from langchain_community.embeddings import BaichuanTextEmbeddings
from langchain_experimental.text_splitter import SemanticChunker
# 1. 环境配置与初始化
# 设置百川API密钥(用于生成文本嵌入)
os.environ['BAICHUAN_API_KEY'] = 'sk-732b2b80be7bd800cb3a1dbc330722b4'
# 初始化百川文本嵌入模型
embeddings = BaichuanTextEmbeddings()
# 读取待分割的文本数据
with open('test.txt', encoding='utf8') as f:
text_data = f.read()
print("="*80)
print("原始文本长度:", len(text_data))
print("="*80)
# ------------------------------------------------------------------------------
# 模式1: percentile(百分位模式)- 最常用
# 核心逻辑:计算所有相邻句子嵌入的相似度,取N百分位作为阈值
# 当相邻句子相似度低于该百分位阈值时,视为语义断点
# 参数说明:
# - breakpoint_threshold: 百分位数值(0-100),默认20
# ------------------------------------------------------------------------------
print("\n【模式1】breakpoint_threshold_type='percentile'(百分位模式)")
print("模式说明:基于相邻句子相似度的百分位设定断点,适合大多数场景")
text_splitter_percentile = SemanticChunker(
embeddings=embeddings,
breakpoint_threshold_type='percentile',
breakpoint_threshold=20 # 默认值,可调整(值越大,分割越细)
)
docs_percentile = text_splitter_percentile.create_documents([text_data])
print(f"分割后文档数量: {len(docs_percentile)}")
print(f"第1段内容:\n{docs_percentile[0].page_content[:500]}..." if docs_percentile else "无分割结果")
print("-"*60)
# ------------------------------------------------------------------------------
# 模式2: standard_deviation(标准差模式)
# 核心逻辑:以所有相邻句子相似度的均值 ± N倍标准差作为阈值
# 当相似度低于(均值 - N*标准差)时,视为断点(默认N=1)
# ------------------------------------------------------------------------------
print("\n【模式2】breakpoint_threshold_type='standard_deviation'(标准差模式)")
print("模式说明:基于相似度分布的标准差设定断点,适合数据分布较均匀的文本")
text_splitter_std = SemanticChunker(
embeddings=embeddings,
breakpoint_threshold_type='standard_deviation',
breakpoint_threshold=1 # 默认值,可调整
)
docs_std = text_splitter_std.create_documents([text_data])
print(f"分割后文档数量: {len(docs_std)}")
print(f"第1段内容:\n{docs_std[0].page_content[:500]}..." if docs_std else "无分割结果")
print("-"*60)
# ------------------------------------------------------------------------------
# 模式3: interquartile(四分位模式)
# 核心逻辑:基于四分位距(IQR)计算阈值,鲁棒性更强(抗异常值)
# 阈值 = 第一四分位数(Q1) - N*IQR(默认N=1.5)
# ------------------------------------------------------------------------------
print("\n【模式3】breakpoint_threshold_type='interquartile'(四分位模式)")
print("模式说明:基于四分位距设定断点,抗异常值干扰,适合噪声较大的文本")
text_splitter_iqr = SemanticChunker(
embeddings=embeddings,
breakpoint_threshold_type='interquartile',
breakpoint_threshold=1.5 # 默认值,可调整
)
docs_iqr = text_splitter_iqr.create_documents([text_data])
print(f"分割后文档数量: {len(docs_iqr)}")
print(f"第1段内容:\n{docs_iqr[0].page_content[:500]}..." if docs_iqr else "无分割结果")
print("="*80)
特点:
- 基于语义相似度分割,保留完整语义单元
- 提供三种分割模式,适应不同文本特性
- 依赖嵌入模型,处理成本相对较高
- 适合对语义连贯性要求高的场景
3. 基于标题结构的切割
HTML文档切割
利用HTML中的标题标签(h1-h6)进行层级分割,保留文档原生结构。
from langchain_text_splitters import RecursiveCharacterTextSplitter, HTMLHeaderTextSplitter
html_string = """
<!DOCTYPE html>
<html>
<body>
<div>
<h1>Foo</h1>
<p>Some intro text about Foo.</p>
<div>
<h2>Bar main section</h2>
<p>Some intro text about Bar.</p>
<h3>Bar subsection 1</h3>
<p>Some text about the first subtopic of Bar.</p>
<h3>Bar subsection 2</h3>
<p>Some text about the second subtopic of Bar.</p>
</div>
<div>
<h2>Baz</h2>
<p>Some text about Baz</p>
</div>
<br>
<p>Some concluding text about Foo</p>
</div>
</body>
</html>
"""
label_split = [ # 定义章节的结构
('h1', '大章节 1'),
('h2', '小节 2'),
('h3', '章节中的小点 3'),
]
html_splitter = HTMLHeaderTextSplitter(label_split)
docs_list = html_splitter.split_text(html_string)
print('切割之后的结果: -------------------')
print(docs_list)
label_split_2 = [ # 定义章节的结构
('h1', '大章节'),
('h2', '小节'),
('h3', '章节中的小点'),
('h4', '小点中的子节点'),
]
html_splitter = HTMLHeaderTextSplitter(label_split_2)
docs_list = html_splitter.split_text_from_url('https://plato.stanford.edu/entries/goedel/')
print(docs_list[0])
print('--------------------')
print('总共有多少个docs: ', len(docs_list))
print('--------------------------------')
print(docs_list[1])
# 由于章节的内容太多,可以切两次
# 默认的分隔符:["\n\n", "\n", " ", ""]
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=600,
chunk_overlap=20,
# 长度函数
length_function=len, # 默认是len
# 是否使用正则表达式
is_separator_regex=False,
# 分隔符
separators=[
"\n\n",
"\n",
".",
"?",
"!",
"。",
"!",
"?",
",",
",",
" "
]
)
docs2_list = text_splitter.split_documents(docs_list)
print('---------------------------------')
print('总共有多少个docs: ', len(docs2_list), '\n')
print('---------------------------------')
print(docs2_list[1], '\n')
Markdown文档切割
针对Markdown的标题语法(#、##、###等)进行结构化分割。
from langchain_text_splitters import RecursiveCharacterTextSplitter, MarkdownHeaderTextSplitter
with open(r'F:\python测试\智谱-langchain\测试数据\Foo.md', encoding='utf8') as f:
text_data = f.read()
label_split = [
('#', '大章节'),
('##', '小节'),
('###', '小点')
]
# strip_headers :是否在内容中删除章节的标题
markdown_splitter = MarkdownHeaderTextSplitter(label_split, strip_headers=False)
docs_list = markdown_splitter.split_text(text_data)
print(docs_list)
特点:
- 保留文档原生结构信息
- 可通过
label_split定义多级标题映射 - 支持直接从URL读取HTML内容进行分割
- 适合处理结构化文档,如技术文档、博客文章等
六、组合切割策略
对于长文档,可采用多级切割策略:
- 先使用标题分割器按文档结构切割为大章节
- 再使用递归字符分割器对过长章节进行二次分割(如上述HTML切割示例中的二次处理)
七、总结
不同切割方式适用于不同场景:
- 结构化文档(HTML/Markdown)优先使用标题分割器
- 非结构化文本可选择递归字符分割器
- 对语义连贯性要求高的场景(如长文档理解)推荐使用语义分割器
实际应用中需根据文档类型、长度及业务需求选择合适的切割策略与参数,以达到最佳的RAG系统性能。
1605

被折叠的 条评论
为什么被折叠?



