一、RAG文本分割
RAG(Retrieval-Augmented Generation,检索增强生成)模型是一种结合了检索 和生成能力的自然语言处理模型。 它通过检索相关的文档片段,并将这些信息作为生成过程的上下文,以提高生成质量 和准确性。
在RAG模型中,文本分割是一个非常关键的步骤。合理地分割文档文本, 不仅能够提高检索的效率,还能更有效地将检索到的信息提供给生成模型,使生成内 容更加连贯和准确。
文本分割优点:
提升检索效率: 文本分割的首要原因是为了提高检索的效率。对于较长的文档,直接检索整篇文 档可能会导致信息冗余或者重要信息丢失。因此,合理的文本分割将长文档分成 多个段落或片段,这样每个片段可以单独进行检索,从而提高检索的精度和速 度。
更好的信息匹配: 当文档被合理地分割后,检索算法可以更加精确地匹配用户查询和文档片段。这 样不仅能够减少噪声,还可以确保检索到的内容更加相关,生成模型在使用这些 片段进行回答时能够生成更加相关和有意义的内容。
增强生成质量: 检索增强生成模型依赖检索到的内容作为生成输入的一部分。如果检索到的文档 片段不准确或上下文不完整,生成的结果可能会偏离用户的期望。文本分割可以 确保每个文档片段足够独立,且能够提供完整的上下文信息,从而提高生成的准 确性和上下文连贯性。
降低计算复杂度: 长文本的直接处理会大大增加模型的计算量和时间开销,甚至有些大模型并不支 持超长文本的输入。通过将文本分割成多个较小的片段,可以只针对特定的片段 进行处理,从而减少计算量,提高响应速度。这不仅优化了模型的性能,还减少 了生成结果时的延迟。
二、文本分割方法
2.1、字符分割
字符分割是最基础的文本分割方式,按照指定的分隔符进行分割。chunk_size 指的 是每个分割片段(chunk)的长度。chunk_overlap 指的是每个分割片段之间的重叠 部分。
注意: split_text 和 split_documents 不同:
split_text:处理单纯的字符串。
split_documents:处理包含元数据的文档对象。
# 导入LangChain的字符文本分割器
from langchain.text_splitter import CharacterTextSplitter# 定义要分割的中文文本
text = "在西天取经的几百年前黑风山上有一只黑熊精占山为王,自称黑风大王。"# 创建字符文本分割器实例
# 参数说明:
# chunk_size=15: 每个文本块的最大字符数
# chunk_overlap=2: 相邻文本块之间的重叠字符数
# separator="": 分割符设为空字符串(按字符分割)
splitter = CharacterTextSplitter(chunk_size=15,chunk_overlap=2,separator=""
)# 执行文本分割
chunks = splitter.split_text(text)# 打印分割结果
print(chunks)
['在西天取经的几百年前黑风山上有', '上有一只黑熊精占山为王,自称黑', '称黑风大王。']
2.2、递归字符文本分割
递归字符文本分割是字符分割的升级版,它以字符分割为基础,但在分割时引入了递 归的机制。 在初步分割之后,再对一些不完整或冗长的片段进行进一步细分,从而获得更加细粒 度的分割。
这个方法比简单的字符分割更加灵活,可以通过递归深度来控制分割的粒度。 RecursiveCharacterTextSplitter不设置separators时,默认的separators参数为 ["\n\n", "\n", " ", ""]:将按不同的字符递归地分割(按照这个优先级["\n\n", "\n", " ", ""]),文本分割器首先在"\n\n"处尝试分割,如果分出的块过大(大于 chunk_size),则找到"\n"处尝试分割以此类推,若都不满足则按照字符划分。
# 导入LangChain的递归字符文本分割器
from langchain.text_splitter import RecursiveCharacterTextSplitter# 定义要分割的中文文本
text = "在西天取经的几百年前黑风山上有一只黑熊精占山为王,这里只是为了占位没什么用,自称黑风大王。"# 创建递归字符文本分割器实例
# 参数说明:
# chunk_size=15: 每个文本块的最大字符数
# chunk_overlap=3: 相邻文本块之间的重叠字符数
# separators=[...]: 分割符优先级列表(按顺序尝试分割)
splitter = RecursiveCharacterTextSplitter(chunk_size=15, # 每个分块最大15个字符chunk_overlap=3, # 分块间重叠3个字符separators=["\n\n", # 双换行(最高优先级)"\n", # 单换行" ", # 空格".", # 英文句号",", # 英文逗号",", # 中文逗号"。", # 中文句号"" # 最后按字符分割(最低优先级)]
)# 执行文本分割
chunks = splitter.split_text(text)# 打印分割结果
print(chunks)
['在西天取经的几百年前黑风山上有', '山上有一只黑熊精占山为王', ',这里只是为了占位没什么用', ',自称黑风大王。']
递归分割机制:
会按照
separators
列表中的顺序,优先尝试用高级别的分隔符如果高级别分隔符无法满足
chunk_size
要求,会降级使用更低级别的分隔符
参数特点:
chunk_overlap=3
确保分块之间有3个字符的重叠,保持上下文连贯中文标点","和"。"被明确列为分隔符,确保在标点处自然分割
分割策略:
优先在段落(\n\n)、句子(。)级别分割
其次在短语(,)和词语(空格)级别分割
最后才会按单个字符分割
中文适配:
专门添加了中文标点作为分隔符
确保中文文本能在语义合理的边界处分割
2.3、特定文档分割(以markdown为例):
特定文档分割是基于文档结构对文本进行分割的一种方式。不同的文档类型通常有各 自的结构化信息,例如书籍中的章节和段落、网页中的HTML标签等。 利用这些预定义的结构化信息,可以更加自然地分割文本,保证片段的上下文连贯 性。
# 导入Markdown标题文本分割器
from langchain.text_splitter import MarkdownHeaderTextSplitter# 定义包含Markdown标题的文本内容
text = """
# 第一章
在西天取经的几百年前,黑风山上。\n\n在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。
# 第二章
测试。
"""# 定义需要分割的标题级别和对应的元数据名称
# 格式: [(标题标记, 元数据字段名), ...]
headers_to_split_on = [("#", "Header 1"), # 一级标题,保存到Header 1元数据字段("##", "Header 2"), # 二级标题,保存到Header 2元数据字段
]# 创建Markdown标题分割器实例
splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on # 传入标题配置
)# 执行文本分割
chunks = splitter.split_text(text)# 打印分割结果
print(chunks)
# 预期输出结构:
# [
# {
# 'content': '在西天取经的几百年前...', # 正文内容
# 'metadata': {'Header 1': '第一章'} # 标题元数据
# },
# {
# 'content': '测试。',
# 'metadata': {'Header 1': '第二章'}
# }
# ]
[Document(metadata={'Header 1': '第一章'}, page_content='在西天取经的几百年前,黑风山上。 \n在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。'), Document(metadata={'Header 1': '第二章'}, page_content='测试。')]
2.4、实例
# 导入必要的库
from langchain_community.document_loaders import TextLoader # 文本加载器
from langchain.text_splitter import RecursiveCharacterTextSplitter # 递归字符分割器# 1. 加载TXT文档
# 创建TextLoader实例,指定文件路径和编码格式
text_loader = TextLoader("黑悟空.txt", encoding="UTF-8")
# 加载文档内容,返回Document对象列表
documents = text_loader.load()# 2. 定义递归字符分割器
text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, # 每个分块的最大字符数chunk_overlap=20, # 相邻分块间的重叠字符数separators=["\n\n", "\n", " ", ".", ",", ",", "。", ""] # 分割符优先级列表# 分割符优先级说明:# 1. 首先尝试用双换行符(\n\n)分割# 2. 然后尝试单换行符(\n)# 3. 接着是空格、英文标点# 4. 最后是中文标点# 5. 如果以上都不适用,则按单个字符分割
)# 3. 执行文档分割
# 对加载的文档进行分割,返回分割后的Document对象列表
splits_docs = text_splitter.split_documents(documents)# 4. 打印分割结果
# 遍历所有分块,打印序号和内容
for i, chunk in enumerate(splits_docs):print(f"分块 {i+1}: \n{chunk}\n") # 打印分块编号和内容# 每个chunk是一个Document对象,包含page_content和metadata属性# 补充说明:
# 1. 适合处理包含中文标点的文本
# 2. 会尽量在段落、句子边界处进行分割
# 3. 重叠部分(chunk_overlap)确保上下文连贯性
# 4. 输出结果保留了原始文档的结构信息