ð。谢谢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相似,我们通过检查该方法的类型提示来推断执行者的输入和输出模式。如果您不想依靠类型提示,也可以将此信息用作参数。
这是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)