乐趣区

关于神经网络:系列教程-用Jina搭建PDF搜索引擎Part-1

 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 中文本块和图像块。

  1. 用 ImageMagick 和 OCR 对 PDF 中的每一页进行截图。
  2. 将 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, 继续关注本系列教程更新~

退出移动版