Tensorflow中卷积的padding操作

目录

  • Tensorflow中padding为valid的情况
  • Tensorflow中padding为same的情况
  • 和Pytorch的padding简单对比
  • 实验对比
    • 实验1
    • 实验2
    • 实验3
    • 实验4
    • 实验5
    • 实验6

关于Tensorflow中卷积的padding其实在之前的课程中我有讲过,不过本节课会在详细讲解下,并和Pytorch中卷积的padding进行简单的对比。关于Pytorch中卷积的padding可以参考我之前写的一篇文章
在Tensorflow中卷积的padding一般需要指定为same或者valid,并不用去详细计算padding的多少。关于padding后的输出特征矩阵的大小,还是参见如下公式(在Pytorch中就是直接按照这个公式计算的):
o u t p u t _ s i z e = i n p u t _ s i z e − f i l t e r _ s i z e + 2 P s t r i d e + 1 output\_size = \frac{input\_size-filter\_size + 2P}{stride} + 1 output_size=strideinput_size−filter_size+2P​+1


Tensorflow中padding为valid的情况

借用tf中keras模块关于valid的情况介绍:"valid" means no padding.
简单来说,就是在计算过程中不会为输入特征矩阵添加padding。
valid模式具体计算公式如下,后面有实验证明:
o u t p u t _ s i z e = c e i l [ i n p u t _ s i z e − f i l t e r _ s i z e + 1 s t r i d e ] output\_size = ceil\lbrack\frac{input\_size-filter\_size+1}{stride}\rbrack output_size=ceil[strideinput_size−filter_size+1​]
例如input_size=6, filter_size=3, stride=2根据计算参考实验1
o u t p u t _ s i z e = c e i l [ 6 − 3 + 1 2 ] = c e i l [ 2 ] = 2 output\_size = ceil\lbrack\frac{6-3+1}{2}\rbrack=ceil[2]=2 output_size=ceil[26−3+1​]=ceil[2]=2
如下图所示,由于valid模式不会进行padding,所以为了防止出现越界的情况,实际处理的数据只有有图中红色区域,剩下其他区域的数据都被舍弃掉了。

假如把input_size=5其他参数不变, 根据计算(参考实验2)
o u t p u t _ s i z e = c e i l [ 5 − 3 + 1 2 ] = c e i l [ 1.5 ] = 2 output\_size = ceil\lbrack\frac{5-3+1}{2}\rbrack=ceil[1.5]=2 output_size=ceil[25−3+1​]=ceil[1.5]=2
此时刚好所有数据都能被利用(依旧没有padding)。


Tensorflow中padding为same的情况

借用tf中keras模块关于same的情况介绍:"same" results in padding evenly to the left/right or up/down of the input such that output has the same height/width dimension as the input.
大致的意思是说会在输入特征矩阵的周围进行padding填充,一般是为了将所有的数据都覆盖到。但是保证输出h/w维度与输入h/w维度保持不变这句话是有误导性的。因为一般使用same时,stride都是设置为1的,所以输出特征矩阵的高和宽与输入特征矩阵的高和宽是一样的,但是如果stride大于1,那后面这句话肯定就是有问题的了,一般height和width都会变。
same模式具体计算公式如下,后面有实验证明:
o u t p u t _ s i z e = c e i l [ i n p u t _ s i z e s t r i d e ] output\_size = ceil\lbrack\frac{input\_size}{stride}\rbrack output_size=ceil[strideinput_size​]

例如input_size=5, filter_size=3, stride=1根据计算(参考实验3)
o u t p u t _ s i z e = c e i l [ 5 1 ] = c e i l [ 5 ] = 5 output\_size = ceil\lbrack\frac{5}{1}\rbrack=ceil[5]=5 output_size=ceil[15​]=ceil[5]=5

如下图所示,为了防止出现越界的情况,same模式会自动进行padding,所以在输入特征矩阵上下左右,都使用0元素进行了padding填充。实际处理的数据为图中红色区域(保证所有信息被利用到的同时,保证高和宽不变)。

再看一个比较特殊的情况,假设input_size=4, filter_size=3, stride=2根据计算(参考实验4) 此时输出特征矩阵的高度和宽度就缩减为原来的一半了。
o u t p u t _ s i z e = c e i l [ 4 2 ] = c e i l [ 2 ] = 2 output\_size = ceil\lbrack\frac{4}{2}\rbrack=ceil[2]=2 output_size=ceil[24​]=ceil[2]=2

通过分析能够发现,此时只需要在左右中的一侧以及上下中的一侧进行1个像素的pandding填充即可。那么问题来了到底填充在哪边呢?通过实验发现,tensorflow底层是在右侧以及下侧进行填充的,可见参考实验4。通过padding后,所有的数据都能被覆盖到。


和Pytorch的padding简单对比

接着谈谈我个人的看法,在Pytorch中的padding一般都是人为计算指定的(个人感觉更灵活),而在Tensorflow中一般只用指定是same或者valid即可(个人感觉更方便)。
但有时为了对比tensorflow和pytorch的计算结果(希望拥有相同的padding行为),例如在实验4和实验5中当input_size=4, filter_size=3, stride=2时,为了覆盖所有数据一般padding的数值是等于1的。但在Tensorflow中(padding="same")是在输入特征矩阵的右侧和下侧进行填充的,而在Pytorch中是在左侧和上侧进行填充的。
假设这里将Tensorflow的padding方式改成和Pytorch中的一样,此时就不能使用same方式了。此时,比较常见的方法是先手动padding,然后在用valid的方式,参考实验6。


实验对比

Tensorflow使用的版本是2.4
Pytorch使用的版本是1.6
为了方便计算,在初始化卷积核参数时,权重全部置为1.

实验1

Tensorflow中当input_size=6, filter_size=3, stride=2padding="valid"时:

import numpy as np
from tensorflow import keras
from tensorflow.keras import layers

np.random.seed(0)
image = np.random.randint(low=0, high=5, size=[6, 6]).astype(np.float32)

# [B, H, W, C] for tensorflow
print(image)
tf_image = np.expand_dims(image, axis=[0, 3])

conv = layers.Conv2D(filters=1,
                     kernel_size=3,
                     strides=2,
                     padding="valid",
                     use_bias=False,
                     kernel_initializer=keras.initializers.constant(1.))
conv.build([1, 6, 6, 1])
result = np.squeeze(conv(tf_image).numpy())
print(result)

实验2

Tensorflow中当input_size=5, filter_size=3, stride=2padding="valid"时:

import numpy as np
from tensorflow import keras
from tensorflow.keras import layers

np.random.seed(0)
image = np.random.randint(low=0, high=5, size=[5, 5]).astype(np.float32)

# [B, H, W, C] for tensorflow
print(image)
tf_image = np.expand_dims(image, axis=[0, 3])

conv = layers.Conv2D(filters=1,
                     kernel_size=3,
                     strides=2,
                     padding="valid",
                     use_bias=False,
                     kernel_initializer=keras.initializers.constant(1.))
conv.build([1, 5, 5, 1])
result = np.squeeze(conv(tf_image).numpy())
print(result)

实验3

Tensorflow中当input_size=5, filter_size=3, stride=1padding="same"时:

import numpy as np
from tensorflow import keras
from tensorflow.keras import layers

image = np.ones(shape=[5, 5], dtype=np.float32)

# [B, H, W, C] for tensorflow
print(image)
tf_image = np.expand_dims(image, axis=[0, 3])

conv = layers.Conv2D(filters=1,
                     kernel_size=3,
                     strides=1,
                     padding="same",
                     use_bias=False,
                     kernel_initializer=keras.initializers.constant(1.))
conv.build([1, 5, 5, 1])
result = np.squeeze(conv(tf_image).numpy())
print(result)

实验4

Tensorflow中当input_size=4, filter_size=3, stride=2padding="same"时:

import numpy as np
from tensorflow import keras
from tensorflow.keras import layers

image = np.ones(shape=[4, 4], dtype=np.float32)

# [B, H, W, C] for tensorflow
print(image)
tf_image = np.expand_dims(image, axis=[0, 3])

conv = layers.Conv2D(filters=1,
                     kernel_size=3,
                     strides=2,
                     padding="same",
                     use_bias=False,
                     kernel_initializer=keras.initializers.constant(1.))
conv.build([1, 4, 4, 1])
result = np.squeeze(conv(tf_image).numpy())
print(result)

实验5

在Pytorch中,当input_size=4, filter_size=3, stride=2, padding=1时:

import numpy as np
import torch

image = np.ones(shape=[4, 4], dtype=np.float32)

# [B, C, H, W] for Pytorch
print(image)
torch_image = torch.as_tensor(np.expand_dims(image, axis=[0, 1]))

conv = torch.nn.Conv2d(in_channels=1,
                       out_channels=1,
                       kernel_size=3,
                       stride=2,
                       padding=1,
                       bias=False)
torch.nn.init.ones_(conv.weight)

result = np.squeeze(conv(torch_image).detach().numpy())

print(result)

实验6

Tensorflow中当input_size=4, filter_size=3, stride=2padding="valid"时(事先手动pandding):

import numpy as np
from tensorflow import keras
from tensorflow.keras import layers

image = np.ones(shape=[4, 4], dtype=np.float32)

# [B, H, W, C] for tensorflow
print(image)
tf_image = np.expand_dims(image, axis=[0, 3])

# ((top_pad, bottom_pad), (left_pad, right_pad))
padding = layers.ZeroPadding2D(padding=((1, 0), (1, 0)))
conv = layers.Conv2D(filters=1,
                     kernel_size=3,
                     strides=2,
                     padding="valid",
                     use_bias=False,
                     kernel_initializer=keras.initializers.constant(1.))
conv.build([1, 4, 4, 1])
result = np.squeeze(conv(padding(tf_image)).numpy())
print(result)

(0)

相关推荐