Numpy: 支持鸭阵强制

创建于 2019-06-25  ·  55评论  ·  资料来源: numpy/numpy

在与有问题的@shoyer@pentschev@mrocklin进行讨论后,打开此问题(https://github.com/dask/dask/issues/4883)。 AIUI在NEP 22中进行了讨论(因此,我主要是在这里模仿其他人的想法,以重新进行讨论并纠正我自己的误解;)。

对于各种下游数组库而言,具有确保我们有一些鸭子数组(例如ndarray )的功能将非常有用。 这有点类似于np.asanyarray ,但是不需要子类化。 它将允许库返回它们自己的(鸭)数组类型。 如果对象不支持适当的转换,则可以回退以处理ndarray子类, ndarray s以及将其他事物(嵌套列表)强制转换为ndarray s。

cc @njsmith (与人合着NEP 22)

01 - Enhancement numpy.core

最有用的评论

也许quack_array :)

所有55条评论

提议的实现将类似于以下内容:

import numpy as np

# hypothetical np.duckarray() function
def duckarray(array_like):
  if hasattr(array_like, '__duckarray__'):
    # return an object that can be substituted for np.ndarray
    return array_like.__duckarray__()
  return np.asarray(array_like)

用法示例:

class SparseArray:
  def __duckarray__(self):
    return self
  def __array__(self):
    raise TypeError

np.duckarray(SparseArray())  # returns a SparseArray object
np.array(SparseArray())  # raises TypeError

在这里,我使用np.duckarray__duckarray__作为占位符,但对于这些名称,我们可能会做得更好。 请参阅NEP 22中的术语

“ Duck array”目前可以很好地用作占位符,但它的措辞很专业,可能会使新用户感到困惑,因此我们可能想为实际的API函数选择其他东西。 不幸的是,“类似数组”的概念已被视为“可以强制转换为数组的任何事物”(包括列表对象),而“ anyarray”的概念已被视为“共享ndarray实现的事物”,但具有不同的语义”,这与鸭子数组相反(例如,np.matrix是“ anyarray”,但不是“ duck array”)。 这是一个经典的自行车棚,所以现在我们仅使用“鸭子阵列”。 一些可能的选项包括:arrayish,伪数组,nominalarray,ersatzarray,arraymimic等。

其他一些命名思路: np.array_compatible()np.array_api() ...。

np.array_compatible可以工作,尽管我不确定我比duckarray更喜欢它。 np.array_api我不喜欢,给出了错误的想法恕我直言。

由于经过很长时间我们都没有想出一个更好的名字,所以也许我们应该祝福“鸭子数组”这个名字……

我喜欢兼容的单词,也许我们可以想到这条线以及as_compatible_array (有点暗示所有兼容的对象都是数组)。 as可能很烦人(部分是因为所有as函数都没有空格)。 “鸭子”在图书馆中看起来不错,但是对于随机的人来说,我觉得有点奇怪。 因此,我认为当且仅当我们希望下游用户大量使用“鸭子”时(即,即使我开始为自己/一个小实验室编写一个小工具),我也不喜欢“鸭子”。

也许quack_array :)

为了进一步讨论该主题,还有另外一种情况未被np.duckarray覆盖,即创建具有基于现有类型的类型的新数组,类似于np.empty_like函数。

>>> import numpy as np, cupy as cp
>>> a  = cp.array([1, 2])
>>> b = np.ones_like(a)
>>> type(b)
<class 'cupy.core.core.ndarray'>

另一方面,如果我们有一个array_like ,我们想通过NumPy的API创建一个CuPy数组,那是不可能的。 我认为以下内容会有所帮助:

import numpy as np, cupy as cp
a  = cp.array([1, 2])
b = [1, 2]
c = np.asarray(b, like=a)

有什么想法/建议吗?

也许是np.copy_like? 我们将要仔细定义哪些属性
(例如,是否包含dtype)从另一个数组复制。

在2019年7月1日,星期一,上午5:40 Peter Andreas Entschev <
[email protected]>写道:

为了进一步讨论该主题,还有另一种情况未涉及
使用np.duckarray,这是基于类型创建新数组
在现有类型上,类似于np.empty_like之类的功能。
目前,我们可以执行以下操作:

将numpy导入为np,将cupy导入为cp >>> a = cp.array([1,2])>>> b = np.ones_like(a)>>> type(b)

另一方面,如果我们有一个要创建的array_like
通过NumPy的API来创建CuPy数组,这是不可能的。 我认为那是
像这样有帮助:

导入numpy为np,cupy为cp
一个= cp.array([1,2])
b = [1,2]
c = np.asarray(b,like = a)

有什么想法/建议吗?

-
您收到此邮件是因为有人提到您。
直接回复此电子邮件,在GitHub上查看
https://github.com/numpy/numpy/issues/13831?
或使线程静音
https://github.com/notifications/unsubscribe-auth/AAJJFVRSYHUYHMPWQTW2NLLP5H3LRANCNFSM4H3HQWAA

np.copy_like听起来也不错。 我同意,我们很可能应该有办法控制诸如dtype

对初学者的问题很抱歉,但是是否应该像np.copy_like这样的东西作为对NEP-22的修正,应该在邮件列表中进行讨论,或者最合适的方法是什么?

我们对此并没有严格的规定,但我倾向于将np.copy_likenp.duckarray (或我们称之为的东西)放到一个新的强制/创建鸭子数组的NEP中。它像NEP 18一样是说明性的,而不是像NEP 22这样的“信息性”。它不需要太长,引用NEP 18/22的动机很明显。

关于np.copy_like()一个注释:它肯定应该使用__array_function__ (或类似的东西)进行分派,因此可以在任一数组类型上定义np.copy_like(sparse_array, like=dask_array)类的操作。

很好,谢谢您提供的信息,我同意您的发货建议。 我将为NEP实施np.duckarraynp.copy_like并于本周为此提交一份PR草案。

太棒了,谢谢彼得!

在2019年7月1日,星期一,上午9:29 Peter Andreas Entschev <
[email protected]>写道:

很好,谢谢您提供的信息,我同意您的发货建议。 一世
将在NEP上工作,以实现np.duckarray和
np.copy_like,并为此提交一个PR草案。

-
您收到此邮件是因为有人提到您。
直接回复此电子邮件,在GitHub上查看
https://github.com/numpy/numpy/issues/13831?
或使线程静音
https://github.com/notifications/unsubscribe-auth/AAJJFVR2KTPAZ4JPWDYYMFLP5IWHNANCNFSM4H3HQWAA

我很高兴,也非常感谢您对此工作的想法和支持!

array_likecopy_like函数在我认为的主命名空间中有点奇怪,因为我们无法使用默认实现(至少没有一个会为正确的想法而做)昏昏欲睡/黄昏/稀疏/等),对吗? 它们仅在被覆盖时才有用。 还是我在这里缺少一种创建任意非numpy数组对象的方法?

没错,只有在您想支持鸭子输入时,这些才真正有用。 但是可以肯定的是,即使参数只是NumPy数组, np.duckarraynp.copy_like也可以工作,它们只相当于np.array / np.copy

所有数组实现都有copy方法对吗? 用它代替copy_like应该可以,为什么还要添加一个新函数呢?

array_like我可以看到需要,但我们可能要讨论将其放置在何处。

np.duckarray对我来说确实有意义。

我倾向于将np.copy_like和np.duckarray(或我们称为它的东西)放到一个新的强制/创建鸭子数组的NEP中,这是像NEP 18这样的说明性而不是像NEP 22这样的“信息性”。

+1

我可以看到array_like的需求,但我们可能要讨论将其放置在何处。

实际上,我想用np.copy_like类的东西解决这种情况。 我没有测试过,但是如果数组不是NumPy,则可能np.copy已经正确调度了。

为了清楚起见,您是否还提到函数np.array_like ? 我故意避免使用这样的名称,因为我认为它可能会使现有的对array_like -arrays的引用造成混淆。 但是,我现在确实意识到np.copy_like可能意味着必要的副本,并且我认为具有类似于np.asarray的行为会很好,在这种情况下,仅当副本不是NumPy时才会发生数组。 在这种情况下这里讨论,最好是使只有副本,如果a不是同一类型b在通话中,如np.copy_like(a, like=b)

我还没有测试过,但是如果数组不是NumPy,则可能np.copy已经正确调度了。

它应该经过修饰以支持__array_function__

为了清楚起见,您是否还提到函数np.array_like ? 我故意避免使用这样的名称,因为我认为它可能会混淆所有现有的对array_like-arrays的引用。

是。 是的,同意这可能会造成混淆。

但是,我现在确实意识到np.copy_like可能意味着必要的副本,

是的,该名称表示数据副本。

可能暗示必须复制,我认为行为类似于np.asarray会很好,

我以为那是np.duckarray

我认为上述彼得的例子可能有助于阐明这一点。 为了简单起见,下面复制并在np.copy_like了字幕。

import numpy as np, cupy as cp
a  = cp.array([1, 2])
b = [1, 2]
c = np.copy_like(b, like=a)

我以为那是np.duckarray。

实际上, np.duckarray基本上不会执行任何操作,只是返回数组本身(如果被覆盖),否则返回np.asarray (导致NumPy数组)。 例如,我们无法从Python列表中获得CuPy数组。 我们仍然需要可以为array_like调度到CuPy(或任何其他like=数组)的函数。

感谢@jakirkham提供更新的示例。

c = np.copy_like(b, like=a)

因此,它将通过a.__array_function__分派给CuPy并在该属性不存在时失败(例如a=<scipy.sparse matrix>将不起作用)吗? 感觉到我们需要针对此类情况的新名称空间或新的互操作性实用程序包。 要么将其保留,要么将其留给一个功能更强大的将来调度机制,在该机制中,可以简单地执行以下操作:

with cupy_backend:
   np.array(b)

在主要名称空间中引入对NumPy本身不支持在__array_function__限制范围内工作的新功能似乎有点不健康...。

因此,它将通过a.__array_function__分派给CuPy并在该属性不存在时失败(例如a=<scipy.sparse matrix>将不起作用)吗?

我不会说它一定会失败。 例如,我们可以默认为NumPy并发出警告(或根本不发出警告)。

感觉到我们需要针对此类情况的新名称空间或新的互操作性实用程序包。 要么将其留给功能更完善的未来调度机制

当然,拥有一个功能齐全的调度机制会很好,但是我想由于它的复杂性和向后兼容性问题,以前没有这样做过吗? 讨论发生时我不在身边,所以只是猜测。

在主要名称空间中引入新的函数对于NumPy本身不支持围绕__array_function__的限制工作似乎是不健康的。

我当然明白您的意思,但我也认为,如果我们将太多内容从主要名称空间中移开,可能会吓跑用户。 也许我错了,这只是一个印象。 无论哪种方式,我都没有提议实现不适用于NumPy的功能,而仅在单独使用NumPy时绝对不是绝对必要的。

在主要名称空间中引入新的函数对于NumPy本身不支持围绕__array_function__的限制工作似乎是不健康的。

实际上,从这个意义上说, np.duckarray也不属于主命名空间。

实际上,从这个意义上说, np.duckarray也不属于主命名空间。

我认为这是更可辩护的(类似于asarray ,它基本上会检查“这是否符合我们对ndarray的鸭子类型的定义”),但是可以。 如果我们还想公开array_function_dispatch ,并且我们有np.lib.mixins.NDArrayOperatorsMixin并计划编写更多的mixin,那么对所有与互操作性相关的东西都应该使用一个明智的新子模块。

当然,拥有一个功能齐全的调度机制会很好,但是我想由于它的复杂性和向后兼容性问题,以前没有这样做过吗? 讨论发生时我不在身边,所以只是猜测。

我认为有多种原因。 __array_function__与我们已经拥有的东西类似,因此更容易推理。 它具有较低的开销。 它可以在约6个月的时间范围内进行设计和实施, @ shoyer提出了一个有力的理由,我们需

对于所有与互操作性相关的事物,明智的新子模块可能是有意义的。

我没有提出任何异议,我认为在某处而不是在某处拥有功能会更好。 :)

我认为有多种原因。 __array_function__与我们已经拥有的东西类似,因此更容易推理。 它具有较低的开销。 它可以在约6个月的时间范围内进行设计和实施, @ shoyer提出了一个有力的理由,我们需

但是,如果我们想更广泛地利用__array_function__ ,我们现在是否还有其他选择来实现np.duckarraynp.copy_like (或其他我们决定称之为的东西)? 我愿意接受所有替代方案,但是现在我看不到任何替代方案,而是采用全功能调度方式,这可能会花费很长时间并限制__array_function__非常大(对于我所见过的大多数更复杂的情况,基本上使它不切实际)。

但是,如果我们想更广泛地利用__array_function__ ,我们现在是否还有其他选择来实现np.duckarraynp.copy_like (或其他我们决定称之为的东西)?

我认为您确实需要像这样的一组实用程序功能,才能从涵盖部分用例到超过80%的用例。 我认为没有办法解决。 我只是不喜欢弄乱主命名空间,所以建议为这些命名空间找到更好的位置。

我愿意接受所有替代方案,但是现在我看不到任何替代方案,而是采用全功能调度方式,这可能会花费很长时间并限制__array_function__非常大(对于我所见过的大多数更复杂的情况,基本上使它不切实际)。

我的意思是,我们只是在这里插入一些明显的漏洞? 我们永远不会涵盖所有“更复杂的案例”。 假设您要覆盖np.errstatenp.dtype ,这只是基于协议的方法不会发生。

至于替代方案, uarray尚不存在,我还不相信开销会降低得足够低,以至于默认情况下可以在NumPy中使用,但是它已经接近了,我们将尝试使用它创建scipy.fft后端系统(WIP PR:https://github.com/scipy/scipy/pull/10383)。 如果确实可以证明这一点,则应将其视为完整的多调度解决方案。 它已经有了一个带有Dask / Sparse / CuPy / PyTorch / XND后端的numpy API,其中一些已经足够完整,可以使用: https :

使用uarray的分派方法当然很有趣。 尽管我仍然担心我们如何处理元数组(例如Dask,xarray等)。 请查看此评论以获取详细信息。 尚不清楚此问题是否已解决(但是,如果我错过了某些内容,请纠正我)。 我有兴趣与SciPy的其他人一起尝试并提出我们如何解决此问题的方法。

请查看此评论以获取详细信息。 尚不清楚此问题是否已解决(但是,如果我错过了某些内容,请纠正我)。

我认为上周的更改可以解决此问题,但不确定-让我们将其留给另一个线程。

我有兴趣与SciPy的其他人一起尝试并提出我们如何解决此问题的方法。

我会在那里,很高兴见到你。

也许np.coerce_like()np.cast_like()copy_like更好,所以很明显不一定需要复制。 所需的功能确实与.cast()方法非常相似,不同的是我们要转换数组类型和dtypes,并且它应该是函数而不是协议,因此可以通过任一参数来实现。

使用uarray的分派方法当然很有趣。 尽管我仍然担心我们如何处理元数组(例如Dask,xarray等)。

uarray支持多个后端,因此这样的工作应该有效

with ua.set_backend(inner_array_backend), ua.set_backend(outer_array_backend):
  s = unumpy.sum(meta_array)

这可以通过让元数组在其实现内部调用ua.skip_backend来完成,或者如果元数组的后端在类型不匹配时返回NotImplemented

抄送: @hameerabbasi

我将对此进行扩展:通常,对于dask.array ,任何带有da都将被编写而无需skip_backend。 使用NumPy的任何东西都需要skip_backend。

或者,对于da您始终可以跳过调度并直接调用自己的实现,并且在各处都有skip_backend(dask.array)

至于没有附加数组的调度函数,例如onescast ,您只需设置一个后端即可完成。 与np.errstatenp.dtype 。 有一个示例覆盖np.ufunc中的unumpy

对于最初的问题, uarray提供了__ua_convert__协议,它正是这样做的。 另一种方法是让后端直接覆盖asarray

感谢您的抬头上uarray ,@rgommers,@ peterbell10,@hameerabbasi。

但是正如我所见,您必须在启动计算之前设置适当的后端,对吗? __array_function__的优点之一是库可以与其他库完全无关,例如,Dask不需要知道CuPy的存在。

@pentschev直到最近,我们才添加了“注册”后端的功能,但是直到我们推荐只有NumPy(或参考实现)才可以这样做。 然后,使用Dask的用户将只需要一个set_backend。

明白了,我猜这是什么@rgommers中提到https://github.com/numpy/numpy/issues/13831#issuecomment -507432311,在指向后端https://github.com/Quansight-Labs/uarray / tree / master / unumpy。

很抱歉有这么多问题,但是如果某些假设的应用程序依赖于各种后端,例如NumPy和Sparse,根据用户输入,也许所有内容都将仅是NumPy,仅是稀疏或两者兼而有之。 @ peterbell10提到了支持多个后端https://github.com/numpy/numpy/issues/13831#issuecomment -507458331,但是可以自动选择后端,还是需要分别处理这三种情况?

因此,对于这种情况,理想情况下,您将注册NumPy,使用稀疏上下文管理器,并在适当时从稀疏返回NotImplemented ,这将使NumPy有所回落。

在SciPy @rgommers@danielballan上,我谈到了这个问题。 我们得出结论,继续添加duckarray (使用该名称)将很有价值。 就是说,这听起来像定于1.18。 如果我误会了一切,请纠正我。 鉴于此,可以开始公关吗?

我们得出结论,继续添加duckarray (使用该名称)将很有价值。 就是说,这听起来像定于1.18。 如果我误会了一切,请纠正我。 鉴于此,可以开始公关吗?

这一切对我来说听起来很棒,但是最好以简短的NEP开头来阐明确切的建议。 参见https://github.com/numpy/numpy/issues/13831#issuecomment -507334210

当然可以。 🙂

至于先前提出的复制点,我很好奇这是否无法通过现有机制解决。 特别是这些行呢?

a2 = np.empty_like(a1)
a2[...] = a1[...]

诚然,最好将这一步降低到一行。 只是好奇这是否已经适用于该用例,或者我们是否遗漏了一些东西。

我们得出结论,继续添加duckarray(使用该名称)将很有价值。

这一切对我来说听起来很棒,但是最好以简短的NEP开头来阐明确切的建议。 参见#13831(评论)

我已经开始写了,但是还不能完成(对不起,我的糟糕计划https://github.com/numpy/numpy/issues/13831#issuecomment-507336302)。

至于先前提出的复制点,我很好奇这是否无法通过现有机制解决。 特别是这些行呢?

a2 = np.empty_like(a1)
a2[...] = a1[...]

诚然,最好将这一步降低到一行。 只是好奇这是否已经适用于该用例,或者我们是否遗漏了一些东西。

您可以这样做,但可能需要特殊的复制逻辑(例如在CuPy https://github.com/cupy/cupy/pull/2079中)。

也就是说,复制功能可能是最好的,以避免不必要的此类附加代码。

另一方面,这将替代asarray 。 所以我想知道是否要代替NEP-18提出的新功能,而不是copy_like新功能:

这些将需要他们自己的协议:
...
array和asarray,因为它们明确用于强制转换为实际的numpy.ndarray对象。

如果有机会我们想重新讨论一下,也许最好开始一个新线程。 有任何想法,建议或异议吗?

只是为了在我上面的评论中清楚起见,我自己不知道新协议是否是一个好主意(可能涉及许多我未预见到的繁琐细节),实际上只是想知道我们是否应该重新考虑并讨论这个主意。 。

在SciPy'19的开发人员会议和sprint中达成的共识是:在采取任何后续措施之前,让我们获得1.17.0并获得一些实际经验。

真的只是想知道我们是否应该重新考虑和讨论这个想法。

可能是的,但是几个月后。

可能是的,但是几个月后。

好的,谢谢您的答复!

至于先前提出的复制点,我很好奇这是否无法通过现有机制解决。 特别是这些行呢?

a2 = np.empty_like(a1)
a2[...] = a1[...]

诚然,最好将这一步降低到一行。 只是好奇这是否已经适用于该用例,或者我们是否遗漏了一些东西。

我的主要问题是,它对于不可变的鸭子数组不起作用,这并不是很常见。 同样,对于NumPy,分配数组然后填充它的额外开销可能几乎为零,但是我不确定这对所有鸭子数组都是正确的。

至于先前提出的复制点,我很好奇这是否无法通过现有机制解决。 特别是这些行呢?

a2 = np.empty_like(a1)
a2[...] = a1[...]

诚然,最好将这一步降低到一行。 只是好奇这是否已经适用于该用例,或者我们是否遗漏了一些东西。

您可以这样做,但是可能需要特殊的复制逻辑(例如在CuPy cupy / cupy#2079中)。

也就是说,复制功能可能是最好的,以避免不必要的此类附加代码。

另一方面,这将替代asarray 。 所以我想知道是否要代替NEP-18提出的新功能,而不是copy_like新功能:

这些将需要他们自己的协议:
...
array和asarray,因为它们明确用于强制转换为实际的numpy.ndarray对象。

如果有机会我们想重新讨论一下,也许最好开始一个新线程。 有任何想法,建议或异议吗?

我认为用新协议更改np.arraynp.asarray的行为不是一个好主意。 它们的既定含义是强制转换为NumPy数组,这基本上就是为什么我们需要np.duckarray

这就是说,我们可以考虑增加一个like参数duckarray 。 那将需要从上面的简化提议中更改协议-也许使用__array_function__而不是像__duckarray__这样的专用协议? 我还没有真正考虑到这一点。

至于先前提出的复制点,我很好奇这是否无法通过现有机制解决。 特别是这些行呢?

a2 = np.empty_like(a1)
a2[...] = a1[...]

诚然,最好将这一步降低到一行。 只是好奇这是否已经适用于该用例,或者我们是否遗漏了一些东西。

我的主要问题是,它对于不可变的鸭子数组不起作用,这并不是很常见。 同样,对于NumPy,分配数组然后填充它的额外开销可能几乎为零,但是我不确定这对所有鸭子数组都是正确的。

这还算公平。 实际上,我们已经可以简化事情了。 例如,今天它可以与CuPy和Sparse一起使用。

a2 = np.copy(a1)

这还算公平。 实际上,我们已经可以简化事情了。 例如,今天它可以与CuPy和Sparse一起使用。

a2 = np.copy(a1)

是的,但是我们还想“将这个鸭子数组复制到另一个鸭子数组的类型中”

我认为用新协议更改np.arraynp.asarray的行为不是一个好主意。 它们的既定含义是强制转换为NumPy数组,这基本上就是为什么我们需要np.duckarray

我对此也不确定,甚至我都不愿提出这个问题,这就是为什么我直到今天才知道。

就是说,我们可以考虑向duckarray添加一个like参数。 那将需要从上面的简化建议中更改协议-也许使用__array_function__而不是像__duckarray__这样的专用协议? 我还没有真正考虑到这一点。

我不知道这样做是否会带来任何麻烦,尽管我们可能需要谨慎一些,但我倾向于这个想法。 这似乎在各个级别上都是多余的,但也许可以遵循现有模式,而不是添加like参数,我们可以使用duckarrayduckarray_like

是的,但是我们还想“将这个鸭子数组复制到另一个鸭子数组的类型中”

以此np.copyto基础在

以此np.copyto基础在

如果我错了,请随时纠正我,但我假设您的意思是:

np.copyto(cupy_array, numpy_array)

假设NumPy愿意更改当前行为(例如, asarray总是表示目标是NumPy数组),那是否可行, copyto做相同的假设?

np.copyto已经支持__array_function__分派,但大致相当于:

def copyto(dst, src):
    dst[...] = src

我们想要相当于:

def copylike(src, like):
    dst = np.empty_like(like)
    dst[...] = src
    return dst

np.copyto已经支持__array_function__分派,但大致相当于:

def copyto(dst, src):
    dst[...] = src

我们想要相当于:

def copylike(src, like):
    dst = np.empty_like(like)
    dst[...] = src
    return dst

正确,这就是我们想要的。 copyto将被分派,并且如果源和目标具有相同的类型,则可以工作,我们需要一些东西可以分派到目标数组的库。

根据我们的想法, copyto仍然有意义。 以下面的用例为例。

np.copyto(cp.ndarray, np.random.random((3,)))

正如我们所讨论的,这可以转化为诸如分配和复制数据之类的东西。 如果我们在dst左右调度(在这种情况下cp.ndarray ),则具有不可变数组的库也可以以适当的方式实现这一点。 这也使我们不必添加新的API(NumPy仅提供但不使用),这似乎是一个问题。

刚刚浮出水面的是我最近发生的另一种想法,值得考虑一下这些API在其他库之间的下游含义(例如,Dask和Xarray的交互方式)。

此页面是否有帮助?
0 / 5 - 0 等级