【问题标题】:Accessing neighboring cells for numpy array访问 numpy 数组的相邻单元格
【发布时间】:2019-03-29 15:06:36
【问题描述】:

如何以有效的方式访问和修改 2D numpy 数组的周围 8 个单元?

我有一个像这样的二维 numpy 数组:

arr = np.random.rand(720, 1440)

对于每个网格单元,我想减少 10% 的中心单元,周围的 8 个单元(角单元减少),但前提是周围的单元值超过 0.25。我怀疑这样做的唯一方法是使用 for 循环,但想看看是否有更好/更快的解决方案。

-- 编辑:基于循环的求解:

arr = np.random.rand(720, 1440)

for (x, y), value in np.ndenumerate(arr):
    # Find 10% of current cell
    reduce_by = value * 0.1

    # Reduce the nearby 8 cells by 'reduce_by' but only if the cell value exceeds 0.25
    # [0] [1] [2]
    # [3] [*] [5]
    # [6] [7] [8]
    # * refers to current cell

    # cell [0]
    arr[x-1][y+1] = arr[x-1][y+1] * reduce_by if arr[x-1][y+1] > 0.25 else arr[x-1][y+1]

    # cell [1]
    arr[x][y+1] = arr[x][y+1] * reduce_by if arr[x][y+1] > 0.25 else arr[x][y+1]

    # cell [2]
    arr[x+1][y+1] = arr[x+1][y+1] * reduce_by if arr[x+1][y+1] > 0.25 else arr[x+1][y+1]

    # cell [3]
    arr[x-1][y] = arr[x-1][y] * reduce_by if arr[x-1][y] > 0.25 else arr[x-1][y]

    # cell [4] or current cell
    # do nothing

    # cell [5]
    arr[x+1][y] = arr[x+1][y] * reduce_by if arr[x+1][y] > 0.25 else arr[x+1][y]

    # cell [6]
    arr[x-1][y-1] = arr[x-1][y-1] * reduce_by if arr[x-1][y-1] > 0.25 else arr[x-1][y-1]

    # cell [7]
    arr[x][y-1] = arr[x][y-1] * reduce_by if arr[x][y-1] > 0.25 else arr[x][y-1]

    # cell [8]
    arr[x+1][y-1] = arr[x+1][y-1] * reduce_by if arr[x+1][y-1] > 0.25 else arr[x+1][y-1]

【问题讨论】:

  • 添加一个有效的循环解决方案?
  • 结果很大程度上取决于遍历顺序,但是呃。我可以建议的唯一改进是使用 numpy 的意见a=arr[x-1:x+1, y-1:y+1]; a-=value; a[1,1]+=value; a=np.clip(a, 0.25) 你明白了。
  • @WalterTross,如果边界单元格保持不变,我会没事的。
  • 明确一点:正如所写,当您引用它们时,值已经减少了。也就是说,a[0, 0] 可能是 0.4,但在循环到达 a[1, 0] 时会减少到 0.2,因此初始值不会影响 a[1,0]。这是故意的吗?
  • 我感觉这只能迭代完成,因为一步会影响下一步

标签: python numpy


【解决方案1】:

请澄清您的问题

  • 真的像 @jakevdp 在 cmets 中提到的那样,一个循环迭代依赖于另一个循环迭代吗?
  • 如果是这种情况,应该如何处理边界像素?由于一个循环迭代对其他循环迭代的依赖性,这将影响整个结果
  • 请添加一个有效的参考实现(您的参考实现出现越界错误)

边界不变,依赖循环迭代

除了以这种方式使用编译器之外,我没有看到任何其他方式。在这个例子中,我使用了Numba,但如果不这样做,你也可以在Cython 中做同样的事情。

import numpy as np
import numba as nb

@nb.njit(fastmath=True)
def without_borders(arr):
  for x in range(1,arr.shape[0]-1):
    for y in range(1,arr.shape[1]-1):
      # Find 10% of current cell
      reduce_by = arr[x,y] * 0.1

      # Reduce the nearby 8 cells by 'reduce_by' but only if the cell value exceeds 0.25
      # [0] [1] [2]
      # [3] [*] [5]
      # [6] [7] [8]
      # * refers to current cell

      # cell [0]
      arr[x-1][y+1] = arr[x-1][y+1] * reduce_by if arr[x-1][y+1] > 0.25 else arr[x-1][y+1]

      # cell [1]
      arr[x][y+1] = arr[x][y+1] * reduce_by if arr[x][y+1] > 0.25 else arr[x][y+1]

      # cell [2]
      arr[x+1][y+1] = arr[x+1][y+1] * reduce_by if arr[x+1][y+1] > 0.25 else arr[x+1][y+1]

      # cell [3]
      arr[x-1][y] = arr[x-1][y] * reduce_by if arr[x-1][y] > 0.25 else arr[x-1][y]

      # cell [4] or current cell
      # do nothing

      # cell [5]
      arr[x+1][y] = arr[x+1][y] * reduce_by if arr[x+1][y] > 0.25 else arr[x+1][y]

      # cell [6]
      arr[x-1][y-1] = arr[x-1][y-1] * reduce_by if arr[x-1][y-1] > 0.25 else arr[x-1][y-1]

      # cell [7]
      arr[x][y-1] = arr[x][y-1] * reduce_by if arr[x][y-1] > 0.25 else arr[x][y-1]

      # cell [8]
      arr[x+1][y-1] = arr[x+1][y-1] * reduce_by if arr[x+1][y-1] > 0.25 else arr[x+1][y-1]
  return arr

时间安排

arr = np.random.rand(720, 1440)

#non-compiled verson: 6.7s
#compiled version:    6ms (the first call takes about 450ms due to compilation overhead)

这很容易做到大约 1000 倍。根据前 3 点,可能会有更多优化。

【讨论】:

    【解决方案2】:

    不需要循环,避免通常的python循环,它们非常慢。为了提高效率,请尽可能依赖 numpy 内置的矩阵运算、“通用”函数、过滤器、掩码和条件。 https://realpython.com/numpy-array-programmin 对于复杂的计算,向量化还不错,请参阅一些图表和基准Most efficient way to map function over numpy array(只是不要将其用于更简单的矩阵运算,例如单元格的平方,内置函数会表现出色)

    很容易看出,由于 8 个邻居(减少了 0.1),每个内部单元格将乘以 0.9 最多 8 倍,另外由于是一个中心单元格, 但它不能降低到 .25/.9 = 5/18 以下。对于边界和角单元格的减少次数分别为 6 和 3 次。

    因此

    x1 = 700  # for debugging use lesser arrays
    x2 = 1400
    
    neighbors = 8 # each internal cell has 8 neighbors
    
    
    for i in range(neighbors):
         view1 = arr[1:-1, 1:-1] # internal cells only
         arr [1:x1, 1:-1] = np.multiply(view1,.9, where = view1 > .25)
    
    arr [1:-1, 1:-1] *= .9 
    

    边界和角落的处理方式相同,邻居分别为 5 和 3,视图不同。我猜这三种情况都可以用复杂的 where 情况合并到一个公式中,但速度会适中,因为边框和角落只占所有单元格的一小部分。

    这里我使用了一个小循环,但它只有 8 次重复。它也应该可以摆脱循环,使用幂、对数、整数部分和最大函数,导致有点笨拙,但有点快的单线,大约

          numpy.multiply( view1, x ** numpy.max( numpy.ceil( (numpy.log (* view1/x... / log(.9)
    

    我们还可以尝试另一种有用的技术,向量化。 矢量化正在构建一个函数,然后可以将其应用于数组的所有元素。

    为了改变,让预设边距/阈值找出要乘以的确切系数。这是代码的样子

    n = 8
    decrease_by = numpy.logspace(1,N,num=n, base=x, endpoint=False)
    
    margins = decrease_by * .25
    
    # to do : save border rows for further analysis, skip this for simplicity now    
    view1 = a [1: -1, 1: -1]
    
    def decrease(x):
    
    
        k = numpy.searchsorted(margin, a)
        return x * decrease_by[k]
    
    f = numpy.vectorize(decrease)
    f(view1)
    

    备注 1 可以尝试使用不同的方法组合,例如使用矩阵算术而不是矢量化的预计算边距。也许还有更多技巧可以稍微加快上述每个解决方案或上述组合的速度。

    备注 2 PyTorch 与 Numpy 功能有很多相似之处,但可以从 GPU 中受益匪浅。如果你有一个不错的 GPU,可以考虑 PyTorch。尝试了基于 gpu 的 numpy(gluon,废弃的 gnumpy,minpy) 更多关于 gpu 的信息 https://stsievert.com/blog/2016/07/01/numpy-gpu/

    【讨论】:

    • 感谢文章链接!但是,恐怕np.vectorize()“本质上是一个for循环”。
    • 您能否确认您的解决方案是否给出了正确的结果?例如。与 max9111 函数 without_borders(arr)(由 numba 加速的 OP 原始解决方案)或我的函数 reduce_(arr) 返回的结果进行比较,两者都返回相同(正确)的结果。
    • 1.我没有测试它可能有错字或错误,但无论哪种情况,我认为提供的代码都不能很好地符合问题陈述或请求者的需求。看起来其他评论者和/或版主对 requester.2 的一些代码感到害怕。即使是这样,这个问题也可能有不止一个正确的解决方案。例如,减少的顺序并不重要,即使它会影响结果。我想像让我们尝试降低对比度摆脱噪音等任务
    • Andy 和 max 都给出了非常准确的答案。然而,我个人觉得 Walter 的解决方案更有趣,因为问题更多是关于避免循环的可能性。
    • 其实我更喜欢 Walter 的“滚动”解决方案(使用 numpy.pad 可以轻松修复边界)
    【解决方案3】:

    此答案假定您真的想要完全按照您在问题中所写的内容进行操作。好吧,几乎完全一样,因为您的代码崩溃是因为索引超出范围。解决这个问题的最简单方法是添加条件,例如,

    if x > 0 and y < y_max:
        arr[x-1][y+1] = ...
    

    主要操作不能使用 numpy 或 scipy 向量化的原因是所有单元格都被一些相邻单元格“减少”,而这些相邻单元格已经被“减少”了。 Numpy 或 scipy 将在每个操作中使用邻居的未受影响值。在我的另一个答案中,我展示了如何使用 numpy 执行此操作,如果您被允许将操作分成 8 个步骤进行分组,每个步骤都沿着一个特定邻居的方向,但每个步骤都使用该步骤中的 unaffected 值邻居。正如我所说,在这里我假设您必须按顺序进行。

    在我继续之前,让我在你的代码中交换 xy。您的阵列具有典型的屏幕尺寸,其中 720 是高度,1440 是宽度。图像通常按行存储,并且默认情况下,ndarray 中最右边的索引是变化更快的索引,因此一切都有意义。诚然,这违反直觉,但正确的索引是 arr[y, x]

    可以应用于您的代码的主要优化(在我的 Mac 上将执行时间从 ~9 秒缩短到 ~3.9 秒)是在不需要时不将单元格分配给自身,再加上in-place multiplication and 使用 [y, x] 而不是 [y][x] 索引。像这样:

    y_size, x_size = arr.shape
    y_max, x_max = y_size - 1, x_size - 1
    for (y, x), value in np.ndenumerate(arr):
        reduce_by = value * 0.1
        if y > 0 and x < x_max:
            if arr[y - 1, x + 1] > 0.25: arr[y - 1, x + 1] *= reduce_by
        if x < x_max:
            if arr[y    , x + 1] > 0.25: arr[y    , x + 1] *= reduce_by
        if y < y_max and x < x_max:
            if arr[y + 1, x + 1] > 0.25: arr[y + 1, x + 1] *= reduce_by
        if y > 0:
            if arr[y - 1, x    ] > 0.25: arr[y - 1, x    ] *= reduce_by
        if y < y_max:
            if arr[y + 1, x    ] > 0.25: arr[y + 1, x    ] *= reduce_by
        if y > 0 and x > 0:
            if arr[y - 1, x - 1] > 0.25: arr[y - 1, x - 1] *= reduce_by
        if x > 0:
            if arr[y    , x - 1] > 0.25: arr[y    , x - 1] *= reduce_by
        if y < y_max and x > 0:
            if arr[y + 1, x - 1] > 0.25: arr[y + 1, x - 1] *= reduce_by
    

    另一个优化(在我的 Mac 上将执行时间进一步降低到 ~3.0 秒)是通过使用具有额外边界单元的数组来避免边界检查。我们不关心边界包含什么值,因为它永远不会被使用。代码如下:

    y_size, x_size = arr.shape
    arr1 = np.empty((y_size + 2, x_size + 2))
    arr1[1:-1, 1:-1] = arr
    for y in range(1, y_size + 1):
        for x in range(1, x_size + 1):
            reduce_by = arr1[y, x] * 0.1
            if arr1[y - 1, x + 1] > 0.25: arr1[y - 1, x + 1] *= reduce_by
            if arr1[y    , x + 1] > 0.25: arr1[y    , x + 1] *= reduce_by
            if arr1[y + 1, x + 1] > 0.25: arr1[y + 1, x + 1] *= reduce_by
            if arr1[y - 1, x    ] > 0.25: arr1[y - 1, x    ] *= reduce_by
            if arr1[y + 1, x    ] > 0.25: arr1[y + 1, x    ] *= reduce_by
            if arr1[y - 1, x - 1] > 0.25: arr1[y - 1, x - 1] *= reduce_by
            if arr1[y    , x - 1] > 0.25: arr1[y    , x - 1] *= reduce_by
            if arr1[y + 1, x - 1] > 0.25: arr1[y + 1, x - 1] *= reduce_by
    arr = arr1[1:-1, 1:-1]
    

    据记录,如果可以使用 numpy 或 scipy 对操作进行矢量化,则该解决方案的速度提升至少为 35 倍(在我的 Mac 上测量)。

    注意:如果 numpy did 按顺序对数组切片进行操作,则以下将产生阶乘(即,正整数乘积到一个数字)——但它不会:

    >>> import numpy as np
    >>> arr = np.arange(1, 11)
    >>> arr
    array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10])
    >>> arr[1:] *= arr[:-1]
    >>> arr
    array([ 1,  2,  6, 12, 20, 30, 42, 56, 72, 90])
    

    【讨论】:

      【解决方案4】:

      您的数组大小是典型的屏幕大小,所以我猜单元格是 [0, 1) 范围内的像素值。现在,像素值永远不会相互相乘。如果是,则操作将取决于范围(例如 [0, 1) 或 [0, 255]),但它们从不这样做。所以我假设当你说“减少 10% 的单元格”时,你的意思是“减去 10% 的单元格”。但即便如此,该操作仍然取决于它应用于单元格的顺序,因为首先计算单元格的总变化然后应用它(如在卷积中)的常用方法会导致某些单元格值变为负值(例如, 0.251 - 8 * 0.1 * 0.999) ,如果它们是像素,则没有意义。

      现在让我假设您真的想要将单元格彼此相乘并乘以一个因子,并且您希望首先让每个单元格受其邻居编号 0 的影响(您的编号),然后按其邻居编号 1,依此类推,邻居编号为 2、3、5、7 和 8。通常,从目标单元的“角度”定义此类操作比从源细胞的那个。由于 numpy 在完整数组(或其视图)上快速运行,因此执行此操作的方法是将所有邻居移动到要修改的单元格的位置。 Numpy 没有shift(),但它有一个roll(),就我们的目的而言,它同样好,因为我们不关心边界单元,根据您的评论,可以将其恢复为原始值作为最后一步。代码如下:

      import numpy as np
      
      arr = np.random.rand(720, 1440)
      threshold = 0.25
      factor    = 0.1
      #                                                0 1 2
      #                                    neighbors:  3   5
      #                                                6 7 8
      #                                                       ∆y  ∆x    axes
      arr0 = np.where(arr  > threshold, arr  * np.roll(arr,   (1,  1), (0, 1)) * factor, arr)
      arr1 = np.where(arr0 > threshold, arr0 * np.roll(arr0,   1,       0    ) * factor, arr0)
      arr2 = np.where(arr1 > threshold, arr1 * np.roll(arr1,  (1, -1), (0, 1)) * factor, arr1)
      arr3 = np.where(arr2 > threshold, arr2 * np.roll(arr2,       1,      1 ) * factor, arr2)
      arr5 = np.where(arr3 > threshold, arr3 * np.roll(arr3,      -1,      1 ) * factor, arr3)
      arr6 = np.where(arr5 > threshold, arr5 * np.roll(arr5, (-1,  1), (0, 1)) * factor, arr5)
      arr7 = np.where(arr6 > threshold, arr6 * np.roll(arr6,  -1,       0    ) * factor, arr6)
      res  = np.where(arr7 > threshold, arr7 * np.roll(arr7, (-1, -1), (0, 1)) * factor, arr7)
      # fix the boundary:
      res[:,  0] = arr[:,  0]
      res[:, -1] = arr[:, -1]
      res[ 0, :] = arr[ 0, :]
      res[-1, :] = arr[-1, :]
      

      请注意,即便如此,主要步骤与您在解决方案中所做的不同。但它们必然如此,因为在 numpy 中重写您的解决方案会导致在同一操作中读取和写入数组,而这不是 numpy 可以以可预测的方式做的事情。

      如果您改变主意,决定用减法代替乘法,只需将np.roll 之前的*s 列更改为-s 列。但这只是朝着正确卷积(2D 图像上常见且重要的操作)方向迈出的第一步,不过,您需要完全重新表述您的问题。

      两个注意事项:在您的示例代码中,您像 arr[x][y] 一样对数组进行了索引,但在 numpy 数组中,默认情况下,最左边的索引是变化最慢的一个,即在 2D 中,垂直的一个,以便正确索引是arr[y][x]。这可以通过数组大小的顺序来确认。其次,在图像、矩阵和 numpy 中,垂直维度通常表示为向下增加。这会导致您对邻居的编号与我的不同。如有必要,只需将垂直位移乘以 -1。


      编辑

      这是一个产生完全相同结果的替代实现。它稍微快一些,但修改了数组:

      arr[1:-1, 1:-1] = np.where(arr[1:-1, 1:-1] > threshold, arr[1:-1, 1:-1] * arr[ :-2,  :-2] * factor, arr[1:-1, 1:-1])
      arr[1:-1, 1:-1] = np.where(arr[1:-1, 1:-1] > threshold, arr[1:-1, 1:-1] * arr[ :-2, 1:-1] * factor, arr[1:-1, 1:-1])
      arr[1:-1, 1:-1] = np.where(arr[1:-1, 1:-1] > threshold, arr[1:-1, 1:-1] * arr[ :-2, 2:  ] * factor, arr[1:-1, 1:-1])
      arr[1:-1, 1:-1] = np.where(arr[1:-1, 1:-1] > threshold, arr[1:-1, 1:-1] * arr[1:-1,  :-2] * factor, arr[1:-1, 1:-1])
      arr[1:-1, 1:-1] = np.where(arr[1:-1, 1:-1] > threshold, arr[1:-1, 1:-1] * arr[1:-1, 2:  ] * factor, arr[1:-1, 1:-1])
      arr[1:-1, 1:-1] = np.where(arr[1:-1, 1:-1] > threshold, arr[1:-1, 1:-1] * arr[2:  ,  :-2] * factor, arr[1:-1, 1:-1])
      arr[1:-1, 1:-1] = np.where(arr[1:-1, 1:-1] > threshold, arr[1:-1, 1:-1] * arr[2:  , 1:-1] * factor, arr[1:-1, 1:-1])
      arr[1:-1, 1:-1] = np.where(arr[1:-1, 1:-1] > threshold, arr[1:-1, 1:-1] * arr[2:  , 2:  ] * factor, arr[1:-1, 1:-1])
      

      【讨论】:

      • numpy没有shift,但是可以单独处理border。或者只是用 10 秒填充阵列。 (用 0 减去)
      【解决方案5】:

      编辑:啊,我知道当您说“减少”时,您的意思是乘法,而不是减法。我也没有意识到你想要减少复合,而这个解决方案没有做到这一点。所以这是不正确的,但我会留下它以防万一。

      您可以使用scipy.signal.convolve2d 以矢量化方式执行此操作:

      import numpy as np
      from scipy.signal import convolve2d
      
      arr = np.random.rand(720, 1440)
      
      mask = np.zeros((arr.shape[0] + 2, arr.shape[1] + 2))
      mask[1:-1, 1:-1] = arr
      mask[mask < 0.25] = 0
      conv = np.ones((3, 3))
      conv[1, 1] = 0
      
      arr -= 0.1 * convolve2d(mask, conv, mode='valid')
      

      这来自于以另一种方式思考您的问题:每个正方形应该减去所有周围值的 0.1 倍。 conv 数组对此进行编码,我们使用scipy.signal.convolve2d 将其滑过mask 数组以累积应减去的值。

      【讨论】:

      • 这个问题显然是指卷积。这是正确的解决方案,干得好。虽然可以通过高通滤波器来改善它,所以你不需要在那里应用面具!
      • @jakevdp 正如您在评论中指出的那样,这不是线性过滤器。换句话说:与卷积不同,a 的条目在同一个循环中被更改和引用,因此结果与给定的循环解中的结果不完全相同。
      • 恐怕这是不正确的,除了这里的减法是乘法而不是减法。卷积在整个数组及其原始单元格上进行操作,但我们希望逐个单元格按顺序进行,对前面步骤进行的缩减会影响后续步骤。
      • 我不认为我们想按顺序操作,只是你的。提出问题的人必须在压力下分享他的代码,问题陈述中没有提到顺序性。订单显然对他来说并不重要,因为他没有回答多个澄清请求。
      【解决方案6】:

      我们可以使用线性索引来做到这一点。如上所述,您的实现取决于您如何遍历数组。所以我假设我们想要修复数组,计算出每个元素乘以什么,然后简单地应用乘法。所以我们如何遍历数组并不重要。

      每个元素的乘积由下式给出:

      1 if a[i,j] < 0.25 else np.prod(neighbours_a*0.1)
      

      所以我们将首先遍历整个数组,得到每个元素的 8 个邻居,将它们相乘,乘以 0.1^8,然后应用这些值与 a 的条件元素乘法。

      为此,我们将使用线性索引并偏移它们。因此,对于具有 m 行 n 列的数组,第 i,j 个元素具有线性索引 in + j。要向下移动一行,我们只需将 n 添加为 (i+1),第 j 个元素具有线性索引 (i+1)n + j = (in + j) + n。这种算法提供了一个很好的方法来获取每个点的邻居,因为邻居都是从每个点固定的偏移量。

      import numpy as np
      
      # make some random array
      columns = 3
      rows = 3
      a = np.random.random([rows, columns])
      
      # this contains all the reduce by values, as well as padding values of 1.
      # on the top, bot left and right. we pad the array so we dont have to worry 
      # about edge cases, when gathering neighbours. 
      pad_row, pad_col = [1, 1], [1,1]
      reduce_by = np.pad(a*0.1, [pad_row, pad_col], 'constant', constant_values=1.)
      
      # build linear indices into the [row + 2, column + 2] array. 
      pad_offset = 1
      linear_inds_col = np.arange(pad_offset, columns + pad_offset)
      linear_row_offsets = np.arange(pad_offset, rows + pad_offset)*(columns + 2*pad_offset)
      linear_inds_for_array = linear_inds_col[None, :] + linear_row_offsets[:, None]
      
      # get all posible row, col offsets, as linear offsets. We start by making
      # normal indices eg. [-1, 1] up 1 row, along 1 col, then make these into single
      # linear offsets such as -1*(columns + 2) + 1 for the [-1, 1] example
      offsets = np.array(np.meshgrid([1, -1, 0], [1, -1, 0])).T.reshape([-1, 2])[:-1, :]
      offsets[:,0] *= (columns + 2*pad_offset)
      offsets = offsets.sum(axis=1)
      
      # to every element in the flat linear indices we made, we just have to add
      # the corresponding linear offsets, to get the neighbours
      linear_inds_for_neighbours = linear_inds_for_array[:,:,None] + offsets[None,None,:]
      
      # we can take these values from reduce by and multiply along the channels
      # then the resulting [rows, columns] matrix will contain the potential
      # total multiplicative factor to reduce by (if a[i,j] > 0.25)
      relavent_values = np.take(reduce_by, linear_inds_for_neighbours)
      reduce_by = np.prod(relavent_values, axis=2)
      
      # do reduction
      val_numpy = np.where(a > 0.25, a*reduce_by, a)
      
      # check same as loop
      val_loop = np.copy(a)
      for i in range(rows):
          for j in range(columns):
              reduce_by = a[i,j]*0.1
              for off_row in range(-1, 2):
                  for off_col in range(-1, 2):
                      if off_row == 0 and off_col == 0:
                          continue
                      if 0 <= (i + off_row) <= rows - 1 and 0 <= (j + off_col) <= columns - 1:
                          mult = reduce_by if a[i + off_row, j + off_col] > 0.25 else 1.
                          val_loop[i + off_row, j + off_col] *= mult
      
      
      print('a')
      print(a)
      print('reduced np')
      print(val_numpy)
      print('reduce loop')
      print(val_loop)
      print('equal {}'.format(np.allclose(val_numpy, val_loop)))
      

      【讨论】:

        【解决方案7】:

        尝试使用熊猫

        import pandas as pd
        # create random array as pandas DataFrame
        df = pd.DataFrame(pd.np.random.rand(720, 1440))  
        # define the centers location for each 9x9
        Center_Locations = (df.index % 3 == 1,
                            df.columns.values % 3 == 1)
        # new values for the centers, to be use later
        df_center = df.iloc[Center_Locations] * 1.25
        # change the df, include center
        df = df * 0.9 
        # replacing only the centers values   
        df.iloc[Center_Locations] = df_center 
        

        【讨论】:

        • 那是一些强大的熊猫魔法。介意扩展一下它的作用吗?
        • 通过n%3==1定义它为“中心”的位置,并保存以备后用(df_center)。全部改0.9,把保存的*1.25放回去
        【解决方案8】:

        无法避免循环,因为归约是按顺序执行的,而不是并行执行的。

        这是我的实现。对于每个(i,j),创建以a[i,j] 为中心的a 的3x3 块视图(我暂时将其值设置为0,使其低于阈值,因为我们不想减少它)。对于边界处的(i,j),块在拐角处为 2x2,在其他地方为 2x3 或 3x2。然后块被阈值屏蔽,未被屏蔽的元素乘以a_ij*0.1

        def reduce(a, threshold=0.25, r=0.1):
            for (i, j), a_ij in np.ndenumerate(a):
                a[i,j] = 0       
                block = a[0 if i == 0 else (i-1):i+2, 0 if j == 0 else (j-1):j+2]   
                np.putmask(block, block>threshold, block*a_ij*r)  
                a[i,j] = a_ij   
            return a
        

        请注意,减少也是从它们周围的单元格上的边界单元格开始执行的,即循环从数组的第一个角开始,a[0, 0] 有 3 个邻居:a[0,1]a[1,0] 和 @ 987654330@,如果大于 0.25,则减少 a[0,0]*0.1。然后它转到有 5 个邻居等的单元格a[0,1]。如果你想严格在有 8 个邻居的单元格上操作,即大小为 3x3 的窗口,循环应该从a[1,1]a[-2, -2],并且函数应该修改如下:

        def reduce_(a, threshold=0.25, r=0.1):
            ''' without borders -- as in OP's solution'''
            for (i, j), a_ij in np.ndenumerate(a[1:-1,1:-1]):
                block = a[i:i+3, j:j+3]
                mask = ~np.diag([False, True, False])*(block > threshold)
                np.putmask(block, mask, block*a_ij*r)   
            return a
        

        例子:

        >>> a = np.random.rand(4, 4)
        array([[0.55197876, 0.95840616, 0.88332771, 0.97894739],
               [0.06717366, 0.39165116, 0.10248439, 0.42335457],
               [0.73611318, 0.09655115, 0.79041814, 0.40971255],
               [0.34336608, 0.39239233, 0.14236677, 0.92172401]])
        
        >>> reduce(a.copy())    
        array([[0.00292008, 0.05290198, 0.00467298, 0.00045746],
               [0.06717366, 0.02161831, 0.10248439, 0.00019783],
               [0.00494474, 0.09655115, 0.00170875, 0.00419891],
               [0.00016979, 0.00019403, 0.14236677, 0.0001575 ]])
        
        >>> reduce_(a.copy())
        array([[0.02161831, 0.03753609, 0.03459563, 0.01003268],
               [0.06717366, 0.00401381, 0.10248439, 0.00433872],
               [0.02882996, 0.09655115, 0.03095682, 0.00419891],
               [0.00331524, 0.00378859, 0.14236677, 0.00285336]])
        

        3x2 数组的另一个例子:

        >>> a = np.random.rand(3, 2)
        array([[0.17246979, 0.42743388],
               [0.1911065 , 0.41250723],
               [0.73389051, 0.22333497]])
        
        >>> reduce(a.copy())
        array([[0.17246979, 0.00737194],
               [0.1911065 , 0.0071145 ],
               [0.01402513, 0.22333497]])
        
        >>> reduce_(a.copy())  # same as a because there are no cells with 8 neighbors
        array([[0.17246979, 0.42743388],
               [0.1911065 , 0.41250723],
               [0.73389051, 0.22333497]])
        

        【讨论】:

          【解决方案9】:

          通过将问题分析为更小的问题,我们看到,@jakevdp 解决方案确实可以完成这项工作,但忘记检查术语 mask&lt;0.25 after 与掩码卷积,因此某些值可能会下降后来在 0.25 之后(每个像素可能有 8 个测试),所以必须有一个 for 循环,除非有一个我没听说过的内置函数..

          这是我的建议:

          # x or y first depends if u want rows or cols , .. different results
          for x in range(arr.shape[1]-3):
              for y in range(arr.shape[0]-3):
                  k = arr[y:y+3,x:x+3]
                  arr[y:y+3,x:x+3] = k/10**(k>0.25)
          

          【讨论】:

          • 这是一个反例:arr = np.array([[0.17246979, 0.42743388], [0.1911065 , 0.41250723], [0.73389051, 0.22333497]])。您的代码返回相同的 arr 而没有任何更改。请参阅我的答案中的示例。
          • 怎么样:arr = np.array([[0.06322375, 0.03942972, 0.73541247, 0.84798866, 0.71042087], [0.20283542, 0.27995178, 0.84733291, 0.93385641, 0.9154688 ], [0.16607985, 0.08221938, 0.83687028, 0.04745399, 0.56243368], [0.59424876, 0.08783288, 0.9240022 , 0.60541983, 0.58984991], [0.90215043, 0.47615277, 0.53946544, 0.71912684, 0.84109332]]),我认为您的代码给出的结果不正确。例如。新的arr[1,1] 应该是 0.00176996,但你有 0.0279952(这是原始值)。
          • @AndyK ,我更愿意让 OP 决定
          • 当然 OP 会做出决定,但您应该能够解释为什么您的代码会执行此操作。在我提供的最后一个示例中,您的代码返回的结果显然是错误的:它仅通过将 arr 的某些项乘以 0.1 来更改它们。例如。 arr[1,1] = 0.279952 -&gt; 0.0279952arr[2,2] = 0.83687 -&gt; 0.083687a[1,2] = 0.847333 -&gt; 0.0847333 等等。当然我可能是错的,这就是我要求你确认的原因。
          猜你喜欢
          • 2012-09-18
          • 1970-01-01
          • 1970-01-01
          • 2016-07-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-16
          相关资源
          最近更新 更多