我的矩阵很简单,就像:

# 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到4,000*80.同时,A矩阵缺乏规律性,从任意点循环可能需要遍历整个矩阵才能成功匹配

推荐答案

正如@Julien在 comments 中指出的那样,一般来说,我们可以使用滑动窗口来完成这类任务.

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
                                                       ^^^^^^^^

作为另一种 Select ,我认为使用Summed-area table是一个好主意.

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

因为我们不需要能够计算任何给定位置的总和,所以我采用了这个 idea 并将其实现为只需要单线缓存.

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相关问答推荐

如何确保Flask应用程序管理面板中的项目具有单击删除功能?

有没有办法清除气流中的僵尸

预期LP_c_Short实例而不是_ctyles.PyCStructType

Pandas 按照特殊规则保留每n行

每个组每第n行就有Pandas

sys.modulesgo 哪儿了?

如何使用没有Selenium的Python在百思买着陆页面上处理国家/地区 Select ?

添加包含中具有任何值的其他列的计数的列

根据给定日期的状态过滤查询集

如何避免Chained when/then分配中的Mypy不兼容类型警告?

Pandas - groupby字符串字段并按时间范围 Select

按顺序合并2个词典列表

Python解析整数格式说明符的规则?

try 将一行连接到Tensorflow中的矩阵

在Python中动态计算范围

实现自定义QWidgets作为QTimeEdit的弹出窗口

如何使Matplotlib标题以图形为中心,而图例框则以图形为中心

Tkinter菜单自发添加额外项目

如何排除prefecture_related中查询集为空的实例?

matplotlib图中的复杂箭头形状