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层)的维度变化如下:
在这里插入图片描述

发布了31 篇原创文章 · 获赞 18 · 访问量 6040

猜你喜欢

转载自blog.csdn.net/weixin_40027284/article/details/83384555