百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

系列教程 | 用Jina搭建PDF搜索引擎Part 1

csdh11 2024-12-31 10:26 4 浏览

PDF Search 系列教程来咯,在 Part 1 中,我们将演示如何从 PDF 中提取、处理并存储图像及文本。

随着神经搜索 (Neural Search) 技术的普及,越来越多开发者,开始尝试用 Jina 解决非结构化数据的索引和搜索问题。本系列教程中,我们将演示如何用 Jina 搭建一个 PDF 搜索引擎

具体内容如下:

* Part 1 将介绍如何从 PDF 中提取、处理并存储图像及文本

* Part 2 将演示如何将这些信息输入到 CLIP 中(CLIP 是一个可以理解图像及文本的深度学习模型)。提取 PDF 图像及文本信息后,CLIP 将生成索引,输入图像或文本,即可进行语义相似性搜索。

* Part 3 通过客户端及 Streamlit 前端,对索引进行搜索。

* Part 4 为其他相关演示,如提取元数据等。

前序简介:预期目标 & 技术栈

预期目标:搭建一个 PDF 搜索引擎,用户输入文本或上传图片,搜索引擎即可返回类似的图片和文本片段,并附带原始 PDF 链接。

本文将着重讲解如何将一个 900 多页的 PDF 处理成可供搜索的向量。

本教程将涉及以下技术栈:

DocArray:a data structure for unstructured data. 通过这个工具可以封装 PDF 文件、文本块、图像块以及搜索引擎的其他输入/输出。

Jina为 DocArray Document 搭建流水线及神经搜索引擎,并将其扩展到云端。

Jina Hub无需逐一创建处理单元,可直接使用云端可复用模块。

教程详解:提取 PDF 中的文本及图像

提取 PDF 中的文本及图像,有以下方法可供选择:

1. 用 Jina Hub 上的 PDFSegmenter Executor,提取 PDF 中文本块和图像块。

2. 用 ImageMagick 和 OCR 对 PDF 中的每一页进行截图。

3. 将 PDF 转换为 HTML,图片提取到目录,再次将 HTML 转换为文本(这里我们使用的是 Pandoc )。

本文将使用方法 1,提取 PDF 中的文本及图像。

1、创建 PDF(也可使用已有文件)

首先,我们需要一个示例文件,从维基百科中选择一个词条,并导出为 PDF 作为示例文档。本教程中我们用到的是 Rabbit 词条(也可以称为文章)。

本教程中使用的浏览器为 Chrome

注意:

* 禁用页眉、页脚等设置,以免索引中出现类似 4/798 页等无关信息。

* 可以尝试通过改变页面大小来避免分页

2、提取 PDF 中的文本及图像

借助 Jina Hub 中的 Executor,在 Flow 中运行并提取 PDF 中的数据。在 Jina 中,Flow 是执行重要任务的 Pipeline,可以建立可搜索的 PDF 文档索引,或通过索引进行搜索。

每个 Flow 包括多个 Executor,每个 Executor 负责一个小任务。这些 Executor 串联在一起,对 Document 进行端到端的处理。

这里我们用到了 Jina Hub 上的 Executor--PDFSegmenter

使用 Jina Sandbox,即可释放本地资源,将运行转移到云端:

from docarray import DocumentArray
from jina import Flow

docs = DocumentArray.from_files("data/*.pdf", recursive=True)

flow = (
    Flow()
    .add(uses="jinahub+sandbox://PDFSegmenter", install_requirements=True, name="segmenter")
)

with flow:
  indexed_docs = flow.index(docs)

将 PDF 文档转换为 DocumentArray 形式。在 Jina 中,每一段数据(文本、图像、PDF 等)都是一个Document,一组Document 组成一个 DocumentArray。

通过 documentary.from _ files () 即可从一个目录自动加载所有内容。

DocumentArray 输入到 Flow 后,处理过的 DocumentArray 将存储在 indexed _ docs 中。

在 rabbit.pdf 中, Indexed _ docs 只包含了一个包括文本块和图像块的 Document。

下图为 DocumentArray 摘要,其中包含了 indexed_docs.summary()

通过 indexed_docs[0].chunks.summary() 查看部分文本块或图像块:

如上图所示,Document 中一共包括 58 个块,分为 tensor(图像)和字符串(文本)。

从每个 chunk 中打印 chunk.content

chunks = indexed_docs[0].chunks

for chunk in chunks:
  print(chunk.content)

图像的形式为 tensor,文本则是一段很长的字符串

3、处理数据

对数据进行以下处理:

* 将文本片段分片为更小的块,如句子。上述长字符串包含了过多信息,通过 sentencize,可以从每一个文本块中得到一个明确的语义信息。

* 对图像进行归一化处理,便于后续在深度学习模型中进行编码。

3.1 将文本进行分句 (sentencizing)

句子示例如下:

* It was a dark and stormy night.

* What do a raven and a writing desk have in common?

* Turn to p.13 to read about J.R.R. Tolkien pinging google.com in 3.4 seconds.

使用 Jina Hub 的 Sentencizer Executor,运行这些字符串。

from docarray import DocumentArray, Document
from jina import Executor

docs = DocumentArray(
    [
        Document(text="It was a dark and stormy night."),
        Document(text="What do a raven and a writing desk have in common?"),
        Document(text="Turn to p.13 to read about J.R.R. Tolkien pinging google.com in 3.4 seconds")
    ]
)

exec = Executor.from_hub("jinahub://Sentencizer")

exec.segment(docs, parameters={})

for doc in docs:
    for chunk in doc.chunks:
        print(chunk.text)

    print("---")

输入上述三个句子后,得到以下输出:

上图可知 p.13 中的标点符号,被识别成了句号。这里可以借助 SpacySentencizer 进行优化。

SpacySentencizer 是一个 Executor,可以将 spaCy 的 sentencizer 集成到 Jina。

只需修改第 12 行代码如下:

from docarray import DocumentArray, Document
from jina import Executor

docs = DocumentArray(
    [
        Document(text="It was a dark and stormy night."),
        Document(text="What do a raven and a writing desk have in common?"),
        Document(text="Turn to p.13 to read about J.R.R. Tolkien pinging google.com in 3.4 seconds")
    ]
)

exec = Executor.from_hub("jinahub://SpacySentencizer")

exec.segment(docs, parameters={})

for doc in docs:
    for chunk in doc.chunks:
        print(chunk.text)

    print("---")

现在的结果如下图所示:

将 Executor 添加到 Flow 中:

from docarray import DocumentArray
from jina import Flow

docs = DocumentArray.from_files("data/*.pdf", recursive=True)

flow = (
    Flow()
    .add(uses="jinahub+sandbox://PDFSegmenter", install_requirements=True, name="segmenter")
    .add(uses=ChunkSentencizer, name="chunk_sentencizer")
)

with flow:
  indexed_docs = flow.index(docs)

3.2 对图像进行归一化处理

from jina import Executor, requests
import numpy as np

class ImageNormalizer(Executor):
    @requests(on="/index")
    def normalize_chunks(self, docs, **kwargs):
        for doc in docs:
            for chunk in doc.chunks[...]:
                if chunk.blob:
                    chunk.convert_blob_to_image_tensor()

                if hasattr(chunk, "tensor"):
                    if chunk.tensor is not None:
                        chunk.convert_image_tensor_to_uri()
                        chunk.tags["image_datauri"] = chunk.uri
                        chunk.tensor = chunk.tensor.astype(np.uint8)
                        chunk.set_image_tensor_shape((64, 64))
                        chunk.set_image_tensor_normalization()
  • 代码解读:

1-6: 通用 Executor 调用代码。第 5 行规定Executor 只有在调用索引 endpoint 时才能处理 Document。

8: 通过 [ ... ] 启用递归,依次对 chunk 进行处理。

9: 出现 blob 后将其转换为张量,以适应 CLIP 编码器。

12-18: 假设出现张量,我们需要把未处理张量的数据 uri 添加到元数据(即 tags)中,以便于后续检索并在前端展示图像。

为了防止文本块与图像块互相干扰:

from docarray import DocumentArray
from jina import Flow

docs = DocumentArray.from_files("data/*.pdf", recursive=True)

flow = (
    Flow()
    .add(uses="jinahub+sandbox://PDFSegmenter", install_requirements=True, name="segmenter")
    .add(uses=ChunkSentencizer, name="chunk_sentencizer")
    .add(uses=ImageNormalizer, name="image_normalizer")
)

with flow:
  indexed_docs = flow.index(docs)

通过上述过程,我们实现了:

* 构建一个全新的 PDF

* 将 PDF 分成文本和图像两部分

* 进一步将文本块分割成句子块

* 对图像进行归一化处理

效果如下图所示:

通过一个新的 Executor--ChunkMerger,将文本块和图像块放在同一个 level:

from jina import Executor, requests
import numpy as np

class ImageNormalizer(Executor):
    @requests(on="/index")
    def normalize_chunks(self, docs, **kwargs):
        ...
        
        
class ChunkMerger(Executor):
    @requests(on="/index")
    def merge_chunks(self, docs, **kwargs):
        for doc in docs:  # level 0 document
            for chunk in doc.chunks:
                if doc.text:
                    docs.pop(chunk.id)
            doc.chunks = doc.chunks[...]

完成分句 (sentencize) 后,将其直接放到 Flow 中,代码如下:

from docarray import DocumentArray
from executors import ChunkSentencizer, ChunkMerger, ImageNormalizer
from jina import Flow

docs = DocumentArray.from_files("data/*.pdf", recursive=True)

flow = (
    Flow()
    .add(uses="jinahub+sandbox://PDFSegmenter", install_requirements=True, name="segmenter")
    .add(uses=ChunkSentencizer, name="chunk_sentencizer")
    .add(uses=ChunkMerger, name="chunk_merger")
    .add(uses=ImageNormalizer, name="image_normalizer")
)

with flow:
  indexed_docs = flow.index(docs)

以上就是本系列教程 Part 1 的全部内容。在 Part 2 中,我们将为 Flow 添加一个编码器,使用 CLIP 将文本和图像编码为向量,从而简化的语义搜索的过程。

欢迎大家关注 Jina AI,持续关注本系列教程更新~

相关推荐

10种常见的MySQL错误,你可中招?

【51CTO.com快译】如果未能对MySQL8进行恰当的配置,您非但可能遇到无法顺利访问、或调用MySQL的窘境,而且还可能给真实的应用生产环境带来巨大的影响。本文列举了十种MySQL...

MYSQL数据库基础和常用语法汇总03篇-数据查询

【数据查询语言】(1)查询系统内部函数返回内容和算式计算显示MYSQL的版本:selectversion();显示当前时间:selectnow();显示年月日:SELECTDAYOFMONTH...

MySql 十分钟查询数据库500W数据逐条输出比分页查询快100倍

大家好,我是云枫,今天给大家分享我最近困扰我许久的一个问题,大数据量场景下需要把一张表的数据全部查询出来处理加工然后入库另外一张表遇到问题对于这样的场景其实就类似于ETL工具处理数据抽取,数据加工,数...

DBA技术分享(九)- MySQL数据库中查找最常用的数据类型

一、概述今天分享几个关于MySQL数据类型的查询,具体如下:在MySQL数据库中查找最常用的数据类型...

sql查看mysql中各数据库大小

mysql数据库版本:mysql5.7用SQL命令查看Mysql数据库大小要想知道每个数据库的大小的话,步骤如下:1、进入information_schema数据库(存放了其他的数据库的信息)use...

mysql数据误删除后的恢复方法

1、首先查看是否开启了mysql数据操作日志,如果没有那记得打开为以后做好准备。查看的方法如下:在mysql中执行命令:showvariableslike'%bin%';通过上图...

数据库数据恢复—MYSQL数据库MYI文件损坏的数据恢复案例

mysql数据库故障:mysql数据库文件ibdata1、MYI、MYD损坏。故障表现:1、数据库无法进行查询等操作;2、使用mysqlcheck和myisamchk无法修复数据库。mysql数据库数...

「删库跑路」使用Binlog日志恢复误删的MySQL数据

前言“删库跑路”是程序员经常谈起的话题,今天,我就要教大家如何删!库!跑!路!开个玩笑,今天文章的主题是如何使用Mysql内置的Binlog日志对误删的数据进行恢复,读完本文,你能够了解到:MySQL...

MySQL常用基础用法集锦,都在这里了

大家还记得我们之前介绍过MySQL的执行顺序吗?MySQL数据插入INSERTINTO与条件查询WHERE的基本用法(二)。本节课我们将给大家介绍MySQL中常用的几个关键字SELECT/HAVIN...

MySQL数据实时同步到Elasticsearch解决方案

概述在实际的项目开发与运维过程中,MySQL常常扮演着业务数据库的核心角色,以其强大的事务处理能力和数据完整性保障,支撑着系统的稳定运行。然而,随着数据量的急剧增长和查询复杂度的不断提升,单一依赖...

MySql中查询JSON结构数据

由于以前用的数据里面,为了方便后续的扩展,很多用了JSON结构存储在mysql字段里面,于是就经常需要去使用mysql进行JSON数据的检索,比如用:SELECT*FROMdataWHERE...

大数据量查询容易OOM?试试MySQL流式查询

一、前言程序访问MySQL数据库时,当查询出来的数据量特别大时,数据库驱动把加载到的数据全部加载到内存里,就有可能会导致内存溢出(OOM)。其实在MySQL数据库中提供了流式查询,允许把符合条...

Oracle通过dblink连接mysql数据库实时数据同步

1.检查oracle数据库所在的主机上是否已经装了mysqlodbc客户端rpm-qa|grepmysql-connect...

如何使用Python连接MySQL数据库?

数据分析离不开数据库,如何使用python连接数据库呢?听我娓娓道来哈该笔记参考了PyMySQL官方文档和《python数据采集》关于数据存储的部分,欢迎大家去阅读原著,相信会理解的更加透彻。...

Mysql联表update数据

1.MySQLUPDATEJOIN语法在MySQL中,可以在UPDATE语句中使用JOIN子句执行跨表更新。MySQLUPDATEJOIN的语法如下:拉勾IT课小编为大家分解UPDATE...