【问题标题】:Can't save custom subclassed model无法保存自定义子类模型
【发布时间】:2018-08-12 07:53:41
【问题描述】:

tf.keras.Model subclassing 的启发,我创建了自定义模型。
我可以训练它并获得成功的结果,但是我无法保存它
我使用 python3.6 和 tensorflow v1.10(或 v1.9)

这里是最小的完整代码示例:

import tensorflow as tf
from tensorflow.keras.datasets import mnist


class Classifier(tf.keras.Model):
    def __init__(self):
        super().__init__(name="custom_model")

        self.batch_norm1 = tf.layers.BatchNormalization()
        self.conv1 = tf.layers.Conv2D(32, (7, 7))
        self.pool1 = tf.layers.MaxPooling2D((2, 2), (2, 2))

        self.batch_norm2 = tf.layers.BatchNormalization()
        self.conv2 = tf.layers.Conv2D(64, (5, 5))
        self.pool2 = tf.layers.MaxPooling2D((2, 2), (2, 2))

    def call(self, inputs, training=None, mask=None):
        x = self.batch_norm1(inputs)
        x = self.conv1(x)
        x = tf.nn.relu(x)
        x = self.pool1(x)

        x = self.batch_norm2(x)
        x = self.conv2(x)
        x = tf.nn.relu(x)
        x = self.pool2(x)

        return x


if __name__ == '__main__':
    (x_train, y_train), (x_test, y_test) = mnist.load_data()

    x_train = x_train.reshape(*x_train.shape, 1)[:1000]
    y_train = y_train.reshape(*y_train.shape, 1)[:1000]

    x_test = x_test.reshape(*x_test.shape, 1)
    y_test = y_test.reshape(*y_test.shape, 1)

    y_train = tf.keras.utils.to_categorical(y_train)
    y_test = tf.keras.utils.to_categorical(y_test)

    model = Classifier()

    inputs = tf.keras.Input((28, 28, 1))

    x = model(inputs)
    x = tf.keras.layers.Flatten()(x)
    x = tf.keras.layers.Dense(10, activation="sigmoid")(x)

    model = tf.keras.Model(inputs=inputs, outputs=x)
    model.compile(optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"])
    model.fit(x_train, y_train, epochs=1, shuffle=True)

    model.save("./my_model")

错误信息:

1000/1000 [==============================] - 1s 1ms/step - loss: 4.6037 - acc: 0.7025
Traceback (most recent call last):
  File "/home/user/Data/test/python/mnist/mnist_run.py", line 62, in <module>
    model.save("./my_model")
  File "/home/user/miniconda3/envs/ml3.6/lib/python3.6/site-packages/tensorflow/python/keras/engine/network.py", line 1278, in save
    save_model(self, filepath, overwrite, include_optimizer)
  File "/home/user/miniconda3/envs/ml3.6/lib/python3.6/site-packages/tensorflow/python/keras/engine/saving.py", line 101, in save_model
    'config': model.get_config()
  File "/home/user/miniconda3/envs/ml3.6/lib/python3.6/site-packages/tensorflow/python/keras/engine/network.py", line 1049, in get_config
    layer_config = layer.get_config()
  File "/home/user/miniconda3/envs/ml3.6/lib/python3.6/site-packages/tensorflow/python/keras/engine/network.py", line 1028, in get_config
    raise NotImplementedError
NotImplementedError

Process finished with exit code 1

我查看了错误行,发现 get_config 方法检查 self._is_graph_network

有人处理这个问题吗?

谢谢!

更新 1:
在 keras 2.2.2 上(不是 tf.keras)
找到评论(用于模型保存)
文件:keras/engine/network.py
函数:get_config

# 子类网络不可序列化
# (除非序列化由
实现 # 子类网络的作者)。

所以,显然它不会工作......
我想知道,他们为什么不在documentation 中指出这一点(例如:“使用没有保存能力的子类!”)

更新 2:
发现于keras documentation

在子类模型中,模型的拓扑定义为 Python 代码
(而不是作为图层的静态图)。这意味着模型的
无法检查或序列化拓扑。结果,以下
方法和属性不适用于子类模型:

model.inputs 和 model.outputs。
model.to_yaml() 和 model.to_json()
model.get_config() 和 model.save()。

因此,没有办法通过使用子类来保存模型。
只能使用Model.save_weights()

【问题讨论】:

  • 子类模型不能序列化的原因是keras需要跟踪每个张量的历史以确定图的结构,每个张量应该输出tf.keras.layers.Layer,但是,子类化模型在其call 方法中包含像tf.nn.relu 这样的幼稚tensorflow 操作,因此无法序列化
  • 那么,如果我在里面只使用tf.keras呢?答:不行
  • 我的建议是,如果你真的想要子类模型,那么忘记Model.save,使用Model.save_weights只保存模型的权重,用Model.load_weights加载权重,否则如果你仍然想要保存整个模型而不是只保存权重,你必须遵循 keras 的功能 api 指南
  • 哦,真的很有帮助。 Model.save_weights 工作正常。非常感谢!

标签: python-3.x tensorflow keras


【解决方案1】:

TensorFlow 2.2

感谢@cal 提醒我新的 TensorFlow 已支持保存自定义模型!

使用 model.save 保存整个模型并使用 load_model 恢复以前存储的子类模型。以下代码 sn-ps 描述了如何实现它们。

class ThreeLayerMLP(keras.Model):

  def __init__(self, name=None):
    super(ThreeLayerMLP, self).__init__(name=name)
    self.dense_1 = layers.Dense(64, activation='relu', name='dense_1')
    self.dense_2 = layers.Dense(64, activation='relu', name='dense_2')
    self.pred_layer = layers.Dense(10, name='predictions')

  def call(self, inputs):
    x = self.dense_1(inputs)
    x = self.dense_2(x)
    return self.pred_layer(x)

def get_model():
  return ThreeLayerMLP(name='3_layer_mlp')

model = get_model()
# Save the model
model.save('path_to_my_model',save_format='tf')

# Recreate the exact same model purely from the file
new_model = keras.models.load_model('path_to_my_model')

见:Save and serialize models with Keras - Part II: Saving and Loading of Subclassed Models

TensorFlow 2.0

TL;DR:

  1. 不要将model.save() 用于自定义子类 keras 模型;
  2. 请改用save_weights()load_weights()

在 Tensorflow 团队的帮助下,保存自定义子类 Keras 模型的最佳实践是保存其权重并在需要时将其加载回来。

我们不能简单保存一个 Keras 自定义子类模型的原因是它包含自定义代码,无法安全序列化。但是,当我们具有相同的模型结构和自定义代码时,可以保存/加载权重没有任何问题。

Keras 的作者 Francois Chollet 写了一篇很棒的教程,关于如何在 Colab 中的 Tensorflow 2.0 中保存/加载顺序/功能/Keras/自定义子类模型here。在保存子类模型部分,它说:

顺序模型和功能模型是表示 DAG 层的数据结构。因此,它们可以安全地序列化和反序列化。

子类模型的不同之处在于它不是数据结构,而是 一段代码。模型的架构是通过主体定义的 的调用方法。这意味着模型的架构 无法安全地序列化。要加载模型,您需要拥有 访问创建它的代码(模型子类的代码)。 或者,您可以将此代码序列化为字节码(例如 通过酸洗),但这是不安全的,通常不便携。

【讨论】:

  • 请注意,通过将权重和架构彼此分开保存,您会丢失使用 model.save() 保存的编译信息(优化器和损失)
  • 看来您现在可以根据tensorflow's documentation 使用自定义代码保存完整模型。您的答案可以编辑:)
  • TensorFlow 2.2 的附加说明:您必须先致电model._set_inputs,然后才能使用。
  • @Huan,我用model.save("NameOfModel", save_format='tf')保存,但是用loaded_model = keras.models.load_model('./NameOfModel')加载模型后,我得到ValueError Python inputs incompatible with input_signature: inputs: ( Tensor("IteratorGetNext:0", shape=(None, 2), dtype=int32)) input_signature: ( TensorSpec(shape=(None, 2), dtype=tf.int64, name='input_1'))
  • 感谢您的回答。但它也适用于 RNN 吗?因为一旦在我的用例中包含self.cell = layers.SimpleRNNCell(10) self.rnn = layers.RNN(self.cell, return_sequences=True, return_state=False),我就无法使用save_model
【解决方案2】:

根据1.13 pre-release patch notes,这将在即将发布的版本中修复:

  • Keras 和 Python API:
    • 现在可以通过tf.contrib.saved_model.save_keras_model 保存子类 Keras 模型。

编辑: 看起来这并不像笔记所暗示的那样完成。 docs for that function for v1.13 状态:

模型限制: - 顺序和功能模型始终可以保存。 - 子类模型只能在 serving_only=True 时保存。这是由于当前实现复制模型以导出训练和评估图。由于无法确定子类模型的拓扑结构,因此无法克隆子类模型。未来子类模型将完全可导出。

【讨论】:

    【解决方案3】:

    Tensorflow 2.1 允许以 SavedModel 格式保存子类模型

    从我开始使用 Tensorflow 开始,我就一直是 Model Subclass 的粉丝,我觉得这种构建模型的方式更加 Python 和协作友好。但是使用这种方法保存模型始终是一个痛点。

    最近我开始更新我的知识并接触到以下information,这对于 Tensorflow 2.1 来说似乎是正确的:

    子类模型

    我找到this

    第二种方法是使用 model.save 来保存整个模型并通过 使用 load_model 恢复之前存储的子类模型。

    最后将模型、重量和其他内容保存到 SavedModel 文件中

    最后是confirmation

    保存自定义对象: 如果您使用 SavedModel 格式,您可以 跳过本节。 HDF5 和 SavedModel 之间的主要区别是 HDF5 使用对象配置来保存模型架构,而 SavedModel 保存执行图。因此,SavedModels 能够 保存自定义对象,如子类模型和自定义层,而不保存 需要原始代码。

    我亲自对此进行了测试,并且有效地,子类模型的 model.save() 生成了 SavedModel 保存。不再需要使用 model.save_weights() 或相关函数,它们现在更多用于特定用例。

    对于我们所有对模型子类化感兴趣的人来说,这应该是这条痛苦道路的终结。

    【讨论】:

      【解决方案4】:

      我找到了解决它的方法。创建一个新模型并从保存的 .h5 模型中加载权重。这种方式不是首选,但它适用于 keras 2.2.4 和 tensorflow 1.12。

      class MyModel(keras.Model):  
           def __init__(self, inputs, *args, **kwargs):
                outputs = func(inputs)
           super(MyModel, self).__init__( inputs=inputs, outputs=outputs, *args, **kwargs)
      
      def get_model():  
          return MyModel(inputs, *args, **kwargs)
      
      model = get_model()
      model.save(‘file_path.h5’)
      
      model_new = get_model()  
      model_new.compile(optimizer=optimizer, loss=loss, metrics=metrics) 
      model_new.load_weights(‘file_path.h5’)  
      model_new.evaluate(x_test, y_test, **kwargs)
      

      【讨论】:

        【解决方案5】:

        更新:7 月 20 日

        最近我还尝试创建子类层和模型。编写自己的 get_config() 函数可能很困难。所以我使用了model.save_weights(path_to_model_weights)model.load_weights(path_to_model_weights)。当您想要加载权重时,请记住创建具有与 model.load_weights() 相同架构的模型。有关详细信息,请参阅 tensorflow guide

        旧答案(仍然正确) 实际上,tensorflow 文档中说:

        为了保存/加载具有自定义层的模型或子类模型,您应该覆盖 get_config 和可选的 from_config 方法。此外,您应该使用注册自定义对象,以便 Keras 知道它。

        例如:

        class Linear(keras.layers.Layer):
            def __init__(self, units=32, **kwargs):
                super(Linear, self).__init__(**kwargs)
                self.units = units
        
            def build(self, input_shape):
                self.w = self.add_weight(
                    shape=(input_shape[-1], self.units),
                    initializer="random_normal",
                    trainable=True,
                )
                self.b = self.add_weight(
                    shape=(self.units,), initializer="random_normal", trainable=True
                )
        
            def call(self, inputs):
                return tf.matmul(inputs, self.w) + self.b
        
            def get_config(self):
                config = super(Linear, self).get_config()
                config.update({"units": self.units})
                return config
        
        
        layer = Linear(64)
        config = layer.get_config()
        print(config)
        new_layer = Linear.from_config(config)
        

        输出是:

        {'name': 'linear_8', 'trainable': True, 'dtype': 'float32', 'units': 64}
        

        您可以使用这个简单的代码。例如,在函数“get_config()”中,删除 config.update(),看看发生了什么。有关详细信息,请参阅 thisthis。这些是 tensorflow 网站上的 Keras 指南。

        【讨论】:

        • 您写的答案是针对自定义层的,不适用于问题中已经提出的子类模型
        【解决方案6】:

        tf.saved_model.save之前使用model.predict

        【讨论】:

        • 这与提出的问题无关。预测不会做任何事情
        【解决方案7】:

        实际上是用

        重新创建模型
        keras.models.load_model('path_to_my_model')
        

        不适合我

        首先我们必须从构建的模型中保存_weights

        model.save_weights('model_weights', save_format='tf')
        

        然后 我们必须为子类 Model 启动一个新实例,然后用一个记录和构建模型的 load_weights 编译和 train_on_batch

        loaded_model = ThreeLayerMLP(name='3_layer_mlp')
        loaded_model.compile(optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"])
        loaded_model.train_on_batch(x_train[:1], y_train[:1])
        loaded_model.load_weights('model_weights')
        

        这在 TensorFlow==2.2.0 中完美运行

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2023-01-03
          • 1970-01-01
          • 2020-05-11
          • 2019-10-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多