如何实现固定长度的空间金字塔池化层?

kar*_*TUM 5 python deep-learning tensorflow

我想实现本文中介绍的空间金字塔池化层。

正如论文设置,关键点是定义 max_pooling 层的变体内核大小和步幅大小,即:

kernel_size = ceil(a/n)
stride_size = floor(a/n)
Run Code Online (Sandbox Code Playgroud)

其中a是输入张量空间大小,n是金字塔级别,即池化输出的空间箱。

我尝试用张量流实现这一层:

import numpy as np
import tensorflow as tf


def spp_layer(input_, name='SPP_layer'):
    """
    4 level SPP layer.

    spatial bins: [6_6, 3_3, 2_2, 1_1]

    Parameters
    ----------
    input_ : tensor
    name : str

    Returns
    -------
    tensor
    """
    shape = input_.get_shape().as_list()

    with tf.variable_scope(name):

        spp_6_6_pool = tf.nn.max_pool(input_,
                                      ksize=[1,
                                             np.ceil(shape[1]/6).astype(np.int32),
                                             np.ceil(shape[2]/6).astype(np.int32),
                                             1],
                                      strides=[1, shape[1]//6, shape[2]//6, 1],
                                      padding='SAME')
        print('SPP layer level 6:', spp_6_6_pool.get_shape().as_list())

        spp_3_3_pool = tf.nn.max_pool(input_,
                                      ksize=[1,
                                             np.ceil(shape[1]/3).astype(np.int32),
                                             np.ceil(shape[2]/3).astype(np.int32),
                                             1],
                                      strides=[1, shape[1]//3, shape[2]//3, 1],
                                      padding='SAME')
        print('SPP layer level 3:', spp_3_3_pool.get_shape().as_list())

        spp_2_2_pool = tf.nn.max_pool(input_,
                                      ksize=[1,
                                             np.ceil(shape[1]/2).astype(np.int32),
                                             np.ceil(shape[2]/2).astype(np.int32),
                                             1],
                                      strides=[1, shape[1]//2, shape[2]//2, 1],
                                      padding='SAME')
        print('SPP layer level 2:', spp_2_2_pool.get_shape().as_list())

        spp_1_1_pool = tf.nn.max_pool(input_,
                                      ksize=[1,
                                             np.ceil(shape[1]/1).astype(np.int32),
                                             np.ceil(shape[2]/1).astype(np.int32),
                                             1],
                                      strides=[1, shape[1]//1, shape[2]//1, 1],
                                      padding='SAME')
        print('SPP layer level 1:', spp_1_1_pool.get_shape().as_list())

        spp_6_6_pool_flat = tf.reshape(spp_6_6_pool, [shape[0], -1])
        spp_3_3_pool_flat = tf.reshape(spp_3_3_pool, [shape[0], -1])
        spp_2_2_pool_flat = tf.reshape(spp_2_2_pool, [shape[0], -1])
        spp_1_1_pool_flat = tf.reshape(spp_1_1_pool, [shape[0], -1])

        spp_pool = tf.concat(1, [spp_6_6_pool_flat,
                                 spp_3_3_pool_flat,
                                 spp_2_2_pool_flat,
                                 spp_1_1_pool_flat])

    return spp_pool
Run Code Online (Sandbox Code Playgroud)

但当输入大小不同时,它不能保证相同长度的池化输出。

如何解决这个问题呢?

小智 0

是的,现在的输出大小不是恒定的,并且查看您的代码,您的各个池操作似乎将具有在两个数字之间交替的输出大小。原因是输出大小(至少对于“SAME”而言)是通过以下公式计算的

out_height = ceil(float(in_height) / float(strides[1]))
Run Code Online (Sandbox Code Playgroud)

如果对于步幅,我们使用本质上是 in_height/n 的下限,那么输出将在 n 和 n+1 之间波动。为了确保稳定性,您需要做的是使用 ceil 操作来代替步幅值。spp_6_6 池的更改代码为

ksize=[1, np.ceil(shape[1]/6).astype(np.int32), np.ceil(shape[2]/6).astype(np.int32), 1]
spp_6_6_pool = tf.nn.max_pool(input_, ksize=ksize,strides=ksize, padding='SAME')
Run Code Online (Sandbox Code Playgroud)

为了清楚起见,我在 tf.nn.max_pool() 调用之外定义了 ksize。因此,如果您也使用 ksize 来确定步幅,那么应该会有效。如果您在数学上进行舍入,只要输入维度的大小至少是最大金字塔大小 n 的值的两倍,您的输出大小就应该与“相同”填充保持一致!

与您的问题有些相关,在您的第一个最大池操作中,您的 ksize 参数是

ksize=[1, np.ceil(shape[1]/6).astype(np.int32), np.ceil(shape[1]/6).astype(np.int32), 1]
Run Code Online (Sandbox Code Playgroud)

对于 ksize 的第三个元素,您使用了 shape[1]/6 而不是 shape[2]/6。我认为这是一个拼写错误,所以我在上面的代码中更改了它。

我知道在论文中,步幅被视为 a/n 的下限而不是 ceil,但到目前为止,使用张量流的本机池化操作,无法使其按预期工作。“有效”池不会产生任何接近您想要的结果。

好吧...如果您真的愿意投入时间,您可以将输入大小以最大金字塔尺寸(在本例中为 6)为模,并独立处理所有这六种情况。但我找不到一个很好的理由。Tensorflow 的 pad 与其他库(例如 Caffe)不同,因此本质上会存在差异。上述解决方案将为您提供他们在论文中的目标,即一个池化层的金字塔,其中图像的不相交区域以不同的粒度级别进行最大池化。

编辑:实际上,如果您使用 tf.pad() 自己手动填充输入并为每个最大池操作创建一个新输入,以便新输入的高度和宽度是 n 的整齐倍数,那么它将与您已经拥有的代码。