如何在numpy中获得许多滚动窗口切片?

问题描述

我有以下numpy数组:

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

我希望最后一个维度中的每个元素[1][2][3]等与第二个维度中的以下n数组连接。如果发生溢出,则可以用0填充元素。例如,对于n = 2

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

我想使用内置的numpy函数来实现此目的,以获得良好的性能,并且还想反向执行此操作,即,将n = -2换档是公平的游戏。该怎么做?

对于n = -2

[[[0,[0,3]],[[0,6]],9]]]

对于n = 3

[[[1,0]]]

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

如何对此进行概括,以便数字1、2、3等本身可以成为numpy数组?

解决方法

这是一种实现方法:

from skimage.util import view_as_windows

if n>=0:
  a = np.pad(a.reshape(*a.shape[:-1]),((0,0),(0,n)))
else:
  n *= -1
  a = np.pad(a.reshape(*a.shape[:-1]),(n,0)))

b = view_as_windows(a,(1,n+1))
b = b.reshape(*b.shape[:-2]+(n+1,))

a是您的输入数组,而b是您的输出:

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

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

说明

  • np.pad(a.reshape(*a.shape[:-1]),n)))在数组的右侧填充足够的零以防止窗口溢出(类似地,在负n的左侧填充左侧)
  • view_as_windows(a,n+1))根据问题的需要从数组中创建形状为(1,n+1)的窗口。
  • b.reshape(*b.shape[:-2]+(n+1,))摆脱了(1,n+1)形窗口创建的长度1的额外尺寸,并将b重塑为所需的形状。请注意,参数*b.shape[:-2]+(n+1,)只是两个元组的串联,以创建单个元组作为形状。
,

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

  1. 您有一个数组,数组的形状为(3,6,1),步幅为(6,1,1) * element_size

    x = ...
    n = ...  # Must not be zero,but you can special-case it to return the original array
    
  2. 您想将其转换为形状为(3,|n| + 1),因此跨度为(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. 现在,您可以使用自定义形状直接索引到缓冲区中,并大步获得所需的结果。代替使用适当的步幅(6 * (|n| + 1),1) * element_size,我们将每个重复的元素直接索引到原始数组的相同缓冲区中,这意味着将调整步幅。中间尺寸将移动一个元素,而不是适当的|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()

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