在推断时更改保存的张量流模型输入形状

问题描述

我到处搜索,但是找不到任何东西。看起来太奇怪了,没人能遇到和我一样的问题……让我解释一下:

我已经训练了 Tensorflow 2 自定义模型。在训练期间,我使用了set_shape((None,320,14)),以便Tensorflow知道形状(由于任何原因都无法推断出它的形状--“)。 我还使用以下方法每100个时间段保存一次自定义模型

model.save(os.path.join('models','pb',FLAGS.task_name + '-%i' % epoch))

因此,在第100个时期,我将有一个文件models/pb/my_name-100,其中包含

  • 资产
  • 变量
  • saved_model.pb

现在,在推断时,我只想加载模型(没有所有代码)。因此,我创建了另一段仅加载模型并进行预测的代码...基本模板如下:

class NeuralNetwork:
    def __init__(self,model):
        self.model = tf.keras.models.load_model(model)

    def predict(self,input_tensor):
        pred = self.model(input_tensor[None,...])
        return pred[0]

其中input_tensor的大小(H,W,14),因此input_tensor[None,...]的大小: (没有,H,W,14)。

问题在于,因为我在训练期间将形状设置为(None,320,320,14)...这个愚蠢的Tensorflow期望输入为(None,320,320,14)-_- “ !!!。我的神经网络是一个完全卷积的神经网络,所以我真的不在乎输入形状。出于记忆原因,我在训练期间将其设置为(320,320,14)...

在预测过程中,我希望能够对任何形状进行预测。

很显然,我可以执行一个预处理功能,从输入图像中提取大小为(320、320)的色块并将其平铺。例如,我的input_tensor的大小可能是(30、320、320、14)

然后在进行预测之后,我可以从图块重建图像...但是我不想这样做。

  • 首先,因为创建图块并从图块重建图像需要花费一些时间
  • 第二,因为由于卷积中的填充为0,结果会有些偏离。这意味着我需要创建重叠的图块,并对重叠部分的结果取平均值,以避免在重建过程中出现伪像

所以我的问题很简单: 如何在推断时告诉tensorflow接受任何宽度和高度?天哪,好烦。我不敢相信没有简单的方法可以做到这一点

解决方法

我回答了我自己的问题。 不幸的是,我的回答不能使所有人满意。 TF中发生了很多令人费解的事情(更不用说在搜索帮助时,其中大多数与第一个API ... -_-“有关)。

无论如何,这是“解决方案”

在我的神经网络中,我实现了自定义层来模仿pytorch函数AdaptiveAvgPool2D。我的实现实际上使用了tf.nn_avg_pool,需要动态计算 kernel_size stride 。这是我的代码,仅供参考:

class AdaptiveAvgPool2d(layers.Layer):
    def __init__(self,output_shape,data_format='channels_last'):
        super(AdaptiveAvgPool2d,self).__init__(autocast=False)
        assert data_format in {'channels_last','channels_first'},\
            'data format parameter must be in {channels_last,channels_first}'

        if isinstance(output_shape,tuple):
            self.out_h = output_shape[0]
            self.out_w = output_shape[1]
        elif isinstance(output_shape,int):
            self.out_h = output_shape
            self.out_w = output_shape
        else:
            raise RuntimeError(f"""output_shape should be an Integer or a Tuple2""")

        self.data_format = data_format

    def call(self,inputs,mask=None):
        # input_shape = tf.shape(inputs)
        input_shape = inputs.get_shape().as_list()

        if self.data_format == 'channels_last':
            h_idx,w_idx = 1,2
        else: # can use else instead of elif due to assert in __init__
            h_idx,w_idx = 2,3

        stride_h = input_shape[h_idx] // self.out_h
        stride_w = input_shape[w_idx] // self.out_w

        k_size_h = stride_h + input_shape[h_idx] % self.out_h
        k_size_w = stride_w + input_shape[w_idx] % self.out_w

        pool = tf.nn.avg_pool(
            inputs,ksize=[k_size_h,k_size_w],strides=[stride_h,stride_w],padding='VALID',data_format='NHWC' if self.data_format == 'channels_last' else 'NCHW')

        return pool

问题在于,我正在使用inputs.get_shape().as_list() 恢复int值而不是Tensor(...,type=int)。实际上,tf.nn.avg_pool接受ksizestrides参数的 Int 列表...

换句话说,我不能使用tf.shape(inputs),因为它返回一个Tensor(...,type=int),并且没有办法通过评估它来从Tensor中恢复一个int ...

我实现函数的方式工作得很好,问题在于, Tensorflow 推断出幕后的大小,并保存.pb文件中所有张量的大小。我保存了。

实际上,您可以使用任何TextEditor(SublimeText)轻松打开.pb文件,并亲自查看预期的TensorShape。在我的情况下是`TensorShape: [null,320、320、14]

因此,使用set_shape((None,None,14))代替set_shape((None,320,14))nothing实际上并不能解决问题...

问题是平均池层不接受动态内核大小/步幅...。

然后我意识到实际上这个tfa.layers.AdaptiveAveragePooling2D有一个张量流函数。所以,我可能会选择它,这样会很好,对吧?

不完全是,这个tensorflow函数使用了其他tf.split这样的tf.function。 tf.split的问题在于,如果要分割的尺寸为X大小,并且要输出尺寸为Y的张量。如果X%Y!= 0,则tf.split会引发错误。 ..虽然Pytorch的功能更强大,并且处理案例为X%Y!= 0。

以不同的方式放置它,这意味着,为了让我使用tfa.layers.AdaptiveAveragePooling2D,我需要确保此函数接收的张量可以被传递给该函数的标量整除。 / p>

例如,就我而言, 输入图像的大小为:(320、320,任意大小),tfa.layers.AdaptiveAveragePooling2D接收到的输入张量为:(40、40,任意大小)。

这意味着,在训练期间,我的张量的空间尺寸被8除。为了使其正常工作,我应该选择可以除以40的尺寸。假设我选择 5

这意味着在预测期间,如果tfa.layers.AdaptiveAveragePooling2D收到的输入维度也可以被 5 整除,那么我的神经网络将起作用。但是我们已经知道我的输入图像比张量接收到的tfa.layers.AdaptiveAveragePooling2D大8倍,所以这意味着在预测时,我可以使用任何图像大小,只要:

  • H%(8 * 5)== 0和W%(8 * 5)== 0 其中HW分别是我输入图像的高度和宽度。

为此,我们可以实现一个简单的功能: new_W = W + W%40(在此示例中为40 ...) new_H = H + H%40(在此示例中为40 ...)

此功能将拉伸图像但不会拉伸太多,因此应该就可以了。

总结:

  • 我的AdaptivePooling使用静态形状,但是我不能这样做,因为它在不接受动态形状的引擎盖下使用tf.nn.avg_pool
  • tfa.layers.AdaptiveAveragePooling2D可以解决,但是因为它依赖于tf.split,它对不精确的划分并不可靠,所以它也不是完美的
  • 基本解决方案是在调用预测之前使用tfa.layers.AdaptiveAveragePooling2D并创建一个预处理函数,以使张量在tf.split约束下可以正常工作
  • 最后,这也不是一个好的解决方案。因为,在训练期间,如果我收到大小为(40,40)的张量,并希望得到大小为(5,5) avg 输出,则意味着我基本上对(8,8)个特征取平均值一个功能。
  • 问题在于,如果我在推断时间内对较大的图像执行此操作,则将获得较大的张量。假设:(100,200)。但是由于我的输出将始终为(5,5),这意味着我这次将平均(20,40)个特征来检索一个特征...

由于如果我采用这种方式,则在训练和推理之间存在差异,因此推断较大的图像可能会导致结果不一致 就我而言,方法是按我在第一篇文章中介绍的那样批处理图像...

希望对您有些帮助。