通过滥用as_strided在NumPy中的迭代算法

4

我想知道是否可能使用 as_strided 和一些编辑内存的操作来编写一个不使用 for 循环的迭代算法。

例如,如果我想要编写一个算法,将数组中的数字替换为其相邻元素的和。 我想到了这个可怕的解决方法(是的它会将一个元素与其右侧的 2 个相邻元素相加,但只是为了得到一个想法):

import numpy as np

a = np.arange(10)
ops = 2
a_view_window = np.lib.stride_tricks.as_strided(a, shape = (ops,a.size - 2, 3), strides=(0,) + 2*a.strides)
a_view = np.lib.stride_tricks.as_strided(a, shape = (ops,a.size - 2), strides=(0,) + a.strides)
np.add.reduce(a_view_window, axis = -1, out=a_view)
print(a)

我正在使用一个包含10个数字的数组,并创建这种奇怪的视图,它增加了维度而不改变步幅。因此,我的想法是,在假新维度上运行缩减操作,并覆盖以前的值,因此当它到达下一个主要维度时,它必须从已经被覆盖的数据中读取,并迭代执行加法。

很遗憾,这种方法不起作用:(

(是的,我知道这是一种可怕的做事方式,但我对底层的numpy东西如何工作,以及它是否可以以这种方式被突破感到好奇)


我不确定我完全理解你想做什么。为了参考,你想要什么样的输出? - Mad Physicist
@MadPhysicist,对于设置为1的操作,我会期望a=[3,6,9,15,…],对于设置为2的操作,我会期望a=[18,30,…]。但是Jerome给出的答案表明numpy比我更聪明,并且不会让我这样自我毁灭(可悲)。 - Simon Tartakovksy
a_view_window[0].sum(axis=-1) 产生了 [3,6,9,...],一个滑动求和。通过带有 out 的表达式将其写入 a_view,进而写入 a(保留了一些未更改的值)。如果您想要复制对数组进行迭代的效果,则 np.add.at 是推荐的工具。它避免了 += 所做的缓冲。我不知道在这里是否有使用它的方法。as_strided 允许进行 write,但建议将其设置为 false,因为写的结果可能很难预测。as_strided 对于像移动平均这样的事物非常好,但不适用于顺序操作。 - hpaulj
是的,希望由于a_view具有“冗余”的主维度(只是覆盖自身),因此当np.add.reduce循环查看时,它会迭代地进行求和。但是,ufuncs可以防止这种就地未定义行为。 - Simon Tartakovksy
1个回答

3
此代码在Numpy 1.13之前会导致未定义行为,并且在更新版本中以非就地方式运行,以避免重叠/别名问题。事实上,您不能假定Numpy按照给定的顺序迭代输入/输出数组视图。实际上,Numpy经常使用SIMD指令来加速代码,有时告诉编译器视图彼此不重叠/别名(使用restrict关键字),以便它们可以生成更高效的代码。有关更多信息,请阅读ufunc文档(和此问题):

在早期的NumPy版本中,ufunc输入和输出操作数存在内存重叠会导致未定义结果的操作,由于数据依赖性问题。 在NumPy 1.13.0中,这些操作的结果现在被定义为与没有内存重叠的等价操作相同。
受影响的操作现在会生成临时副本,以消除数据依赖性。 由于检测这些情况在计算方面很昂贵,因此使用一种启发式方法,可能在极少数情况下导致不必要的临时副本。 对于启发式能够分析的数据依赖性足够简单的操作,即使数组重叠,也不会进行临时复制,如果可以推断出不需要副本。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接