本周在docarray中
#python #opentelemetry #microservices #pytorch

ð。谢谢DocArray team的这篇来宾博客文章!

自docarray v2的last alpha release以来已经两个星期了。从那时起,已经发生了很多事情 - 我们合并了我们非常自豪的功能,并且我们哭泣着欢乐和痛苦的眼泪,试图强迫Python做我们想要的事情。如果您想了解有趣的Python Edge案例或遵循DocArray V2开发的进步,那么您会在此博客文章中来到正确的位置!

对于那些不知道的人,docarray是代表,发送和存储多模式数据的库,重点是 ml 神经搜索。

ðdocArraylinkï¼https://rebrand.ly/devTo-docarray

该项目刚刚搬到了Linux foundation AI and Data,为了庆祝其第一个生日,我们决定从头开始重写,这主要是由于设计转变和意愿从头开始巩固代码库。还可以吃蛋糕,我们不得不给它

那么,过去两个星期发生了什么?

更少的详细API

Docarray的目标之一是为我们的用户提供强大的抽象来表示嵌套数据。为了在v2中这样做,我们允许BaseDocument嵌套。 (嗯,这实际上只是pydantic的一个功能,其设计引起我们将其用作后端的原因之一)。

from docarray import BaseDocument
from docarray.documents import Image, Text

class MyBanner(BaseDocument):
    title: Text
    image: Image

class MyPoster(BaseDocument):
    left: MyBanner
    right: MyBanner

这是一个强大的设计模式,但是当使用我们的预定义文档类时,API有点冗长:

banner_1 = MyBanner(title=Text(text='hello'), image=Image(url='myimage.png'))
banner_2 = MyBanner(title=Text(text='bye bye'), image=Image(url='myimage2.png'))

poster = MyPoster(left=banner_1, right=banner_2)

新的API看起来像这样:

banner_1 = MyBanner(title='hello', image='myimage.png')
banner_2 = MyBanner(title='bye bye', image='myimage2.png')

poster = MyPoster(left=banner_1, right=banner_2)

这是少量的详细信息。我们基本上覆盖了Pydantic的预定义文档验证器,以使我们进行这种智能铸造。但是,我们没有自动制作,从某种意义上说,如果您创建文档,您仍然需要使用详细的API。这是因为这种铸造并不总是很明显。例如,查看此文档:

class MyDoc(BaseDocument):
   title: str
   description: str

doc = MyDoc('hello') # won't work

n这种情况,应该在哪里分配'hello'?标题还是描述?没有明显的方法可以做到,所以我们宁愿让用户定义它,至少在我们找到更好的方法之前。

我们正在考虑:

  • 参考订单,并在列表中制作第一个字符串。但这违背了此重写的核心价值之一:我们不暗中做事。
  • 允许用户以字段对象或功能以某种方式标记“主”字段。

从外面看,这看起来像是一个小问题。但是我们相信真正的魔鬼是细节,因此我们花了无数的时间来争论如此简单的API。伙计,那是时候我们不会回来了。 ðâ€

好奇吗?查看此PR:

ðdocarrayprï¼https://rebrand.ly/docarray-PR

__torch_function__,or:如何使Pytorch更加自信

我们在__torch_function__概念上缠绕着很多乐趣。

我们的TorchTensor类是torch.Tensor的子类,它注入了一些有用的功能(主要是在类型级别表达其形状的能力:TorchTensor[3, 224, 224]和Protobuf Serialization),Pytorch带有围绕子分类,动态派遣以及所有的全部机械那个爵士。

该机械的一部分是__torch_function__,这是一种魔术方法,允许各种对象像火炬张量一样处理。您希望您的班级实例能够通过torch.stack([your_instance, another_instance])等函数处理,或者直接添加到torch.Tensor中?没问题,只需在您的班级中实现__torch_function__,在那里处理,然后离开!甚至无需子类torch.Tensor

import torch

class MyClass:
    def __init__(self, others=None):
        self._others = others or []

    @classmethod
    def __torch_function__(cls, func, types, args=(), kwargs=None):
        if func is torch.stack or func is torch.Tensor.add:
            # we know how to handle these!
            return cls.combine(args)
        else:
            # ... but are clueless about the rest
            return NotImplemented

    @classmethod
    def combine(cls, *others):
        return cls(others=list(others))

print(torch.stack([MyClass(), MyClass()]))
# outputs:
# <__main__.MyClass object at 0x7fd290c55190>
print(torch.rand(3, 4, 5) + MyClass())
# outputs:
# <__main__.MyClass object at 0x7f363e2ed0d0>

现在,上面的示例不是一个非常有用的示例,但是您会得到这个想法:__torch_function__使您可以创建表现的对象 像火炬张量一样,没有 he/em> torch张量。

,但请坚持下去。 TorchTensor 的实例是火炬张量,因为它们直接从torch.Tensor继承!因此,所有功能都已经存在,我们从torch.Tensor继承了__torch_function__,我们不需要关心任何一个,对吗?

好吧,不是很。

问题是,我们没有一个torch.Tensor的子类;我们有很多:TorchTensor是显而易见的,但是还有TorchTensor[3, 224, 224]TorchTensor[128]TorchTensor['batch', 'c', 'w', 'h']等。所有这些都是单独的类!

更加精确,所有参数化类(末尾具有[...]的类别)是TorchTensor的直接子类,并且是彼此的兄弟姐妹(以后变得很重要)。<<<<<<<<< br>

                                    torch.Tensor
                                         ^
                                         |
       ---------------------------> TorchTensor <------
      ^                   ^                            ^
      |                   |           ....             |
TorchTensor[128] TorchTensor[1, 128]  ....   TorchTensor['batch', 'c', 'w', 'h']

那么问题在哪里?

问题实质上在于types__torch_function__的论点。它包含以上示例中传递给原始Pytorch函数调用的所有参数的类型。同样,在上面的stack示例中,这只是元组(MyClass, MyClass)

这只是为了方便__torch_function__的实施者。它使他们可以根据类型快速决定是否可以处理给定的输入。

让我们看一下__torch_function__的默认pytorch(torch.Tensor)如何做出该决定:

@classmethod
def __torch_function__(cls, func, types, args=(), kwargs=None):
    # ... some stuff here
    if not all(issubclass(cls, t) for t in types):
        return NotImplemented
    # ... more stuff here

您已经可以猜出出现问题了吗?

让我通过显示失败情况给您一个提示:

data = torch.rand(128)
print(TorchTensor[128](data) + TorchTensor[1, 128](data))

当在__torch_function__中处理此调用时,从torch.Tensor继承时,cls将为TorchTensor[128],而types将包含TorchTensor[1, 128]

有意义的:这些是​​此加法中涉及的两个类别。

但是Pytorch会做什么?

它会伸出双手并放弃!

TypeError: unsupported operand type(s) for +: 'TorchTensor[128]' and 'TorchTensor[1, 128]'

TorchTensor[128]不是 TorchTensor[1, 128]的子类;他们是兄弟姐妹!因此,上面的子类检查将失败,Pytorch将宣布它对如何结合这两个类别的实例绝对没有线索。

但是,请问!这两个类都从torch.Tensor继承!相信自己,您 do 知道如何与他们打交道!只需像普通张量一样对待它们!

这已经是整个问题的解决方案:我们需要给Pytorch提供一点信心,告诉它像对待我们已经知道和喜欢的torch.Tensor班级一样对待我们的自定义课程。

那么,我们如何给这个隐喻的PEP谈话?实际上很简单:

@classmethod
def __torch_function__(cls, func, types, args=(), kwargs=None):
    # this tells torch to treat all of our custom tensors just like
    # torch.Tensor's. Otherwise, torch will complain that it doesn't
    # know how to handle our custom tensor type.
    docarray_torch_tensors = TorchTensor.__subclasses__()
    types_ = tuple(
        torch.Tensor if t in docarray_torch_tensors else t for t in types
    )
    return super().__torch_function__(func, types_, args, kwargs)

这是当前为TorchTensor提供动力的__torch_function__的实现。它只是一件事:对于任何是TorchTensor子类的类,它都会更改types参数,然后再将其传递给__torch_function__的默认实现。它将所有此类类型的所有类型替换为torch.Tensor,告诉Pytorch它有这个!

etvoilã,它有效:

data = torch.rand(128)
print(TorchTensor[128](data) + TorchTensor[1, 128](data))
# outputs:
# TorchTensor[128]([0.0454, 1.3724, ..., 1.3329, 0.9239,])

这个公关证明了我们如何指导Pytorch拥有更多的自尊心,并且是最真实,最好的自我:

1 in°https://github.com/docarray/docarray/pull/1037/files

早期支持Jina的Docarray V2

好吧,这并不是一项新功能,但是我们一直在努力在Jina中对Docarray V2进行早期支持。

Docarray与Jina的关系类似于Pydantic与FastAPI的关系:

  • FastAPI是一个使用Pydantic模型定义API模式的HTTP框架。
  • Jina是一个GRPC/HTTP框架,使用DocArray文档定义API架构。

当然还有其他概念上的差异,但是要充分理解Jina的新变化,这样的看待它很有趣。 docarray实际上是在pydantic的顶部建造的,并在此基础上添加了多模式的机器学习。

这是新界面的示例:

from jina import Executor, requests
from docarray import BaseDocument, DocumentArray
from docarray.documents import Image
from docarray.typing import AnyTensor

import numpy as np

class InputDoc(BaseDocument):
    img: Image

class OutputDoc(BaseDocument):
    embedding: AnyTensor

class MyExec(Executor):
    @requests(on='/bar')
    def bar(
        self, docs: DocumentArray[InputDoc], **kwargs
    ) -> DocumentArray[OutputDoc]:
        docs_return = DocumentArray[OutputDoc](
            [OutputDoc(embedding=np.zeros((100, 1))) for _ in range(len(docs))]
        )
        return docs_return

主要区别在于,执行人不一定要进行现场修改,而是可以返回其他文档类型。例如,我们有一个玩具编码器,该编码器将图像作为输入和返回嵌入。与FastAPI相似,我们通过检查该方法的类型提示来推断执行者的输入和输出模式。如果您不想依靠类型提示,也可以将此信息用作参数。

ð13¶。Check the v2 docs for more information

这是PR:

1at。https://rebrand.ly/docarrayV2-PR

漂亮的印刷

我们将从docarray v1到v2的漂亮打印移植了一下,然后对其进行了整理,以反映新的V2模式!在引擎盖下,我们依靠很棒的rich库来处理与UI相关的所有内容。

检查PR以获取更多信息!
ðhttps://rebrand.ly/docarrayV2-Pretty-printing

文档商店

我们目前完全重新思考Document Stores。要点是:

  • 每个文档商店都将具有架构分配的,就像documentArray一样,但具有更多(依赖于后端的)选项和配置。
  • 混合搜索和多向量搜索的一流支持。
  • 支持搜索嵌套文档。

如果您对完整(初步)设计感到好奇可以详细检查here。但这是一个小品尝者:

# define schema
class MyDoc(BaseDocument):
    url: ImageUrl
    tensor: TorchTensor[128]

da = DocumentArray[MyDoc](...)  # data to index

store = DocumentStore[MyDoc](storage='MyFavDB', ...)

# index data
store.index(da)

# search through query builder
with store.query_builder() as q:
    # build complex (composite) query
    q.find(torch.tensor(...), field='image', weight=0.3)
    q.find(torch.tensor(...), field='description')
    q.filter("price < 200")
    q.text_search('jeans', field='title')

results = store.execute_query(q)

除了现在刚刚进入实际代码的第一个设计之外,我们很高兴分享我们Weaviate紧密合作,以使我们的文档存储尽可能好!

到目前为止,他们为我们的设计提供了许多有价值的意见,我们期待在实际实施中进行协作。

最后,关于文档商店发射计划:我们当前的计划是启动与三个支持后端的文档存储的转世:Weaviate,ElasticSearch ,一个< strong>“设备矢量搜索” 库(哪个?仍然是tbd)。

不幸的是,我们的能力在发布日不允许更多向量数据库,我们绝对会喜欢它并相应地加速我们的时间表。如果您感到很感兴趣, reach out to us on Discord

作者

约翰内斯·梅斯纳(Johannes Messner),亚历克斯(Alex C-G),萨米·贾格(Sami Jaghouar)

原始链接

https://jina.ai/news/this-week-in-docarray-1/