上一篇文章我们研究了如何将PDF文件分解为可用的块,以便构建一个搜索引擎。
本文将讲解获取块同时: 1、使用 CLIP 模型将它们编码为 embedding 向量。2、将它们存储在索引中以便于搜索。
对句子进行编码
通过对块进行编码,将它们转换为模型(本例中为 CLIP)可以理解的内容。虽然许多模型是单模态的 (即只处理一种类型的数据,如文本、图像或音频) ,但 CLIP 是多模态的,这意味着它可以将不同类型数据的特征映射到相同的向量空间中,以便于可以直接计算不同模态数据间的相关性。
我们可以搜索图像到图像,文本到图像,图像到文本或文本到文本等。
1
Jina Hub针对只搜索一种类型模态的型号:\
文本:
TransformerTorchEncoder,TransformerSentenceEncoder,SpacyTextEncoder
图片来源: ImageTorchEncoder
添加编码器执行器
1
以前的流程
1、提取文本段落和图像。2、将文本进行分句。3、为内容整洁,将所有文本块移动到同一级别。4、对图像进行预处理,准备编码。代码如下(包括将PDF加载到DocumentArray等):
from docarray import DocumentArrayfrom executors import ChunkSentencizer, ChunkMerger, ImageNormalizerfrom jina import Flow
docs = DocumentArray.from_files("data/*.pdf", recursive=True)
flow = ( Flow() .add(uses="jinahub+sandbox://PDFSegmenter", 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)
2
现在的流程
只添加 CLIPEncoder ,或直接从Jina Hub添加此执行器,不用担心如何手动集成模型。在沙盒中执行操作,不用担心需要手动计算:
from docarray import DocumentArrayfrom executors import ChunkSentencizer, ChunkMerger, ImageNormalizerfrom jina import Flow
docs = DocumentArray.from_files("data/*.pdf", recursive=True)
flow = ( Flow() .add(uses="jinahub+sandbox://PDFSegmenter", name="segmenter") .add(uses=ChunkSentencizer, name="chunk_sentencizer") .add(uses=ChunkMerger, name="chunk_merger") .add(uses=ImageNormalizer, name="image_normalizer") .add(uses="jinahub+sandbox://CLIPEncoder", name="encoder"))
with flow: indexed_docs = flow.index(docs)
运行 Flow,查看有关以下内容的一些摘要信息可知: 1、顶级文档。2、顶级文档的每个块级别(即图像和句子)。
失败情况分析
1 失败情况概览
1、错误,如:
encoder/rep-0@113300[E]:UnidentifiedImageError(‘cannot identify image file <_io.BytesIO object at 0x7fdc143e9810>’) 2、summaries 里没有embedding的内容。
2 失败原因
1、我们只用到了第一层(top-level)的Document,句子和图存在了chunks里(是第二层及以后了),所以top-level的embedding是空的,实际要用的是在chunks里的句子和图。2、如果直接将用clipencoder用在了top-level的Document,也就是整个PDF文件上,它会直接把整个pdf当做了图来进行处理。\
3解决方案
添加traversal_path参数,从而能在嵌套结构中进行搜索,同时避免处理了原先top-level的PDF整体。
flow = ( Flow() .add(uses="jinahub+sandbox://PDFSegmenter", name="segmenter") .add(uses=ChunkSentencizer, name="chunk_sentencizer") .add(uses=ChunkMerger, name="chunk_merger") .add(uses=ImageNormalizer, name="image_normalizer") .add( uses="jinahub+sandbox://CLIPEncoder", name="encoder", uses_with={"traversal_paths": "@c"}, ))
其中 "@c" 是遍历第一级区块, "@cc" :遍历第二级块......; "@c,cc" 是遍历第一级和第二级块; [...] 是遍历所有块。语法详细使用说明通过以下链接或点击阅读原文访问。docarray.jina.ai/fundamental…
此时,输入一个句子,可以获得匹配的段落。
由于本文处理的 PDF (即句子或图像)中只有一个级别的块,因此可以继续应用 "@c"
再次运行 Flow 并检查输出:
with flow: indexed_docs = flow.index(docs, show_progress=True)
# See summary of indexed Documentsindexed_docs.summary()
# See summary of all the chunks of indexed Documentsindexed_docs[0].chunks.summary()
将块存储在索引中
如果不将块和嵌入存储在索引中,程序退出时,他们无法自动存储。
在本文例子中,我们将使用 SimpleIndexer ,因为只是构建基础知识。
在实际应用中, 还可以使用 Weaviate 或 Qdrant 后端,或来自Jina Hub的其他效率更高的索引器(如 AnnLiteIndexer )。
需要注意的一点是: 我们需要牢记块级别,因此将添加一个参数:traversal_right
flow = ( Flow() .add(uses="jinahub+sandbox://PDFSegmenter", name="segmenter") .add(uses=ChunkSentencizer, name="chunk_sentencizer") .add(uses=ChunkMerger, name="chunk_merger") .add(uses=ImageNormalizer, name="image_normalizer") .add( uses="jinahub+sandbox://CLIPEncoder", name="encoder", uses_with={"traversal_paths": "@c"}, ) .add( uses="jinahub://SimpleIndexer", install_requirements=True, name="indexer", uses_with={"traversal_right": "@c"}, ))
1 运行情况
当运行 Flow 时,我们将在磁盘上看到一个文件夹。继续运行文件夹可以看到结构:workspacetree workspace
workspace
└── SimpleIndexer
└── 0
└── index.db
2 directories, 1 file
可以看到这只是一个 SQLite 文件:file workspace/SimpleIndexer/0/index.db。
它只使用 DocArray SQLite 文档存储。需要时可以直接使用磁盘加载,更加方便易行。
下期将更新第三部分,敬请期待!\