tensowflow实现离散卷积运算
0. 基本概念
矩阵的卷积运算主要用在图像处理中,假设输入信号为x,激活响应为h,则其卷积定义为:
激活响应(也称为核)的维度通常为奇数,中心元素下标为(0,0),index如下所示
与卷积核h不同,输入图像x和输出图像y的(0,0)点都在左上角,横坐标往右index增大,纵坐标往下index增大。因此,输出图像y的横坐标从0到M-1,纵坐标从0到N-1,这里M和N分别是输出图像宽度、高度上的像素点的个数。如果要在x左方补零,则这些0元素的横坐标取负数;如果要在x上方补零,这些零元素的纵坐标取负数。rot180(h)在padded_x上滑动的时候,取出rot180(h)笼罩的区域(该区域有可能含x补零的区域,对应x的负index),将对应区域做加权和。上式中x[i,j]的i和j取值从-∞到-∞的意义就是x中的被rot180(h)的当前位置笼罩着的左、右边界index及上、下边界index。每次滑动要做加权平均的时候,i、j的边界index取值是不一样的。不难观察到,对于x_patch是从左边界取到右边界(i=-∞到∞),对于卷积核是从右边界取到左边界;对于x_patch是从上边界取到下边界(j=-∞到∞),对于卷积核是从下边界取到上边界;因此我们说卷积核被旋转了180度。
1. 求输出矩阵的大小并对原始array进行pad处理
A:输入图像,B:卷积核。假设输入图像A大小为ma x na,卷积核B大小为mb x nb
1.1 full (tensorflow没有该参数)
(optm,optn):ceil( (ma+mb-1)/mstride ) x ceil( (na+nb-1)/nstride )
需要采取pad操作:宽度上需要pad的总数目是Pm=(optm-1)×mstride+mb-ma,其中左方pad: floor(Pm/2) ;高度上需要pad的总数目是Pn=(optn-1)×nstride+nb-na,其中上方pad: floor(Pn/2)
1.2 same
(optm,optn):ceil( ma / mstride ) x ceil( na / nstride )
需要采取pad操作:宽度上需要pad的总数目是Pm=(optm-1)×mstride+mb-ma,其中左方pad: floor(Pm/2) ;高度上需要pad的总数目是Pn=(optn-1)×nstride+nb-na,其中上方pad: floor(Pn/2)
特殊地,如果stride=1(matlab),则输入、输出大小不变,Pm=mb-1,而我们通常让卷积核的维度是奇数,这样上下左右可以均匀pad
更特殊地,如果mb=mstride=sqrt(ma)或ma/2, nb=nstride=sqrt(na)或na/2,则退化为valid
例子
x = tf.constant([[1., 2., 3.],
[4., 5., 6.]]) #两行三列的矩阵,用2x2的kernel,stride=2来pool
x = tf.reshape(x, [1, 2, 3, 1]) # give a shape accepted by tf.nn.max_pool
valid_pad = tf.nn.max_pool(x, [1, 2, 2, 1], [1, 2, 2, 1], padding='VALID')
same_pad = tf.nn.max_pool(x, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME')
valid_pad.get_shape() == [1, 1, 1, 1] # valid_pad is [5.] 一行一列
same_pad.get_shape() == [1, 1, 2, 1] # same_pad is [5., 6.] 一行两列
解释一下same的结果:高度方向(行方向)na=nb=2, optn=1, pn=0;宽度方向(列方向)ma=3,mb=2, optm=2, pm=1 其中左边pad=0 。最终从原来的2x3变成一个pn x pm =2x4的padded array,pool后的输出结果为optn x optm = 1x2
1.3 valid
(optm,optn): ceil( ( ma- mb +1)/ mstride ) x ceil( ( na- nb +1)/ nstride )
特殊地,如果mb=mstride,nb=nstride,则optm= ma/mb, optn= na /nb
此时不采取pad操作
2. 将卷积核旋转180度
如果是互相关操作,卷积核不用旋转,直接与其笼罩的padded_x各元素做加权和:
注意这里的h才是需要滑动的函数(filter),上式中x[i,j]的i和j取值从-∞到-∞的意义为卷积核的左、右边界index及上、下边界index。对于给定卷积核来说,每次滑动要做加权平均的时候,i、j的边界index取值是固定不变的。不难观察到,对于卷积核是从左边界取到右边界(i=-∞到∞),对于x_patch依然是从左边界取到右边界;对于卷积核是从上边界取到下边界(j=-∞到∞),对于x_patch依然是从上边界取到下边界;因此我们说卷积核不用旋转。
3. 滑动卷积核,将卷积核从padded array的左上角,按照stride依次移动直到右下角
其实不论对于什么情况,我们总的输出图像的宽度m和高度n可以用如下公式计算:
4. 每步移动的时候,将旋转后的卷积核与其笼罩的padded arrayd的元素做加权求和
例子1
这里由于是卷积操作的其中一次滑动步,对于这一步来说,i、j是从0到2
如果是互相关的话,对于每一步滑动,i、j都是取-1到1
例子2
假设有这样一张图,双通道
第一个通道:
第二个通道:
import tensorflow as tf
a=tf.constant([
[[1.0,2.0,3.0,4.0],
[5.0,6.0,7.0,8.0],
[8.0,7.0,6.0,5.0],
[4.0,3.0,2.0,1.0]],
[[4.0,3.0,2.0,1.0],
[8.0,7.0,6.0,5.0],
[1.0,2.0,3.0,4.0],
[5.0,6.0,7.0,8.0]]
])
a=tf.reshape(a,[1,4,4,2])
pooling=tf.nn.max_pool(a,[1,2,2,1],[1,1,1,1],padding='VALID')
with tf.Session() as sess:
print("image:")
image=sess.run(a)
print (image)
print("reslut:")
result=sess.run(pooling)
print (result)
输出image张量的第一列实际上对应了第一张图,第二列对应了第二张图
image:
[[[[ 1. 2.]
[ 3. 4.]
[ 5. 6.]
[ 7. 8.]]
[[ 8. 7.]
[ 6. 5.]
[ 4. 3.]
[ 2. 1.]]
[[ 4. 3.]
[ 2. 1.]
[ 8. 7.]
[ 6. 5.]]
[[ 1. 2.]
[ 3. 4.]
[ 5. 6.]
[ 7. 8.]]]]
输出result张量的第一列实际上对应了第一张图池化的结果,第二列对应了第二张图池化的结果
reslut:
[[[[ 8. 7.]
[ 6. 6.]
[ 7. 8.]]
[[ 8. 7.]
[ 8. 7.]
[ 8. 7.]]
[[ 4. 4.]
[ 8. 7.]
[ 8. 8.]]]]
第一图池化结果:
第二图池化结果:
5. 总结
对于卷积神经网络来说,每个卷积层(l层)的维度变化如下: