本尼托·马丁写的。
从PDF中提取文本是许多AI和LLM(大型语言模型)应用中的一个关键且常常具有挑战性的步骤。高质量的文本提取在改进下游流程(如分词、创建嵌入或在向量数据库中进行索引)方面起着重要作用,从而提升应用的整体性能。PyMuPDF 是一个流行的库,因其简单、快速且高质量的文本提取功能而广受欢迎。
在这篇博客中,我们将探讨由Artifex(PyMuPDF的创建者)最近推出的免费库PyMuPDF4LLM。这个名为PyMuPDF4LLM的新库旨在简化从PDF中提取文本的工作,特别是为大型语言模型(LLM)和检索增强生成(RAG)的应用提供支持。它提供了两种关键的格式。
- pymupdf4llm.to_markdown() :提取为 Markdown 格式的内容:
- pymupdf4llm.LlamaMarkdownReader() :提取内容并转换为 LlamaIndex 文档对象:
我们将重点关注 _tomarkdown,因为它包含了多个可以让它实现功能的超参数,比如图像提取,使其既适用于文本处理,也适用于多模态应用。
让我们来深入吧!
主要特点所有的超参数(超参数是指在模型训练过程中需要调整的参数)可以在API文档中找到。虽然我们会详细讲解几个参数,但PyMuPDF4LLM的关键特性可以总结如下:
- 文本提取:提取Markdown格式的内容。
- 内容拆分:支持将元数据、表格和图片列表添加到提取的内容中。
- 图片提取:定义图片的尺寸、分辨率和格式。
- 嵌入图片:图片直接嵌入Markdown输出中。
- 文本单词提取:精确提取PDF中的单词。
这是第一步,如下所示,文本将被提取为Markdown格式,可以选择要提取文档的哪些页面。
!pip install -qq pymupdf4llm # 安装pymupdf4llm库
import pymupdf4llm
# 将 PDF 文档转换为 Markdown 格式
md_text = pymupdf4llm.to_markdown(doc="/content/document.pdf", # PDF 文件路径
pages = [0, 1, 2])
输出内容
#### 在提供适当引用的前提下,谷歌特此授予许可,仅用于新闻报道或学术作品中复制本文档中的表格和图表。\n\n## 注意力,这就是你需要的\n\n\n**
阿什什瓦尼[∗]**
谷歌大脑
[email protected]\n\n```\n**
利奥恩·琼斯[∗]**
谷歌研究院
[email protected]\n\n```\n\n**
诺姆·沙泽尔[∗]**
谷歌大脑
[email protected]\n\n```\n
分块处理
注:此处“分块处理”指的是将数据或文本分割成较小的管理单元,以便于处理。
这里就有趣了。纯Markdown文本对于LLM应用来说并不理想——元数据在提高模型准确性和性能方面起着关键作用。如下面的例子所示,可以从中提取大量信息,包括文档的创建日期、文件路径、图像坐标以及目录(TOC),所有这些都可以丰富应用的上下文信息。
md文本 = pymupdf4llm.转换为Markdown(doc="/content/document.pdf", # 文件路径,指向PDF文档
pages = [0, 1, 2], # 指定要转换的页面
page_chunks = True) # 指定是否将页面分割成片段
# 将PDF文件转换为Markdown文本,并指定要转换的页面及其片段
注释:
pymupdf4llm
是一个库或模块名称,用于将PDF文件转换为Markdown文本。转换为Markdown
表示将文件转换为Markdown格式。doc="/content/document.pdf"
指定PDF文件路径。pages=[0, 1, 2]
指定要转换的页面。page_chunks=True
表示是否将页面分割成片段。
下面的输出结果是通过添加了page_chunks
选项得到的多个片段其中之一。
输出片段
{'metadata': {'format': 'PDF 1.5',
'title': '',
'author': '',
'subject': '',
'keywords': '',
'creator': 'LaTeX with hyperref',
'producer': 'pdfTeX-1.40.25',
'creationDate': 'D:20240410211143Z',
'modDate': 'D:20240410211143Z',
'trapped': '',
'encryption': None,
'file_path': '/content/document.pdf',
'page_count': 15,
'page': 3},
'toc_items': [[2, '编码器和解码器栈', 3], [2, '注意力机制', 3]],
'tables': [],
'images': [{'number': 0,
'bbox': (196.5590057373047,
72.00198364257812,
415.43902587890625,
394.4179992675781),
'transform': (218.8800048828125,
0.0,
-0.0,
322.416015625,
196.5590057373047,
72.00198364257812),
'width': 1520,
'height': 2239,
'colorspace': 3,
'cs-name': 'DeviceRGB',
'xres': 96,
'yres': 96,
'bpc': 8,
'size': 264957}],
'graphics': [],
'text': '\n\n图 1 中:Transformer 模型架构。\n\nTransformer 使用堆叠的自注意力和逐点全连接层来组成编码器和解码器,如图 1 中的左半部分和右半部分所示。\n\n**3.1 编码器和解码器栈。**\n\n**编码器:** 编码器由 N = 6 层相同的层组成。'
'words': []}]
目录项(toc_items
)以格式 [层级, 标题, 页码
] 提取为,其中 层级
是层级级别(一级为 1,二级为 2,三级为 3 等,以此类推)。
在之前的例子中,并没有从 images
字段中提取图片,尽管我们可以看到相关的 images
字段。需要将 write_images
参数设置为 True
。此外,还可以选择其他参数,例如图片格式、dpi(分辨率),或者保存提取图片的文件路径。
# 将PDF文件转换为Markdown格式,并将图片保存到指定路径
md_text = pymupdf4llm.to_markdown(doc="/content/document.pdf",
pages = [0, 1, 2],
page_chunks = True,
write_images = True,
image_path = "/content/images",
image_format = "jpg",
dpi = 200)
通过这样做,图像将保存在指定的文件夹中,并在相应代码块的 Markdown 文本中的相应字段中添加一个占位符。该占位符将包含文档名称、页码和图像编号。
输出图片
'graphics': [],
'text': '\n\n图1:\n\nTransformer模型采用了这种整体架构,使用堆叠的自注意力机制和点对点的前馈网络。\n\n'
如上所示,提取的图像如下所示。从文件中提取图像的能力使得它们可用于多模态LLM应用(多模态大语言模型应用),增加了额外的功能和灵活性层。
来源自Arxiv网站(https://arxiv.org/abs/1706.03762)
此外,你可以通过使用embed_images
参数将图片以base64编码直接嵌入Markdown文本中。这种方法会把图片嵌入到Markdown文件里,但会增加文件的大小,并且无法单独保存图片。
就像识别图像并将它们的坐标包含在 JSON 字典中一样,每个识别出的表格也会将其坐标添加到相应的部分中。
生成的表格
'toc_items': [[2, '英语句法解析', 9]],
'tables': [{'bbox': (108.0,
129.87200927734375,
508.73699951171875,
384.1730041503906),
'rows': 8,
'columns': 3}],
'images': [],
'graphics': [],
'text': '表3:Transformer架构的变种。未列出的参数与基础模型中的保持一致。所有指标均基于德英翻译测试集newstest2013。列出的困惑度是根据我们的字节对编码计算出的每个词元的困惑度,不应与按单词计算的困惑度相比较
单词抽取
现在我们已经提取了文本、表格和图片中的内容,这使得我们的LLM应用有了更多的元数据。为了进一步丰富元数据,我们可以通过设置extract_words
参数。这将生成一个按阅读顺序排列的词汇列表,与每个特定片段关联,并包含相应的坐标信息,就像处理表格和图片一样。这包括表格单元格内的文字以及页面上的多列文字,类似下图所示。
作者:阿提法克斯
md_text = pymupdf4llm.to_markdown(doc="/content/document.pdf",
pages = [5, 6, 7],
page_chunks = True,
write_images = True,
image_path = "/content/images",
image_format = "jpg",
dpi = 200,
extract_words = True)
和之前的输出一样,我们可以在相应的字段中看到单词序列。
单词序列输出
'graphics': [],
'text': '表1:最长路径长度表',
'words': [(107.69100189208984,
71.19241333007812,
129.12155151367188,
81.05488586425781,
'表',
0,
0,
0),
(131.31829833984375,
71.19241333007812,
138.9141845703125,
81.05488586425781,
'1:',
0,
0,
1),
(144.78195190429688,
71.19241333007812,
185.4658203125,
81.05488586425781,
'最大',
0,
0,
2),
(187.65281677246094,
71.19241333007812,
204.46530151367188,
81.05488586425781,
'路径长度',
0,
0,
3),
有了这样的文档结构,我们不仅可以丰富我们的向量数据库,还可以添加纯文本 Markdown、图像、表格及其相应的坐标,还能控制一些图像参数,比如分辨率或格式。这种方法极大地增强了任何需要从 PDF 中获取信息的应用程序,特别是那些需要高精度响应的应用程序。
技术笔记:多模态应用(多种模态的集成)作者名:Benito Martin (由AI生成)
所以,当我们能够从文本中提取出带有元数据和图片的丰富内容后,我们能用 PyMuPDF4LLM 做什么呢?LLM 应用程序可以完成各种任务,但是能够包含文本和图片则可以让多模态模型的应用成为可能,从而提升应用程序的表现。
让我们创建一个笔记本(点击链接查看),来展示如何使用PyMuPDF4LLM构建一个结合了Llama Index框架和Qdrant向量存储的多模态应用。
该应用程序遵循这些步骤。这是基于RAG(检索增强生成)架构的一个简单工作流程,增加了在向量数据库中收集图像(除了文本外)的能力,从而可以检索图像。需要熟悉此工作流程及其常用库的使用。
- 安装库和依赖项:pymupdf4llm、llamaindex 和 openai 的 clip 嵌入
- 使用 pymupdf4llm 加载文档
- 文档对象定制:根据 pymupdf4llm 提取的信息选择元数据
- 向量存储中的集合创建:文本应用通常只有一个文本集合,但在这种情况下,我们将添加一个包含图像嵌入的第二个集合
- 索引:这一步是将我们的文本和图像索引到向量存储中的必要步骤
- 内容检索:根据用户的查询,我们将检索到最相似的文本片段和图像
步骤 1:安装库和设置环境变量
首先,我们需要安装几个库,比如pymupdf4llm
,llama index,qdrant和clip embeddings,以便为我们的图像生成嵌入向量。此外,我们还需要一个OPENAI_KEY来为LLM模型提供支持。
# 安装必要的Python包
!pip install -qq, pymupdf4llm # 用于处理PDF文档的库
!pip install -qq, llama-index # llama-index相关的核心库
!pip install -qq, llama-index-vector-stores-qdrant # llama-index与Qdrant向量存储的集成
!pip install -qq, git+https://github.com/openai/CLIP.git # 安装CLIP库,CLIP是一个开源项目
!pip install -qq, llama-index-embeddings-clip # llama-index与CLIP嵌入的集成
!pip install -qq, llama-index qdrant-client # llama-index与Qdrant客户端的集成
# 将用户数据中获取的OPENAI_API_KEY设置到环境变量中
os.environ["OPENAI_API_KEY"] = userdata.get('OPENAI_API_KEY')
步骤2:加载文件
然后我们加载文档,将文档分割成小块并提取jpg格式的图片以便进一步处理。为了简化,我们不会选择所有上述选项,但这可以根据应用程序的具体需求进行进一步的定制。
# 进行 Markdown 转换
docs = pymupdf4llm.to_markdown(doc="/content/document.pdf",
page_chunks = True,
write_images = True,
image_path = "/content/images",
image_format = "jpg")
步骤3:调整文档设置
Llama Index 需要一个 Document 对象。这些 Document 可以包含有用的 元数据
,并且会在每个片段上创建一个索引,这将有助于检索步骤。
因此,我们将根据从pymupdf4llm
提取的数据进行定制。文本将是主要内容,而作为元数据我们选择toc_items
、images
、page
和file_path
。这一设置可以根据具体应用进一步扩展和调整,但拥有如我们所见的丰富内容,使我们能够从一开始就增强过程,这对于高效利用LLM应用来说至关重要。
llama_documents = []
for document in docs:
# 提取 'metadata' 字段并将某些元素转换为所需格式
metadata = {
"file_path": document["metadata"].get("file_path"),
"page": str(document["metadata"].get("page")),
"images": str(document.get("images")),
"toc_items": str(document.get("toc_items")),
}
# 使用仅包含文本和已清理的元数据来创建 Document 对象
llama_document = Document(
text=document["text"],
metadata=metadata,
text_template="文本模板: Metadata: {metadata_str}\n-----\nContent: {content}",
)
llama_documents.append(llama_document)
第四步:创建向量数据库
一旦我们的文档符合所需的格式,并且图片已提取出来,我们就能使用Qdrant创建两个集合,一个用于图片,另一个用于文本。
正如之前提到的,这些集合将分别存储文本和图像的嵌入表示,当用户进行查询时,系统会一起检索这两个部分。
# 创建Qdrant客户端
client = qdrant_client.QdrantClient(location=":memory:")
# 创建文本数据集合
client.create_collection(
collection_name="text_collection",
vectors_config=VectorParams(size=1536, distance=Distance.COSINE) # 余弦距离
)
# 创建图像数据集合
client.create_collection(
collection_name="image_collection",
vectors_config=VectorParams(size=512, distance=Distance.COSINE) # 余弦距离
)
# 文本存储初始化
text_store = QdrantVectorStore(
client=client, collection_name="text_collection"
)
# 图像存储初始化
image_store = QdrantVectorStore(
client=client, collection_name="image_collection"
)
第五步:创建一个多媒体索引
既然我们的向量存储已经准备好了,我们可以将这些图像和文本分别存储在对应的集合中,文本集合会生成文本节点,而图像集合会生成图像节点。为此,Llama Index 提供了多模态向量存储索引(MultiModalVectorStoreIndex),我们需要指定文本和图像的路径。
storage_context = StorageContext.from_defaults(
vector_store=text_store, image_store=image_store
)
## 上下文图片
image_path = "/content/images"
## 读取指定路径下的图片文件
image_documents = SimpleDirectoryReader(image_path).load_data()
index = MultiModalVectorStoreIndex.from_documents(
llama_documents + image_documents,
storage_context=storage_context)
这些块代表我们预先定义的块。如果需要将块进一步细分,LlamaIndex 同样支持这种细分。为了简化,我们将保持 pymupdf4llm 提取的块大小不变。
步骤6:取内容
最后一步是提出一个问题并检查我们的流程效率如何。如下代码将检索1个文本节点和1个图像节点。
# 设置查询和检索器
query = "你能提供一张多头注意力的图片吗?"
retriever = index.as_retriever(similarity_top_k=1, image_similarity_top_k=1) # 检索器用于从索引中检索信息
retrieval_results = 检索结果 = retriever.retrieve(query)
以下代码段可以帮助我们可视化图像和文本的得分最高的节点。
import matplotlib.pyplot as plt
from PIL import Image
def plot_images(image_paths):
images_shown = 0
plt.figure(figsize=(16, 9))
for img_path in image_paths:
if os.path.isfile(img_path):
image = Image.open(img_path)
plt.subplot(2, 3, images_shown + 1)
plt.imshow(image)
plt.xticks([])
plt.yticks([])
images_shown += 1
if images_shown >= 9:
break
retrieved_image = []
for res_node in retrieval_results:
if isinstance(res_node.node, ImageNode):
print("找到了得分最高的图片节点")
print("-----------------------")
retrieved_image.append(res_node.node.metadata["file_path"])
else:
print("找到了得分最高的文本节点")
print("-----------------------")
display_source_node(res_node, source_length=200)
我们看到结果相当满意,因为文本部分引用了图2,这与图像部分中的内容相符。
作者是 Benito Martin
总结在这篇文章里,我们使用 pymupdf4llm
强大的功能构建了一个简单的RAG流程,用于从文档中提取元数据和图片资料,从而提升了应用的功能。只需要一行代码,即可提取所有相关数据,大大加速了管道创建过程。
Artifex的这个新LLM工具的独特功能,结合其他集成工具,例如与LlamaIndex集成的LlamaMarkdownReader
,为改进LLM应用程序并减少开发时间提供了新的可能性。
如果你喜欢看这些内容,你可以通过以下方式,比如,帮助我:
- 拍手 并在 Medium 上 关注 我!👏 👏 👏
- 关注 我的 GitHub 🎶 🎷 🎶
- 星标我的 项目 ⭐⭐⭐
- 在 LinkedIn 上分享我的内容吧! 💯💯💯
- 请我喝杯 咖啡,或者在 GitHub Sponsors 支持我 🚀🚀🚀
- 要 找 我合作吗? 👨💻👨💻👨💻
祝你编程愉快!
共同學習,寫下你的評論
評論加載中...
作者其他優質文章