RAG(Retrieval-Augmented Generation,检索增强生成)模型是一种结合了检索和生成能力的自然语言处理模型。它通过检索相关的文档片段,并将这些信息作为生成过程的上下文,以提高生成质量和准确性。在RAG模型中,文本分割是一个非常关键的步骤。合理地分割文档文本,不仅能够提高检索的效率,还能更有效地将检索到的信息提供给生成模型,使生成内容更加连贯和准确。
RAG系统中,文本分割的实现方法有很多,具体可以根据任务需求和文档类型选择合适的策略:
字符分割是最基础的文本分割方式,按照指定的分隔符进行分割。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)
运行效果:
['在西天取经的几百年前黑风山上有', '上有一只黑熊精占山为王,自称黑', '称黑风大王。']
递归字符文本分割是字符分割的升级版,它以字符分割为基础,但在分割时引入了递归的机制。在初步分割之后,再对一些不完整或冗长的片段进行进一步细分,从而获得更加细粒度的分割。这个方法比简单的字符分割更加灵活,可以通过递归深度来控制分割的粒度。 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)
运行效果:
['在西天取经的几百年前黑风山上有', '一只黑熊精占山为王', ',自称黑风大王。']
特定文档分割是基于文档结构对文本进行分割的一种方式。不同的文档类型通常有各自的结构化信息,例如书籍中的章节和段落、网页中的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='第二章二级内容展示')]
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'}