根据我的理解,这四种方法:
predict
、predict_on_batch
、predict_step
,以及直接向前穿过模型(例如 model(x, training=False)
或 __call__()
)
都应该给出相同的结果,有些只是在处理批量数据与单个样本的方式上比其他更有效。
但是我实际上在我正在从事的图像超分辨率(升级)任务中得到了不同的结果:
for lowres, _ in val.take(1):
# Get a randomly cropped region of the lowres image for upscaling
lowres = tf.image.random_crop(lowres, (150, 150, 3)) # uint8
# Need to add a dummy batch dimension for the predict step
model_inputs = tf.expand_dims(lowres, axis=0) # (1, 150, 150, 3), uint8
# And convert the uint8 image values to float32 for input to the model
model_inputs = tf.cast(model_inputs, tf.float32) # float32
preds = model.predict_on_batch(model_inputs)
min_val = tf.reduce_min(preds).numpy()
max_val = tf.reduce_max(preds).numpy()
print("Min value: ", min_val)
print("Max value: ", max_val)
preds = model.predict(model_inputs)
min_val = tf.reduce_min(preds).numpy()
max_val = tf.reduce_max(preds).numpy()
print("Min value: ", min_val)
print("Max value: ", max_val)
preds = model.predict_step(model_inputs)
min_val = tf.reduce_min(preds).numpy()
max_val = tf.reduce_max(preds).numpy()
print("Min value: ", min_val)
print("Max value: ", max_val)
preds = model(model_inputs, training=False) # __call__()
min_val = tf.reduce_min(preds).numpy()
max_val = tf.reduce_max(preds).numpy()
print("Min value: ", min_val)
print("Max value: ", max_val)
打印:
Min value: -6003.622
Max value: 5802.6826
Min value: -6003.622
Max value: 5802.6826
Min value: -53.7696
Max value: 315.1499
Min value: -53.7696
Max value: 315.1499
predict_step
和__call__()
都给出了由放大图像定义的“正确”答案,看起来是正确的。
如果有帮助的话,我很乐意分享有关模型的更多详细信息,但现在我想我应该就此保留,以免问题变得过于复杂。起初我想知道这些方法是否根据训练/推理模式有不同的结果,但我的模型不使用任何
BatchNorm
或 Dropout
层,所以这在这里应该不会产生影响。它完全由:Conv2D
、Add
、tf.nn.depth_to_space
(像素随机播放)和 Rescaling
层组成。就是这样。它也不使用任何子类化或重写任何方法,仅使用 keras.Model(inputs, outputs)
。
你知道为什么这些预测方法会给出不同的答案吗?
编辑: 我已经能够创建一个最低限度可重现的示例,您可以在其中看到问题。请参阅:https://www.kaggle.com/code/quackaddict7/really-minimum-reproducible-example
我最初无法在最小的示例中重现该问题。我最终添加回数据集、批处理、数据增强、训练、模型文件保存/恢复,并最终发现问题是 GPU 与 CPU!所以我将所有这些都拿回来作为我的最小示例。如果您运行附带的笔记本,您将看到在 CPU 上,所有四种方法都给出相同的答案,并具有随机初始化的权重。但如果换成 P100 GPU,
predict
/predict_on_batch
与 predict_step
/前向传递 (__call__
) 不同。
所以我想此时我的问题是,为什么 CPU 与 GPU 结果不同?
我已经测试了
tf.keras==2.12.0
中给定的示例代码,发现 API 中可能存在错误,并且仅在 GPU 上失败。在您的示例代码中,由于 relu
激活而发生不匹配。如果我们设置其他任何内容,即 selu
或 elu
甚至 leaky_relu
,它们将按预期工作。
def ResBlock(inputs):
x = layers.Conv2D(64, 3, padding="same")(inputs)
x = layers.Conv2D(64, 3, padding="same")(x)
x = layers.Add()([inputs, x])
return x
为了继续使用
relu
方法,暂时可以采用以下修复。
def relu(x):
return keras.backend.maximum(x, 0)
def ResBlock(inputs):
x = layers.Conv2D(64, 3, padding="same")(inputs)
x = keras.layers.Lambda(relu, output_shape=lambda shape: shape)(x)
x = layers.Conv2D(64, 3, padding="same")(x)
x = layers.Add()([inputs, x])
return x
这里是完整的代码供参考。
型号
def relu(x):
return keras.backend.maximum(x, 0)
def ResBlock(inputs):
x = layers.Conv2D(64, 3, padding="same")(inputs)
x = keras.layers.Lambda(
relu, output_shape=lambda shape: shape
)(x)
x = layers.Conv2D(64, 3, padding="same")(x)
x = layers.Add()([inputs, x])
return x
def Upsampling(inputs, factor=2, **kwargs):
x = layers.Conv2D(
64 * (factor ** 2), 3, padding="same", **kwargs
)(inputs)
x = tf.nn.depth_to_space(x, block_size=factor)
x = layers.Conv2D(
64 * (factor ** 2), 3, padding="same", **kwargs
)(x)
x = tf.nn.depth_to_space(x, block_size=factor)
return x
def make_model(num_filters, num_of_residual_blocks):
input_layer = layers.Input(shape=(None, None, 3))
x = layers.Rescaling(scale=1.0 / 255)(input_layer)
x = x_new = layers.Conv2D(num_filters, 3, padding="same")(x)
for i in range(num_of_residual_blocks):
x_new = ResBlock(x_new)
x_new = layers.Conv2D(num_filters, 3, padding="same")(x_new)
x = layers.Add()([x, x_new])
x = Upsampling(x)
x = layers.Conv2D(3, 3, padding="same")(x)
output_layer = layers.Rescaling(scale=255)(x)
return keras.Model(input_layer, output_layer)
推理
lowres = tf.random.uniform(
shape=(150, 150, 3),
minval=0,
maxval=256,
dtype='float32'
)
model_inputs = tf.expand_dims(lowres, axis=0)
predict_out = model.predict(model_inputs)
predict_on_batch_out = model.predict_on_batch(model_inputs)
predict_call_out = model(model_inputs, training=False).numpy()
predict_step_out = model.predict_step(model_inputs).numpy()
print(
predict_out.shape,
predict_on_batch_out.shape,
predict_call_out.shape,
predict_step_out.shape
)
1/1 [==============================] - 1s 1s/step
(1, 600, 600, 3) (1, 600, 600, 3) (1, 600, 600, 3) (1, 600, 600, 3)
日志检查
# OK
np.testing.assert_allclose(
predict_out,
predict_on_batch_out,
1e-5, 1e-5
)
# OK
np.testing.assert_allclose(
predict_on_batch_out,
predict_call_out,
1e-5, 1e-5
)
# OK
np.testing.assert_allclose(
predict_call_out,
predict_step_out,
1e-5, 1e-5
)