numpy.lib.stride_tricks.as_strided的使用

as_strided函数的使用

numpy.lib.stride_tricks.as_strided是numpy包中一个用以形成子矩阵的函数。
它可以从原矩阵中生成子矩阵,而且子矩阵可以交叉。
主要用于对矩阵进行卷积运算,如用2 * 2 矩阵对 4 * 4的矩阵进行卷积,如果stride为1,那么卷积结果为一个3 * 3的矩阵,该函数就可以用来生成一个3 * 3 * 2 * 2 的张量,即需要卷积的3 * 3个输入矩阵的 2 * 2 的子区域。

函数API

numpy.lib.stride_tricks.as_strided(x, shape=None, strides=None, subok=False, writeable=True)

通过给定的shapestrides,生成一个原矩阵的view

Parameters:

  • x:ndarray:
    • 用以生成新张量的矩阵
  • shape:一个int的序列(注:如元组,数组),可选项
    • 需要生成的新的张量的shape,默认为x.shape
  • strides: 一个int的序列(注:如元组,数组),可选项
    • 需要生成的新的张量的跨度,默认为x.strides
  • subokbool,可选项
    • v 1.10新增
    • 如果为True则保存生成的新张量(默认只是原矩阵的一种view)
  • writeablebool,可选
    • v 1.12新增
    • 如果设为False那么返回的张量将只读,否则如果原矩阵可写,生成的张量也可写。推荐设为False

Return:

  • view:ndarry

参数解析

shape

shape即为最终生成的张量的shape
如对于卷积操作,我们想要得到的输出的shape 可以通过计算公式得到:
假设:输入的图像为W * H 的矩阵,卷积核为w * h 的矩阵,stride为s,不考虑padding(因为padding和生成子矩阵无关,应该在先前提前填充),那么输出的w,h分别为
w o = W − w s + 1 , h o = H − h s + 1 w_o = \frac{W - w}s + 1,\quad h_o = \frac{H - h}s + 1 wo=sWw+1,ho=sHh+1
那么shape应为( (W-w)/s+1,(H-h)/s+1),而其中每一个元素都是通过 输入矩阵中的 w * h 子矩阵和卷积核卷积得到的,我们想把所有需要进行卷积计算的子矩阵叠在一起,用并行来加速运算,所以我们想得到一个( (W-w)/s+1,(H-h)/s+1,w,h )的张量,其中最后两维是输入矩阵中相应的卷积区域。

如果A为输入矩阵,K为卷积核,那么这个shape可以通过以下方式得到

shape = tuple(np.subtrct(A.shape,K.shape) / s + 1 ) + K.shape 

强烈建议通过已有的shape进行计算而不要自己设置,不然很容易出现问题

strides

strides必须和shape是同型的,比如上面得出的shape为4个元素,那么strides也必须为4个元素。

strides中每个元素是对应维度的跨度,单位是字节。

首先补充一个知识,一个矩阵A在内存中是从最后一维开始逐个存储的,如一个shape(3,2)的矩阵,在内存中,存储顺序是A[0,0]A[0,1]A[1,0]A[1,1]A[2,0]A[2,1]。可以把其看作是一个进制数,某维进制就是该维度的元素个数。

可以看到,在其他维保持不变的情况下,一个维度越靠近最后一维,它索引的变化引起的内存偏移实际上越小。

下面说明strides

其实每个ndarray都有一个属性是strides,例如一个shape(3,2)dtypenp.int16的一个矩阵A,那么它的strides即为(4,2)。它是通过以下的方式得到:

在其他维索引不变的情况下,对于第0维两个相邻的索引,如A[0,c]A[1,c],它们之间在内存上相差了2个元素,每个元素是16bit 即2字节,所以总共偏移量为4字节,所以strides的第一个元素值为4.

同样,对于第1维两个相邻索引:A[c,0],A[c,1],它们在内存上是相邻的,即偏移量仅为一个元素的字节:2,所以strides的第二个元素值为2.

同理,很容易得到,对于想生成的张量,第0维是两个在行上相邻的子矩阵,偏移为A.strides[0];第1维是两个在列上相邻的子矩阵,偏移为A.strides[1],而后两维和A是一样的,所以最终的strides可以通过以下方式得到:

strides = A.strides * 2