问题描述
我到处搜索,但是找不到任何东西。看起来太奇怪了,没人能遇到和我一样的问题……让我解释一下:
我已经训练了 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
接受ksize
和strides
参数的 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
其中
H
和W
分别是我输入图像的高度和宽度。
为此,我们可以实现一个简单的功能: 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)个特征来检索一个特征...
由于如果我采用这种方式,则在训练和推理之间存在差异,因此推断较大的图像可能会导致结果不一致 就我而言,方法是按我在第一篇文章中介绍的那样批处理图像...
希望对您有些帮助。