python - 从矩阵中找到第一个匹配子矩阵的快速方法

标签 python numpy matrix comparison

我的矩阵很简单,例如:

# python3 numpy
>>> A
array([[0., 0., 1., 1., 1.],
       [0., 0., 1., 1., 1.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.]])
>>> P
array([[0., 0., 0., 0.]])

我需要在 A 中找到一个与 P (1x4) 大小相同的全零区域(一个就足够了)。 所以正确的答案包括:

(2, 0)  # The vertex coordinates of the all-zero rectangular region that P can be matched
(2, 1)
(3, 0)
(3, 1)
(4, 0)
(4, 1)
# Just get any 1 answer

实际上我的A矩阵会达到30,000*30,000的大小。我担心如果写成循环语句会很慢。有什么快速的方法吗?

P的大小不确定,从10*30到4000*80。同时A矩阵缺乏规律性,从任意点开始循环可能需要遍历整个矩阵才能成功匹配

最佳答案

正如@Julien在评论中指出的,一般来说,我们可以使用滑动窗口来完成此类任务。

def find_all_zero_region_by_sliding_window(a, shape):
    x, y = np.nonzero(np.lib.stride_tricks.sliding_window_view(a, shape).max(axis=-1).max(axis=-1) == 0)
    return np.stack((x, y), axis=-1)


find_all_zero_region_by_sliding_window(A, P.shape)

但是,不幸的是,这需要大量内存。

numpy.core._exceptions.MemoryError: Unable to allocate 11.3 TiB for an array with shape (26001, 29921, 4000) and data type float32
                                                       ^^^^^^^^

作为替代方案,我认为使用 Summed-area table是个好主意。

它与上面的滑动窗口方法类似,但我们不是寻找最大值,而是计算总和(非常有效)并搜索其为零的位置。 请注意,这假设 A 不包含任何负值。否则,您将不得不使用numpy.abs

由于我们不需要计算任何给定位置的总和,因此我调整了这个想法并将其实现为仅需要单行缓存。

import numpy as np
from typing import Tuple


def find_all_zero_region(arr: np.ndarray, kernel_size: Tuple[int, int]) -> np.ndarray:
    input_height, input_width = arr.shape
    kernel_height, kernel_width = kernel_size

    matches = []

    # Calculate summed_line for y==0.
    summed_line = arr[:kernel_height].sum(axis=0)

    for y in range(input_height - kernel_height + 1):
        # Update summed_line for row y.
        if y != 0:  # Except y==0, which already calculated above.
            # Adding new row and subtracting old row.
            summed_line += arr[y + kernel_height - 1] - arr[y - 1]

        # Calculate kernel_sum for (y, 0).
        kernel_sum = summed_line[:kernel_width].sum()
        if kernel_sum == 0:
            matches.append((y, 0))

        # Calculate kernel_sum for (y, 1) to (y, right-edge).
        # Using the idea of a summed-area table, but in 1D (horizontally).
        (all_zero_region_cols,) = np.nonzero(kernel_sum + np.cumsum(summed_line[kernel_width:] - summed_line[:-kernel_width]) == 0)
        for col in all_zero_region_cols:
            matches.append((y, col + 1))

    if not matches:
        # For Numba, output must be a 2d array.
        return np.zeros((0, 2), dtype=np.int64)
    return np.array(matches, dtype=np.int64)

正如你所看到的,这使用了循环,但它应该比你想象的要快得多,因为所需的内存相对较小,并且计算/比较的次数大大减少。 这是一些计时代码。

import time


rng = np.random.default_rng(0)
A = rng.integers(0, 2, size=(30000, 30000)).astype(np.float32)

P = np.zeros(shape=(4000, 80))

# Create an all-zero region in the bottom right corner which will be searched last.
A[-P.shape[0] :, -P.shape[1] :] = 0

started = time.perf_counter()
result = find_all_zero_region(A, P.shape)
print(f"{time.perf_counter() - started} sec")
print(result)
# 3.541154200000001 sec
# [[26000 29920]]

此外,使用 Numba 可以使该函数更快。 只需添加注释即可:

import numba


@numba.njit("int64[:,:](float32[:,:],UniTuple(int64,2))")
def find_all_zero_region_with_numba(arr: np.ndarray, kernel_size: Tuple[int, int]) -> np.ndarray:
    ...
started = time.perf_counter()
find_all_zero_region_with_numba(A, P.shape)
print(f"{time.perf_counter() - started} sec")
# 1.6005743999999993 sec

请注意,我实现它是为了查找全零区域的所有位置,但您也可以使其返回第一个区域。 由于它使用循环,平均执行时间会更快。

关于python - 从矩阵中找到第一个匹配子矩阵的快速方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76903859/

相关文章:

Python:匹配 BeautifulSoup 文本中的正则表达式

python - 创建距离矩阵?

python numpy linalg 求解器 : Wrong answer

java - Oj 算法并发

Python + Qt : pyqtProperty, 样式表和事件处理程序

python - 如何将 Python 对象转换为 Cython 扩展类型的 std::vector 并返回?

python - 在 pandas 中查找包含正确列设置值的行

python - python中曲线的距离矩阵

python - 在 numpy 中执行多种方式

二维数组可以这样初始化吗?