深入浅出理解TensorFlow的padding填充算法

一、参考资料

notes_on_padding_2

二、TensorFlow的padding算法

本文以TensorFlow v2.14.0版本为例,介绍TensorFlow的padding算法。

tf.nn.conv2d

python 复制代码
# https://github.com/tensorflow/tensorflow/blob/v2.14.0/tensorflow/python/ops/nn_ops.py#L2257-L2361

padding: Either the `string` `"SAME"` or `"VALID"` indicating the type of
      padding algorithm to use, or a list indicating the explicit paddings at
      the start and end of each dimension. See
      [here](https://www.tensorflow.org/api_docs/python/tf/nn#notes_on_padding_2)
      for more information.  When explicit padding is used and data_format is
      `"NHWC"`, this should be in the form `[[0, 0], [pad_top, pad_bottom],
      [pad_left, pad_right], [0, 0]]`. When explicit padding used and
      data_format is `"NCHW"`, this should be in the form `[[0, 0], [0, 0],
      [pad_top, pad_bottom], [pad_left, pad_right]]`.

1. 引言

tf.nn.conv2d and tf.nn.max_pool2d 函数都有padding参数,在执行函数之前,都需要进行填充padding(零元素)操作。padding参数可以是 VALIDSAMEVALID 表示no-padding不填充,SAME表示需要padding。

对于convolutions,用零元素填充;对于pools,填充值可以忽略,例如max_pool,其滑动窗口会忽略填充值。

2. VALID padding

padding='VALID' 表示不填充,这种情况下,输出的尺寸一般小于输入的尺寸。

对于 conv2d,它的输出尺寸为:

text 复制代码
out_height = ceil((in_height - filter_height + 1) / stride_height)
out_width  = ceil((in_width - filter_width + 1) / stride_width)

其中,filter_height and filter_width 表示滤波器fillter的尺寸。

3. SAME padding

padding='SAME' 可以对空间的各个维度进行padding。对于 conv2d,它的输出尺寸为:

text 复制代码
out_height = ceil(in_height / stride_height)
out_width  = ceil(in_width / stride_width)

重要说明:如果不关注padding的内部实现机制,该结论可以直接使用。

对于每个维度方向的padding,可以表示为:

python 复制代码
if (in_height % strides[1] == 0):
  pad_along_height = max(filter_height - stride_height, 0)
else:
  pad_along_height = max(filter_height - (in_height % stride_height), 0)
if (in_width % strides[2] == 0):
  pad_along_width = max(filter_width - stride_width, 0)
else:
  pad_along_width = max(filter_width - (in_width % stride_width), 0)

最终,对于 top, bottom, left and right 各维度的padding为:

python 复制代码
pad_top = pad_along_height // 2
pad_bottom = pad_along_height - pad_top
pad_left = pad_along_width // 2
pad_right = pad_along_width - pad_left

其中,the division by 2 表示两侧(top vs bottom, right vs left)的padding,而 the bottom and right sides 两侧需要填充剩余的padding。例如,when pad_along_height is 5, we pad 2 pixels at the top and 3 pixels at the bottom. 注意:该种padding方式与其他的深度学习框架(例如,PyTorch and Caffe)不同,其他的深度学习框架需要明确padding的数量,且在两侧padding相同的数量。

Note that this is different from existing libraries such as PyTorch and Caffe, which explicitly specify the number of padded pixels and always pad the same number of pixels on both sides.

3.1 代码示例

python 复制代码
in_height = 5
filter_height = 3
stride_height = 2

in_width = 2
filter_width = 2
stride_width = 1

inp = tf.ones((2, in_height, in_width, 2))
filter = tf.ones((filter_height, filter_width, 2, 2))
strides = [stride_height, stride_width]
output = tf.nn.conv2d(inp, filter, strides, padding='SAME')
output.shape[1]  # output_height: ceil(5 / 2)=3

output.shape[2] # output_width: ceil(2 / 1)=2

3.2 计算padding尺寸

已知条件:

text 复制代码
(in_height, in_width)=(5, 2)
(filter_height, filter_width)=(3, 2)
(strides[1], strides[2])=(2, 1)

先计算Height方向的padding,可得:

text 复制代码
in_height % strides[1] = 5%2 = 1

则满足以下公式:

python 复制代码
pad_along_height = max(filter_height - (in_height % stride_height), 0)

代入公式,可得:

text 复制代码
pad_along_height = max(3-(5%2), 0)=max(3-1, 0)=2
pad_top = pad_along_height // 2 = 2 // 2 = 1
pad_bottom = pad_along_height - pad_top = 2-1 = 1

由此可知,在 top 方向填充1,在 bottom 方向填充1。

再计算 Width 方向的padding,可得:

text 复制代码
in_width % strides[2] = 2%1 = 0

则满足以下公式:

text 复制代码
pad_along_width = max(filter_width - stride_width, 0)

代入公式,可得:

text 复制代码
pad_along_heght = max(2-1, 0) = 1
pad_left = pad_along_width // 2 = 1 // 2 = 0
pad_right = pad_along_width - pad_left = 1-0 = 1

由此可知,在 left 方向不填充,在 right 方向填充1。

综上所述,填充的示意图如下:

填充之后,输入尺寸由(5,2) 扩充为 (7,3)。

3.3 计算output尺寸

标准卷积输出尺寸的计算公式:
o = i + 2 p − k s + 1 i = size of input o = size of output p = p a d d i n g k = size of kernel s = s t r i d e s ( 1 ) o=\frac{i+2p-k}s+1 \quad \begin{array}{l} \\i=\textit{size of input}\\o=\textit{size of output}\\p=padding\\k=\textit{size of kernel}\\s=strides\end{array}\quad (1) o=si+2p−k+1i=size of inputo=size of outputp=paddingk=size of kernels=strides(1)

计算Height方向的输出尺寸,可得:
o u t _ h e i g h t = i n _ h e i g h t + ( p a d _ t o p + p a d _ b o t t o m ) − f i l t e r _ h e i g h t s t r i d e s [ 1 ] + 1 ( 2 ) out\_height=\frac{in\_height+(pad\_top+pad\_bottom)-filter\_height}{strides[1]}+1\quad (2) out_height=strides[1]in_height+(pad_top+pad_bottom)−filter_height+1(2)

将已知条件代入上述 公式 ( 2 ) 公式(2) 公式(2) 中,可得:
o u t _ h e i g h t = 5 + ( 1 + 1 ) − 3 2 + 1 = 3 out\_height=\frac{5+(1+1)-3}{2}+1=3 out_height=25+(1+1)−3+1=3

计算Width方向的输出尺寸,可得:
o u t _ w i d t h = i n _ w i d t h + ( p a d _ l e f t + p a d _ r i g h t ) − f i l t e r _ w i d t h s t r i d e s [ 2 ] + 1 ( 3 ) out\_width=\frac{in\_width+(pad\_left+pad\_right)-filter\_width}{strides[2]}+1\quad (3) out_width=strides[2]in_width+(pad_left+pad_right)−filter_width+1(3)

将已知条件代入上述 公式 ( 3 ) 公式(3) 公式(3) 中,可得:
o u t _ w i d t h = 2 + ( 0 + 1 ) − 2 1 + 1 = 2 out\_width=\frac{2+(0+1)-2}{1}+1=2 out_width=12+(0+1)−2+1=2

综上所述,输出尺寸为(3, 2),与代码验证的结果一致。

4. Explicit padding

在TensorFlow中,也可以指定padding的数量。但需要注意的是,padding 参数为 list 类型,而不是Tensor,且该参数的格式与 tf.pad 相同。

对于 conv2d,当 data_format='NHWC'padding 的参数格式为 [[0, 0], [pad_top, pad_bottom], [pad_left, pad_right], [0, 0]] ,第一个 [[0, 0]] 表示 batch维度上no-padding不填充,最后一个 [[0, 0]] 表示 channel 维度上no-padding不填充。

For example, in the 2D case, the list is in the format [[0, 0], [pad_top, pad_bottom], [pad_left, pad_right], [0, 0]] when data_format is its default value of 'NHWC'. The two [0, 0] pairs indicate the batch and channel dimensions have no padding, which is required, as only spatial dimensions can have padding.

4.1 代码示例

python 复制代码
inp = tf.ones((1, 3, 3, 1))
filter = tf.ones((2, 2, 1, 1))
strides = [1, 1]
padding = [[0, 0], [1, 2], [0, 1], [0, 0]]
output = tf.nn.conv2d(inp, filter, strides, padding=padding)
tuple(output.shape)  # (1, 5, 3, 1)

# Equivalently, tf.pad can be used, since convolutions pad with zeros.
inp = tf.pad(inp, padding)
# 'VALID' means to use no padding in conv2d (we already padded inp)
output2 = tf.nn.conv2d(inp, filter, strides, padding='VALID')
tf.debugging.assert_equal(output, output2)

4.2 计算padding尺寸

已知条件:

text 复制代码
(in_height, in_width)=(3, 3)
(filter_height, filter_width)=(2, 2)
(strides[1], strides[2])=(1, 1)
(pad_top, pad_bottom)=(1, 2)
(pad_left, pad_right)=(0, 1)

从已知条件中可以看出,在 top 方向填充1,在 bottom 方向填充2。在 left 方向不填充,在 right 方向填充1。

综上所述,填充的示意图如下:

填充之后,输入尺寸由(3,3) 扩充为 (6,4)。

4.3 计算output尺寸

将已知条件代入上述 公式 ( 2 ) 公式(2) 公式(2) 中,可得:
o u t _ h e i g h t = 3 + ( 1 + 2 ) − 2 1 + 1 = 5 out\_height=\frac{3+(1+2)-2}{1}+1=5 out_height=13+(1+2)−2+1=5

将已知条件代入上述 公式 ( 3 ) 公式(3) 公式(3) 中,可得:
o u t _ w i d t h = 3 + ( 0 + 1 ) − 2 1 + 1 = 3 out\_width=\frac{3+(0+1)-2}{1}+1=3 out_width=13+(0+1)−2+1=3

综上所述,输出尺寸为(5, 3),与代码验证的结果一致。

5. 区分卷积层和池化层中的padding

卷积层与池化层中的padding不一样。对于卷积层,以零元素进行padding,再与kernel相乘(卷积操作)。对于池化层,没有相乘的过程。例如,对于一个4x4的 average pooling,其padding对最终结果没有影响。

5.1 代码示例

python 复制代码
x_in = np.array([[
  [[2], [2]],
  [[1], [1]],
  [[1], [1]]]])
kernel_in = np.array([  # simulate the avg_pool with conv2d
 [ [[0.25]], [[0.25]] ],
 [ [[0.25]], [[0.25]] ]])
x = tf.constant(x_in, dtype=tf.float32)
kernel = tf.constant(kernel_in, dtype=tf.float32)
conv_out = tf.nn.conv2d(x, kernel, strides=[1, 1, 1, 1], padding='SAME')
pool_out = tf.nn.avg_pool(x, [2, 2], strides=[1, 1, 1, 1], padding='SAME')
print(conv_out.shape, pool_out.shape)
# (1, 3, 2, 1) (1, 3, 2, 1)
tf.reshape(conv_out, [3, 2]).numpy()  # conv2d takes account of padding
"""
array([[1.5, 0.75],
		[1., 0.5],
		[0.5, 0.25]], dtype=float32)
"""

tf.reshape(pool_out, [3, 2]).numpy()  # avg_pool excludes padding
"""
array([[1.5, 1.5],
		[1., 1.],
		[1., 1.0]], dtype=float32)
"""

5.2 计算padding尺寸

已知条件:

text 复制代码
(in_height, in_width)=(3, 2)
(filter_height, filter_width)=(2, 2)
(strides[1], strides[2])=(1, 1)

先计算Height方向的padding,可得:

text 复制代码
in_height % strides[1] = 3%1 = 0

则满足以下公式:

text 复制代码
pad_along_height = max(filter_height - stride_height, 0)

代入公式,可得:

text 复制代码
pad_along_height = max(2-1, 0) = 1
pad_top = pad_along_height // 2 = 1 // 2 = 0
pad_bottom = pad_along_height - pad_top = 1-0 = 1

由此可知,在 top 方向填充0,在 bottom 方向填充1。

再计算 Width 方向的padding,可得:

text 复制代码
in_width % strides[2] = 2%1 = 0

则满足以下公式:

text 复制代码
pad_along_width = max(filter_width - stride_width, 0)

代入公式,可得:

text 复制代码
pad_along_heght = max(2-1, 0) = 1
pad_left = pad_along_width // 2 = 1 // 2 = 0
pad_right = pad_along_width - pad_left = 1-0 = 1

由此可知,在 left 方向不填充,在 right 方向填充1。

综上所述,填充的示意图如下:

填充之后,输入尺寸由(3,2) 扩充为 (4,3)。

5.3 计算output尺寸

将已知条件代入上述 公式 ( 2 ) 公式(2) 公式(2) 中,可得:
o u t _ h e i g h t = 3 + ( 0 + 1 ) − 2 1 + 1 = 3 out\_height=\frac{3+(0+1)-2}{1}+1=3 out_height=13+(0+1)−2+1=3

将已知条件代入上述 公式 ( 3 ) 公式(3) 公式(3) 中,可得:
o u t _ w i d t h = 2 + ( 0 + 1 ) − 2 1 + 1 = 2 out\_width=\frac{2+(0+1)-2}{1}+1=2 out_width=12+(0+1)−2+1=2

综上所述,输出尺寸为(3, 2),与代码验证的结果一致。

5.4 计算卷积操作的结果

5.5 计算池化操作的结果

相关推荐
_.Switch2 分钟前
Python机器学习:自然语言处理、计算机视觉与强化学习
python·机器学习·计算机视觉·自然语言处理·架构·tensorflow·scikit-learn
没有余地 EliasJie5 小时前
Windows Ubuntu下搭建深度学习Pytorch训练框架与转换环境TensorRT
pytorch·windows·深度学习·ubuntu·pycharm·conda·tensorflow
_.Switch21 小时前
Python机器学习框架介绍和入门案例:Scikit-learn、TensorFlow与Keras、PyTorch
python·机器学习·架构·tensorflow·keras·scikit-learn
码匠许师傅3 天前
【STM32开发笔记】移植AI框架TensorFlow到STM32单片机【下篇】
人工智能·stm32·单片机·ai·tensorflow
FL16238631294 天前
运行tensorflow报错InternalError: libdevice not found at ./libdevice.10.bc解决方法
人工智能·python·tensorflow
水上冰石4 天前
TensorFlow+springboot 花生病虫害叶片识别,如何训练模型
spring boot·tensorflow·neo4j
deflag4 天前
第T2周:TensorFlow实现彩色图片分类(CIFAR10数据集),并实现自己的真实图片分类
python·机器学习·分类·tensorflow
开始King5 天前
Tensorflow 2.0 cnn训练cifar10 准确率只有0.1 [已解决]
人工智能·cnn·tensorflow
jun7788959 天前
探索 TensorFlow:构建强大的机器学习模型
机器学习·tensorflow·neo4j
kolaseen10 天前
tensorflow底层架构
人工智能·python·深度学习·架构·tensorflow·gpu·cuda