如何将函数应用于矩阵的每个元素?

How to apply a function to each element of a matrix?

对于矩阵的每个元素,我想计算 k 个最近的单元格的总和。 来自这个函数

def sum_neighbors(a, radius, row_number, column_number):
    A = [[a[i][j] if  i >= 0 and i < len(a) and j >= 0 and j < len(a[0]) else 0
                for j in range(column_number-1-radius, column_number+radius)]
                    for i in range(row_number-1-radius, row_number+radius)]
    return np.sum(A)

我可以做到。例如,如果我有以下矩阵

A
array([[1, 8, 2, 2, 8, 2, 4, 2, 6],
       [3, 6, 9, 9, 1, 8, 0, 3, 9],
       [7, 4, 2, 7, 8, 5, 8, 8, 1],
       [8, 3, 2, 3, 6, 9, 9, 1, 6],
       [1, 1, 3, 5, 6, 2, 9, 1, 8],
       [7, 0, 8, 7, 9, 8, 4, 4, 6],
       [1, 8, 5, 4, 7, 4, 8, 7, 0],
       [2, 8, 0, 3, 5, 6, 4, 3, 9],
       [1, 5, 1, 9, 9, 4, 5, 7, 9]])

我可以运行获取以下内容,对于每个值,半径为 2 个单元格的所有值的总和。

s = np.shape(A)
As = np.zeros(s)
for i in range(0,s[0]):
    for j in range(0,s[1]):
        As[i][j]=neighbors(A, 2, i, j)

As
array([[ 18.,  29.,  40.,  49.,  55.,  45.,  39.,  43.,  34.],
       [ 29.,  42.,  60.,  77.,  81.,  75.,  75.,  73.,  56.],
       [ 40.,  55.,  76.,  99., 104., 104., 103., 104.,  81.],
       [ 42.,  60.,  86., 115., 121., 129., 126., 130., 101.],
       [ 40.,  64.,  95., 125., 131., 147., 140., 139., 109.],
       [ 40.,  60.,  86., 122., 126., 148., 149., 144., 108.],
       [ 39.,  57.,  79., 112., 122., 136., 134., 141., 108.],
       [ 34.,  51.,  79., 115., 127., 135., 140., 144., 108.],
       [ 32.,  46.,  69.,  99., 110., 110., 117., 118.,  88.]])

但是,对于大型矩阵,循环需要无限的时间。有没有办法在没有循环的情况下将函数应用于矩阵的每个元素?

问题

处理边界问题的方法是用 k 零填充数组。由于您要将这些值加到数组中,因此没有任何效果——它只会让生活更轻松。考虑原始数组的 top-left 角:

1 8 2 ...
3 6 9 ...
7 4 2 ...
.     .
.      .
.       .

例如,您想从 1 的每个方向观察距离 k = 2 的角落。通过用 k = 2 零填充 A,我们得到

0 0 0 0 0 ...
0 0 0 0 0 ...
0 0 1 8 2 ...
0 0 3 6 9 ...
0 0 7 4 2 ...
.         .
.          .
.           .

填充有助于边界,但此过程的技巧是利用 numpy 的 stride_tricks sub-module。我们实际上是在执行 2D 卷积,是的,您可以为此使用 scipy,如果您碰巧安装了它并且它已经依赖于您的项目。

解决方案

from numpy.lib.stride_tricks import sliding_window_view as windows

k = 2  # radius; k >= 1
w = 2*k + 1  # window size; 1x1, 3x3, 5x5, ...

# A = np.array(...)  # from question
neighbor_sums = windows(np.pad(A, k), (w, w)).sum(axis=(2, 3))

k = 1 的输出:

array([[18, 29, 36, 31, 30, 23, 19, 24, 20],
       [29, 42, 49, 48, 50, 44, 40, 41, 29],
       [31, 44, 45, 47, 56, 54, 51, 45, 28],
       [24, 31, 30, 42, 51, 62, 52, 51, 25],
       [20, 33, 32, 49, 55, 62, 47, 48, 26],
       [18, 34, 41, 54, 52, 57, 47, 47, 26],
       [26, 39, 43, 48, 53, 55, 48, 45, 29],
       [25, 31, 43, 43, 51, 52, 48, 52, 35],
       [16, 17, 26, 27, 36, 33, 29, 37, 28]])

对于k = 2:

array([[ 42,  60,  77,  81,  75,  75,  73,  56,  41],
       [ 55,  76,  99, 104, 104, 103, 104,  81,  57],
       [ 60,  86, 115, 121, 129, 126, 130, 101,  75],
       [ 64,  95, 125, 131, 147, 140, 139, 109,  77],
       [ 60,  86, 122, 126, 148, 149, 144, 108,  80],
       [ 57,  79, 112, 122, 136, 134, 141, 108,  79],
       [ 51,  79, 115, 127, 135, 140, 144, 108,  84],
       [ 46,  69,  99, 110, 110, 117, 118,  88,  66],
       [ 31,  47,  68,  78,  74,  85,  87,  66,  52]])

scipy

如果您已经在项目中使用 scipy 作为依赖项,您不妨利用其专门的 convolve2d() 功能:

k = 2  # radius; k >= 1
w = 2*k + 1  # window size; 1x1, 3x3, 5x5, ...

neighbor_sums = scipy.signal.convolve2d(A, np.ones((w, w), int), mode='same')

性能

快速基准测试显示 scipy 的 convolve2D() 函数确实更快:

In [18]: ones = np.ones((w, w), int)

In [19]: padded_A = np.pad(A, k)

In [20]: %timeit convolve2d(A, ones, mode='same')
9.79 µs ± 786 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [21]: %timeit windows(padded_A, (w, w)).sum(axis=(2, 3))
34.5 µs ± 6.47 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)