Numpy将输入数组作为`out`参数传递给ufunc

Mad*_*ist 15 python numpy in-place numpy-ufunc

如果类型正确,提供输入数组作为numpy中的ufunc的可选输出参数通常是安全的吗?例如,我已经验证以下工作:

>>> import numpy as np
>>> arr = np.array([1.2, 3.4, 4.5])
>>> np.floor(arr, arr)
array([ 1.,  3.,  4.])
Run Code Online (Sandbox Code Playgroud)

数组类型必须与输出兼容或与输出相同(它是一个浮点数numpy.floor()),否则会发生这种情况:

>>> arr2 = np.array([1, 3, 4], dtype = np.uint8)
>>> np.floor(arr2, arr2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: ufunc 'floor' output (typecode 'e') could not be coerced to provided output parameter (typecode 'B') according to the casting rule ''same_kind''
Run Code Online (Sandbox Code Playgroud)

因此,给定一个正确类型的数组,通常可以安全地应用ufuncs吗?或者是floor()一个例外情况?文档没有说清楚,以下两个线程也没有与问题相关:

  1. Numpy修改数组到位?
  2. Numpy Ceil和Floor"out"Argument

编辑:

作为第一顺序猜测,我认为它基于http://docs.scipy.org/doc/numpy/user/c-info.ufunc-tutorial.html上的教程,经常但并不总是安全的.在计算过程中使用输出数组作为中间结果的临时持有者似乎没有任何限制.虽然类似于floor()ciel()可能不需要临时存储,但更复杂的功能可能会.话虽这么说,整个现有的库可能会记住这一点.

nbe*_*dou 8

numpy 函数的参数out是写入结果的数组。使用的主要优点out是避免在不必要的地方分配新内存。

在作为输入传递的同一数组上使用函数的输出写入是否安全?没有通用的答案,这取决于函数在做什么。

两个例子

以下是两个类似 ufunc 的函数的示例:

In [1]: def plus_one(x, out=None):
   ...:     if out is None:
   ...:         out = np.zeros_like(x)
   ...: 
   ...:     for i in range(x.size):
   ...:         out[i] = x[i] + 1
   ...:     return out
   ...: 

In [2]: x = np.arange(5)

In [3]: x
Out[3]: array([0, 1, 2, 3, 4])

In [4]: y = plus_one(x)

In [5]: y
Out[5]: array([1, 2, 3, 4, 5])

In [6]: z = plus_one(x, x)

In [7]: z
Out[7]: array([1, 2, 3, 4, 5])
Run Code Online (Sandbox Code Playgroud)

功能shift_one

In [11]: def shift_one(x, out=None):
    ...:     if out is None:
    ...:         out = np.zeros_like(x)
    ...: 
    ...:     n = x.size
    ...:     for i in range(n):
    ...:         out[(i+1) % n] = x[i]
    ...:     return out
    ...: 

In [12]: x = np.arange(5)

In [13]: x
Out[13]: array([0, 1, 2, 3, 4])

In [14]: y = shift_one(x)

In [15]: y
Out[15]: array([4, 0, 1, 2, 3])

In [16]: z = shift_one(x, x)

In [17]: z
Out[17]: array([0, 0, 0, 0, 0])
Run Code Online (Sandbox Code Playgroud)

对于函数来说plus_one没有问题:当参数 x 和 out 是同一个数组时,就得到了预期的结果。但是shift_one当参数 x 和 out 是同一个数组时,该函数会给出令人惊讶的结果,因为数组

讨论

对于 形式的函数out[i] := some_operation(x[i]),例如plus_one上面的函数,还有函数 Floor、ceil、sin、cos、tan、log、conj 等,据我所知,使用参数 out 将结果写入输入是安全的。

对于采用“out[i] := some_operation(x[i], y[i]) 形式的两个输入参数的函数来说也是安全的,例如 numpy 函数加、乘、减。

对于其他功能,则视具体情况而定。如下所示,矩阵乘法并不安全:

In [18]: a = np.arange(4).reshape((2,2))

In [19]: a
Out[19]: 
array([[0, 1],
       [2, 3]])

In [20]: b = (np.arange(4) % 2).reshape((2,2))

In [21]: b
Out[21]: 
array([[0, 1],
       [0, 1]], dtype=int32)

In [22]: c = np.dot(a, b)

In [23]: c
Out[23]: 
array([[0, 1],
       [0, 5]])

In [24]: d = np.dot(a, b, out=a)

In [25]: d
Out[25]: 
array([[0, 1],
       [0, 3]])
Run Code Online (Sandbox Code Playgroud)

最后一点:如果实现是多线程的,不安全函数的结果甚至可能是不确定的,因为它取决于数组元素的处理顺序。


lxo*_*xop 2

这是一个老问题,但有一个更新的答案:

是的,它很安全。在Numpy 文档中,我们看到从 v1.13 开始:

ufunc 输入和输出操作数具有内存重叠的操作定义为与不存在内存重叠的等效操作相同。受影响的操作根据需要制作临时副本以消除数据依赖性。由于检测这些情况的计算成本很高,因此使用启发式方法,这在极少数情况下可能会导致不必要的临时副本。对于数据依赖性足够简单以供启发式分析的操作,即使数组重叠也不会创建临时副本,如果可以推断出副本是不必要的。例如, np.add(a, b, out=a) 不会涉及副本。