1. 什么的文本分割器?

文本切割器(Text Splitter) 是自然语言处理中的一个工具,用于将长文本(如文档、文章、网页内容等)分割成较小的、可管理的文本块(chunks)。

2.为什么需要文本分割器?

在自然语言处理和大语言模型的时代,文本分割变得至关重要,主要原因如下:

  1. 处理长文本限制:大多数大语言模型都有上下文窗口限制。例如,一个模型可能一次只能处理4096个令牌。如果要分析一本几百页的书,必须先将书分割成模型能够“消化”的小块。

  2. 提升检索效果:在检索增强生成中,我们首先将文档库分割成块,然后为这些块创建向量索引。当用户提问时,系统会检索与问题最相关的几个块,并将其提供给LLM来生成答案。分割的质量直接影响了检索的准确性。一个语义完整、大小合适的块更容易被准确检索。

  3. 聚焦关键信息:将文本分割后,可以更精确地定位到包含特定信息的段落,避免无关信息的干扰。

  4. 结构化非结构化数据:文本分割是将非结构化文本数据转化为更结构化形式的第一步,便于后续的分析和处理。

3.原理

  1. 将文档分成小的、有意义的块(句子)。
  2. 将小的块组合成一个更大的快,直到达到一定的大小。
  3. 一旦达到一定的大小,接着开始创建于下一个块重叠的部分。

注意!!!!!!!

在配置文本分割器时,有两个非常重要的参数:

  • 块大小:定义每个文本片段的最大长度。

  • 块重叠:定义相邻两个块之间重叠的文本量。

为什么需要重叠???????????
        为了避免在分割点时丢失重要的上下文信息。例如,一个关键概念可能在一段的末尾被引入,在下一段的开头被详细解释。如果没有重叠,检索到其中一个块时可能会丢失另一部分相关信息。

4.主要的文本分割方法

文本分割器可以根据其分割策略分为几种主要类型:

1. 基于规则/分隔符的分割

这种方法依赖预定义的规则或字符(分隔符)来切分文本。

  • 简单字符分割:最原始的方法,直接按字符数或令牌数切割,完全不考虑语义,会切断单词和句子。

  • 分隔符分割:使用标点符号或特定字符作为分割点。

    • 常用分隔符:句号(.)、换行符(\n)、逗号(,)、分号(;)等。

    • 优点:实现简单、速度快、可解释性强。

    • 缺点:严重依赖分隔符的质量和密度,对于结构不规整的文本效果差。

2. 递归分割

这是一种更智能的规则方法,是目前最流行和通用的策略之一。它采用分层、递归的方式使用分隔符。

  • 工作原理

    1. 首先使用一组按优先级排序的分隔符列表(例如:["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""])。

    2. 尝试用第一个(也是最大的)分隔符(如"\n\n")来分割文本。

    3. 检查分割后的每个块,如果某个块仍然太大,就递归地使用下一个优先级较低的分隔符(如"\n")对这个块进行分割。

    4. 重复此过程,直到所有块都满足大小要求或分隔符列表用完。

  • 优点:能很好地保留文本的层级结构(段落 -> 句子),生成的块大小均匀,适用性非常广。

  • 缺点:仍然是基于表面特征,而非深层语义。

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='星穹织梦​
暮色像浸了墨的宣纸,缓缓晕染开整个苍穹。当最后一缕霞光隐入远山,星空便如一幅藏在夜色里的织锦,被晚风轻轻展开。碎金般的星子缀在墨蓝的天幕上,有的疏朗如棋局,有的稠密似花雨,呼吸间仿佛能嗅到星子散发的清冽气息,带着宇宙深处的静谧与温柔。​
我躺在草地上,看银河如一条发光的绸带,横亘在天际。牛郎织女星隔河相望,千百年来诉说着未改的牵挂,那些流淌在星光里的传说,让每一颗星都有了温热的灵魂。几颗流星倏然划过,拖着细碎的光尾,像谁不小心打翻了装着萤火虫的琉璃瓶,光屑簌簌落下,在眼底织成转瞬即逝的梦。我伸出手,指尖似乎触到了星光的微凉,那是跨越亿万光年的问候,温柔得能抚平心底所有褶皱。​
星光漫过肩头,像一层薄纱笼罩着我。此刻,所有的喧嚣都沉了下去,只剩下心跳与星子的脉动同频。原来宇宙这样辽阔,而那些困扰我们的烦恼,不过是尘埃里的微光。星子不语,却用永恒的光芒告诉我们,时光漫长,万物有章,每一个孤独的灵魂,都能在星空中找到属于自己的慰藉与方向。​
夜深了,星光依旧温柔。它们是宇宙的诗行,是岁月的印记,在无边的夜色里,织就一场关于永恒与美好的梦,让每个仰望的人,都能在梦里寻到一片心灵的栖息地。'

还有很多分割器正在学习中,欢迎大家提出的各种建议。

又是一起努力学习的快乐时光!!!

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐