张量流的tf.nn.max_pool中的'SAME'和'VALID'填充有什么区别?


309

什么是“相同”和“有效”填充之间的区别tf.nn.max_pooltensorflow

我认为,“有效”表示在进行最大池化时,边缘外部不会出现零填充。

根据深度学习卷积算法指南,它说池运算符中将没有填充,即仅使用的“ VALID” tensorflow。但是最大池的“相同”填充是tensorflow什么?


3
检查tensorflow.org/api_guides/python/…以获得详细信息,这是tf完成的方式。
百利楚

3
这是关于可视化的一个非常详细的答案
rbinnun

4
查看这些惊人的GIF,以了解填充和跨距的工作原理。链接
Deepak

1
@GabrielChu您的链接似乎已消失,现在已重定向到常规概述。
马特

随着Tensorflow升级到2.0,事情将被Keras取代,我相信您可以在Keras文档中找到池化信息。@matt
GabrielChu

Answers:


163

我将举一个例子使其更加清晰:

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


603

如果您喜欢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"填充时,没有“虚构的”填充输入。该图层仅使用有效的输入数据。

公平地说“ SAME”的意思是“如果图像宽度不是滤镜宽度的倍数或图像高度不是滤镜高度的倍数,则使用零填充以确保不必更改滤镜大小“?例如,如果宽度是问题,那么“用零填充直至滤波器宽度的倍数”?
StatsSorceress

2
回答我自己的问题:不,这不是零填充的意义。您可以选择要与输入配合使用的过滤器大小(包括零填充),但不要在过滤器大小之后选择零填充。
StatsSorceress

我不明白你自己的答案@StatsSorceress。在我看来,您添加了足够的零(以尽可能对称的方式),以便所有输入都被某个过滤器覆盖,对吗?
guillefix

2
很好的答案,只是添加一下:如果张量值可以为负,则max_pooling的填充为-inf
Tones29

如果在ksize = 2,stride = 2并使用相同填充的情况下输入宽度是偶数怎么办?...那么它不应该用零填充吗?...。我是在看darkflow代码仓库时说的,他们使用的是SAME pad,maxpool的步幅= 2,ksize = 2 .... maxpooling图像宽度从416像素宽度减少到208像素之后。谁能澄清一下?
K.vindi

161

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

  • "SAME":输出大小输入大小相同。这要求过滤器窗口滑到输入图的外部,因此需要填充。
  • "VALID":过滤器窗口停留在输入地图内的有效位置,因此输出大小缩小filter_size - 1。没有填充发生。

65
这最终是有帮助的。到这一点,似乎SAMEVALID可能也被称为foobar
omatai

7
我认为“输出大小输入大小相同 ”仅在步幅为1时才是正确的
。– omsrisagar

92

所述TensorFlow卷积示例给出关于之间的差的概述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]))

46

填充是增加输入数据大小的操作。如果是一维数据,则只需在数组上附加/添加常量,在二维中,将这些常量包围在矩阵中。在n-dim中,将常数包围在n-dim超立方体中。在大多数情况下,此常数为零,称为零填充。

这是一个p=1应用于2-d张量的零填充示例: 在此处输入图片说明


您可以对内核使用任意填充,但是某些填充值的使用频率要比其他填充值高:

  • 有效填充。最简单的情况是完全没有填充。只需保持数据不变即可。
  • 相同的填充有时也称为HALF填充。之所以称为SAME,是因为对于步幅= 1的卷积(或对于池化),它应产生与输入大小相同的输出。之所以称为HALF,是因为对于一个大小的内核k 在此处输入图片说明
  • 全填充是最大填充,不会导致仅填充元素的卷积。对于内核大小k,此填充等于k - 1

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


32

快速说明

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

SAME:对输入使用填充(如果需要),以使输入图像完全被滤镜覆盖,并跨步指定。对于第1步,这将确保输出图像大小相同输入。

笔记

  • 这同样适用于转换层和最大池层
  • 术语“有效”有点用词不当,因为如果您删除部分图像,事情不会变得“无效”。有时您甚至可能想要那样。这应该被称为NO_PADDING改为。
  • 术语“相同”也是错误的称呼,因为只有当输出尺寸与输入尺寸相同时,步幅为1才有意义。例如,对于跨度为2的步,输出尺寸将为一半。应该应该AUTO_PADDING改为调用它。
  • SAME(即自动填充模式)下,Tensorflow将尝试在左右两侧均匀地填充填充。
  • VALID(即无填充模式)下,如果您的过滤器和步幅未完全覆盖输入图像,Tensorflow将在右边和/或底部单元格下降。

19

我引用官方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

对于“有效”填充,输出高度和宽度的计算公式如下:

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

填充值始终为零。


1
坦白地说,这是唯一有效且完整的答案,不仅仅限于步幅为1。而且,所需要的只是来自文档的引文。+1
P-Gn

1
得到这个答案非常有用,特别是因为您指向的链接不再起作用,而且看来Google删除了tf网站上的信息!
丹尼尔(Daniel

12

填充有三种选择:有效(无填充),相同(或一半),完整。您可以在以下位置找到说明(在Theano中):http : //deeplearning.net/software/theano/tutorial/conv_arithmetic.html

  • 有效或无填充:

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

  • 相同或一半填充:

当s = 1时,相同的填充使输出的大小与输入的大小相同。如果s = 1,则填充的零数为(k-1)。

  • 全填充:

完全填充意味着内核将在整个输入上运行,因此,在最后,内核可能会遇到唯一的一个输入,而其他输入可能为零。如果s = 1,则填充的零数为2(k-1)。如果s = 1,则输出长度为((输入长度​​)+(k-1))。

因此,填充数:(有效)<=(相同)<=(满)


8

启用/禁用填充。确定输入的有效大小。

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

SAME:提供填充。每当您的操作引用邻域(无论大小)时,当该邻域超出原始张量时,都会提供零值,以使该操作也可以处理边界值。
使用3x3的内核和10x10的图像,您将在整个10x10区域上进行卷积。


8

有效填充:这是零填充。希望没有混乱。

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)

相同的 填充:首先要理解这有点棘手,因为我们必须分别考虑两个条件,如官方文档中所述

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

情况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中的实际作用。


7

根据此处的说明以及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')

6

综上所述,“有效”填充表示没有填充。卷积层的输出大小根据输入大小和内核大小而缩小。

相反,“相同”填充表示使用填充。当跨度设置为1时,在计算卷积时,通过在输入数据周围附加一定数量的“ 0边界”,将卷积层的输出大小保持为输入大小。

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



2

补充YvesgereY的好答案,我发现此可视化非常有用:

填充可视化

填充有效 ”是第一个数字。过滤器窗口停留在图像内部。

填充“ 相同 ”是第三个数字。输出大小相同。


本文上找到它。


0

Tensorflow 2.0兼容答案:上面已经提供了有关“有效”和“相同”填充的详细说明。

但是,Tensorflow 2.x (>= 2.0)为了社区的利益,我将在中指定不同的Pooling Function及其各自的Command 。

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_pool2d或者tf.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的更多信息,请参阅此迁移指南

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.