为什么保存并重新加载一维卷积 keras 模型会导致它无法泛化到更宽的窗口?

问题描述 投票:0回答:1

因此,我正在使用张量流提供的示例笔记本来详细说明时间序列格式数据的处理。

https://www.tensorflow.org/tutorials/structed_data/time_series

一切都很顺利,只是有一个关于保存和加载模型的快速问题。对于我当前的研究,我需要能够训练模型、保存模型,然后能够重新加载它以供稍后测试。

笔记本的完整代码可以在上面的链接中找到,但本质上训练和编译过程涉及以下方法,其中模型和窗口形状被输入

MAX_EPOCHS = 20

def compile_and_fit(model, window, patience=2):
  early_stopping = tf.keras.callbacks.EarlyStopping(monitor='val_loss',
                                                    patience=patience,
                                                    mode='min')

  model.compile(loss=tf.keras.losses.MeanSquaredError(),
                optimizer=tf.keras.optimizers.Adam(),
                metrics=[tf.keras.metrics.MeanAbsoluteError()])

  history = model.fit(window.train, epochs=MAX_EPOCHS,
                      validation_data=window.val,
                      callbacks=[early_stopping])
  return history

有问题的模型看起来像

conv_model = tf.keras.Sequential([
    tf.keras.layers.Conv1D(filters=32,
                           kernel_size=(CONV_WIDTH,),
                           activation='relu'),
    tf.keras.layers.Dense(units=32, activation='relu'),
    tf.keras.layers.Dense(units=1),
])

在笔记本中,这本质上是运行训练/编译方法并测试以查看其是否评估的过程

history = compile_and_fit(conv_model, conv_window)

IPython.display.clear_output()
val_performance['Conv'] = conv_model.evaluate(conv_window.val)
performance['Conv'] = conv_model.evaluate(conv_window.test, verbose=0)

此后,按照以下步骤在更宽的窗口上进行测试

wide_window = WindowGenerator(
    input_width=24, label_width=24, shift=1,
    label_columns=['T (degC)'])
print("Wide window")
print('Input shape:', wide_window.example[0].shape)
print('Labels shape:', wide_window.example[1].shape)
print('Output shape:', conv_model(wide_window.example[0]).shape)

这部分工作正常,但如果我添加两行来保存并重新加载模型,如图所示

history = compile_and_fit(conv_model, conv_window)
conv_model.save('test.keras')
conv_model = tf.keras.models.load_model('test.keras')
IPython.display.clear_output()
val_performance['Conv'] = conv_model.evaluate(conv_window.val)
performance['Conv'] = conv_model.evaluate(conv_window.test, verbose=0)

然后运行

print("Wide window")
print('Input shape:', wide_window.example[0].shape)
print('Labels shape:', wide_window.example[1].shape)
print('Output shape:', conv_model(wide_window.example[0]).shape)

我收到以下错误。

Wide window
Input shape: (32, 24, 19)
Labels shape: (32, 24, 1)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[58], line 4
      2 print('Input shape:', wide_window.example[0].shape)
      3 print('Labels shape:', wide_window.example[1].shape)
----> 4 print('Output shape:', conv_model(wide_window.example[0]).shape)

File ~/py38-env/lib/python3.8/site-packages/keras/src/utils/traceback_utils.py:70, in filter_traceback.<locals>.error_handler(*args, **kwargs)
     67     filtered_tb = _process_traceback_frames(e.__traceback__)
     68     # To get the full stack trace, call:
     69     # `tf.debugging.disable_traceback_filtering()`
---> 70     raise e.with_traceback(filtered_tb) from None
     71 finally:
     72     del filtered_tb

File ~/py38-env/lib/python3.8/site-packages/keras/src/engine/input_spec.py:298, in assert_input_compatibility(input_spec, inputs, layer_name)
    296 if spec_dim is not None and dim is not None:
    297     if spec_dim != dim:
--> 298         raise ValueError(
    299             f'Input {input_index} of layer "{layer_name}" is '
    300             "incompatible with the layer: "
    301             f"expected shape={spec.shape}, "
    302             f"found shape={display_shape(x.shape)}"
    303         )

ValueError: Input 0 of layer "sequential_3" is incompatible with the layer: expected shape=(None, 3, 19), found shape=(32, 24, 19)

当我使用 .h5 文件格式保存并重新加载时也会发生这种情况。即使我更改名称并重试,它仍然会引发错误。请注意,它训练的窗口大小是

CONV_WIDTH = 3
conv_window = WindowGenerator(
    input_width=CONV_WIDTH,
    label_width=1,
    shift=1,
    label_columns=['T (degC)'])

但它应该推广到更宽的窗口,并且在未保存和重新加载模型时确实如此。

任何有关为什么会发生这种情况的见解都将不胜感激,谢谢!

tensorflow keras model save
1个回答
0
投票

保存后,模型的

input_shape
好像不灵活了。
您可以使用
print(conv_model.input_shape)
查看预期的输入形状。

如果我们通过训练,我们可以看到 input_shape 如何变化。请注意,目前还没有保存:

conv_model = tf.keras.Sequential([...])

模型创建后,

conv_model
没有
.input_shape
,因为第一层(conv1d)没有
Input
层,也没有
input_shape=(...)
参数。该模型尚未获得数据,也不知道预期的输入形状。

print('Output shape:', conv_model(wide_window.example[0]).shape)

现在模型获得了数据,我们得到了

conv_model.input_shape=(32, 3, 19)
。这是一个非常明确的形状,因为通常第一个维度(批量维度)将是
None
,表示该维度的灵活形状。这是因为最后一批通常不能保证长度为 32(带有
batch_size=32
),但可能是数据的其余部分。

 history = compile_and_fit(conv_model, conv_window)

现在模型获取了具有不同批次长度的完整数据,我们得到了

conv_model.input_shape=(None, 3, 19)
。窗口长度和特征仍然是固定的,因为它们对于每个步骤都是相同的,但现在第一个维度的形状变得灵活。

print('Output shape:', conv_model(wide_window.example[0]).shape)

如果我们为模型提供

wide_window
作为输入,输入形状会再次发生变化:
conv_model.input_shape=(None, None, 19)
。现在时间轴也变得灵活了,因为之前的值 3 不再适合。请注意,这只有效,因为模型一开始就没有 input_shape。如果您向
tf.keras.layers.Input(3, 19)
模型添加
Sequential
层,则会出现与您的问题相同的错误。

当您在训练后保存并加载模型时,似乎形状

(None, 3, 19)
已修复,因为当您自己设置输入形状时,它会被修复,例如
Input
层。
加载模型和正常模型之间唯一(重要)的区别是
.input_spec
属性:

conv_model.input_spec = None
loaded_conv_model.input_spec = [InputSpec(shape=(None, 3, 19), ndim=3)]

如果将该属性设置回“无”(

loaded_conv_model.input_spec=None
),它现在可以再次使用灵活输入,但这看起来有点老套。如果您知道您将使用灵活的时间轴数据(并非所有序列的长度都相同),您可以直接在模型中设置它:

conv_model = tf.keras.Sequential([
    tf.keras.layers.Input((None, 19)),  # batch dimension gets omitted here
    ...  # rest of the model
])

现在模型获得了输入形状

conv_model.input_shape=(None, None, 19)
,并且可以适应不同大小的批次和窗口长度。

© www.soinside.com 2019 - 2024. All rights reserved.