tensorflow tf.nn.max_pool 中的 'SAME' 和 'VALID' padding 有什么区别?

What is the difference between 'SAME' and 'VALID' padding in tf.nn.max_pool of tensorflow?

tensorflowtf.nn.max_pool 中填充 'SAME' 和 'VALID' 有什么区别?

在我看来,'VALID'意味着当我们做max pool时,边缘外不会有零填充。

根据A guide to convolution arithmetic for deep learning,它说池运算符中不会有填充,即只使用tensorflow的'VALID'。 但是 tensorflow 中最大池的 'SAME' 填充是多少?

TensorFlow Convolution 示例概述了 SAMEVALID 之间的区别:

  • 对于 SAME 填充,输出高度和宽度计算如下:

     out_height = ceil(float(in_height) / float(strides[1]))
     out_width  = ceil(float(in_width) / float(strides[2]))
    

  • 对于 VALID 填充,输出高度和宽度计算如下:

     out_height = ceil(float(in_height - filter_height + 1) / float(strides[1]))
     out_width  = ceil(float(in_width - filter_width + 1) / float(strides[2]))
    

我举个例子更清楚:

  • x:输入图像[2, 3],1通道
  • valid_pad:具有 2x2 内核、步长 2 和有效填充的最大池。
  • same_pad:具有 2x2 内核、步长 2 和相同填充的最大池(这是 经典 方法)

输出形状为:

  • valid_pad:这里没有填充,所以输出形状是 [1, 1]
  • same_pad:在这里,我们将图像填充到形状 [2, 4](使用 -inf 然后应用最大池化),因此输出形状为 [1, 2]

x = tf.constant([[1., 2., 3.],
                 [4., 5., 6.]])

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.]

如果你喜欢 ascii 艺术:

  • "VALID" = 无填充:

       inputs:         1  2  3  4  5  6  7  8  9  10 11 (12 13)
                      |________________|                dropped
                                     |_________________|
    
  • "SAME" = 零填充:

                   pad|                                      |pad
       inputs:      0 |1  2  3  4  5  6  7  8  9  10 11 12 13|0  0
                   |________________|
                                  |_________________|
                                                 |________________|
    

在这个例子中:

  • 输入宽度=13
  • 过滤器宽度 = 6
  • 步幅 = 5

备注:

  • "VALID" 只会删除最右边的列(或最底部的行)。
  • "SAME" 尝试左右均匀填充,但如果要添加的列数是奇数,它将在右侧添加额外的列,如本例中的情况(相同逻辑垂直应用:底部可能有一行额外的零)。

编辑:

关于名字:

  • 使用 "SAME" 填充,如果您使用 1 的步幅,则图层的输出将具有与其输入相同 的空间维度。
  • 使用 "VALID" 填充,没有 "made-up" 填充输入。该层仅使用 有效 输入数据。

根据解释 here 并跟进 Tristan 的回答,我通常使用这些快速功能进行完整性检查。

# a function to help us stay clean
def getPaddings(pad_along_height,pad_along_width):
    # if even.. easy..
    if pad_along_height%2 == 0:
        pad_top = pad_along_height / 2
        pad_bottom = pad_top
    # if odd
    else:
        pad_top = np.floor( pad_along_height / 2 )
        pad_bottom = np.floor( pad_along_height / 2 ) +1
    # check if width padding is odd or even
    # if even.. easy..
    if pad_along_width%2 == 0:
        pad_left = pad_along_width / 2
        pad_right= pad_left
    # if odd
    else:
        pad_left = np.floor( pad_along_width / 2 )
        pad_right = np.floor( pad_along_width / 2 ) +1
        #
    return pad_top,pad_bottom,pad_left,pad_right

# strides [image index, y, x, depth]
# padding 'SAME' or 'VALID'
# bottom and right sides always get the one additional padded pixel (if padding is odd)
def getOutputDim (inputWidth,inputHeight,filterWidth,filterHeight,strides,padding):
    if padding == 'SAME':
        out_height = np.ceil(float(inputHeight) / float(strides[1]))
        out_width  = np.ceil(float(inputWidth) / float(strides[2]))
        #
        pad_along_height = ((out_height - 1) * strides[1] + filterHeight - inputHeight)
        pad_along_width = ((out_width - 1) * strides[2] + filterWidth - inputWidth)
        #
        # now get padding
        pad_top,pad_bottom,pad_left,pad_right = getPaddings(pad_along_height,pad_along_width)
        #
        print 'output height', out_height
        print 'output width' , out_width
        print 'total pad along height' , pad_along_height
        print 'total pad along width' , pad_along_width
        print 'pad at top' , pad_top
        print 'pad at bottom' ,pad_bottom
        print 'pad at left' , pad_left
        print 'pad at right' ,pad_right

    elif padding == 'VALID':
        out_height = np.ceil(float(inputHeight - filterHeight + 1) / float(strides[1]))
        out_width  = np.ceil(float(inputWidth - filterWidth + 1) / float(strides[2]))
        #
        print 'output height', out_height
        print 'output width' , out_width
        print 'no padding'


# use like so
getOutputDim (80,80,4,4,[1,1,1,1],'SAME')

stride为1时(更典型的是卷积而不是池化),我们可以想到以下区别:

  • "SAME":输出大小与输入大小相同。这需要过滤器 window 滑到输入映射之外,因此需要填充。
  • "VALID":过滤器 window 停留在输入映射中的 有效 位置,因此输出大小缩小 filter_size - 1。没有填充发生。

padding有3种选择:valid(无padding)、same(or half)、full。你可以在这里找到解释(在 Theano 中): http://deeplearning.net/software/theano/tutorial/conv_arithmetic.html

  • 有效或无填充:

有效填充不涉及零填充,因此它仅涵盖有效输入,不包括人为生成的零。如果步幅 s=1,则输出长度为 ((输入长度) - (k-1)) 内核大小 k。

  • 相同或一半填充:

当s=1时,相同的填充使得输出的大小与输入的大小相同。如果s=1,补零的个数是(k-1).

  • 全填充:

全填充意味着内核运行在所有输入上,所以在最后,内核可能只满足一个输入而其他输入为零。如果 s=1,则填充的零数为 2(k-1)。如果 s=1.

则输出的长度为 ((输入的长度) + (k-1))

故padding个数:(valid)<=(same)<=(full)

填充是一种增加输入数据大小的操作。在一维数据的情况下,您只需 append/prepend 具有常数的数组,在 2-dim 中,您用这些常数包围矩阵。在 n-dim 中,你用常量包围你的 n-dim 超立方体。在大多数情况下,此常数为零,称为零填充。

这是一个将 p=1 应用于二维张量的零填充示例:


您可以为内核使用任意填充,但某些填充值的使用频率高于其他填充值:

  • 有效填充。最简单的情况,意味着根本没有填充。保持数据不变即可。
  • SAME padding 有时称为 HALF padding。它被称为 SAME 因为对于步幅为 1 的卷积(或池化),它应该产生与输入相同大小的输出。它被称为 HALF 因为对于大小为 k
  • 的内核
  • FULL padding 是最大填充,不会导致仅填充元素上的卷积。对于大小为 k 的内核,此填充等于 k - 1.

要在TF中使用任意填充,可以使用tf.pad()

我从官方 tensorflow 文档中引用这个答案 https://www.tensorflow.org/api_guides/python/nn#Convolution 对于 'SAME' 填充,输出高度和宽度计算如下:

out_height = ceil(float(in_height) / float(strides[1]))
out_width  = ceil(float(in_width) / float(strides[2]))

顶部和左侧的填充计算如下:

pad_along_height = max((out_height - 1) * strides[1] +
                    filter_height - in_height, 0)
pad_along_width = max((out_width - 1) * strides[2] +
                   filter_width - in_width, 0)
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

对于 'VALID' 填充,输出高度和宽度计算如下:

out_height = ceil(float(in_height - filter_height + 1) / float(strides[1]))
out_width  = ceil(float(in_width - filter_width + 1) / float(strides[2]))

并且填充值始终为零。

快速说明

VALID:不应用任何填充,即假设所有尺寸都是 有效 ,以便输入图像完全被您指定的过滤器和步幅覆盖。

SAME:对输入应用填充(如果需要),以便输入图像完全被您指定的过滤器和步幅覆盖。对于步幅 1,这将确保输出图像大小与输入相同

注释

  • 这同样适用于卷积层和最大池化层
  • 术语 "valid" 有点用词不当,因为如果您删除部分图像,事情不会变成 "invalid"。有时你甚至可能想要那个。这可能应该被称为 NO_PADDING 而不是。
  • 术语 "same" 也是用词不当,因为只有当输出维度与输入维度相同时,它才对步幅为 1 有意义。例如,步幅为 2 时,输出维度将减半。这可能应该被称为 AUTO_PADDING 而不是。
  • SAME(即自动填充模式)中,Tensorflow 将尝试在左右两侧均匀分布填充。
  • VALID(即无填充模式)中,如果您的过滤器和步幅未完全覆盖输入图像,Tensorflow 将向右 and/or 底部单元格移动。

VALID 填充:这是零填充。希望没有混淆。

x = tf.constant([[1., 2., 3.], [4., 5., 6.],[ 7., 8., 9.], [ 7., 8., 9.]])
x = tf.reshape(x, [1, 4, 3, 1])
valid_pad = tf.nn.max_pool(x, [1, 2, 2, 1], [1, 2, 2, 1], padding='VALID')
print (valid_pad.get_shape()) # output-->(1, 2, 1, 1)

SAME padding:这首先有点难以理解,因为我们必须分别考虑 official docs 中提到的两个条件。

我们将输入设为 ,输出设为 ,填充设为 ,步幅设为 ,内核大小设为 (只有一个考虑单一维度)

案例 01:

案例 02:

被计算为可以用于填充的最小值。由于 的值已知,因此可以使用此公式 找到 的值。

让我们算出这个例子:

x = tf.constant([[1., 2., 3.], [4., 5., 6.],[ 7., 8., 9.], [ 7., 8., 9.]])
x = tf.reshape(x, [1, 4, 3, 1])
same_pad = tf.nn.max_pool(x, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME')
print (same_pad.get_shape()) # --> output (1, 2, 2, 1)

这里x的维度是(3,4)。那么如果取水平方向(3):

如果取垂直方向(4):

希望这有助于理解 SAME 填充在 TF 中的实际工作原理。

填充on/off。确定输入的有效大小。

VALID: 没有填充。卷积等操作仅在 "valid" 的位置执行,即不太靠近张量的边界。
使用 3x3 的内核和 10x10 的图像,您将对边界内的 8x8 区域执行卷积。

SAME: 提供了填充。每当您的操作引用一个邻域(无论多大)时,当该邻域延伸到原始张量之外时将提供零值,以允许该操作也适用于边界值。
使用 3x3 的内核和 10x10 的图像,您将在整个 10x10 区域上执行卷积。

这里,W和H是输入的宽度和高度, F 是滤波器尺寸, P 是填充大小(即要填充的行数或列数)

对于相同的填充:

对于有效填充:

综上所述,'valid'padding就是没有padding。卷积层的输出大小根据输入大小和内核大小缩小。

相反,'same'padding表示使用padding。当stride设置为1时,卷积层的输出大小通过在计算卷积时在输入数据周围附加一定数量的'0-border'来保持输入大小。

希望这种直观的描述对您有所帮助。

Tensorflow 2.0 Compatible Answer:上面已经给出了详细的解释,关于"Valid"和"Same"padding。

但是,为了社区的利益,我将在 Tensorflow 2.x (>= 2.0) 中指定不同的池化函数及其各自的命令。

1.x中的函数:

tf.nn.max_pool

tf.keras.layers.MaxPool2D

Average Pooling => None in tf.nn, tf.keras.layers.AveragePooling2D

2.x中的函数:

tf.nn.max_pool 如果在 2.x 和 tf.compat.v1.nn.max_pool_v2 中使用tf.compat.v2.nn.max_pool,如果从 1.x 迁移到 2.x。

tf.keras.layers.MaxPool2D 如果在 2.x 和

中使用

tf.compat.v1.keras.layers.MaxPool2Dtf.compat.v1.keras.layers.MaxPooling2Dtf.compat.v2.keras.layers.MaxPool2Dtf.compat.v2.keras.layers.MaxPooling2D,如果从 1.x 迁移到 2.x。

Average Pooling => tf.nn.avg_pool2dtf.keras.layers.AveragePooling2D 如果在 TF 2.x 和

tf.compat.v1.nn.avg_pool_v2tf.compat.v2.nn.avg_pooltf.compat.v1.keras.layers.AveragePooling2Dtf.compat.v1.keras.layers.AvgPool2Dtf.compat.v2.keras.layers.AveragePooling2Dtf.compat.v2.keras.layers.AvgPool2D , 如果从 1.x 迁移到 2.x.

有关从 Tensorflow 1.x 迁移到 2.x 的更多信息,请参阅此 Migration Guide

作为对 YvesgereY 出色回答的补充,我发现这个可视化非常有用:

padding 'valid'是第一个数字。过滤器 window 保留在图像内部。

padding 'same'是第三个数字。输出大小相同。


在这个 article

上找到

可视化学分:vdumoulin@GitHub