【问题标题】:What is the difference between 'SAME' and 'VALID' padding in tf.nn.max_pool of tensorflow?张量流的 tf.nn.max_pool 中的“SAME”和“VALID”填充有什么区别?
【发布时间】:2023-03-07 17:20:02
【问题描述】:

tf.nn.max_pool of tensorflow 中的“SAME”和“VALID”填充有什么区别?

在我看来,“VALID”意味着当我们进行最大池时,边缘之外不会有零填充。

根据A guide to convolution arithmetic for deep learning,它表示池运算符中不会有填充,即只需使用tensorflow 的“有效”。 但是tensorflow 中最大池的“相同”填充是什么?

【问题讨论】:

  • 查看tensorflow.org/api_guides/python/… 了解详情,tf 就是这样完成的。
  • 这是一个漂亮的detailed answer with visualizations
  • 查看这些令人惊叹的 GIF 以了解填充和步幅的工作原理。 Link
  • @GabrielChu 您的链接似乎已失效,现在重定向到一般概述。
  • 随着 Tensorflow 升级到 2.0,将会被 Keras 取代,相信您可以在 Keras 文档中找到池化信息。 @马特

标签: python tensorflow deep-learning


【解决方案1】:

如果你喜欢 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。在我看来,您添加了足够的零(以尽可能对称的方式),以便所有输入都被某个过滤器覆盖,对吗?
  • 很好的答案,只是补充一点:如果张量值可能为负,max_pooling 的填充是-inf
  • 如果在 ksize=2、stride=2 且填充相同的情况下输入宽度是偶数怎么办?...那么它不应该是零填充的吗?...我是当我查看darkflow代码仓库时说这个,他们使用相同的pad,stride = 2,ksize = 2 for maxpool ....在maxpooling图像宽度从416像素宽度减少到208像素之后。谁能澄清一下?
【解决方案2】:

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

  • "SAME":输出大小与输入大小相同。这需要过滤器窗口滑出输入映射,因此需要填充。
  • "VALID":过滤器窗口停留在输入映射内的有效位置,因此输出大小缩小了filter_size - 1。不发生填充。

【讨论】:

  • 这终于有用了。到目前为止,似乎SAMEVALID 也可能被称为foobar
  • 我认为“输出大小与输入大小相同”仅在步长为 1 时才成立。
【解决方案3】:

我举个例子让大家更清楚:

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

输出形状为:

  • valid_pad: 这里没有填充所以输出的形状是 [1, 1]
  • same_pad:这里,我们将图像填充到形状 [2, 4](使用-inf 然后应用 max pool),所以输出形状是 [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.]

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

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

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


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

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

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

      【讨论】:

        【解决方案6】:

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

        填充 'valid' 是第一个数字。过滤器窗口留在图像内。

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


        article找到它

        可视化学分:vdumoulin@GitHub

        【讨论】:

        • 非常直接的答案!
        • 这对我来说是最好的解决方案。可视化讲述了这个故事。谢谢
        【解决方案7】:

        快速说明

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

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

        备注

        • 这同样适用于 conv 层和 max pool 层
        • “有效”一词有点用词不当,因为如果您丢弃部分图像,事情不会变成“无效”。有时你甚至可能想要那个。这应该改为 NO_PADDING
        • 术语“相同”也是用词不当,因为只有当输出维度与输入维度相同时,步幅为 1 才有意义。例如,对于 2 的步幅,输出尺寸将是一半。这应该改为 AUTO_PADDING
        • SAME(即自动填充模式)下,Tensorflow 将尝试在左右两边均匀分布填充。
        • VALID(即无填充模式)中,如果您的过滤器和步幅未完全覆盖输入图像,Tensorflow 将丢弃右侧和/或底部单元格。

        【讨论】:

          【解决方案8】:

          我从官方 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
          • 有这个答案非常有用,特别是因为您指向的链接不再起作用,而且 Google 似乎从 tf 网站上删除了该信息!
          • 这应该是问题的答案!确实是唯一完整的答案。
          【解决方案9】:

          填充有三种选择:valid(无填充)、same(或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))。

          因此,填充数:(有效)

          【讨论】:

            【解决方案10】:

            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 填充:首先这有点难以理解,因为我们必须分别考虑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 中的实际工作原理。

            【讨论】:

              【解决方案11】:

              总而言之,“有效”填充意味着没有填充。卷积层的输出大小根据输入大小和内核大小而缩小。

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

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

              【讨论】:

                【解决方案12】:

                根据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')
                

                【讨论】:

                  【解决方案13】:

                  填充开/关。确定输入的有效大小。

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

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

                  【讨论】:

                    【解决方案14】:

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

                    对于相同的填充:

                    对于 VALID 填充:

                    【讨论】:

                      【解决方案15】:

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

                      但是,为了社区的利益,我将在 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_v2tf.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.AvgPool2D tf.compat.v2.keras.layers.AveragePooling2Dtf.compat.v2.keras.layers.AvgPool2D,如果从 1.x 迁移到 2.x。

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

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2023-04-02
                        • 1970-01-01
                        • 1970-01-01
                        • 2018-01-17
                        • 1970-01-01
                        • 2011-02-18
                        • 1970-01-01
                        相关资源
                        最近更新 更多