关于算法:一文读懂Jina生态的Dataclass

40次阅读

共计 8874 个字符,预计需要花费 23 分钟才能阅读完成。

Jina AI 始终致力于构建简略、易用、全托管的最佳工具,来帮忙开发者疾速搭建多模态、跨模态利用。而作为工程师,咱们始终在致力开发新的性能和 API,以满足用户对多模态数据处理的诸多场景须要。

Jina 现反对的 Dataclass 新个性提供了更丰盛的默认办法反对,大大简化了定义类对象的代码量,代码简洁清晰。本文我将向你介绍 Dataclass 所带来的便利性,为什么要应用它,以及演示如何应用它。

作者介绍
Jina AI 机器学习工程师 Johannes Messner

Dataclass 是一个数据类,顾名思义,数据类只须要关怀数据,而和具体行为解耦。Dataclass 是对 Document 更高层次的封装,能够更好地示意一个多模态文档。如图所示,你能够利用装璜器 @dataclass,将右边的多模态文档的信息示意为左边代码片段。

dataclass-example

总的来说,这个新个性会更好地晋升开发者的体验,让开发者近乎于应用自然语言般封装本人的数据,并拓展使其成公共可用的服务。缩小了对 DocArray 及其个性的思考,更多地思考本人的数据和工作:你能够依据本人的数据来自定义 Dataclass,疾速地示意本人的数据,实现本人的工作。此外最重要的是,DocArray 和 Jina 都反对了这个新个性。

在这个新个性的开发过程中,为了保障用户的最佳体验,咱们不得不做出一些轻微的设计和改良,以进步其效率、可用性和便携性。因而,让咱们借此机会回顾一下这些决定,咱们做出这些决定的起因,以及咱们认为你会喜爱这个新个性的起因。

过来是怎么做的
Document[1] 和 DocumentArray[2] 始终以来都是极其灵便的数据结构,基本上能够包容任何类型的数据。然而在过来,咱们应用的是和其他软件雷同的形式,提供开发者须要的所有工具,并通知开发者如何与这些工具交互,如何让数据去适配这些工具。

举个例子,依照之前的办法,当你想要示意一篇蕴含多种模态信息的论文,外面蕴含注释文本、图片、该图片的形容文本、许多参考文献的超链接、以及一些元数据。在之前,咱们须要这样建模:

from docarray import Document

modelling your data as a nested Document

image = 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],

)

咱们须要将论文建模为嵌套的 Document,次要数据 (如注释和标签) 位于顶层,其余数据放在 chunk(块) 级别,每个块都有别离的 Document 用于保留数据。整体构造如下图所示:

Document 整体构造

当你想要拜访刚刚编译的数据,须要输出如下代码:

author_str = article.tags[“author”]
image_tensor = article.chunks[0].tensor
first_reference_uri = article.chunks[2].chunks[0].uri
哦豁😅 忽然你不得不思考块的问题,还有块的块,块的索引,而你想做的只是拜访作者、图像和参考文献。显然,这里须要做出扭转。

当初:以你的数据为核心
Jina 经典的 Document、DocumentArray 以及它们提供的 API 仍然十分好用,但之前存在的问题是:只有开发者才最理解本人的数据,而像块这样的概念可能无奈天然地映射到手头的工作里来。

于是,咱们提供了 Dataclass!

口说无凭,让咱们用 Dataclass 从新建模上文提到的多模态文档:首先定义数据的构造,而后填充数据。

from docarray import Document, DocumentArray, dataclass
from typing import List
from docarray.typing import Image, Text, JSON

首先,咱们定义数据的构造

@dataclass
class 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)

实际上这和人类对世界的认识的形式很类似了,简直等价于用自然语言去形容这篇多模态文档。换句话说,Dataclase 充当了从事实世界到 DocArray 世界的映射。你简直能够把它看作是一个十分丑陋的__init__()办法。

与此同时,咱们也取得了更清晰的 Document 构造。

新的 Document 构造

以上这些看起来曾经相当不错了,但接下来才是真正酷的局部,也是咱们最新性能所带来的货色。当咱们想在 article 拜访数据时,咱们能够:

image_doc = article.image # returns a Document
image_tensor = article.image.tensor # returns the image tensor
author_str = article.metadata.tags[“author”]
first_reference_uri = article.references[0].text
这里是在 DocumentArray 级别拜访自定义 Dataclass 的语法:

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”]
如图所示,即便将 Dataclass 转换为 Document 或 DocumentArray 之后,你依然能够依据你自定义的 Dataclass 来推理数据及嵌套数据。此外,当初”块”曾经一去不复返了,你能够间接拜访 Image、reference 等理论数据。

Document Everywhere
在下面的代码里你可能会奇怪,🤔 为什么我须要调用 article.image.tensor 来获取图像向量,调用 article.image 不就够了吗?为什么要通过 Document 这个两头步骤?

有如下 3 个重要因素,使得必须返回一个残缺的 Document,而不只是存储在 Document 里的数据。

  1. 灵活性:DocArray 是实用于任何类型数据的数据结构,所以灵活性始终是咱们的首要任务之一。因而,咱们不是返回特定的数据类型,而是返回一个 Document,因为这是最灵便的数据表示,你能够用它做任何你想做的简单的工作。
  2. Documents Everywhere:DocArray 和 Jina 中的简直每个操作都将 Document(或 DocumentArray)作为输出,并返回一个作为输入。
  3. 更好地融入 Jina 生态:一旦进入到 Jina 生态,将 Document 作为返回类型就变得至关重要,下文我将具体介绍起因。

从本地到云端
始终以来,DocArray 专一于本地和单片机开发者的体验,Jina 将 DocArray 扩大到云端。到目前为止,咱们只探讨了应用 DocArray 进行本地开发,当初让咱们把注意力转移到 Jina 的微服务世界。

首先请释怀,应用 Document 的 Dataclass 并不受限于运行环境。它能够在任意 Executor 中实现,不论是在你的电脑上,还是散布寰球的 Kubernetes 集群里,又或是在 JCloud。

from docarray import Document, dataclass
from docarray.typing import Image, Text
from jina import Executor, Flow, requests
import numpy as np

@dataclass
class Article:

image: Image
description: Text

article_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 embding

with Flow().add(uses=ImagEncoder) as f:

f.index(inputs=article)

接下来,让咱们看看 Dataclass 是如何在一个真正实际的 demo 中发挥作用的。

理论示例
假如咱们当初有一些非常简单的文章,由图像和形容组成,咱们想要创立这些文章的 embedding。为此,咱们先对图像和形容文本别离进行编码,以便将这些向量示意合并成整篇文章的最终 embedding。

from docarray import Document, dataclass
from docarray.typing import Image, Text
from jina import Executor, Flow, requests
import numpy as np

@dataclass
class Article:

image: Image
description: Text

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

如代码所示,图片和文本对应的 embedding 存储到了对应的 Document 中。对立的 Document 的格局标准,使得咱们在寻找和应用相应 Embedding 时能够说是棘手拈来,开发起来极其舒服。

通用性

看到这里,心愿你和咱们一样为 Documents 交互的新形式而兴奋。可能你会放心:既然这些 Dataclass 是齐全个性化的,那我要怎么确保本人上传到 Jina Hub[3]的 Executor 能供其余社区用户复用,又如何确保能应用由其余社区用户共享的 Executor 呢?

无需放心,DocArray 解决这种问题只是洒洒水。

尽管当初咱们仍然反对文档级选择器语法 (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 = embeddings

class 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 = embeddings

class 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_path 匹配其自定义数据类:

f = (

Flow()
.add(uses=ImageEncoder, name="ImageEncoder")
.add(uses=TextEncoder, name="TextEncoder", needs="gateway")
.add(uses=EmbeddingCombiner, name="Combiner", needs=["ImageEncoder", "TextEncoder"])

)

@dataclass
class Article:

image: Image
description: Text

article_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)

这样一来,每个用户都能够定义他们本人的 Dataclass,应用自定义的数据类实现开发工作。并且这些数据类不光在 Executor 是通用的,还能够在整个 Jina 生态系统中重复使用。

下一步等你来摸索

process

Dataclass 为解决多模态数据的数据科学家和机器学习工程师提供了极好的表达能力,使他们可能以十分直观的形式示意图像、文本、视频、网格、表格数据。本文只介绍了它的局部性能,还有更多弱小的性能等你来摸索!

• 自定义数据类型:DocArray 为数据类接口提供了许多常见的类型,如 Text、Image、JSON 等。但你也能够定义和应用你本人的类型,包含定义从数据类到 Document,以及返回的自定义映射。

• 嵌套数据类:一些简单的畛域须要更简单的建模。例如,一篇文章实际上可能由多个段落组成,每个段落蕴含一个图片、一个形容和一个注释文本。你能够通过在文章的 Dataclass 中嵌套一个段落的 Dataclass 的列表来轻松地示意。

• 子索引:在下面的例子中,咱们应用 EmbeddingCombiner 为每个 Document 生成顶层 embedding,以用于神经搜寻工作。然而对于某些工作,你可能心愿不在顶层进行搜寻,而是在模式层进行搜寻。比方你不心愿找到整体上类似的文章,而是心愿找到那些有类似图片的文章,这就是子索引可能做到的事件。

援用链接

[1] Document: https://docarray.jina.ai/fund…
[2] DocumentArray: https://docarray.jina.ai/fund…
[3] Jina Hub: https://hub.jina.ai/

退出 J-Tech 交换社群

官网:Jina.ai

社区:Slack.jina.ai

开源:http://Github.com/Jina-ai

私信后盾退出 vx 群

正文完
 0