假设您有一个 (A, B) 零数组 (a) 和 (M, N) 一数组 (b)。按轴拆分问题。在第一个轴上可以占据A - M + 1 的位置,在第二个轴上可以占据B - N + 1,对于任何其他轴,依此类推。因此,可能性总数为(A - M + 1) * (B - N + 1)。
可视化不同位置的最直观的方法是考虑将b 放入a 并带有一些偏移量。但还有另一种方式。我们可以将任何给定的位置看作是一个数组的视图,该数组被放置在一个四面都填充了零的数组中:
m = np.pad(b, np.subtract(a.shape, b.shape))
现在您可以为您感兴趣的每个位置创建m 的视图。例如,如果我们使用A = B = 3 和M = N = 2,第一个位置是m[1:4, 1:4],第二个位置是m[1:4, 0:3] 等 m 看起来像这样:
array([[0, 0, 0, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 0, 0, 0]])
如果您将 m 创建为 C 连续数组,则可以使用 np.lib.stride_tricks.as_strided 的神秘魔力一次获取所有可能的视图。从m[A - M, B - N] 开始,您可以添加两个额外的维度,只需后退一个元素,在前两个维度中使用与m 相同的步幅。这很容易推广到两个以上的维度:
def get_positions(a_shape, b_shape):
# Check input shapes
if len(a_shape) != len(b_shape):
raise ValueError('a and b must have the same number of dimensions')
d = np.subtract(a_shape, b_shape)
if (d < 0).any():
raise ValueError('a must be larger than b')
# Make padded buffer
m = np.pad(np.ones(b_shape, np.uint8), d)
# Find initial offset as a tuple of slices
offset = tuple(slice(off, None) for off in d)
# Find new dimensions
shape = tuple(d + 1) + a_shape
# Find new strides
strides = tuple(-s for s in m.strides) + m.strides
# Make a view
view = np.lib.stride_tricks.as_strided(m[offset], shape=shape, strides=strides)
# Return a copy with the leading dims merged
return view.reshape(-1, *a_shape)
最后的reshape 操作被强制复制数组,因为否则它是m 缓冲区中非常不连续的视图。如果您同意,只需返回两倍维度的视图即可。
你原来的例子变成:
>>> get_positions((3, 3), (2, 2))
array([[[1, 1, 0],
[1, 1, 0],
[0, 0, 0]],
[[0, 1, 1],
[0, 1, 1],
[0, 0, 0]],
[[0, 0, 0],
[1, 1, 0],
[1, 1, 0]],
[[0, 0, 0],
[0, 1, 1],
[0, 1, 1]]], dtype=uint8)
这种矢量化解决方案适用于任意数量的维度。您还可以在不复制数据的 2D 中进行简化的 for 循环:
a = np.zeros((A, B), dtype=np.uint8)
b = np.ones((M, N), dtype=np.uint8)
m = np.pad(b, (A - M, B - N))
for i in range(A - M, -1, -1):
for j in range(B - N, -1, -1):
print(m[i:i + A, j:j + B])
您可以使用itertools.product 轻松推广到任意维度:
from itertools import product
a_shape = (...)
b_shape = (...)
d = np.subtract(a_shape, b_shape)
m = np.pad(np.ones(b_shape, dtype=np.uint8), d)
for offset in product(*[range(off, -1, -1) for off in d]):
index = tuple(slice(off, off + sz) for off, sz in zip(offset, a_shape))
print(m[index])
在get_positions 函数上使用循环的优势可能有三方面:
- 我希望循环更快
- 循环使用较少的内存,因为它提供真实视图而不是复制整个数组。
- 循环允许您使用 numpy 支持的全部 32 个维度,而该函数将一半用于辅助轴。