简体   繁体   English

带有重叠切片的NumPy就地操作

[英]NumPy in-place operations with overlapping slices

Consider this out-of-place operation: 请考虑以下不适当的操作:

>>> b = numpy.asarray([1, 2, 3])
>>> b[1:] = b[1:] - b[:-1]
>>> b
array([1, 1, 1])

Now consider the in-place operation: 现在考虑就地操作:

>>> a = numpy.asarray([1, 2, 3])
>>> a[1:] -= a[:-1]
>>> a
array([1, 1, 2])

They give different results, which I did not expect. 他们给出了不同的结果,这是我没想到的。

Update: It seems this behavior changed; 更新:看来这种行为已经改变; now they give the same result. 现在他们给出相同的结果。

I would have assumed NumPy would have done the subtraction in the correct order (backward) so that they would give equivalent results to the out-of-place subtraction. 我以为NumPy会以正确的顺序(向后)进行减法,以便他们将得出与原位减法等效的结果。

My question is: is this intended behavior on NumPy's part, or is it a bug, or is the result undefined? 我的问题是:这是NumPy的预期行为,还是错误,或者结果不确定?

Undefined, or at least hard-to-understand, may be best answer. 最好的答案是未定义或至少难以理解。 Another SO answer claims 另一个SO答案声称

a[1:] -= a[:-1]

gets translated, by the interpreter, into something like 由口译员翻译成类似

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])

evaluates the same as: 计算结果与:

In [175]: for i in range(9):
     ...:     a[i+1] = a[i+1]-a[i]

I can sort of see how it gets this from the nested __setitem__ etc expression. 我可以看到它是如何从嵌套的__setitem__等表达式中获取的。 I'm try to replicate it with np.nditer . 我尝试使用np.nditer复制它。

the reverse that you mention would be 你提到的相反

In [178]: for i in range(8,-1,-1):
     ...:     a[i+1] = a[i+1]-a[i]

I don't there's any way that numpy could deduce that such a reverse iteration is required. 我没有任何办法让numpy推断出需要这样的反向迭代。 The 2nd argument to __setitem__ evaluates just fine with forward iteration. __setitem__的第二个参数的正向迭代评估就很好。 Buffering that term is the only simple solution. 缓冲该术语是唯一简单的解决方案。

The .at ufunc method was introduced as a way to get around buffering issues in expressions like a[idx] += b . 引入.at ufunc方法是一种解决a[idx] += ba[idx] += b表达式中的缓冲问题的方法。 In particular when idx has dupicates. 特别是当idx具有重复性时。 Should the effect on a be cumulative or should just the last instance apply. 应在影响a是累积的或应该只是最后一个实例应用。

In your example if behaves just like a[1:] - a[:-1] : 在您的示例中,if的行为类似于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])

That's because 3rd argument to add.at is fully evaluated before being used. 这是因为add.at的第3个参数在使用add.at经过充分评估。 It's a temporary copy. 这是一个临时副本。 I know from other tests that add.at is slower than the ordinary a[idx] += . 从其他测试中我知道add.at比普通a[idx] +=慢。 [I'm a little confused as to what 'buffering' add.at is bypassing; [我对绕过什么'缓冲' add.at感到有些困惑; how that's different from the apparent lack of buffering that is giving problems here?] 与明显缺乏缓冲的地方有何不同?]

Why use the += notation? 为什么使用+=表示法? Just to make the code more compact? 只是为了使代码更紧凑? or in hopes of making it faster? 还是希望更快? But if speed is the goal, do we want numpy throw in some extra buffering, just to make it safer? 但是,如果速度是目标,我们是否希望numpy抛出一些额外的缓冲,只是为了使其更加安全?


An nditer equivalent of the a[1:] -= a[:-1] 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
...

The iteration can be simplified 迭代可以简化

In [197]: it = np.nditer([a[1:],a[:-1]], op_flags=['readwrite'])
In [198]: for i,j in it:
     ...:     i[...] -= j

Because it's a view the iterated value from a[:-1] will reflect changes made in the previous loop. 因为它是一个视图,所以从a[:-1]迭代的值将反映在上一个循环中所做的更改。

I'm not sure that c version of nditer is being using in the array += expression, but the intention of nditer was to consolidate the iteration coding into one unified framework. 我不确定在数组+=表达式中是否正在使用nditer c版本,但是nditer的目的是将迭代编码整合到一个统一的框架中。


Another interesting observation is that if I define 另一个有趣的发现是,如果我定义

idx = array([1, 2, 3, 4, 5, 6, 7, 8, 9])

then 然后

a[idx] -= a[:-1]
a[1:] -= a[idx-1]
a[idx] -= a[idx-1]

all give the desired array([0, 1, 1, 1, 1, 1, 1, 1, 1, 1]) . 全部给出所需的array([0, 1, 1, 1, 1, 1, 1, 1, 1, 1]) In other words both sides of the -= have to be 'views/slices'. 换句话说, -=两侧都必须是“视图/切片”。 That must be the buffering that add.at bypasses. 那一定是add.at绕过的缓冲。 That a[idx-1] is a copy is obvious. 很明显a[idx-1]是一个副本。 That a[idx]-= throws in a buffer, while a[1:]-= does not, is not so obvious. a[idx]-=会抛出一个缓冲区,而a[1:]-=不会,这并不是很明显。

This behavior was previously undefined, but since NumPy 1.13.0 , operations with overlapping input and output now behave as if inputs were copied first. 此行为以前未定义,但是自NumPy 1.13.0起 ,输入和输出重叠的操作现在的行为就好像首先复制输入一样。 Quoting the release notes: 引用发行说明:

Operations where ufunc input and output operands have memory overlap produced undefined results in previous NumPy versions, due to data dependency issues. 由于数据相关性问题,ufunc输入和输出操作数具有内存重叠的操作在以前的NumPy版本中产生了不确定的结果。 In NumPy 1.13.0, results from such operations are now defined to be the same as for equivalent operations where there is no memory overlap. 在NumPy 1.13.0中,现在将此类操作的结果定义为与没有内存重叠的等效操作相同。

Operations affected now make temporary copies, as needed to eliminate data dependency. 现在,受影响的操作会根据需要创建临时副本,以消除数据依赖性。 As detecting these cases is computationally expensive, a heuristic is used, which may in rare cases result to needless temporary copies. 由于检测这些情况在计算上很昂贵,因此使用了一种启发式方法,在极少数情况下可能会导致不必要的临时副本。 For operations where the data dependency is simple enough for the heuristic to analyze, temporary copies will not be made even if the arrays overlap, if it can be deduced copies are not necessary. 对于数据依赖关系足够简单以进行启发式分析的操作,即使阵列重叠也不会创建临时副本,即使可以推断出这些副本也不是必需的。 As an example, np.add(a, b, out=a) will not involve copies. 例如, np.add(a, b, out=a)将不涉及副本。

Here is the relevant issue on GitHub . 这是GitHub上的相关问题

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM