网络知识 娱乐 七爪源码:DocArray 和 Jina 中的下一级多模态

七爪源码:DocArray 和 Jina 中的下一级多模态

在 Jina AI,我们始终致力于为神经搜索和其他多模态应用(例如生成艺术)构建最佳工具。

在过去的几周里,我们一直在努力开发新功能和 API,这些新功能和 API 使处理任何数据——无论是你的数据,无论是什么——都变得轻而易举。

至关重要的是,我们将重点放在使所有这些工作在本地(在 DocArray 中)以及在云中(在 Jina 中)。

总体而言,这代表了您与 DocArray 和 Jina 交互方式的重大转变。或者更确切地说,它们如何适应您:您将能够为您的数据定义自己的惯用语,并纯粹根据您所在领域已经熟悉的术语进行推理。少考虑 DocArray 及其特性,多考虑您的数据和任务。

在此过程中,我们必须做出许多细微的设计决策,直到我们最终达到我们充实的理念。

因此,让我们借此机会回顾一下这些决定,我们做出这些决定的原因,以及我们认为您会喜欢它们的原因。

过去的情况如何

Document 和 DocumentArray 一直是极其灵活的数据结构,基本上可以容纳任何类型的数据。

但是很长一段时间以来,我们采取了与大多数软件包相同的方法:我们实现了我们和社区需要的所有功能,围绕这些功能我们设计了一个我们认为有意义的 API,然后将我们的包发布给全世界,祝大家黑客愉快!

本质上,我们为您提供了您需要的所有工具,但我们还告诉您如何与这些工具交互,以及如何使您的数据适合它们。

例如,按照这种方法,您可以表示一篇科学论文,其中包含一张图像、该图像的描述、许多参考文献,以及正文和一些元数据。

你会这样建模:

from docarray import Document# modelling your data as a nested Documentimage = Document(uri="myimage.jpg").load_uri_to_image_tensor()description = Document(text="this is my awesome image")references = [ Document(uri="https://arxiv.org/abs/2109.08970"), Document(uri="https://arxiv.org/abs/1706.03762"),]reference_doc = Document(chunks=references)article = Document( text="this is the main text of the article", tags={"author": "you", "release": "today"}, chunks=[image, description, reference_doc],)

您可以将论文建模为嵌套文档,主要数据位于顶层,所有其他数据位于块级别,每个块都是其自己的文档并保存自己的数据。


这会给你一个像这样的整体结构:

然后,您当然可以访问刚刚编译的数据:

author_str = article.tags["author"]image_tensor = article.chunks[0].tensorfirst_reference_uri = article.chunks[2].chunks[0].uri

突然间,您必须对块进行推理,Jina AI 的这些人提出了一个概念,以及块的块和块的索引,而您想要做的就是访问您的图像和您的参考!

显然,这需要改变。


以您的数据为中心

在 Jina AI,我们仍然喜欢我们的经典 Document、DocumentArray 以及它们提供的 API,但问题是:您最了解自己的数据,而像块这样的概念可能无法自然地映射到您手头的任务。

那么我们为什么不让你定义自己的 API?

为什么我们不适应你而不是你适应我们?

输入,Jina 数据类!

让我们从上面重新建模文章,但现在让我们以一种实际上类似于人类对世界的看法的方式来做:

from docarray import Document, DocumentArray, dataclassfrom typing import Listfrom docarray.typing import Image, Text, JSON@dataclassclass Article: image: Image image_description: Text main_text: Text metadata: JSON references: List[Text]article_dataclass = Article( image="myimage.jpg", image_description="this is my awesome image", main_text="this is the main text of the article", metadata={"author": "you", "release": "today"}, references=["https://arxiv.org/abs/2109.08970", "https://arxiv.org/abs/1706.03762"],)article = Document(article_dataclass)

我们得到了同样的结果,我们所要做的就是定义数据的结构,然后填充它。

在这里,数据类用作从现实世界和您的域到 DocArray 世界的映射。 您几乎可以将其视为一个非常花哨的 __init__() 方法。

到目前为止一切都很好,但是当我们尝试从文章中访问数据时会发生什么?

这就是真正酷的部分开始的地方,也是我们最新功能带来的东西。 让我们来看看:

image_doc = article.image # returns a Documentimage_tensor = article.image.tensor # returns the image tensorauthor_str = article.metadata.tags["author"]first_reference_uri = article.references[0].text

我们也不要忘记访问自定义模式的 DocumentArray 级别语法:

da = DocumentArray([Document(article_dataclass) for _ in range(3)])image_docs = da["@.[image]"]image_and_description_docs = da["@.[image, image_description]"]image_tensors = image_docs[:, "tensor"]

如您所见,即使在将数据类转换为 Document 或 DocumentArray 之后,您仍然可以根据您定义的模式来推理您的数据和子数据。

块已经不复存在,取而代之的是,您可以直接访问图像、参考等,即与您的域相关的实际数据。


无处不在的文件

您在上一个代码片段中可能偶然发现的一件事是:

为什么我需要调用 article.image.tensor 来获取我的图像张量?对 article.image 的调用还不够吗?为什么要通过文档进行中介步骤?

你是对的!在这个简单的例子中,后者确实是更优雅的界面。

尽管如此,还是有三个重要的考虑促使我们像 article.image 这样的调用返回一个成熟的 Document 而不是存储在其中的数据。

  1. 灵活性:因为 DocArray 是适用于任何类型数据的数据结构,所以灵活性始终是我们的首要任务之一。因此,我们不是返回特定的数据类型,而是返回一个 Document,这是我们所知道的最灵活的数据表示。然后你可以用它做任何你想做的深奥的事情,我们不评判!
  2. 无处不在的文档:DocArray 和 Jina 中的几乎每个操作都将 Document(或 DocumentArray)作为输入,并返回一个作为输出。我们希望让我们的堆栈没有意外,因此您可以继续假设 Documents 是您将获得的。
  3. Jina 中的支持(Executors):一旦开始认真的工作,Document 作为返回类型的力量就变得至关重要。继续阅读以了解原因!


多模式到云端

到目前为止,我们只讨论了使用 DocArray 进行本地开发。现在让我们将注意力转移到 Jina 的微服务世界。

首先要做的事:你会很欣慰地知道,访问 Document 模式并不依赖于你的本地环境,并且绝对可以在 Executor 内部进行,无论它是在你的笔记本电脑上运行,还是在全球的 Kubernetes 集群上运行,或者在 JCloud 中:

from docarray import Document, dataclassfrom docarray.typing import Image, Textfrom jina import Executor, Flow, requestsimport numpy as np@dataclassclass Article: image: Image description: Textarticle_dataclass = Article(image="myimage.jpg", description="this is an awesome image")article = Document(article_dataclass)class ImageEncoder(Executor): @requests(on="/index") def encode_image(self, docs, *args, **kwargs): image_tensor = docs[0].image.tensor docs[0].embedding = np.zeros(image_tensor.shape) # dummy embdingwith Flow().add(uses=ImagEncoder) as f: f.index(inputs=article)

但这仅仅是开始——让我们看看原生多模态如何在更现实的例子中发挥作用。


多模态在行动

让我们看一个多模式应用程序的实际示例:我们再次有一个非常简单的文章,由图像和描述组成,我们想要创建此类文章的向量嵌入。

为此,我们希望使用各自的嵌入模型对图像和描述进行编码,然后将这些表示组合成整篇文章的最终嵌入向量。

在这里,我们举一个可能看起来像的例子:

from docarray import Document, dataclassfrom docarray.typing import Image, Textfrom jina import Executor, Flow, requestsimport numpy as np@dataclassclass Article: image: Image description: Textarticle_dataclass = Article(image="myimage.jpg", description="this is my cool image")article = Document(article_dataclass)class ImageEncoder(Executor): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.model = lambda t: np.random.rand( 128 ) # initialize dummy image embedding model @requests(on="/encode") def encode_image(self, docs, **kwargs): for d in docs: image = d.image image.embedding = self.model(image.tensor)class TextEncoder(Executor): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.model = lambda t: np.random.rand( 128 ) # initialize dummy text embedding model @requests(on="/encode") def encode_text(self, docs, **kwargs): for d in docs: description = d.description description.embedding = self.model(description.text)class EmbeddingCombiner(Executor): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.model = lambda emb1, emb2: np.concatenate( [emb1, emb2] ) # initialize dummy model to combine embeddings @requests(on="/encode") def encode_text(self, docs, **kwargs): for d in docs: d.embedding = self.model(d.image.embedding, d.description.embedding)f = ( Flow() .add(uses=ImageEncoder, name="ImageEncoder") .add(uses=TextEncoder, name="TextEncoder", needs="gateway") .add(uses=EmbeddingCombiner, name="Combiner", needs=["ImageEncoder", "TextEncoder"]))with f: da = f.post(inputs=article, on="/encode")

在这里,作为原则的无处不在的文档之美真正开始闪耀。

由于 d.image 和 d.description 返回完整的文档,我们可以在其中存储相应的嵌入,并且 EmbeddingCombiner 可以轻松找到它们,使用它们,并将整体嵌入存储在顶层。


保持互操作性

如果您已经做到了这一点,那么您希望您和我们一样对这种与 Documents 交互的新方式感到兴奋。

但你可能也有一个担忧:这些定制的模式肯定是超级个性化的吗?您如何与社区分享您的 Executors,进而使用 Jina Hub 的 Executors?

不要害怕,这根本不是问题!

解决这个问题的主要思路如下:不使用文档级选择器语法 (d.image),而是使用 DocumentArray 级语法 (da['@.[image]']) 来保持最大的互操作性。

为了更具体,让我们从上面重构 Executor 代码:

class ImageEncoder(Executor): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.model = lambda t: np.random.rand( len(t), 128 ) # initialize dummy image embedding model @requests(on="/encode") def encode_image(self, docs, parameters, **kwargs): path = parameters.get("access_path", "@r") image_docs = docs[path] embeddings = self.model(image_docs[:, "tensor"]) image_docs.embeddings = embeddingsclass TextEncoder(Executor): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.model = lambda t: np.random.rand( len(t), 128 ) # initialize dummy text embedding model @requests(on="/encode") def encode_text(self, docs, parameters, **kwargs): path = parameters.get("access_path", "@r") text_docs = docs[path] embeddings = self.model(text_docs[:, "text"]) text_docs.embeddings = embeddingsclass EmbeddingCombiner(Executor): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.model = lambda emb1, emb2: np.concatenate( [emb1, emb2], axis=1 ) # initialize dummy model to combine embeddings @requests(on="/encode") def combine(self, docs, parameters, **kwargs): image_path = parameters.get("image_access_path", "@r") text_path = parameters.get("text_access_path", "@r") image_docs = docs[image_path] text_docs = docs[text_path] combined_embeddings = self.model(image_docs.embeddings, text_docs.embeddings) docs.embeddings = combined_embeddings

在此重写之后,每个连接到这些 Executor 的客户端都可以为其自己的参数提供匹配其自定义模式的 access_paths:

f = ( Flow() .add(uses=ImageEncoder, name="ImageEncoder") .add(uses=TextEncoder, name="TextEncoder", needs="gateway") .add(uses=EmbeddingCombiner, name="Combiner", needs=["ImageEncoder", "TextEncoder"]))@dataclassclass Article: image: Image description: Textarticle_dataclass = Article(image="myimage.jpg", description="this is my cool image")article = Document(article_dataclass)with f: da = f.post( inputs=article, on="/encode", parameters={ "ImageEncoder__access_path": "@.[image]", "TextEncoder__access_path": "@.[description]", "Combiner__image_access_path": "@.[image]", "Combiner__text_access_path": "@.[description]", }, )print(da[0].embedding.shape)

这样,每个用户都可以定义自己的模式,并纯粹根据这些模式进行推理——而 Executor 仍然足够通用,可以在整个生态系统中重复使用。


下一步是什么

到目前为止,我们希望您相信我们的新设计方向,但本博客中介绍的功能并不是您能做的一切。

  • 自定义模态类型:DocArray 为数据类接口提供了许多常用类型,例如 Text、Image、JSON 等等。但是您也可以定义和使用您自己的类型,包括定义从数据类到 Document 以及返回的自定义映射。
  • 嵌套数据类:一些复杂的领域需要更复杂的模型。例如,一篇文章实际上可能包含多个段落,其中每个段落包含一个图像、一个描述和一个 main_text。您可以通过在文章数据类中嵌套段落数据类列表来轻松表示这一点。
  • 二级索引(即将推出):在上面的编码示例中,我们使用 EmbeddingCombiner 为每个文档生成顶级嵌入,准备用于神经搜索应用程序。但是对于某些任务,您可能不想在顶层执行搜索,而是在模态级别执行搜索:也许您不想查找总体相似的文章,而是希望查找仅共享相似图像的文章。这就是我们文档存储中的二级索引很快就能做到的!


关注七爪网,获取更多APP/小程序/网站源码资源!