本篇的主要内容:
tf.nn.conv2d()
函数中padding的SAME
填充方式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 是填充,这里只有两个值 SAME
和 VALID
,接下来会重点说明。
后面的参数就是加速之类的选项,不是很重要。
padding有两个参数: VALID
和SAME
, 查了一些资料,发现大都不太清楚或者有错误,然后自己写数据试验了几次,算是差不多搞懂了这里的填充方式,现记录如下:
VALID方式就是这个单词的意思,也就是…不进行填充,非常潇洒,对于多出来的数据,直接丢掉,例如,设数据的长度是8, 设置filter的长度是4 ,stride是2:
data: 1 2 3 4 5 6 7 8
|------|
|-----|
7 8 不够下一次的取值 直接丢弃
这种方式很好理解,但是显然,这样的代价就是后边的一部分数据会丢失,所以一般不会使用。
这才是我们所熟知的 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:
最后所缺少的元素与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 填充数目=k−l%s
有了这个公式,那么结合之前在卷积原理那部分介绍的计算 Feature Map的公式,就可以计算了。
以上~