系列教程

2023-11-20

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("---")

现在的结果如下图所示:

图像的形式为 tensor

将 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 分成文本和图像两部分

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

* 对图像进行归一化处理

效果如下图所示:

如图所示,文本块和图片块并没有处在同一个 level

通过一个新的 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,持续关注本系列教程更新~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

系列教程 的相关文章

随机推荐

  • Word调整标题格式

    将光标放在该级标题之后 右键 编号 定义新编号形式 编号格式改为想要的形式 以第1章为例 中间的数字形式是编号样式决定 需要将光标放在数字所在位置 选择相应形式 最后确定 就可以看到标题形式变为设置的那样
  • Spring Singleton Bean 三级缓存

    spring getBean的三级缓存有点绕 但理清楚了 就会发现也挺简单的 一 循环依赖 循环依赖有多种情况 以下整理的三种 1 构造方法互相依赖 例如ServiceA的构造方法中依赖了ServiceB Service的构造方法中依赖了S
  • 由于回车符引起的shell错误

    今天弟弟写shell时出现一个错误 源代码如下 zip r 1 2 执行时出现错误 我也写了相同的语句 发现是可以执行的 把两个文件对比一看 差别在于 出错shell 正确shell 在linux下的回车是 n 在win下面的回车是 r n
  • IIS上部署Django+vue-element-admin-master

    在Windows2012上通过IIS部署自己的web Django Vue element admin master 文章目录 前言 1 安装IIS和CGI 2 部署Django项目 3 部署vue element admin master
  • 二极管 MOS管 3.23学习笔记

    二级管 外加正向电压导通 外加反向电压截至 受电压极性控制的开关 缺点 1 由于二极管的导通压降 造成输出的电压与输入的高低电压有偏移 向下一级门电 路传递时 有高低电压的偏差 2 带负载能力差 MOS管 栅极 源极 漏极 衬底 当门极与衬
  • Mybatis-Plus insertBatch执行缓慢原因查询

    背景 最近在SpringCloud项目中 使用Mybatis Plus执行一个88万条左右的数据插入MySQL数据库的操作时 发现执行时长竟然长达2个小时 按理讲 MP框架执行如下批处理操作时 XXService insertBatch X
  • 【PyCharm警告】选择性忽略 PEP8 警告

    提示 Class names should use CamelCase convention Inspection info This inspection checks the PEP8naming conventions 为什么 从命名
  • spark SQL基础教程

    1 sparkSQL入门 sparksql专门用于处理结构化的数据 而RDD还可以处理非结构化的数据 sparksql的优点之一是sparkfsql使用统一的api读取不同的数据 第二个优点是可以在语言中使用其他语言 例如python 另外
  • 21电赛D题配置部分

    MJPG Streamer推流 安装MJPG Streamer 编辑 etc apt sources list 文件 删除原文件所有内容 用以下内容取代 deb http mirrors tuna tsinghua edu cn raspb
  • jest搭建vue项目单元测试-现有老项目

    说到项目会分为新建的醒目和老项目两种 jest搭建vue项目单元测试 vue cli创建新项目 我们接下来说现有老项目 现有的vue老项目或者没使用vue cli创建项目搭建jset单元测试 1 安装 npm i vue test util
  • 重启Vcenter命令

    重启Vcenter命令 通过ssh登录Vcenter 输入root 输入shell 输入service control stop all 输入service control start all 停止 启动或重新启动 VMware vCent
  • linux xenserver教程,XenServer常用命令

    监控检查类 xentop 查看XenServer与VM的资源使用情况 xsconsole 进入XenServer管理面板 查看网卡 IP 系统版本 系统时间 硬件信息等 xe task list 查看XenServer临时任务进程 serv
  • 【C语言】验证哥德巴赫猜想

    文章目录 问题来源 题目要求 如何判断素数 主函数 完整代码 效果演示 写代码中的误解 总结 问题来源 这是学校的一个作业 原题如下 题目先给出了哥德巴赫猜想的背景知识 我还真不知道 2000以内的正偶数都能分解成两个质数 素数 之和 题目
  • 毕业设计-基于 PID 控制算法仿真算法研究- Matlab

    目录 前言 课题背景和意义 实现技术思路 一 基本原理 二 无超调 PID 控制器的设计 三 无超调 PID 设计的验证 代码 实现效果图样例 最后 前言 大四是整个大学期间最忙碌的时光 一边要忙着备考或实习为毕业后面临的就业升学做准备 一
  • 安装anaconda及修改conda config 的channels/default_channels

    先说一下安装anaconda的方法 很简单 就是去官网下载然后在本地安装 bash Anaconda3 4 4 0 Linux x86 64 sh 这个过程中要耐心 会有提问 需要输入yes来回应 并且需要按很多的回车 总之 看见让输入ye
  • 转:机器学习的理解

    转李航博士的一篇关于机器学习理解的文章 算算时间 从开始到现在 做机器学习算法也将近八个月了 虽然还没有达到融会贯通的地步 但至少在熟悉了算法的流程后 我在算法的选择和创造能力上有了不小的提升 实话说 机器学习很难 非常难 要做到完全了解算
  • Ridis持久化

    Redis持久化 RDB Redis DataBase Redis会单独创建 fork 一个子进程来进行持久化 会先将数据写入到一个临时文件中 待持久化都结束了 再用这个临时文件替换上次持久化好的文件 整个过程中 主进程是不进行io操作的
  • 8--UI 初步认识 简易计算器

    UI是App的根基 一个App应该是先有UI界面 然后在UI的基础上增加实用功能 2 UI相对简单易学 UI普遍是学习过程中最简单的一块 能快速拥有成就感和学习兴趣 3 UI至关重要 开发中的绝大部分时间都在处理UI 谨记一条IOS软件开发
  • MySQL根据某一个或者多个字段查找重复数据

    sql 查出一张表中重复的所有记录数据 1 表中有id和name 两个字段 查询出name重复的所有数据 select from xi a where a username in select username from xi group
  • 系列教程

    PDF Search 系列教程来咯 在 Part 1 中 我们将演示如何从 PDF 中提取 处理并存储图像及文本 随着神经搜索 Neural Search 技术的普及 越来越多开发者 开始尝试用 Jina 解决非结构化数据的索引和搜索问题