【问题标题】:Modify element and neighbor in numpy 2d array修改numpy 2d数组中的元素和邻居
【发布时间】:2018-02-13 02:52:35
【问题描述】:

我正在尝试找到一种更有效的方法来修改数组的每个元素及其最低邻居。在下面的示例代码中,我根据它们的不同来修改它们,但 change() 函数可以是任何东西。

搜索后,scipy.ndimage.generic_filter() 似乎是理想的使用方法,因为它可以轻松比较元素及其邻居。从ndimage.generic_filter() 获取偏移量后,我将其提供给numpy.roll() 以修改每个元素选择的邻居。

问题在于,对于非常大的数组和多次迭代,循环通过 np.roll() ndimage.generic_filter() 的低效率会影响性能。使用 10000x10000 数组,我下面代码的执行时间是 5m42s。使用 scipy 或 numpy 是否有更有效的方法?

import numpy as np
from scipy import ndimage

dic = {0: (-1, -1), 1: (-1, 0), 2: (-1, 1),
       3: (0, -1),  4: (0, 0),  5: (0, 1),
       6: (1, -1),  7: (1, 0),  8: (1, 1)}

def change(a):
    return (a[4] - min(a)) / 2

def movement(a):
    return np.argmin(a)

P = np.random.uniform(10, size=(5,5))

# determine element change and direction
chng = np.zeros((5, 5)) 
ndimage.generic_filter(P, change, size=3, output=chng)
move = np.random.randint(10, size=(5, 5))
ndimage.generic_filter(P, movement, size=3, output=move)
P -= chng

# determine neighbor change
chng2 = np.zeros((5, 5))
for j in range(9):
    if j == 4:
        continue
    p = np.where(move == j, chng, 0)
    p = np.roll(p, dic[j], axis=(0, 1))
    chng2 += p
P += chng2

编辑:以下是更有效的解决方案。非常感谢@Paul Panzer。

import numpy as np

P = np.random.uniform(10, size=(1000, 1000))

# determine element change and direction
PP = np.bmat([[P[:, -1:], P, P[:, :1]]])
PP = np.bmat([[PP[-1:]], [PP], [PP[:1]]])
PPP = np.lib.stride_tricks.as_strided(PP, (1000, 1000, 3, 3), 2 * PP.strides)
am1 = np.argmin(PPP, axis=3)
i, j, k = np.ogrid[(*map(slice, PPP.shape[:-1]),)]
am0 = np.argmin(PPP[i, j, k, am1], axis=2)
i, j = np.ogrid[(*map(slice, PPP.shape[:-2]),)]
am1 = am1[i, j, am0]
mn = PPP[i, j, am0, am1]
change = (P - mn) / 2
P -= change

# determine neighbor change
am1 -= 1
am0 -= 1
i, j = np.ogrid[(*map(slice, P.shape),)]
np.add.at(P, ((am0 + i) % P.shape[0], (am1 + j) % P.shape[1]), change)

【问题讨论】:

    标签: python arrays numpy scipy


    【解决方案1】:

    您可以使用np.add.at。以下 sn-p 在您的 P -= chng 之后接听:

    >>> P_pp = P.copy()
    >>> dic_i, dic_j = np.indices((3, 3)).reshape(2, 9) - 1
    >>> i, j = np.ogrid[(*map(slice, P_pp.shape),)]
    >>> np.add.at(P_pp, ((dic_i[move] + i) % P_pp.shape[0], (dic_j[move] + j) % P_pp.shape[1]), chng)
    

    由于我们处理了P 的副本,我们现在可以执行您的其余代码,然后:

    # Tada!
    >>> np.allclose(P_pp, P)
    True
    

    更新:这是一种不使用ndimage 计算本地argmin 的方法。一个潜在的优势是,一旦我们有了 argminima,我们就可以廉价地获得相应的最小值。请注意,argmin 已经是 2D 第一个组件在 am0,第二个在 am1。每个范围在02之间,所以中心在1,1,最小值在mn

    >>> P = np.random.uniform(10, size=(5,5))
    >>> PP = np.bmat([[P[:,-1:], P, P[:, :1]]])
    >>> PP = np.bmat([[PP[-1:]], [PP], [PP[:1]]])
    >>> PPP = np.lib.stride_tricks.as_strided(PP, (5, 5, 3, 3), 2 * PP.strides)
    >>> am1 = np.argmin(PPP, axis=3)
    >>> i, j, k = np.ogrid[(*map(slice, PPP.shape[:-1]),)]
    >>> am0 = np.argmin(PPP[i, j, k, am1], axis=2)
    >>> i, j = np.ogrid[(*map(slice, PPP.shape[:-2]),)]
    >>> am1 = am1[i, j, am0]
    >>> mn = PPP[i, j, am0, am1]
    

    【讨论】:

    • 您确定邻居更改的替代解决方案效果很好。不幸的是,我发现新代码的执行时间几乎相同。我应该做更彻底的测试,因为真正的罪魁祸首实际上是两次使用 ndimage.generic_filter(),这几乎占用了整个代码的所有计算时间。现在我正在寻找替换该部分。
    • @Meta 我添加了 generic_filter 的替代品。不知道它如何比较速度。可以试试吗?
    • 使用10000x10000测试阵列,性能从5m42s提升到38s!谢谢两位cmets!
    【解决方案2】:

    这可能就是你要找的https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.convolve2d.html

    in2(Cf 文档)将是一个矩阵,对应于您作为 dict 编写的内容

    dic = {0: (-1, -1), 1: (-1, 0), 2: (-1, 1),
       3: (0, -1),  4: (0, 0),  5: (0, 1),
       6: (1, -1),  7: (1, 0),  8: (1, 1)}
    

    希望对你有帮助

    【讨论】:

    • 虽然卷积与我尝试执行的操作密切相关,但它不会修改最小邻居。相反,它将通过其邻居修改所选元素。我是否特别想念您是如何考虑使用它的?
    猜你喜欢
    • 1970-01-01
    • 2017-03-24
    • 1970-01-01
    • 2022-06-10
    • 2021-11-14
    • 1970-01-01
    • 2018-04-20
    • 1970-01-01
    • 2017-06-18
    相关资源
    最近更新 更多