文本分割器
1. 什么的文本分割器?
文本切割器(Text Splitter) 是自然语言处理中的一个工具,用于将长文本(如文档、文章、网页内容等)分割成较小的、可管理的文本块(chunks)。
2.为什么需要文本分割器?
在自然语言处理和大语言模型的时代,文本分割变得至关重要,主要原因如下:
-
处理长文本限制:大多数大语言模型都有上下文窗口限制。例如,一个模型可能一次只能处理4096个令牌。如果要分析一本几百页的书,必须先将书分割成模型能够“消化”的小块。
-
提升检索效果:在检索增强生成中,我们首先将文档库分割成块,然后为这些块创建向量索引。当用户提问时,系统会检索与问题最相关的几个块,并将其提供给LLM来生成答案。分割的质量直接影响了检索的准确性。一个语义完整、大小合适的块更容易被准确检索。
-
聚焦关键信息:将文本分割后,可以更精确地定位到包含特定信息的段落,避免无关信息的干扰。
-
结构化非结构化数据:文本分割是将非结构化文本数据转化为更结构化形式的第一步,便于后续的分析和处理。
3.原理
- 将文档分成小的、有意义的块(句子)。
- 将小的块组合成一个更大的快,直到达到一定的大小。
- 一旦达到一定的大小,接着开始创建于下一个块重叠的部分。
注意!!!!!!!
在配置文本分割器时,有两个非常重要的参数:
-
块大小:定义每个文本片段的最大长度。
-
块重叠:定义相邻两个块之间重叠的文本量。
为什么需要重叠???????????
为了避免在分割点时丢失重要的上下文信息。例如,一个关键概念可能在一段的末尾被引入,在下一段的开头被详细解释。如果没有重叠,检索到其中一个块时可能会丢失另一部分相关信息。
4.主要的文本分割方法
文本分割器可以根据其分割策略分为几种主要类型:
1. 基于规则/分隔符的分割
这种方法依赖预定义的规则或字符(分隔符)来切分文本。
-
简单字符分割:最原始的方法,直接按字符数或令牌数切割,完全不考虑语义,会切断单词和句子。
-
分隔符分割:使用标点符号或特定字符作为分割点。
-
常用分隔符:句号(
.)、换行符(\n)、逗号(,)、分号(;)等。 -
优点:实现简单、速度快、可解释性强。
-
缺点:严重依赖分隔符的质量和密度,对于结构不规整的文本效果差。
-
2. 递归分割
这是一种更智能的规则方法,是目前最流行和通用的策略之一。它采用分层、递归的方式使用分隔符。
-
工作原理:
-
首先使用一组按优先级排序的分隔符列表(例如:
["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""])。 -
尝试用第一个(也是最大的)分隔符(如
"\n\n")来分割文本。 -
检查分割后的每个块,如果某个块仍然太大,就递归地使用下一个优先级较低的分隔符(如
"\n")对这个块进行分割。 -
重复此过程,直到所有块都满足大小要求或分隔符列表用完。
-
-
优点:能很好地保留文本的层级结构(段落 -> 句子),生成的块大小均匀,适用性非常广。
-
缺点:仍然是基于表面特征,而非深层语义。
3. 基于语义/模型的分割
这是最先进的方法,利用自然语言处理模型来理解文本内容,从而在语义边界处进行分割。
-
工作原理:使用预训练模型(如BERT、Sentence Transformers)来计算句子或段落之间的语义相似度。当模型检测到相邻句子之间的语义发生显著变化(即主题转换)时,就会在此处分割。
-
优点:分割质量最高,能产生语义上高度连贯的块,非常适合复杂的学术论文或长篇报告。
-
缺点:计算成本高、速度慢、实现复杂,需要机器学习专业知识。
4. 基于专门文档结构的分割
针对特定格式的文档,使用其固有的结构进行分割。
-
示例:
-
Markdown分割器:根据标题(
#,##)、代码块(```)等标记来分割,能保留文档的树状结构。 -
HTML分割器:根据标签(如
<p>,<div>,<h1>)进行分割。
-
-
优点:对于目标格式的文档效果极佳,能最大化地利用元信息。
-
缺点:通用性差,只能用于特定类型的文档。
5. 实际应用(以LangChain为例)
在流行的开发框架中,上述策略被封装成了具体的类:
| 分割器类型 | 描述与特点 |
|---|---|
| CharacterTextSplitter | 基于字符的简单分割。直接按固定字符数进行切割,不考虑语义,可能会在单词或句子中间切断。 |
| RecursiveCharacterTextSplitter | 实现了递归分割策略,是官方推荐的默认选择。通过分层使用分隔符列表来切分文本,能更好地保留结构并生成大小均匀的块。 |
| TokenTextSplitter | 按LLM的令牌数进行分割,能更精确地控制上下文窗口。分割依据是模型本身的令牌化方式,而非简单的字符数。 |
| SentenceTransformersTokenTextSplitter | 专为SentenceTransformer模型设计,按该模型的特定令牌化方式进行分割,确保与目标模型完美匹配。 |
| MarkdownHeaderTextSplitter | 基于专门文档结构的分割,用于Markdown。根据标题(#、##)等标记来分割,能保留文档的层级结构。 |
| NLTKTextSplitter / SpacyTextSplitter | 利用NLTK或Spacy库的句子检测功能进行分割,属于更精细的基于规则的分割。能在完整的句子边界处进行切分,保证句子的完整性。 |
在此,个人只学习了几种,如下所示。
1.文档分割(使用递归字符切分器)
加载的文档是利用AI生成的一篇小作文(test.txt).
星穹织梦
暮色像浸了墨的宣纸,缓缓晕染开整个苍穹。当最后一缕霞光隐入远山,星空便如一幅藏在夜色里的织锦,被晚风轻轻展开。碎金般的星子缀在墨蓝的天幕上,有的疏朗如棋局,有的稠密似花雨,呼吸间仿佛能嗅到星子散发的清冽气息,带着宇宙深处的静谧与温柔。
我躺在草地上,看银河如一条发光的绸带,横亘在天际。牛郎织女星隔河相望,千百年来诉说着未改的牵挂,那些流淌在星光里的传说,让每一颗星都有了温热的灵魂。几颗流星倏然划过,拖着细碎的光尾,像谁不小心打翻了装着萤火虫的琉璃瓶,光屑簌簌落下,在眼底织成转瞬即逝的梦。我伸出手,指尖似乎触到了星光的微凉,那是跨越亿万光年的问候,温柔得能抚平心底所有褶皱。
星光漫过肩头,像一层薄纱笼罩着我。此刻,所有的喧嚣都沉了下去,只剩下心跳与星子的脉动同频。原来宇宙这样辽阔,而那些困扰我们的烦恼,不过是尘埃里的微光。星子不语,却用永恒的光芒告诉我们,时光漫长,万物有章,每一个孤独的灵魂,都能在星空中找到属于自己的慰藉与方向。
夜深了,星光依旧温柔。它们是宇宙的诗行,是岁月的印记,在无边的夜色里,织就一场关于永恒与美好的梦,让每个仰望的人,都能在梦里寻到一片心灵的栖息地。
实现代码(使用RecursiveCharacterTextSplitter):
from langchain.text_splitter import RecursiveCharacterTextSplitter
#加载要切分的文档
with open("test.txt") as f:
article = f.read()
#使用递归字符切分器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size = 50,#切分的文本块大小,一般通过长度函数计算
chunk_overlap = 20,# 切分的文本块重叠大小,一般通过长度函数计算
length_function = len,#长度函数,也可以传递tokenize函数
add_start_index = True,# 是否添加开始索引
)
text = text_splitter.create_documents([article])
text
实现结果(以列表形式呈现,如有需要可以直接遍历列表只获取page_content内容):
[Document(metadata={'start_index': 0}, page_content='星穹织梦\u200b'),
Document(metadata={'start_index': 6}, page_content='暮色像浸了墨的宣纸,缓缓晕染开整个苍穹。当最后一缕霞光隐入远山,星空便如一幅藏在夜色里的织锦,被晚'),
Document(metadata={'start_index': 35}, page_content='远山,星空便如一幅藏在夜色里的织锦,被晚风轻轻展开。碎金般的星子缀在墨蓝的天幕上,有的疏朗如棋局,有'),
Document(metadata={'start_index': 65}, page_content='星子缀在墨蓝的天幕上,有的疏朗如棋局,有的稠密似花雨,呼吸间仿佛能嗅到星子散发的清冽气息,带着宇宙深'),
Document(metadata={'start_index': 95}, page_content='仿佛能嗅到星子散发的清冽气息,带着宇宙深处的静谧与温柔。\u200b'),
Document(metadata={'start_index': 125}, page_content='我躺在草地上,看银河如一条发光的绸带,横亘在天际。牛郎织女星隔河相望,千百年来诉说着未改的牵挂,那'),
Document(metadata={'start_index': 154}, page_content='星隔河相望,千百年来诉说着未改的牵挂,那些流淌在星光里的传说,让每一颗星都有了温热的灵魂。几颗流星倏'),
Document(metadata={'start_index': 184}, page_content=',让每一颗星都有了温热的灵魂。几颗流星倏然划过,拖着细碎的光尾,像谁不小心打翻了装着萤火虫的琉璃瓶,'),
Document(metadata={'start_index': 214}, page_content='尾,像谁不小心打翻了装着萤火虫的琉璃瓶,光屑簌簌落下,在眼底织成转瞬即逝的梦。我伸出手,指尖似乎触到'),
Document(metadata={'start_index': 244}, page_content='织成转瞬即逝的梦。我伸出手,指尖似乎触到了星光的微凉,那是跨越亿万光年的问候,温柔得能抚平心底所有褶'),
Document(metadata={'start_index': 274}, page_content='越亿万光年的问候,温柔得能抚平心底所有褶皱。\u200b'),
Document(metadata={'start_index': 298}, page_content='星光漫过肩头,像一层薄纱笼罩着我。此刻,所有的喧嚣都沉了下去,只剩下心跳与星子的脉动同频。原来宇宙'),
Document(metadata={'start_index': 327}, page_content='去,只剩下心跳与星子的脉动同频。原来宇宙这样辽阔,而那些困扰我们的烦恼,不过是尘埃里的微光。星子不语'),
Document(metadata={'start_index': 357}, page_content='我们的烦恼,不过是尘埃里的微光。星子不语,却用永恒的光芒告诉我们,时光漫长,万物有章,每一个孤独的灵'),
Document(metadata={'start_index': 387}, page_content='我们,时光漫长,万物有章,每一个孤独的灵魂,都能在星空中找到属于自己的慰藉与方向。\u200b'),
Document(metadata={'start_index': 430}, page_content='夜深了,星光依旧温柔。它们是宇宙的诗行,是岁月的印记,在无边的夜色里,织就一场关于永恒与美好的梦,'),
Document(metadata={'start_index': 459}, page_content='边的夜色里,织就一场关于永恒与美好的梦,让每个仰望的人,都能在梦里寻到一片心灵的栖息地。')]
2.按字符分割(使用字符切分器)
实现代码(使用CharacterTextSplitter):
from langchain.text_splitter import CharacterTextSplitter
#加载要切分的文档
with open("test.txt") as f:
article = f.read()
#使用字符切分器
text_splitter = CharacterTextSplitter(
separator="。", # 切割的表示字符,默认的是\n\n
chunk_size = 20,
chunk_overlap = 10,
length_function = len,
add_start_index = True,
is_separator_regex=False # 是否使用正则表达式
)
text = text_splitter.create_documents([article])
text
实现结果(以列表形式呈现,如有需要可以直接遍历列表只获取page_content内容):
[Document(metadata={'start_index': 0}, page_content='星穹织梦\u200b\n暮色像浸了墨的宣纸,缓缓晕染开整个苍穹'),
Document(metadata={'start_index': 26}, page_content='当最后一缕霞光隐入远山,星空便如一幅藏在夜色里的织锦,被晚风轻轻展开'),
Document(metadata={'start_index': 61}, page_content='碎金般的星子缀在墨蓝的天幕上,有的疏朗如棋局,有的稠密似花雨,呼吸间仿佛能嗅到星子散发的清冽气息,带着宇宙深处的静谧与温柔'),
Document(metadata={'start_index': 123}, page_content='\u200b\n我躺在草地上,看银河如一条发光的绸带,横亘在天际'),
Document(metadata={'start_index': 150}, page_content='牛郎织女星隔河相望,千百年来诉说着未改的牵挂,那些流淌在星光里的传说,让每一颗星都有了温热的灵魂'),
Document(metadata={'start_index': 199}, page_content='几颗流星倏然划过,拖着细碎的光尾,像谁不小心打翻了装着萤火虫的琉璃瓶,光屑簌簌落下,在眼底织成转瞬即逝的梦'),
Document(metadata={'start_index': 253}, page_content='我伸出手,指尖似乎触到了星光的微凉,那是跨越亿万光年的问候,温柔得能抚平心底所有褶皱'),
Document(metadata={'start_index': 296}, page_content='\u200b\n星光漫过肩头,像一层薄纱笼罩着我'),
Document(metadata={'start_index': 315}, page_content='此刻,所有的喧嚣都沉了下去,只剩下心跳与星子的脉动同频'),
Document(metadata={'start_index': 343}, page_content='原来宇宙这样辽阔,而那些困扰我们的烦恼,不过是尘埃里的微光'),
Document(metadata={'start_index': 373}, page_content='星子不语,却用永恒的光芒告诉我们,时光漫长,万物有章,每一个孤独的灵魂,都能在星空中找到属于自己的慰藉与方向'),
Document(metadata={'start_index': 428}, page_content='\u200b\n夜深了,星光依旧温柔'),
Document(metadata={'start_index': 441}, page_content='它们是宇宙的诗行,是岁月的印记,在无边的夜色里,织就一场关于永恒与美好的梦,让每个仰望的人,都能在梦里寻到一片心灵的栖息地')]
在这里可能会出现提示:
Created a chunk of size 25, which is longer than the specified 20
Created a chunk of size 34, which is longer than the specified 20
Created a chunk of size 61, which is longer than the specified 20
Created a chunk of size 26, which is longer than the specified 20
Created a chunk of size 48, which is longer than the specified 20
Created a chunk of size 53, which is longer than the specified 20
Created a chunk of size 42, which is longer than the specified 20
Created a chunk of size 27, which is longer than the specified 20
Created a chunk of size 29, which is longer than the specified 20
Created a chunk of size 54, which is longer than the specified 20
这表示我们切分的文本块大小太小了,将其扩大到至少61,就不会出现这些提示。
3. 代码文档切割
根据不同语言,对不同语言代码文档进行切割。通过language=Language.PYTHON设置。
实现代码(使用RecursiveCharacterTextSplitter):
from langchain.text_splitter import (
RecursiveCharacterTextSplitter,
Language
)
# 支持解析的编程语言有
# [e.value for e in Language]
#要切割的代码文档示例
PYTHON_CODE="""
def hello_world():
print("hello world)
#调用函数
hello_world()
"""
py_splitter = RecursiveCharacterTextSplitter.from_language(
language=Language.PYTHON,
chunk_size = 50,
chunk_overlap = 10
)
python_docs = py_splitter.create_documents([PYTHON_CODE])
python_docs
实现结果(以列表形式呈现,如有需要可以直接遍历列表只获取page_content内容):
[Document(metadata={}, page_content='def hello_world():\n print("hello world)'),
Document(metadata={}, page_content='#调用函数\nhello_world()')]
4.按token切割
这里可以使用令牌分割器(TokenTextSplitter),也可以使用字符切分器(CharacterTextSplitter)实现。
使用TokenTextSplitter:
from langchain.text_splitter import TokenTextSplitter
with open("test.txt") as f:
article = f.read()
#使用令牌分割器
text_splitter = TokenTextSplitter(
chunk_size = 4000,
chunk_overlap = 30
)
text = text_splitter.create_documents([article])
print(text[0])
使用CharacterTextSplitter:
from langchain.text_splitter import CharacterTextSplitter
#加载要切分的文档
with open("test.txt") as f:
article = f.read()
#使用字符切分器
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
chunk_size = 4000,
chunk_overlap = 30
)
text = text_splitter.create_documents([article])
print(text[0])
实现结果(以列表形式呈现,如有需要可以直接遍历列表只获取page_content内容):
page_content='星穹织梦
暮色像浸了墨的宣纸,缓缓晕染开整个苍穹。当最后一缕霞光隐入远山,星空便如一幅藏在夜色里的织锦,被晚风轻轻展开。碎金般的星子缀在墨蓝的天幕上,有的疏朗如棋局,有的稠密似花雨,呼吸间仿佛能嗅到星子散发的清冽气息,带着宇宙深处的静谧与温柔。
我躺在草地上,看银河如一条发光的绸带,横亘在天际。牛郎织女星隔河相望,千百年来诉说着未改的牵挂,那些流淌在星光里的传说,让每一颗星都有了温热的灵魂。几颗流星倏然划过,拖着细碎的光尾,像谁不小心打翻了装着萤火虫的琉璃瓶,光屑簌簌落下,在眼底织成转瞬即逝的梦。我伸出手,指尖似乎触到了星光的微凉,那是跨越亿万光年的问候,温柔得能抚平心底所有褶皱。
星光漫过肩头,像一层薄纱笼罩着我。此刻,所有的喧嚣都沉了下去,只剩下心跳与星子的脉动同频。原来宇宙这样辽阔,而那些困扰我们的烦恼,不过是尘埃里的微光。星子不语,却用永恒的光芒告诉我们,时光漫长,万物有章,每一个孤独的灵魂,都能在星空中找到属于自己的慰藉与方向。
夜深了,星光依旧温柔。它们是宇宙的诗行,是岁月的印记,在无边的夜色里,织就一场关于永恒与美好的梦,让每个仰望的人,都能在梦里寻到一片心灵的栖息地。'
还有很多分割器正在学习中,欢迎大家提出的各种建议。
又是一起努力学习的快乐时光!!!
更多推荐


所有评论(0)