文本分块是自然语言处理(NLP)中的一项关键技术,其作用是将较长的文本切割成更小、更易于处理的片段。这种分割通常是基于单词的词性和语法结构,例如将文本拆分为名词短语、动词短语或其他语义单位。这样做有助于更高效地从文本中提取关键信息。
在本文中,我将介绍在实际项目中用到几种文本分块策略,了解它们在构建基于大型语言模型(如RAG)的系统中的重要性。此外,我还将介绍如何有效地应用这些文本分块策略。通过这些策略,可以更好地处理和解析大规模文本数据,提高信息提取和语言理解的准确性和效率。
1、文本分块的重要性
在使用大型语言模型(LLM)时,文本分块的重要性不容忽视。这是因为几个关键因素对处理结果有着显著影响。
以一个实例来说明:假设你手头有一个15页的文档,满篇都是文字,你想对其进行摘要和问答处理。在这个过程中,最首要的步骤是提取整个文档的嵌入向量,这里就会遇到一些问题:
- 信息丢失问题:如果一次性提取整个文档的嵌入向量,虽然能够考虑到整个文档的上下文,但可能会丢失关于特定主题的大量有价值信息。这会导致语言模型提供的信息不够准确或存在遗漏。
- 分块大小限制:使用像OpenAI这样的模型时,需要特别关注分块大小。例如,GPT-4有32K的窗口大小限制。虽然这个限制通常不会造成问题,但从一开始就留意这一点是十分必要的。
因此,正确地运用文本分块技术,不仅能提高文本的整体质量和可访问性,还能防止因信息丢失或分块不当而导致的问题。这就是为何在处理大型文档时,采取文本分块而不是直接使用整个文档至关重要的原因。
2、文本分块策略
在自然语言处理中,不同的文本分块策略各具特色,适用于各种场景。接下来,我们将讨论这些策略的优缺点及其适用环境。
2.1传统方法拆分
(1) 简单粗暴方式(正则拆分)
正则表达式是一种在文本处理中广泛使用的工具,它可以通过特定的模式匹配来有效地处理字符串。在中文文本分块的场景中,正则表达式可以用来识别中文标点符号,从而将文本拆分成单独的句子。这种方法依赖于中文句号、“问号”、“感叹号”等标点符号作为句子结束的标志。虽然这种基于模式匹配的方法可能不如基于复杂语法和语义分析的方法精确,但它在大多数情况下足以满足基本的句子分割需求,并且实现起来更为简单直接。
import re
def split_sentences(text):
# 使用正则表达式匹配中文句子结束的标点符号
sentence_delimiters = re.compile(u'[。?!;]|n')
sentences = sentence_delimiters.split(text)
# 过滤掉空字符串
sentences = [s.strip() for s in sentences if s.strip()]
return sentences
text ="文本分块是自然语言处理(NLP)中的一项关键技术,其作用是将较长的文本切割成更小、更易于处理的片段。这种分割通常是基于单词的词性和语法结构,例如将文本拆分为名词短语、动词短语或其他语义单位。这样做有助于更高效地从文本中提取关键信息。"
sentences = split_sentences(text)
print(sentences)
在上面例子中,我们并没有采用任何特定的方式来分割句子。另外,还有许多其他的文本分块技术可以使用,例如词汇化(tokenizing)、词性标注(POS tagging)等。
#output
['文本分块是自然语言处理(NLP)中的一项关键技术,其作用是将较长的文本切割成更小、更易于处理的片段', '这种分割通常是基于单词的词性和语法结构,例如将文本拆分为名词短语、动词短语或其他语义单位', '这样做有助于更高效地从文本中提取关键信息']
(2) Spacy Text Splitter
Spacy是一个用于执行自然语言处理(NLP)各种任务的库。它具有文本拆分器功能,能够在进行文本分割的同时,保留分割结果的上下文信息。
import spacy
input_text = "文本分块是自然语言处理(NLP)中的一项关键技术,其作用是将较长的文本切割成更小、更易于处理的片段。这种分割通常是基于单词的词性和语法结构,例如将文本拆分为名词短语、动词短语或其他语义单位。这样做有助于更高效地从文本中提取关键信息。"
nlp = spacy.load( "zh_core_web_sm" )
doc = nlp(input_text)
for s in doc.sents:
print (s)
通过Spacy,我们可以智能地处理原始文本数据。
#output
文本分块是自然语言处理(NLP)中的一项关键技术,其作用是将较长的文本切割成更小、更易于处理的片段。
这种分割通常是基于单词的词性和语法结构,例如将文本拆分为名词短语、动词短语或其他语义单位。
这样做有助于更高效地从文本中提取关键信息。
2.2递归拆分
递归拆分是一种通过迭代方式,利用一组分隔符将输入文本切分成更小片段的方法。这个过程的特点是,如果在最初的尝试中未能将文本切分成符合预期大小的块,它会继续递归地使用不同的分隔符或标准,直到达到期望的块大小。
以下是一个用Langchain库实现递归拆分的示例。
#input text
input_text = "文本分块是自然语言处理(NLP)中的一项关键技术,其作用是将较长的文本切割成更小、更易于处理的片段。这种分割通常是基于单词的词性和语法结构,例如将文本拆分为名词短语、动词短语或其他语义单位。这样做有助于更高效地从文本中提取关键信息。"
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size = 100 , #设置所需的文本大小
chunk_overlap = 20 )
chunks = text_splitter.create_documents([input_text])
print (chunks)
递归拆分的输出如下所示:
#output
[Document(page_content='文本分块是自然语言处理(NLP)中的一项关键技术,其作用是将较长的文本切割成更小、更易于处理的片段。这种分割通常是基于单词的词性和语法结构,例如将文本拆分为名词短语、动词短语或其他语义单位。这样做有助'), Document(page_content='短语、动词短语或其他语义单位。这样做有助于更高效地从文本中提取关键信息。')]
2.3专门的结构化拆分
(1) HTML 文本拆分器
HTML文本拆分器是一种结构感知的文本分块工具。它能够在HTML元素级别上进行文本拆分,并且会为每个分块添加与之相关的标题元数据。这种拆分器的特点在于其对HTML结构的敏感性,能够精准地处理和分析HTML文档中的内容。
#input html string
html_string = """
<!DOCTYPE html>
<html>
<body>
<div>
<h1>Mobot</h1>
<p>一些关于Mobot的介绍文字。</p>
<div>
<h2>Mobot主要部分</h2>
<p>有关Mobot的一些介绍文本。</p>
<h3>Mobot第1小节</h3>
<p>有关Mobot第一个子主题的一些文本。</p>
<h3>Mobot第2小节</h3>
<p>关于Mobot的第二个子主题的一些文字。</p>
</div>
<div>
<h2>Mobot</h2>
<p>关于Mobot的一些文字</p>
</ div>
<br>
<p>关于Mobot的一些结论性文字</p>
</div>
</body>
</html>
"""
headers_to_split_on = [
( "h1" , "Header 1" ),
( "h2" , "标题 2" ),
( "h3" , "标题 3" ),
]
from langchain.text_splitter import HTMLHeaderTextSplitter
html_splitter = HTMLHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
html_header_splits = html_splitter.split_text(html_string)
print(html_header_split)
仅提取在header_to_split_on
参数中指定的HTML标题。
#output
[Document(page_content='Mobot'), Document(page_content='一些关于Mobot的介绍文字。 nMobot主要部分 Mobot第1小节 Mobot第2小节', metadata={'Header 1': 'Mobot'}), Document(page_content='有关Mobot的一些介绍文本。', metadata={'Header 1': 'Mobot', '标题 2': 'Mobot主要部分'}), Document(page_content='有关Mobot第一个子主题的一些文本。', metadata={'Header 1': 'Mobot', '标题 2': 'Mobot主要部分', '标题 3': 'Mobot第1小节'}), Document(page_content='关于Mobot的第二个子主题的一些文字。', metadata={'Header 1': 'Mobot', '标题 2': 'Mobot主要部分', '标题 3': 'Mobot第2小节'}), Document(page_content='Mobot div>', metadata={'Header 1': 'Mobot'}), Document(page_content='关于Mobot的一些文字 n关于Mobot的一些结论性文字', metadata={'Header 1': 'Mobot', '标题 2': 'Mobot'})]
(2) **Markdown 文本拆分 **
Markdown文本拆分是一种根据Markdown的语法规则(例如标题、Bash代码块、图片和列表)进行文本分块的方法。这种拆分工具具有对结构的敏感性,能够基于Markdown文档的结构特点进行有效的文本分割。
#input markdown string
markdown_text = '# Mobotnn ## Stonenn这是python n这是nn ## markdownnn 这是中文文本拆分'
from langchain.text_splitter import MarkdownHeaderTextSplitter
headers_to_split_on = [
("#", "Header 1"),
("##", "Header 2"),
("###", "Header 3"),
]
markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
md_header_splits = markdown_splitter.split_text(markdown_text)
print(md_header_splits)
MarkdownHeaderTextSplitter 能够根据设定的 headers_to_split_on 参数,将 Markdown 文本进行拆分。这一功能使得用户可以便捷地根据指定的标题将 Markdown 文件分割成不同部分,从而提高编辑和管理的效率。
#output
[Document(page_content='这是pythonn这是', metadata={'Header 1': 'Mobot', 'Header 2': 'Stone'}), Document(page_content='这是中文文本拆分', metadata={'Header 1': 'Mobot', 'Header 2': 'markdown'})]
(3) LaTex 文本拆分
LaTex文本拆分工具是一种专用于代码分块的工具。它通过解析LaTex命令来创建各个块,这些块按照逻辑组织,如章节和小节等。这种方式能够产生更加准确且与上下文相关的分块结果,从而有效地提升LaTex文档的组织和处理效率。
#input Latex string
latex_text = """documentclass{article}begin{document}maketitlesection{Introduction}大型语言模型 (LLM) 是一种机器学习模型,可以在大量文本数据上进行训练,以生成类似人类的语言。近年来,法学硕士在各种自然语言处理任务中取得了重大进展,包括语言翻译、文本生成和情感分析。subsection{法学硕士的历史}最早的法学硕士是在 20 世纪 80 年代开发的和 20 世纪 90 年代,但它们受到可处理的数据量和当时可用的计算能力的限制。然而,在过去的十年中,硬件和软件的进步使得在海量数据集上训练法学硕士成为可能,从而导致subsection{LLM 的应用}LLM 在工业界有许多应用,包括聊天机器人、内容创建和虚拟助理。它们还可以在学术界用于语言学、心理学和计算语言学的研究。end{document}"""
from langchain.text_splitter import LatexTextSplitter
Latex_splitter = LatexTextSplitter(chunk_size= 100 , chunk_overlap= 0 )
latex_splits = Latex_splitter.create_documents([latex_text])
print (latex_splits)
在上述示例中,我们注意到代码分割时的重叠部分设置为0。这是因为在处理代码分割过程中,任何重叠的代码都可能完全改变其原有含义。因此,为了保持代码的原始意图和准确性,避免产生误解或错误,设置重叠部分为0是必要的。
[Document(page_content='documentclass{article}begin{document}maketitlesection{Introduction}大型语言模型 (LLM)'), Document(page_content='是一种机器学习模型,可以在大量文本数据上进行训练,以生成类似人类的语言。近年来,法学硕士在各种自然语言处理任务中取得了重大进展,包括语言翻译、文本生成和情感分析。subsection{法学硕士的历史'), Document(page_content='}最早的法学硕士是在'), Document(page_content='20 世纪 80 年代开发的和 20 世纪 90'), Document(page_content='年代,但它们受到可处理的数据量和当时可用的计算能力的限制。然而,在过去的十年中,硬件和软件的进步使得在海量数据集上训练法学硕士成为可能,从而导致subsection{LLM 的应用}LLM'), Document(page_content='在工业界有许多应用,包括聊天机器人、内容创建和虚拟助理。 它们还可以在学术界用于语言学、心理学和计算语言学的研究。end{document}')]
当你决定使用哪种分块器处理数据时,重要的一步是提取数据嵌入并将其存储在向量数据库(Vector DB)中。上面的例子中使用文本分块器结合 LanceDB 来存储数据块及其对应的嵌入。LanceDB 是一个无需配置、开源且无服务器的向量数据库,其数据持久化在硬盘驱动器上,允许用户在不超出预算的情况下实现扩展。此外,LanceDB 与 Python 数据生态系统兼容,因此你可以将其与现有的数据工具(如 pandas、pyarrow 等)结合使用。
3、总结
总结来说,文本分块是一个看似简单却极为重要的任务,它展示了多种挑战。没有通用的策略适用于所有情况,也没有固定的块大小适合所有解决方案。某些方法可能适用于特定的数据或解决方案,但对其他情况则可能不适用。通过这篇博客,我们不仅强调了文本分块的重要性,还提供了一些在执行文本分块时的新见解。