← 返回首页
RAG的文本分割
发表时间:2025-05-16 15:39:46
RAG的文本分割

1.RAG文本分割简介

RAG(Retrieval-Augmented Generation,检索增强生成)模型是一种结合了检索和生成能力的自然语言处理模型。它通过检索相关的文档片段,并将这些信息作为生成过程的上下文,以提高生成质量和准确性。在RAG模型中,文本分割是一个非常关键的步骤。合理地分割文档文本,不仅能够提高检索的效率,还能更有效地将检索到的信息提供给生成模型,使生成内容更加连贯和准确。

2.为什么需要文本分割

3.文本分割方法

RAG系统中,文本分割的实现方法有很多,具体可以根据任务需求和文档类型选择合适的策略:

3.1 字符分割

字符分割是最基础的文本分割方式,按照指定的分隔符进行分割。chunk_size 指的是每个分割片段(chunk)的长度。chunk_overlap 指的是每个分割片段之间的重叠部分。

注意:split_text 和 split_documents 不同: - split_text :处理单纯的字符串。 - split_documents :处理包含元数据的文档对象。

from langchain.text_splitter import CharacterTextSplitter

text = "在西天取经的几百年前黑风山上有一只黑熊精占山为王,自称黑风大王。"

#可以看到句子并没有被分割,是因为CharacterTextSplitter默认分隔符是'\n\n',而原文中并没有,因此全划分为一句。
#设置separator=""可以得到按字符分割的结果
#splitter = CharacterTextSplitter(chunk_size=15, chunk_overlap=2)
splitter = CharacterTextSplitter(chunk_size=15, chunk_overlap=2, separator="")
chunks = splitter.split_text(text)
print(chunks)

运行效果:

['在西天取经的几百年前黑风山上有', '上有一只黑熊精占山为王,自称黑', '称黑风大王。']

3.2 递归字符文本分割

递归字符文本分割是字符分割的升级版,它以字符分割为基础,但在分割时引入了递归的机制。在初步分割之后,再对一些不完整或冗长的片段进行进一步细分,从而获得更加细粒度的分割。这个方法比简单的字符分割更加灵活,可以通过递归深度来控制分割的粒度。 RecursiveCharacterTextSplitter不设置separators时,默认的separators参数为["\n\n", "\n", " ", ""]:将按不同的字符递归地分割(按照这个优先级["\n\n", "\n", " ",""]),文本分割器首先在"\n\n"处尝试分割,如果分出的块过大(大于chunk_size),则找到"\n"处尝试分割以此类推,若都不满足则按照字符划分。

from langchain.text_splitter import RecursiveCharacterTextSplitter

text = "在西天取经的几百年前黑风山上有一只黑熊精占山为王,自称黑风大王。"
splitter = RecursiveCharacterTextSplitter(chunk_size=15, chunk_overlap=0)
chunks = splitter.split_text(text)
print(chunks)

运行效果:

['在西天取经的几百年前黑风山上有', '一只黑熊精占山为王,自称黑风大', '王。']

注意实际上按照["\n\n", "\n", " ", ""]划分更适用于英文系统,由于中文文本内部并没有这些分隔符,这种方法将不起作用。这样会导致单词在单词块之间拆分。例如句子"在西天取经的几百年前黑风山上有一只黑熊精占山为王,自称黑风大王。"划分后产生'在在西天取经的几百年前黑风山上','有一只黑熊精占山为王,自称黑风', '大王。'的结果。显然划分是不够合理的。

于是我们可以加入句号、逗号等分隔符适应于中文。使得分割效果更合理。

"""对中文优化"""

from langchain.text_splitter import RecursiveCharacterTextSplitter

text = "在西天取经的几百年前黑风山上有一只黑熊精占山为王,自称黑风大王。"
splitter = RecursiveCharacterTextSplitter(chunk_size=15, chunk_overlap=0, separators=[
    "\n\n", "\n", " ", ".", ",", ",", "。", ""])
chunks = splitter.split_text(text)
print(chunks)

运行效果:

['在西天取经的几百年前黑风山上有', '一只黑熊精占山为王', ',自称黑风大王。']

3.3 特定文档分割(以markdown为例)

特定文档分割是基于文档结构对文本进行分割的一种方式。不同的文档类型通常有各自的结构化信息,例如书籍中的章节和段落、网页中的HTML标签等。利用这些预定义的结构化信息,可以更加自然地分割文本,保证片段的上下文连贯性。

from langchain.text_splitter import MarkdownHeaderTextSplitter

text = """
# 第一章
在西天取经的几百年前,黑风山上。\n\n在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。
# 第二章
第二章正文简介。
## 第二章二级标题
第二章二级内容展示
"""

headers_to_split_on = [
    ("#", "Header 1"),
    ("##", "Header 2"),
]

splitter = MarkdownHeaderTextSplitter(headers_to_split_on)
chunks = splitter.split_text(text)
print(chunks)

运行效果:

[Document(metadata={'Header 1': '第一章'}, page_content='在西天取经的几百年前,黑风山上。  \n在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。在西天取经的几百年前,黑风山上。'), Document(metadata={'Header 1': '第二章'}, page_content='第二章正文简介。'), Document(metadata={'Header 1': '第二章', 'Header 2': '第二章二级标题'}, page_content='第二章二级内容展示')]

4 文档读入与分割

LangChain提供了一些文本加载器,使用文档加载器可以从源加载数据转换为Document。Document是一段文本和相关元数据。

from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

# 加载TXT文档
text_loader = TextLoader("黑悟空.txt", encoding="UTF-8")
documents = text_loader.load()

# 定义递归字符分割器
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size = 200,
    chunk_overlap=20,
    separators=["\n\n", "\n", " ", ".", ",", ",", "。", ""]
)


# 开始分割
splits_docs = text_splitter.split_documents(documents)
# print(splits_docs)
for i, chunk in enumerate(splits_docs):
    print(f"Chunk {i+1}: \n {chunk}\n")

运行效果:

Chunk 1: 
 page_content='第一章

在西天取经的几百年前,黑风山上有一只黑熊精占山为王,自称黑风大王。

有一天,黑熊精碰到了一个小和尚。他觉得这个小和尚蛮有趣,于是给了他一些金银财宝,又教给他一些长生的法门。这个小和尚就是后来的金池长老,二人从此结缘。

在这之后,金池也给黑熊精讲一些佛法,黑熊精也有点兴趣,二人也算是有共同话题的朋友。' metadata={'source': '黑悟空.txt'}

Chunk 2: 
 page_content='金池一路坐到观音禅院的长老(毕竟学过长生术,活的最久),受人顶礼膜拜,欲望也随之膨胀。

黑熊精手下有一个是苍狼精,起了个道号叫凌虚子。还有一个是白花蛇怪,一般称呼为白衣秀士。二人虽都和黑熊精结拜,但二者各有派别。凌虚子管着一群狼妖,而白衣秀士则管着一群蛇妖。' metadata={'source': '黑悟空.txt'}

Chunk 3: 
 page_content='凌虚子手下有个小狼妖,想修习佛教。凌虚子是个心地善良的妖怪,有心成全他,就和黑熊精提,黑熊精把这小狼妖介绍到了观音禅院。金池给他起了个法名叫“广谋”。广谋变化作一个小和尚,在禅院里修行得挺安稳。

这事儿传到了白衣秀士耳中。那白衣秀士是个心思缜密之人,生怕凌虚子和金池在暗地里研究些什么,怕对自己不利,因此也找了个小蛇精变成人形,送进了观音禅院。这个小蛇精法名“广智”。' metadata={'source': '黑悟空.txt'}

Chunk 4: 
 page_content='之后取经队伍到了,金池贪图唐僧的袈裟,广谋虽然想修佛,但毕竟是妖怪,对杀人越货的勾当完全不当回事,出了个主意杀掉唐僧。广智则心思缜密,提出放火更容易掩人耳目。于是金池照办,全禅院都搬柴放火。' metadata={'source': '黑悟空.txt'}

Chunk 5: 
 page_content='可是隔墙有耳,他们的计谋被孙悟空听见了。孙悟空将计就计,借了防火罩护住唐僧,又添了把风把观音禅院付之一炬,很多众被焚烧致死。金池长老撞墙自杀(所以游戏里的幽魂,头部是弱点,头部受重击就倒地),广谋、广智逃离。

黑熊精本来是来救火,结果顺走了袈裟,并因此和取经团队发生了冲突。最后被观音套上金箍圈后收去,当了守山大神。至于凌虚子和白衣秀士则被打死了。' metadata={'source': '黑悟空.txt'}

Chunk 6: 
 page_content='西天取经后,序章故事发生,观音菩萨派黑熊精参与了围剿孙悟空。其实黑熊精不想去,反而善财童子(红孩儿)挺想去的,只是观音指名,黑熊精不敢违背。

孙悟空死后,作为报酬,黑熊精终于摘掉了金箍咒,得到了悟空的六根之一,回到了黑风山。

黑熊精试图炼化孙悟空的遗物,但却因法力不够始终无法炼化,于是把遗物供奉在塔顶,用来吸引妖怪做他手下。' metadata={'source': '黑悟空.txt'}

Chunk 7: 
 page_content='这时的黑风山已有百年的时间群龙无首,黑熊精知道光靠蛮力和吸引力很难统合这些妖怪,于是做了一系列举动。不过因为他智力不高,所以这些主意效果也不是很好。

首先他用南海学来的法术把凌虚子复活——但这个法术需要用凌虚子的同族活祭,而且凌虚子以后都要吸食同族的鲜血过活。凌虚子复活后接受不了,上吊自尽了。避火罩本来被黑熊精找到送给凌虚子,也就当给凌虚子陪葬了。' metadata={'source': '黑悟空.txt'}

Chunk 8: 
 page_content='黑熊精见这招不合适,又扶持了一个够强的狼妖,给他起名灵虚子(和凌虚子音同字不同,可能也是黑熊精没啥文化)。但这灵虚子是从狮驼国跑过来的外来户,而且修炼法门过于血腥暴力,引起黑风山本地狼妖的不满,并最终差点引起灵虚子对本地狼妖的大屠杀。不过最终被新任蛇妖统领白衣秀士阻止。' metadata={'source': '黑悟空.txt'}

Chunk 9: 
 page_content='黑熊精对这些事有种无力感,于是重建观音禅院,想用复活凌虚子的法术复活金池长老,以解寂寞。没想到金池的魂魄依然惦记着他生前藏着的财物,没有复活到肉身之上,而是复活到了财物上,成了一个精神不正常的大头怪物。

这期间还发生了一件小事:

黑风山的土地遇到一个老道士。老道士和土地相谈甚欢,然后教了土地定身法和聚形散气等技能。' metadata={'source': '黑悟空.txt'}

Chunk 10: 
 page_content='土地问你为什么教我这个?老道士说,将来会有一个天命人路过此地,我不方便见他,请你代我把这两招传授给他。说完就架云飞走了。

飞走的时候,掉下了一片菩提叶。' metadata={'source': '黑悟空.txt'}