保存、序列化和导出模型

作者:Neel Kovelamudi,Francois Chollet

在 TensorFlow.org 上查看 在 Google Colab 中运行 在 GitHub 上查看源代码 在 keras.io 上查看

简介

Keras 模型包含多个组件

  • 架构或配置,指定模型包含哪些层以及它们如何连接。
  • 一组权重值(“模型状态”)。
  • 优化器(通过编译模型定义)。
  • 一组损失和指标(通过编译模型定义)。

Keras API 将所有这些部分保存在一个统一的格式中,以 .keras 扩展名标记。这是一个包含以下内容的 zip 存档

  • 基于 JSON 的配置文件 (config.json):模型、层和其他可跟踪对象的配置记录。
  • 基于 H5 的状态文件,例如 model.weights.h5(针对整个模型),带有层及其权重的目录键。
  • 一个 JSON 格式的元数据文件,存储诸如当前 Keras 版本之类的东西。

让我们看看它是如何工作的。

如何保存和加载模型

如果您只有 10 秒钟时间阅读本指南,那么您需要知道以下内容。

保存 Keras 模型

model = ...  # Get model (Sequential, Functional Model, or Model subclass)
model.save('path/to/location.keras')  # The file needs to end with the .keras extension

加载回模型

model = keras.models.load_model('path/to/location.keras')

现在,让我们看看详细信息。

设置

import numpy as np
import tensorflow as tf
import keras

保存

本节介绍如何将整个模型保存到单个文件中。该文件将包含

  • 模型的架构/配置
  • 模型的权重值(在训练期间学习到的)
  • 模型的编译信息(如果调用了 compile()
  • 优化器及其状态(如果有)(这使您能够从停止的地方重新开始训练)

API

您可以使用 model.save()keras.models.save_model()(等效)保存模型。您可以使用 keras.models.load_model() 加载它。

推荐的格式是“Keras v3”格式,它使用 .keras 扩展名。但是,还有两种可用的旧格式:TensorFlow SavedModel 格式和旧的 Keras H5 格式

您可以通过以下方式切换到 SavedModel 格式

  • save_format='tf' 传递给 save()
  • 传递没有扩展名的文件名

您可以通过以下方式切换到 H5 格式

  • save_format='h5' 传递给 save()
  • 传递以 .h5 结尾的文件名

示例

def get_model():
    # Create a simple model.
    inputs = keras.Input(shape=(32,))
    outputs = keras.layers.Dense(1)(inputs)
    model = keras.Model(inputs, outputs)
    model.compile(optimizer=keras.optimizers.Adam(), loss="mean_squared_error")
    return model


model = get_model()

# Train the model.
test_input = np.random.random((128, 32))
test_target = np.random.random((128, 1))
model.fit(test_input, test_target)

# Calling `save('my_model.keras')` creates a zip archive `my_model.keras`.
model.save("my_model.keras")

# It can be used to reconstruct the model identically.
reconstructed_model = keras.models.load_model("my_model.keras")

# Let's check:
np.testing.assert_allclose(
    model.predict(test_input), reconstructed_model.predict(test_input)
)
4/4 [==============================] - 2s 4ms/step - loss: 2.9952
4/4 [==============================] - 0s 2ms/step
4/4 [==============================] - 0s 1ms/step

自定义对象

本节介绍在 Keras 保存和重新加载中处理自定义层、函数和模型的基本工作流程。

保存包含自定义对象的模型(例如子类化的层)时,**必须**在对象类上定义 get_config() 方法。如果传递给自定义对象构造函数(__init__() 方法)的参数不是 Python 对象(任何非基本类型,如整数、字符串等),那么**必须**在 get_config() 方法中序列化这些参数,并在 from_config() 类方法中显式地反序列化这些参数。

例如

class CustomLayer(keras.layers.Layer):
    def __init__(self, sublayer, **kwargs):
        super().__init__(**kwargs)
        self.sublayer = sublayer

    def call(self, x):
        return self.sublayer(x)

    def get_config(self):
        base_config = super().get_config()
        config = {
            "sublayer": keras.saving.serialize_keras_object(self.sublayer),
        }
        return {**base_config, **config}

    @classmethod
    def from_config(cls, config):
        sublayer_config = config.pop("sublayer")
        sublayer = keras.saving.deserialize_keras_object(sublayer_config)
        return cls(sublayer, **config)

有关更多详细信息和示例,请参阅 定义配置方法部分

保存的 .keras 文件很轻量级,不存储自定义对象的 Python 代码。因此,要重新加载模型,load_model 需要通过以下方法之一访问任何使用过的自定义对象的定义

  1. 注册自定义对象(**首选**),
  2. 在加载时直接传递自定义对象,或
  3. 使用自定义对象范围

以下是每个工作流程的示例

注册自定义对象(**首选**)

这是首选方法,因为自定义对象注册极大地简化了保存和加载代码。将 @keras.saving.register_keras_serializable 装饰器添加到自定义对象的类定义中,会将该对象在主列表中全局注册,使 Keras 在加载模型时能够识别该对象。

让我们创建一个包含自定义层和自定义激活函数的自定义模型来演示这一点。

示例

# Clear all previously registered custom objects
keras.saving.get_custom_objects().clear()


# Upon registration, you can optionally specify a package or a name.
# If left blank, the package defaults to `Custom` and the name defaults to
# the class name.
@keras.saving.register_keras_serializable(package="MyLayers")
class CustomLayer(keras.layers.Layer):
    def __init__(self, factor):
        super().__init__()
        self.factor = factor

    def call(self, x):
        return x * self.factor

    def get_config(self):
        return {"factor": self.factor}


@keras.saving.register_keras_serializable(package="my_package", name="custom_fn")
def custom_fn(x):
    return x**2


# Create the model.
def get_model():
    inputs = keras.Input(shape=(4,))
    mid = CustomLayer(0.5)(inputs)
    outputs = keras.layers.Dense(1, activation=custom_fn)(mid)
    model = keras.Model(inputs, outputs)
    model.compile(optimizer="rmsprop", loss="mean_squared_error")
    return model


# Train the model.
def train_model(model):
    input = np.random.random((4, 4))
    target = np.random.random((4, 1))
    model.fit(input, target)
    return model


test_input = np.random.random((4, 4))
test_target = np.random.random((4, 1))

model = get_model()
model = train_model(model)
model.save("custom_model.keras")

# Now, we can simply load without worrying about our custom objects.
reconstructed_model = keras.models.load_model("custom_model.keras")

# Let's check:
np.testing.assert_allclose(
    model.predict(test_input), reconstructed_model.predict(test_input)
)
1/1 [==============================] - 0s 443ms/step - loss: 0.1362
1/1 [==============================] - 0s 62ms/step
1/1 [==============================] - 0s 59ms/step

将自定义对象传递给 load_model()

model = get_model()
model = train_model(model)

# Calling `save('my_model.keras')` creates a zip archive `my_model.keras`.
model.save("custom_model.keras")

# Upon loading, pass a dict containing the custom objects used in the
# `custom_objects` argument of `keras.models.load_model()`.
reconstructed_model = keras.models.load_model(
    "custom_model.keras",
    custom_objects={"CustomLayer": CustomLayer, "custom_fn": custom_fn},
)

# Let's check:
np.testing.assert_allclose(
    model.predict(test_input), reconstructed_model.predict(test_input)
)
1/1 [==============================] - 0s 364ms/step - loss: 0.2342
WARNING:tensorflow:5 out of the last 11 calls to <function Model.make_predict_function.<locals>.predict_function at 0x7f834c0913a0> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://tensorflowcn.cn/guide/function#controlling_retracing and https://tensorflowcn.cn/api_docs/python/tf/function for  more details.
1/1 [==============================] - 0s 62ms/step
WARNING:tensorflow:6 out of the last 12 calls to <function Model.make_predict_function.<locals>.predict_function at 0x7f82e07d1550> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://tensorflowcn.cn/guide/function#controlling_retracing and https://tensorflowcn.cn/api_docs/python/tf/function for  more details.
1/1 [==============================] - 0s 61ms/step

使用自定义对象范围

自定义对象范围内的任何代码都将能够识别传递给范围参数的自定义对象。因此,在范围内加载模型将允许加载我们的自定义对象。

示例

model = get_model()
model = train_model(model)
model.save("custom_model.keras")

# Pass the custom objects dictionary to a custom object scope and place
# the `keras.models.load_model()` call within the scope.
custom_objects = {"CustomLayer": CustomLayer, "custom_fn": custom_fn}

with keras.saving.custom_object_scope(custom_objects):
    reconstructed_model = keras.models.load_model("custom_model.keras")

# Let's check:
np.testing.assert_allclose(
    model.predict(test_input), reconstructed_model.predict(test_input)
)
1/1 [==============================] - 0s 371ms/step - loss: 0.2904
1/1 [==============================] - 0s 59ms/step
1/1 [==============================] - 0s 60ms/step

模型序列化

本节介绍仅保存模型配置,不保存其状态。模型配置(或架构)指定模型包含哪些层以及这些层如何连接。如果您有模型的配置,则可以使用新初始化的状态(没有权重或编译信息)创建模型。

API

以下序列化 API 可用

内存中模型克隆

您可以通过 keras.models.clone_model() 对模型进行内存中克隆。这等效于获取配置,然后从其配置重新创建模型(因此它不保留编译信息或层权重值)。

示例

new_model = keras.models.clone_model(model)

get_config()from_config()

调用 model.get_config()layer.get_config() 将返回一个 Python 字典,其中包含模型或层的配置。您应该定义 get_config() 以包含模型或层 __init__() 方法所需的参数。在加载时,from_config(config) 方法将使用这些参数调用 __init__() 来重建模型或层。

层示例

layer = keras.layers.Dense(3, activation="relu")
layer_config = layer.get_config()
print(layer_config)
{'name': 'dense_4', 'trainable': True, 'dtype': 'float32', 'units': 3, 'activation': 'relu', 'use_bias': True, 'kernel_initializer': {'module': 'keras.initializers', 'class_name': 'GlorotUniform', 'config': {'seed': None}, 'registered_name': None}, 'bias_initializer': {'module': 'keras.initializers', 'class_name': 'Zeros', 'config': {}, 'registered_name': None}, 'kernel_regularizer': None, 'bias_regularizer': None, 'activity_regularizer': None, 'kernel_constraint': None, 'bias_constraint': None}

现在让我们使用 from_config() 方法重建层

new_layer = keras.layers.Dense.from_config(layer_config)

顺序模型示例

model = keras.Sequential([keras.Input((32,)), keras.layers.Dense(1)])
config = model.get_config()
new_model = keras.Sequential.from_config(config)

函数式模型示例

inputs = keras.Input((32,))
outputs = keras.layers.Dense(1)(inputs)
model = keras.Model(inputs, outputs)
config = model.get_config()
new_model = keras.Model.from_config(config)

to_json()keras.models.model_from_json()

这类似于 get_config / from_config,不同之处在于它将模型转换为 JSON 字符串,然后可以在没有原始模型类的情况下加载。它也特定于模型,不适用于层。

示例

model = keras.Sequential([keras.Input((32,)), keras.layers.Dense(1)])
json_config = model.to_json()
new_model = keras.models.model_from_json(json_config)

任意对象序列化和反序列化

keras.saving.serialize_keras_object()keras.saving.deserialize_keras_object() API 是通用 API,可用于序列化或反序列化任何 Keras 对象和任何自定义对象。它是保存模型架构的基础,也是 keras 中所有 serialize()/deserialize() 调用的基础。

示例:

my_reg = keras.regularizers.L1(0.005)
config = keras.saving.serialize_keras_object(my_reg)
print(config)
{'module': 'keras.regularizers', 'class_name': 'L1', 'config': {'l1': 0.004999999888241291}, 'registered_name': None}

请注意序列化格式包含所有必要的信息以进行正确的重建

  • module 包含对象来自的 Keras 模块或其他识别模块的名称
  • class_name 包含对象类的名称。
  • config 包含重建对象所需的所有信息
  • registered_name 用于自定义对象。请参阅 此处

现在我们可以重建正则化器。

new_reg = keras.saving.deserialize_keras_object(config)

模型权重保存

您可以选择仅保存和加载模型的权重。这在以下情况下很有用

  • 您只需要模型进行推理:在这种情况下,您不需要重新开始训练,因此您不需要编译信息或优化器状态。
  • 您正在进行迁移学习:在这种情况下,您将训练一个新的模型,重用先前模型的状态,因此您不需要先前模型的编译信息。

内存中权重传输的 API

可以使用 get_weights()set_weights() 在不同的对象之间复制权重

示例

在内存中将权重从一层传输到另一层

def create_layer():
    layer = keras.layers.Dense(64, activation="relu", name="dense_2")
    layer.build((None, 784))
    return layer


layer_1 = create_layer()
layer_2 = create_layer()

# Copy weights from layer 1 to layer 2
layer_2.set_weights(layer_1.get_weights())

在内存中将权重从一个模型传输到具有兼容架构的另一个模型

# Create a simple functional model
inputs = keras.Input(shape=(784,), name="digits")
x = keras.layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = keras.layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = keras.layers.Dense(10, name="predictions")(x)
functional_model = keras.Model(inputs=inputs, outputs=outputs, name="3_layer_mlp")


# Define a subclassed model with the same architecture
class SubclassedModel(keras.Model):
    def __init__(self, output_dim, name=None):
        super().__init__(name=name)
        self.output_dim = output_dim
        self.dense_1 = keras.layers.Dense(64, activation="relu", name="dense_1")
        self.dense_2 = keras.layers.Dense(64, activation="relu", name="dense_2")
        self.dense_3 = keras.layers.Dense(output_dim, name="predictions")

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

    def get_config(self):
        return {"output_dim": self.output_dim, "name": self.name}


subclassed_model = SubclassedModel(10)
# Call the subclassed model once to create the weights.
subclassed_model(tf.ones((1, 784)))

# Copy weights from functional_model to subclassed_model.
subclassed_model.set_weights(functional_model.get_weights())

assert len(functional_model.weights) == len(subclassed_model.weights)
for a, b in zip(functional_model.weights, subclassed_model.weights):
    np.testing.assert_allclose(a.numpy(), b.numpy())

无状态层的案例

由于无状态层不会改变权重的顺序或数量,因此即使存在额外的/缺少的无状态层,模型也可以具有兼容的架构。

inputs = keras.Input(shape=(784,), name="digits")
x = keras.layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = keras.layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = keras.layers.Dense(10, name="predictions")(x)
functional_model = keras.Model(inputs=inputs, outputs=outputs, name="3_layer_mlp")

inputs = keras.Input(shape=(784,), name="digits")
x = keras.layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = keras.layers.Dense(64, activation="relu", name="dense_2")(x)

# Add a dropout layer, which does not contain any weights.
x = keras.layers.Dropout(0.5)(x)
outputs = keras.layers.Dense(10, name="predictions")(x)
functional_model_with_dropout = keras.Model(
    inputs=inputs, outputs=outputs, name="3_layer_mlp"
)

functional_model_with_dropout.set_weights(functional_model.get_weights())

将权重保存到磁盘并将其加载回的 API

可以通过调用 model.save_weights(filepath) 将权重保存到磁盘。文件名应以 .weights.h5 结尾。

示例

# Runnable example
sequential_model = keras.Sequential(
    [
        keras.Input(shape=(784,), name="digits"),
        keras.layers.Dense(64, activation="relu", name="dense_1"),
        keras.layers.Dense(64, activation="relu", name="dense_2"),
        keras.layers.Dense(10, name="predictions"),
    ]
)
sequential_model.save_weights("my_model.weights.h5")
sequential_model.load_weights("my_model.weights.h5")

请注意,更改 layer.trainable 可能会导致模型包含嵌套层时 layer.weights 顺序不同。

class NestedDenseLayer(keras.layers.Layer):
    def __init__(self, units, name=None):
        super().__init__(name=name)
        self.dense_1 = keras.layers.Dense(units, name="dense_1")
        self.dense_2 = keras.layers.Dense(units, name="dense_2")

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


nested_model = keras.Sequential([keras.Input((784,)), NestedDenseLayer(10, "nested")])
variable_names = [v.name for v in nested_model.weights]
print("variables: {}".format(variable_names))

print("\nChanging trainable status of one of the nested layers...")
nested_model.get_layer("nested").dense_1.trainable = False

variable_names_2 = [v.name for v in nested_model.weights]
print("\nvariables: {}".format(variable_names_2))
print("variable ordering changed:", variable_names != variable_names_2)
variables: ['nested/dense_1/kernel:0', 'nested/dense_1/bias:0', 'nested/dense_2/kernel:0', 'nested/dense_2/bias:0']

Changing trainable status of one of the nested layers...

variables: ['nested/dense_2/kernel:0', 'nested/dense_2/bias:0', 'nested/dense_1/kernel:0', 'nested/dense_1/bias:0']
variable ordering changed: True
迁移学习示例

从权重文件加载预训练权重时,建议将权重加载到原始检查点模型中,然后将所需的权重/层提取到新模型中。

示例

def create_functional_model():
    inputs = keras.Input(shape=(784,), name="digits")
    x = keras.layers.Dense(64, activation="relu", name="dense_1")(inputs)
    x = keras.layers.Dense(64, activation="relu", name="dense_2")(x)
    outputs = keras.layers.Dense(10, name="predictions")(x)
    return keras.Model(inputs=inputs, outputs=outputs, name="3_layer_mlp")


functional_model = create_functional_model()
functional_model.save_weights("pretrained.weights.h5")

# In a separate program:
pretrained_model = create_functional_model()
pretrained_model.load_weights("pretrained.weights.h5")

# Create a new model by extracting layers from the original model:
extracted_layers = pretrained_model.layers[:-1]
extracted_layers.append(keras.layers.Dense(5, name="dense_3"))
model = keras.Sequential(extracted_layers)
model.summary()
Model: "sequential_4"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_1 (Dense)             (None, 64)                50240     
                                                                 
 dense_2 (Dense)             (None, 64)                4160      
                                                                 
 dense_3 (Dense)             (None, 5)                 325       
                                                                 
=================================================================
Total params: 54725 (213.77 KB)
Trainable params: 54725 (213.77 KB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________

导出

Keras 还允许您为推理创建模型的轻量级版本,其中仅包含模型的前向传递(call() 方法)。然后,此 TensorFlow SavedModel 工件可以通过 TF-Serving 提供服务,并且不再需要模型的所有原始代码(包括自定义层)来重新加载工件 - 它完全是独立的。

API

  • model.export(),它将模型导出到用于推理的轻量级 SavedModel 工件
  • artifact.serve(),它调用导出工件的前向传递

用于自定义的低级 API

使用 .export() 进行简单导出

让我们通过一个使用函数式模型的 model.export() 的简单示例。

示例

inputs = keras.Input(shape=(16,))
x = keras.layers.Dense(8, activation="relu")(inputs)
x = keras.layers.BatchNormalization()(x)
outputs = keras.layers.Dense(1, activation="sigmoid")(x)
model = keras.Model(inputs, outputs)

input_data = np.random.random((8, 16))
output_data = model(input_data)  # **NOTE**: Make sure your model is built!

# Export the model as a SavedModel artifact in a filepath.
model.export("exported_model")

# Reload the SavedModel artifact
reloaded_artifact = tf.saved_model.load("exported_model")

# Use the `.serve()` endpoint to call the forward pass on the input data
new_output_data = reloaded_artifact.serve(input_data)
INFO:tensorflow:Assets written to: exported_model/assets
INFO:tensorflow:Assets written to: exported_model/assets
Saved artifact at 'exported_model'. The following endpoints are available:

* Endpoint 'serve'
  Args:
    args_0: float32 Tensor, shape=(None, 16)
  Returns:
    float32 Tensor, shape=(None, 1)

使用 ExportArchive 自定义导出工件

ExportArchive 对象允许您自定义导出模型并添加其他服务端点。以下是其关联的 API

  • track() 用于注册要使用的层或模型,
  • add_endpoint() 方法用于注册新的服务端点。
  • write_out() 方法用于保存工件。
  • add_variable_collection 方法用于注册一组要在重新加载后检索的变量。

默认情况下,model.export("path/to/location") 执行以下操作

export_archive = ExportArchive()
export_archive.track(model)
export_archive.add_endpoint(
    name="serve",
    fn=model.call,
input_signature=[tf.TensorSpec(shape=(None, 3), dtype=tf.float32)],  # `input_signature`
changes depending on model.
)
export_archive.write_out("path/to/location")

让我们看一个为 MultiHeadAttention 层自定义此操作的示例。

示例

layer = keras.layers.MultiHeadAttention(2, 2)
x1 = tf.random.normal((3, 2, 2))
x2 = tf.random.normal((3, 2, 2))
ref_output = layer(x1, x2).numpy()  # **NOTE**: Make sure layer is built!

export_archive = keras.export.ExportArchive()  # Instantiate ExportArchive object
export_archive.track(layer)  # Register the layer to be used
export_archive.add_endpoint(  # New endpoint `call` corresponding to `model.call`
    "call",
    layer.call,
    input_signature=[  # input signature corresponding to 2 inputs
        tf.TensorSpec(
            shape=(None, 2, 2),
            dtype=tf.float32,
        ),
        tf.TensorSpec(
            shape=(None, 2, 2),
            dtype=tf.float32,
        ),
    ],
)

# Register the layer weights as a set of variables to be retrieved
export_archive.add_variable_collection("my_vars", layer.weights)
np.testing.assert_equal(len(export_archive.my_vars), 8)
# weights corresponding to 2 inputs, each of which are 2*2

# Save the artifact
export_archive.write_out("exported_mha_layer")

# Reload the artifact
revived_layer = tf.saved_model.load("exported_mha_layer")
np.testing.assert_allclose(
    ref_output,
    revived_layer.call(query=x1, value=x2).numpy(),
    atol=1e-6,
)
np.testing.assert_equal(len(revived_layer.my_vars), 8)
INFO:tensorflow:Assets written to: exported_mha_layer/assets
INFO:tensorflow:Assets written to: exported_mha_layer/assets
Saved artifact at 'exported_mha_layer'. The following endpoints are available:

* Endpoint 'call'
  Args:
    query: float32 Tensor, shape=(None, 2, 2)
    value: float32 Tensor, shape=(None, 2, 2)
  Returns:
    float32 Tensor, shape=(None, 2, 2)

附录:处理自定义对象

定义配置方法

规范

  • get_config() 应返回一个 JSON 可序列化字典,以便与 Keras 架构和模型保存 API 兼容。
  • from_config(config)(一个 classmethod)应返回一个从配置创建的新层或模型对象。默认实现返回 cls(**config)

示例

@keras.saving.register_keras_serializable(package="MyLayers", name="KernelMult")
class MyDense(keras.layers.Layer):
    def __init__(
        self,
        units,
        *,
        kernel_regularizer=None,
        kernel_initializer=None,
        nested_model=None,
        **kwargs
    ):
        super().__init__(**kwargs)
        self.hidden_units = units
        self.kernel_regularizer = kernel_regularizer
        self.kernel_initializer = kernel_initializer
        self.nested_model = nested_model

    def get_config(self):
        config = super().get_config()
        # Update the config with the custom layer's parameters
        config.update(
            {
                "units": self.hidden_units,
                "kernel_regularizer": self.kernel_regularizer,
                "kernel_initializer": self.kernel_initializer,
                "nested_model": self.nested_model,
            }
        )
        return config

    def build(self, input_shape):
        input_units = input_shape[-1]
        self.kernel = self.add_weight(
            name="kernel",
            shape=(input_units, self.hidden_units),
            regularizer=self.kernel_regularizer,
            initializer=self.kernel_initializer,
        )

    def call(self, inputs):
        return tf.matmul(inputs, self.kernel)


layer = MyDense(units=16, kernel_regularizer="l1", kernel_initializer="ones")
layer3 = MyDense(units=64, nested_model=layer)

config = keras.layers.serialize(layer3)

print(config)

new_layer = keras.layers.deserialize(config)

print(new_layer)
{'module': None, 'class_name': 'MyDense', 'config': {'name': 'my_dense_1', 'trainable': True, 'dtype': 'float32', 'units': 64, 'kernel_regularizer': None, 'kernel_initializer': None, 'nested_model': {'module': None, 'class_name': 'MyDense', 'config': {'name': 'my_dense', 'trainable': True, 'dtype': 'float32', 'units': 16, 'kernel_regularizer': 'l1', 'kernel_initializer': 'ones', 'nested_model': None}, 'registered_name': 'MyLayers>KernelMult'} }, 'registered_name': 'MyLayers>KernelMult'}
<__main__.MyDense object at 0x7f82e060d460>

请注意,对于上面的 MyDense,不需要覆盖 from_config,因为 hidden_unitskernel_initializerkernel_regularizer 分别是整数、字符串和内置 Keras 对象。这意味着 from_config 的默认实现 cls(**config) 将按预期工作。

对于更复杂的对象(例如传递给 __init__ 的层和模型),例如,您必须显式地反序列化这些对象。让我们看一个需要覆盖 from_config 的模型示例。

示例:

@keras.saving.register_keras_serializable(package="ComplexModels")
class CustomModel(keras.layers.Layer):
    def __init__(self, first_layer, second_layer=None, **kwargs):
        super().__init__(**kwargs)
        self.first_layer = first_layer
        if second_layer is not None:
            self.second_layer = second_layer
        else:
            self.second_layer = keras.layers.Dense(8)

    def get_config(self):
        config = super().get_config()
        config.update(
            {
                "first_layer": self.first_layer,
                "second_layer": self.second_layer,
            }
        )
        return config

    @classmethod
    def from_config(cls, config):
        # Note that you can also use `keras.saving.deserialize_keras_object` here
        config["first_layer"] = keras.layers.deserialize(config["first_layer"])
        config["second_layer"] = keras.layers.deserialize(config["second_layer"])
        return cls(**config)

    def call(self, inputs):
        return self.first_layer(self.second_layer(inputs))


# Let's make our first layer the custom layer from the previous example (MyDense)
inputs = keras.Input((32,))
outputs = CustomModel(first_layer=layer)(inputs)
model = keras.Model(inputs, outputs)

config = model.get_config()
new_model = keras.Model.from_config(config)

自定义对象如何序列化

序列化格式为通过 @keras.saving.register_keras_serializable 注册的自定义对象提供了一个特殊键。这个 registered_name 键允许在加载/反序列化时轻松检索,同时还允许用户添加自定义命名。

让我们看一下序列化自定义层 MyDense 的配置,我们上面定义了它。

示例:

layer = MyDense(
    units=16,
    kernel_regularizer=keras.regularizers.L1L2(l1=1e-5, l2=1e-4),
    kernel_initializer="ones",
)
config = keras.layers.serialize(layer)
print(config)
{'module': None, 'class_name': 'MyDense', 'config': {'name': 'my_dense_2', 'trainable': True, 'dtype': 'float32', 'units': 16, 'kernel_regularizer': {'module': 'keras.regularizers', 'class_name': 'L1L2', 'config': {'l1': 9.999999747378752e-06, 'l2': 9.999999747378752e-05}, 'registered_name': None}, 'kernel_initializer': 'ones', 'nested_model': None}, 'registered_name': 'MyLayers>KernelMult'}

如所示,registered_name 键包含 Keras 主列表的查找信息,包括包 MyLayers 和我们使用 @keras.saving.register_keras_serializable 装饰器提供的自定义名称 KernelMult。再次查看自定义类定义/注册 这里

请注意,class_name 键包含类的原始名称,允许在 from_config 中正确地重新初始化。

此外,请注意,module 键为 None,因为这是一个自定义对象。