考虑这种不合时宜的操作:
>>> b = numpy.asarray([1, 2, 3])
>>> b[1:] = b[1:] - b[:-1]
>>> b
array([1, 1, 1])
现在考虑就地操作:
>>> a = numpy.asarray([1, 2, 3])
>>> a[1:] -= a[:-1]
>>> a
array([1, 1, 2])
他们给出了不同的结果,这是我没想到的。
我会假设NumPy会以正确的顺序(向后)进行减法,这样他们就可以得到与异地减法相同的结果。
我的问题是:这是NumPy部分的预期行为,还是一个错误,或结果未定义?
答案 0 :(得分:1)
未定义,或至少难以理解,可能是最好的答案。另一个SO答案声称
a[1:] -= a[:-1]
解释器将翻译成类似
的内容a.__setitem__(slice(1,None), a.__getitem__(slice(1,None)).
__isub__(a.__getitem__(slice(None,-1))))
In [171]: a=np.arange(10)
In [172]: a[1:] -= a[:-1]
In [173]: a
Out[173]: array([0, 1, 1, 2, 2, 3, 3, 4, 4, 5])
评估如下:
In [175]: for i in range(9):
...: a[i+1] = a[i+1]-a[i]
我可以从嵌套的__setitem__
等表达式中看到它是如何得到的。我尝试使用np.nditer
复制它。
你提到的反面是
In [178]: for i in range(8,-1,-1):
...: a[i+1] = a[i+1]-a[i]
我没有numpy
能够推断出需要这样的反向迭代的任何方式。 __setitem__
的第二个参数通过前向迭代评估得很好。缓冲该术语是唯一简单的解决方案。
引入.at
ufunc方法是为了解决a[idx] += b
等表达式中的缓冲问题。特别是当idx
有重复时。应该对a
的影响是累积的,还是仅应用最后一个实例。
在您的示例中,如果表现得像a[1:] - a[:-1]
:
In [165]: a=np.arange(10)
In [166]: idx=np.arange(1,10)
In [167]: np.add.at(a, idx, -a[:-1])
In [168]: a
Out[168]: array([0, 1, 1, 1, 1, 1, 1, 1, 1, 1])
那是因为add.at
的第三个参数在被使用之前被完全评估了。它是临时副本。我从其他测试中得知add.at
比普通a[idx] +=
慢。 [我对于什么'缓冲'有点困惑。 add.at
正在绕过;这与在这里出现问题的明显缺乏缓冲有什么不同?]
为什么要使用+=
表示法?只是为了使代码更紧凑?还是希望加快速度?但如果速度是目标,我们是否希望numpy
抛出一些额外的缓冲,只是为了让它更安全?
等同于nditer
a[1:] -= a[:-1]
In [190]: a=np.arange(10)
In [191]: it = np.nditer([a[1:],a[1:],a[:-1]], op_flags=['readwrite'])
In [192]: for i,j,k in it:
...: print(i,j,k)
...: i[...] = j-k
...: print(i)
1 1 0
1
2 2 1
1
3 3 1
2
4 4 2
2
5 5 2
3
6 6 3
...
迭代可以简化
In [197]: it = np.nditer([a[1:],a[:-1]], op_flags=['readwrite'])
In [198]: for i,j in it:
...: i[...] -= j
因为它是一个视图,a[:-1]
的迭代值将反映前一个循环中所做的更改。
我不确定c
版本nditer
正在使用数组+=
表达式,但nditer
的意图是整合迭代编码成一个统一的框架。
另一个有趣的观察是,如果我定义
idx = array([1, 2, 3, 4, 5, 6, 7, 8, 9])
然后
a[idx] -= a[:-1]
a[1:] -= a[idx-1]
a[idx] -= a[idx-1]
全部提供所需的array([0, 1, 1, 1, 1, 1, 1, 1, 1, 1])
。换句话说,-=
的两边都必须是观看/切片'。这必须是add.at
绕过的缓冲。 a[idx-1]
是一个副本是显而易见的。那个a[idx]-=
抛出一个缓冲区,而a[1:]-=
却不是这样。
答案 1 :(得分:1)
此行为以前未定义,但是since NumPy 1.13.0中,输入和输出重叠的操作现在的行为就好像首先复制输入一样。引用发行说明:
由于数据依赖性问题,ufunc输入和输出操作数与内存重叠的操作在以前的NumPy版本中产生了不确定的结果。在NumPy 1.13.0中,现在将此类操作的结果定义为与没有内存重叠的等效操作相同。
受影响的操作现在可以临时复制,以消除数据依赖性。由于检测这些情况在计算上很昂贵,因此使用了一种启发式方法,在极少数情况下可能会导致不必要的临时副本。对于数据依赖关系足够简单以进行启发式分析的操作,即使阵列重叠也不会创建临时副本,即使可以推断出这些副本也不是必需的。例如,
np.add(a, b, out=a)
将不涉及副本。