tf.nn.conv2d()函数以及padding填充方式介绍

tf.nn.conv2d()函数中填充方式的理解

本篇的主要内容:

  • tf.nn.conv2d()函数
  • tf.nn.conv2d()函数中padding的SAME填充方式

tf.nn.conv2d()函数

tf.nn.conv2d()是TensorFlow中用于创建卷积层的函数,这个函数的调用格式如下:

def conv2d(input: Any,
           filter: Any,
           strides: Any,
           padding: Any,
           use_cudnn_on_gpu: bool = True,
           data_format: str = "NHWC",
           dilations: List[int] = [1, 1, 1, 1],
           name: Any = None) -> Any

其中,比较重要的参数是 input, filter, strides, padding。

input 就是输入的数据,格式就是TensorFlow的标准,使用四维矩阵的形式,分别是Btach_size(可以说是要处理的图片数量),height, width,deepth,或者说是channel也就是通道数。

filter 在TensorFlow中称为滤波器,本质就相当于卷积核权重矩阵,这里要注意filter的形式,也是四维数组的形式 ,分别是height高度(就是之前那个移动的框框的高度), width宽度, deepth(channel)深度,最后是要将输入处理成的Feature Map的数目

stride 也就是步长了,按照上面的 “一贯作风”,也是四维数组的形式,分别表示 在batch_size上的步长,高度的步长,宽度的步长以及深度的步长,对应的是input的四个维度,一般对于图片输入来说,只需要改变中间两个值。

padding 是填充,这里只有两个值 SAMEVALID,接下来会重点说明。

后面的参数就是加速之类的选项,不是很重要。

padding参数的两个取值

padding有两个参数: VALIDSAME, 查了一些资料,发现大都不太清楚或者有错误,然后自己写数据试验了几次,算是差不多搞懂了这里的填充方式,现记录如下:

1.VALID

VALID方式就是这个单词的意思,也就是…不进行填充,非常潇洒,对于多出来的数据,直接丢掉,例如,设数据的长度是8, 设置filter的长度是4 ,stride是2:

data:  1 2 3 4 5 6 7 8
       |------|
           |-----|
                  7 8 不够下一次的取值 直接丢弃

这种方式很好理解,但是显然,这样的代价就是后边的一部分数据会丢失,所以一般不会使用。

2.SAME

这才是我们所熟知的 padding 填充,我的试验如下:

首先,使用这样的输入:

输入的图片是 20*20*1 的,filter的尺寸是 5*5*1, stride是5,这时候是可以直接进行处理的,不需要进行填充:

import tensorflow as tf

b = tf.Variable(tf.ones([1,20,20,1]))
wc1 = tf.Variable(tf.ones([5,5,1,1]))

op1 = tf.nn.conv2d(b, wc1, strides=[1,5,5,1], padding='SAME')

init_op = tf.global_variables_initializer()


with tf.Session() as sess:
    sess.run(init_op)
    res = sess.run(op1)
    h = res.shape[1]
    w = res.shape[2]
    print(res.shape)
    print(sess.run(tf.reshape(res, [h, w])))

输出如下:

(1, 4, 4, 1)
[[25. 25. 25. 25.]
 [25. 25. 25. 25.]
 [25. 25. 25. 25.]
 [25. 25. 25. 25.]]

接下来,试验一下最后少了一个元素会怎样:

将输入图像的的shape改成 24*24*1 ,这样的话移动完第4次之后,会剩下4个元素,这样就会缺少一个元素,filter和stride不变(接下来这两个都不变),也就是:

b = tf.Variable(tf.ones([1,24,24,1]))

这时候的输出是:

(1, 5, 5, 1)
[[25. 25. 25. 25. 20.]
 [25. 25. 25. 25. 20.]
 [25. 25. 25. 25. 20.]
 [25. 25. 25. 25. 20.]
 [20. 20. 20. 20. 16.]]

注意最后的输出的shape变成了 5*5*1 的feature map,变化都集中在最后一列,通过6这个结果我们可以推断出,在右侧和下面分别填充了一列(行)0,也就是:

1 1 1 ...1 1 0
1 1 1 ...1 1 0
1 1 1 ...1 1 0
1 1 1 ...1 1 0
0 0 0 ...0 0 0

所以这样才会出现 20 跟 16 这两种值。

接着调整输入,这次来看如果 最后差两个元素

b = tf.Variable(tf.ones([1,23,23,1]))

输出:

(1, 5, 5, 1)
[[16. 20. 20. 20. 16.]
 [20. 25. 25. 25. 20.]
 [20. 25. 25. 25. 20.]
 [20. 25. 25. 25. 20.]
 [16. 20. 20. 20. 16.]]

这次的输出结果像是有些对称,分析这样的输出结果,其实这两种值的出现与上面的情况是一样的,也就是说 上下左右各增加了一行(列)0

0 0 0 ...0 0 0
0 1 1 ...1 1 0
0 1 1 ...1 1 0
0 1 1 ...1 1 0
0 0 0 ...0 0 0

接下来看如果最后少3个元素:

b = tf.Variable(tf.ones([1,22,22,1]))

输出:

(1, 5, 5, 1)
[[16. 20. 20. 20. 12.]
 [20. 25. 25. 25. 15.]
 [20. 25. 25. 25. 15.]
 [20. 25. 25. 25. 15.]
 [12. 15. 15. 15.  9.]]

这次的变化比较大,首先看左边,值依然是16 或者是 20,说明应该增加了一列,来看最右边,最后变成了12 或者 15, 在 5*5的格子里,显然如果右边两列的值都是0,那么结果就是 15,再加上上面的一行是0 ,就会出现 12,所以,结论就是 : 最后缺少3个元素的时候,在前面(左边和上边)会添加一行(列)0, 在后边(下边和右边)会增加两行(列)0

0 0 0 ...0 0 0
0 1 1 ...1 0 0
0 1 1 ...1 0 0
0 1 1 ...1 0 0
0 0 0 ...0 0 0

其实到了这里,规律应该算是比较明显了,再测试一个,如果最后缺少4个元素,按照上面的规律会在前后都增加两行(列):

b = tf.Variable(tf.ones([1,21,21,1]))

输出:

(1, 5, 5, 1)
[[ 9. 15. 15. 15.  9.]
 [15. 25. 25. 25. 15.]
 [15. 25. 25. 25. 15.]
 [15. 25. 25. 25. 15.]
 [ 9. 15. 15. 15.  9.]]

从结果看来,确实是这样的。

那么,我们就可以得到一个结论

如果最后需要补全的数量是n:

  • n 如果是奇数,那么前面增加 (n-1)/2,后面增加(n+1)/2
  • n 如果是偶数,那么前后都增加 n/2

最后所缺少的元素与stride有很大的关系,上面的例子可以说有点特殊,因为移动中并没有任何重叠,那么,我们来验证一个有重叠的:

设置输入的数据 为 6*6*1的,取filter的shape是 3*3*1,stride是 2,这时候,我们计算可以知道最后是缺少一个元素的,

输出的结果是:

(1, 3, 3, 1)
[[9. 9. 6.]
 [9. 9. 6.]
 [6. 6. 4.]]

从结果看来是这样的。

当然,此外,如果设置stride为1,那么也就不需要进行补全了。

那么,我们还是需要计算最后的 Feature Map 的 shape 的,事实上,可以通过这个公式计算:

设 卷积核的长度是 k, 输入的长度是 l, 步长是s(这里对于长宽计算的原理都是一样的)有:
填 充 数 目 = k − l % s 填充数目 = k-l\%s =kl%s
有了这个公式,那么结合之前在卷积原理那部分介绍的计算 Feature Map的公式,就可以计算了。

以上~

你可能感兴趣的:(Tensorflow,深度学习)