首页 > 解决方案 > 如何在numpy中获得许多滚动窗口切片?

问题描述

我有以下 numpy 数组:

[[[1], [2], [3], [1], [2], [3]],
 [[4], [5], [6], [4], [5], [6]],
 [[7], [8], [9], [7], [8], [9]]]

而且我希望最后一维中的每个元素[1],[2]等都与第二维中[3]的以下数组连接。n在溢出的情况下,元素可以用 0 填充。例如,对于n = 2

[[[1, 2, 3], [2, 3, 1], [3, 1, 2], [1, 2, 3], [2, 3, 0], [3, 0, 0]],
 [[4, 5, 6], [5, 6, 4], [6, 4, 5], [4, 5, 6], [5, 6, 0], [6, 0, 0]],
 [[7, 8, 9], [8, 9, 7], [9, 7, 8], [7, 8, 9], [8, 9, 0], [9, 0, 0]]]

我想用内置的 numpy 函数来做到这一点以获得良好的性能,并且也想反过来做,即转移n = -2是公平的游戏。这个怎么做?

对于n = -2

[[[0, 0, 1], [0, 1, 2], [1, 2, 3], [2, 3, 1], [3, 1, 2], [1, 2, 3]],
 [[0, 0, 4], [0, 4, 5], [4, 5, 6], [5, 6, 4], [6, 4, 5], [4, 5, 6]],
 [[0, 0, 7], [0, 7, 8], [7, 8, 9], [8, 9, 7], [9, 7, 8], [7, 8, 9]]]

为了n = 3

[[[1, 2, 3, 1], [2, 3, 1, 2], [3, 1, 2, 3], [1, 2, 3, 0], [2, 3, 0, 0], [3, 0, 0, 0]],
 [[4, 5, 6, 4], [5, 6, 4, 5], [6, 4, 5, 6], [4, 5, 6, 0], [5, 6, 0, 0], [6, 0, 0, 0]],
 [[7, 8, 9, 7], [8, 9, 7, 8], [9, 7, 8, 9], [7, 8, 9, 0], [8, 9, 0, 0], [9, 0, 0, 0]]]

如果数组的当前形状是(height, width, 1),则操作后的形状将是(height, width, abs(n) + 1)

如何概括这一点,以便数字 1、2、3 等本身可以是 numpy 数组?

标签: pythonarraysnumpyconcatenation

解决方案


这听起来像是怪物的教科书应用程序as_strided。它的好处之一是它不需要任何额外的导入。总体思路是这样的:

  1. 你有一个 shape(3, 6, 1)和 strides的数组(6, 1, 1) * element_size

    x = ...
    n = ...  # Must not be zero, but you can special-case it to return the original array
    
  2. 您想将其转换为具有形状(3, 6, |n| + 1)并因此具有 strides的数组(6 * (|n| + 1), |n| + 1, 1) * element_size

  3. 为此,您首先用|n|零填充左侧或右侧:

    pad = np.zeros((x.shape[0], np.abs(n), x.shape[2]))
    x_pad = np.concatenate([x, pad][::np.sign(n)], axis=1)
    
  4. 现在,您可以使用自定义形状和步幅直接索引到缓冲区中以获得您想要的结果。我们不会使用正确的 strides (6 * (|n| + 1), |n| + 1, 1) * element_size,而是将每个重复的元素直接索引到原始数组的同一缓冲区中,这意味着将调整 strides。中间维度将移动一个元素,而不是适当的|n| + 1. 这样一来,这些列就可以准确地从您希望它们开始的位置开始:

    new_shape = (x.shape[0], x.shape[1], x.shape[2] + np.abs(n))
    new_strides = (x_pad.strides[0], x_pad.strides[2], x_pad.strides[2])
    result = np.lib.stride_tricks.as_strided(x_pad, shape=new_shape, strides=new_strides)
    

这里有很多警告。需要注意的最重要的事情是多个数组元素访问相同的内存。我的建议是,如果您除了阅读数据之外还打算做任何事情,请制作一份适当的充实副本:

result = result.copy()

这将为您提供正确大小的缓冲区,而不是疯狂地查看带有填充的原始数据。


推荐阅读