在学习RAG的项目中需要用到PDF文档分块的相关知识点,现在将回顾的NLP中的分块技术以及具体的PDF分块方法总结如下:
NLP分块
-
按字符分块
此方法将文本分解为单个字符。它适用于需要细粒度文本分析的任务,例如字符级语言模型或某些类型的文本预处理。 -
按Token分块
将文本分割成token,是自然语言处理中的一种标准方法。基于令牌的组块对于文本分类、语言建模和其他依赖于token化输入的 NLP 应用程序等任务来说是必不可少的。 -
按段落分块
按段落分段整理文本有助于维护文档的整体结构和流程。此方法适用于需要较大上下文的任务,如文档摘要或内容提取。 -
递归分块
这涉及到重复地将数据分解成更小的块,通常用于分层数据结构。递归组块有利于需要多级分析的任务,如主题建模或层次聚类。 -
语义分块
根据意义而非结构元素对文本进行分组对于需要理解数据上下文的任务至关重要。语义块利用诸如句子嵌入等技术来确保每个块代表一个连贯的主题或想法。 -
代理分块
这种方法的重点是在识别和分组文本的基础上增加参与的代理,如人或组织。它在信息抽取和实体识别任务中非常有用,因为理解不同实体之间的角色和关系非常重要。
基于Langchain的文本分块技术——5行代码
递归字符分块
charSplitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=50)
chunks = charSplitter.split_text(text
token分块
splitter = TokenSplitter(max_tokens=512)
chunks = splitter.split_text(text)
句子分块
# 初始化SentenceSplitter ,每个块最多5个句子
splitter = SentenceSplitter(max_length=5)
chunks = splitter.split_text(text)
正则分块
splitter = RegexSplitter(pattern=r'\n\n+')
chunks = splitter.split_text(text)
Markdown分块
splitter = MarkdownSplitter()
chunks = splitter.split_text(text)
PDF分块
首先需要明白虽然三种PDF分块方法,但是内容最开始都按照不同行划分。如下图所示,每行的内容包含原本内容、id、height、width以及size这些属性,这些属性与后面信息拼接相关。
pdf分块解析
pdf分块解析,尽量保证一个小标题+对应文档在一个文档块,其中文档块的长度分别是512和1024。这种方式划分文本块,是按照文本字体的大小区分!
代码:
def ParseBlock(self, max_seq = 1024):
with pdfplumber.open(self.pdf_path) as pdf:
for i, p in enumerate(pdf.pages):
header = self.GetHeader(p)
if(header == None):
continue
# 这个代码是将页面中的每行代码进行分块 并计算它的位置信息
texts = p.extract_words(use_text_flow=True, extra_attrs = ["size"])[::]
squence = ""
lastsize = 0
for idx, line in enumerate(texts):
if(idx <1):
continue
if(idx == 1):
if(line["text"].isdigit()):
continue
cursize = line["size"]
text = line["text"]
if(text == "□" or text == "•"):
continue
elif(text== "警告!" or text == "注意!" or text == "说明!"):
# 注意标识提醒你:如未按照该警告内容操作可能会对您或者她人造成人身伤害或生命危险-----说明
if(len(squence) > 0):
self.Datafilter(squence, header, i, max_seq = max_seq)
squence = ""
# 两个数值在保留五位小数后相等,那么这个比较表达式的结果将是True;如果不相等,结果将是False。
elif(format(lastsize,".5f") == format(cursize,".5f")):
if(len(squence)>0):
squence = squence + text
else:
squence = text
else:
lastsize = cursize
if(len(squence) < 15 and len(squence)>0):
squence = squence + text
else:
if(len(squence) > 0):
self.Datafilter(squence, header, i, max_seq = max_seq)
squence = text
if(len(squence) > 0):
self.Datafilter(squence, header, i, max_seq = max_seq)
最终分块的结果
通过这个方法我们可以将一个小标题下的信息划分到一起(满足不超过最大长度的情况下)
pdf滑窗法解析
pdf滑窗法解析,把文档句号分割,然后构建滑动窗口,其中文档块的长度分别是256和512。
def SlidingWindow(self, sentences, kernel = 512, stride = 1):
sz = len(sentences)
cur = ""
fast = 0
slow = 0
while(fast < len(sentences)):
sentence = sentences[fast]
if(len(cur + sentence) > kernel and (cur + sentence) not in self.data):
self.data.append(cur + sentence + "。")
cur = cur[len(sentences[slow] + "。"):]
slow = slow + 1
cur = cur + sentence + "。"
fast = fast + 1
def ParseAllPage(self, max_seq = 512, min_len = 6):
all_content = ""
for idx, page in enumerate(PdfReader(self.pdf_path).pages):
page_content = ""
text = page.extract_text()
words = text.split("\n")
for idx, word in enumerate(words):
text = word.strip().strip("\n")
if("...................." in text or "目录" in text):
continue
if(len(text) < 1):
continue
if(text.isdigit()):
continue
page_content = page_content + text
if(len(page_content) < min_len):
continue
all_content = all_content + page_content
sentences = all_content.split("。")
self.SlidingWindow(sentences, kernel = max_seq)
通过这个PDF滑窗法我们可以构造有重叠的文档块,每个文档块的开始是上一个文档块的第二句信息。通过这样避免信息的丢失。
pdf非滑窗法解析
pdf非滑窗法解析,把文档句号分割,然后按照文档块预设尺寸均匀切分,其中文档块的长度分别是256和512。
def ParseOnePageWithRule(self, max_seq = 512, min_len = 6):
for idx, page in enumerate(PdfReader(self.pdf_path).pages):
page_content = ""
text = page.extract_text()
words = text.split("\n")
for idx, word in enumerate(words):
text = word.strip().strip("\n")
if("...................." in text or "目录" in text):
continue
if(len(text) < 1):
continue
if(text.isdigit()):
continue
page_content = page_content + text
if(len(page_content) < min_len):
continue
if(len(page_content) < max_seq):
if(page_content not in self.data):
self.data.append(page_content)
else:
sentences = page_content.split("。")
cur = ""
for idx, sentence in enumerate(sentences):
if(len(cur + sentence) > max_seq and (cur + sentence) not in self.data):
self.data.append(cur + sentence)
cur = sentence
else:
cur = cur + sentence
按照每页PDF分块 设定固定大小的块 每页的内容可能有没有被加入到data文本块中 有些信息会被忽略。