在很多工作中都用失去滑动窗口,比方密集人群计数,标签文件是一张与图片尺寸等大的二维矩阵,人头的核心地位为 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/…