作者: Neel Kovelamudi, Francois Chollet
创建日期: 2023/06/14
最后修改日期: 2023/06/30
描述: 保存、序列化和导出模型的完整指南。
Keras 模型由多个组件组成:
Keras API 将所有这些部分保存为统一格式,以 .keras
擴展名标记。这是一个包含以下内容的 zip 存档:
model.weights.h5
(整个模型),带有层及其权重的目录键。让我们看看这如何运作。
如果你只有 10 秒钟来阅读本指南,以下是你需要了解的内容。
保存 Keras 模型:
model = ... # 获取模型(顺序模型、功能模型或模型子类)
model.save('path/to/location.keras') # 文件名需以 .keras 结尾
加载模型:
model = keras.models.load_model('path/to/location.keras')
现在,让我们看看细节。
import numpy as np
import keras
from keras import ops
本节介绍如何将整个模型保存到一个文件中。该文件将包括:
compile()
)你可以使用 model.save()
或 keras.models.save_model()
(等效)来保存模型。
你可以使用 keras.models.load_model()
将其加载回来。
Keras 3 中唯一支持的格式是“ Keras v3”格式,
它使用 .keras
扩展名。
示例:
def get_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()
# 训练模型。
test_input = np.random.random((128, 32))
test_target = np.random.random((128, 1))
model.fit(test_input, test_target)
# 调用 `save('my_model.keras')` 创建一个 zip 存档 `my_model.keras`。
model.save("my_model.keras")
# 它可以用于以相同的方式重建模型。
reconstructed_model = keras.models.load_model("my_model.keras")
# 让我们检查一下:
np.testing.assert_allclose(
model.predict(test_input), reconstructed_model.predict(test_input)
)
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - loss: 0.4232
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 281us/step
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 373us/step
本节覆盖在 Keras 保存和重新加载中处理自定义层、函数和模型的基本工作流程。
当保存包含自定义对象(例如子类化层)的模型时,必须在对象类上定义 get_config()
方法。
如果传递给自定义对象构造函数(__init__()
方法)的参数不是 Python 对象(如整型、字符串等基本类型以外的任何内容),那么 还必须在 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
需要通过以下方法之一来访问任何自定义对象的定义:
以下是每种工作流的示例:
这是首选的方法,因为自定义对象注册极大简化了保存和加载代码。将 @keras.saving.register_keras_serializable
装饰器添加到自定义对象的类定义上会在主列表中全局注册该对象,从而允许 Keras 在加载模型时识别该对象。
让我们创建一个涉及自定义层和自定义激活函数的自定义模型来演示这一点。
示例:
# 清除所有先前注册的自定义对象
keras.saving.get_custom_objects().clear()
# 注册时,可以选择性地指定包或名称。
# 如果留空,包默认设置为 `Custom`,名称默认设置为
# 类名。
@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
# 创建模型。
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
# 训练模型。
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")
# 现在,我们可以简单地加载,而不必担心我们的自定义对象。
reconstructed_model = keras.models.load_model("custom_model.keras")
# 让我们检查:
np.testing.assert_allclose(
model.predict(test_input), reconstructed_model.predict(test_input)
)
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 46ms/step - loss: 0.2571
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step
load_model()
model = get_model()
model = train_model(model)
# 调用 `save('my_model.keras')` 创建一个压缩包 `my_model.keras`。
model.save("custom_model.keras")
# 加载时,将包含自定义对象的字典传递给
# `keras.models.load_model()` 的 `custom_objects` 参数。
reconstructed_model = keras.models.load_model(
"custom_model.keras",
custom_objects={"CustomLayer": CustomLayer, "custom_fn": custom_fn},
)
# 让我们检查:
np.testing.assert_allclose(
model.predict(test_input), reconstructed_model.predict(test_input)
)
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 37ms/step - loss: 0.0535
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step
在自定义对象范围内的任何代码都能够识别传递给范围参数的自定义对象。因此,在范围内加载模型将允许加载我们的自定义对象。
示例:
model = get_model()
model = train_model(model)
model.save("custom_model.keras")
# 将自定义对象字典传递给自定义对象范围,并将
# `keras.models.load_model()` 调用放置在范围内。
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")
# 让我们检查:
np.testing.assert_allclose(
model.predict(test_input), reconstructed_model.predict(test_input)
)
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 40ms/step - loss: 0.0868
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step
本节仅介绍保存模型的配置,而不包括其状态。 模型的配置(或架构)指定模型包含哪些层,以及这些层如何连接。如果您有模型的配置,则可以创建具有新初始化状态(无权重或编译信息)的模型。
以下序列化 API 可用:
keras.models.clone_model(model)
:制作一个(随机初始化的)模型副本。get_config()
和 cls.from_config()
:分别用于检索层或模型的配置,并根据配置重新创建模型实例。keras.models.model_to_json()
和 keras.models.model_from_json()
:类似,但以 JSON 字符串的形式。keras.saving.serialize_keras_object()
:检索任意 Keras 对象的配置。keras.saving.deserialize_keras_object()
:根据配置重新创建对象实例。您可以通过 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.src.initializers.random_initializers', 'class_name': 'GlorotUniform', 'config': {'seed': None}, 'registered_name': 'GlorotUniform'}, 'bias_initializer': {'module': 'keras.src.initializers.constant_initializers', 'class_name': 'Zeros', 'config': {}, 'registered_name': 'Zeros'}, 'kernel_regularizer': None, 'bias_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 对象和任何自定义对象。它是保存模型架构的基础,也是所有 serialize()
/deserialize()
调用的基础。
示例:
my_reg = keras.regularizers.L1(0.005)
config = keras.saving.serialize_keras_object(my_reg)
print(config)
{'module': 'keras.src.regularizers.regularizers', 'class_name': 'L1', 'config': {'l1': 0.004999999888241291}, 'registered_name': 'L1'}
请注意序列化格式包含所有必要的信息以便于正确重建:
module
包含 Keras 模块或其他标识模块的名称。class_name
包含对象类的名称。config
包含重建对象所需的所有信息。registered_name
用于自定义对象。请参见 这里。现在我们可以重建正则化器。
new_reg = keras.saving.deserialize_keras_object(config)
您可以选择仅保存和加载模型的权重。如果您只需要模型进行推理,这可能会很有用:在这种情况下,您不需要重新开始训练,因此不需要编译信息或优化器状态。您正在进行迁移学习:在这种情况下,您将训练一个新模型,重新使用先前模型的状态,因此您不需要先前模型的编译信息。
可以通过使用 get_weights()
和 set_weights()
在不同对象之间复制权重:
keras.layers.Layer.get_weights()
:返回权重值的 NumPy 数组列表。keras.layers.Layer.set_weights(weights)
:将模型权重设置为提供的值(作为 NumPy 数组)。例子: 将权重从一个层转移到另一个层,内存中
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()
# 从层1复制权重到层2
layer_2.set_weights(layer_1.get_weights())
将权重从一个模型转移到另一个具有兼容架构的模型,内存中
# 创建一个简单的功能模型
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")
# 定义一个具有相同架构的子类模型
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)
# 调用该子类模型一次以创建权重。
subclassed_model(np.ones((1, 784)))
# 从functional_model复制权重到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)
# 添加一个 dropout 层,它不包含任何权重。
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())
可以通过调用 model.save_weights(filepath)
将权重保存到磁盘。
文件名应以 .weights.h5
结尾。
示例:
# 可运行示例
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("\n改变嵌套层的可训练状态...")
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_names != variable_names_2)
variables: ['kernel', 'bias', 'kernel', 'bias']
改变嵌套层的可训练状态...
variables: ['kernel', 'bias', 'kernel', 'bias']
变量顺序改变: False
当从权重文件加载预训练权重时,建议将权重加载到原始检查点模型中,然后将所需的权重/层提取到新模型中。
示例:
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")
# 在单独的程序中:
pretrained_model = create_functional_model()
pretrained_model.load_weights("pretrained.weights.h5")
# 通过从原始模型提取层创建新模型:
extracted_layers = pretrained_model.layers[:-1]
extracted_layers.append(keras.layers.Dense(5, name="dense_3"))
model = keras.Sequential(extracted_layers)
model.summary()
模型: "sequential_4"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓ ┃ 层 (类型) ┃ 输出形状 ┃ 参数 # ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩ │ dense_1 (Dense) │ (None, 64) │ 50,240 │ ├─────────────────────────────────┼───────────────────────────┼────────────┤ │ dense_2 (Dense) │ (None, 64) │ 4,160 │ ├─────────────────────────────────┼───────────────────────────┼────────────┤ │ dense_3 (Dense) │ (None, 5) │ 325 │ └─────────────────────────────────┴───────────────────────────┴────────────┘
总参数: 54,725 (213.77 KB)
可训练参数: 54,725 (213.77 KB)
非可训练参数: 0 (0.00 B)
规格:
get_config()
应返回一个可以 JSON 串行化的字典,以便与 Keras 架构和模型保存 API 兼容。from_config(config)
(一个 classmethod
) 应返回一个从配置创建的新层或模型对象。
默认实现返回 cls(**config)
。注意:如果所有构造函数参数已经是可串行化的,例如字符串和整型,或者非自定义 Keras 对象,则不需要重写 from_config
。但是,对于更复杂的对象,例如传递给 __init__
的层或模型,必须在 __init__
本身中显式处理反序列化或重写 from_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()
# 用自定义层的参数更新配置
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 ops.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'}
<MyDense name=my_dense_1, built=False>
注意,以上对于MyDense
重写from_config
是没有必要的,因为
hidden_units
、kernel_initializer
和kernel_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):
# 注意,您也可以在这里使用[`keras.saving.deserialize_keras_object`](/api/models/model_saving_apis/serialization_utils#deserializekerasobject-function)
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))
# 让我们的第一层成为上一个示例中的自定义层(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.src.regularizers.regularizers', 'class_name': 'L1L2', 'config': {'l1': 1e-05, 'l2': 0.0001}, 'registered_name': 'L1L2'}, '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
,因为这是一个自定义对象。