关于python:纯numpy实现滑动窗口计算继而完成两种池化和寻找稠密区域的任务

45次阅读

共计 2285 个字符,预计需要花费 6 分钟才能阅读完成。

在很多工作中都用失去滑动窗口,比方密集人群计数,标签文件是一张与图片尺寸等大的二维矩阵,人头的核心地位为 1,其余地位为 0。我想求出这张图片中人头最浓密的一块区域(区域尺寸给定),那么怎么求呢?

我想到的方法就是用这个区域的尺寸作为一个固定窗口,在整个标签矩阵中滑动,每滑动到一处,就计算一下以后窗口中“1”的个数,数量最多(加和最大)的区域就是人头最浓密的区域,即以后的窗口。

对于滑动窗口计算,最容易想到的就是用两层 for 循环来实现,但首先它须要解决边界的问题,其次随着图片的尺寸的增大,效率会变地很低,内存占用也十分大。

本文应用 numpy 来实现滑窗计算,并计算元素值相加的和最大的一块区域(区域的尺寸设定为卷积核的尺寸)。为了减少算法的普遍性,标签文件中元素的值不局限于 0,1 两个数字,为了解决这个变动带来的问题,须要在窗口滑动计算后减少一个小块区域内元素相加的动作,顺着这个思路能够额定实现应用 numpy 实现最大和均值两种池化的工作。

拿一个最简略的例子来实现下文的探讨:

$$
X=\left[\begin{matrix} 1 & 2 & 3\\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{matrix} \right]
$$

如果 kernal 的大小是 2 ×2,stride 为 1,那么滑窗计算的后果就是上面的 4 个小矩阵组成的新矩阵 A:

到这里,手工计算版的滑动窗口就完结了。后续工作,4 个小矩阵各自求和,失去 [12,16,24,28] 四个值,可知元素值相加的和最大的一块区域是最右下角的那一块。

上面用程序来复现下面的探讨。

def pool2d(X, kernel_size, stride, padding, pool_mode='avg'):

    # ** 第一步 **
    X = np.pad(X, padding, mode='constant')

    # 第二步
    output_shape = ((X.shape[0] - kernel_size[0] + 2*padding)//stride + 1, (X.shape[1] - kernel_size[1] + 2*padding)//stride + 1)
    
     # 第三步
    A = as_strided(X, shape = output_shape + kernel_size, strides = (stride*X.strides[0], stride*X.strides[1]) + X.strides)
  
    # 第四步
    A = A.reshape(-1, *kernel_size)    # 把四维压缩到三维
  
    # 第五步
    if pool_mode == 'max':
        return A, A.max(axis=(1,2)).reshape(output_shape)    # 实现最大池化工作
    elif pool_mode == 'avg':
        return A, A.mean(axis=(1,2)).reshape(output_shape)   # 实现均值池化工作

第一步:边缘填充

对矩阵进行池化操作,参数 constant 示意间断填充雷同的值,即 padding。个别是全零填充。

第二步:尺寸预计算

事后计算窗口滑动后每个小矩阵 Ai 的尺寸,这部分就能够依据卷积前后尺寸的变动公式来计算:

new=(old-kennel_size+2*padding)/stride+1

在下面的例子中 output_shape 就是(2,2)。【(4-2+2*0)//2+1=2

第三步:滑窗计算

调用as_strided 函数进行窗口滑动计算。该函数次要的参数有三个:

  • 要操作的矩阵,不必多说了。
  • shape:返回矩阵的尺寸,区别于之前的“output_shape”,这个 shape 是指矩阵 A 的尺寸,即所有小矩阵放在一块的尺寸,这个尺寸不肯定等于输出矩阵 X 的尺寸。比方下面的例子,shape 就是(2,2,2,2),而输出矩阵 X 的尺寸是(3,3)
  • strides:这是 numpy 数组的一个属性,官网手册给出的解释是 逾越数组各个维度所须要通过的字节数(bytes)。用下面的矩阵 X 当例子阐明:

    • X[0][0]X[0][1] 须要通过 4 个字节,为什么是 4 个?因为 a 的数据类型是 int32,正好占 4 个字节.
    • X[0][0]X[1][0] 须要通过 12 个字节,为什么是 12 个?因为 python 是 行程序优先 的编程语言,即读取矩阵元素时是一行一行来读的,把矩阵 X 展平,就是[0 1 2 3 4 5 6 7 8],从 0 到 3 就须要遍历 3 个元素,而每个元素都是 4 个字节,所以总共须要 12 个字节。

      注 1:常见的编程语言中,只有 matlab 和 fortran 是列程序优先。

      注 2:想要更深刻地理解 strides,还是举荐文章【卷积算法另一种高效实现,as_strided 详解】

这一步返回的后果就是上面的矩阵 A,尺寸为(2,2,2,2)

![[公式]](https://www.zhihu.com/equatio…

第四步:浓密区域搜查工作

其实滑动窗口计算到第三步就曾经完结了,这一步就是从新调整一下尺寸,将 (2,2,2,2) 调整成(4,2,2),即第一维变成小矩阵的个数,前面两维是小矩阵的尺寸。对每个小矩阵加和再比拟就能晓得加和最大的一块区域了,继而实现浓密区域搜查工作。

第五步:两类池化工作

以均匀池化为例,调用如下函数

A.mean(axis=(1,2)).reshape(output_shape)

axis=(1,2)是因为此时的矩阵 A 维度为(4,2,2),要从第二个维度开始解决。

reshape(output_shape)是因为依照池化工作的要求,输入后果要与小矩阵的维度统一,即(2,2)

程序运行后果:


参考:https://zhuanlan.zhihu.com/p/…

正文完
 0