Keras深度学习框架第九讲:保存、序列化以及导出模型

64 篇文章 0 订阅
48 篇文章 0 订阅

1、绪论

1.1 保存、序列化以及导出模型的概念

  • 保存 (Save): 将模型保存到磁盘或其他存储介质中,以便将来使用。
  • 序列化 (Serialize): 将模型(通常是一个复杂的对象或数据结构)转换为一个可以存储或传输的格式(如JSON、XML、二进制等)。序列化是保存模型的关键步骤,因为它将模型转换为一个可以持久化或在网络中传输的格式。
  • 导出 (Export): 将模型从一种格式或系统转换为另一种格式或系统,以便在其他环境或工具中使用。例如,将TensorFlow模型导出为ONNX格式,以便在PyTorch或其他框架中使用。

在机器学习和深度学习中,这三个步骤通常是保存和重用模型的重要流程。

1.2 Keras 的组建及存储

Keras模型由多个组件组成:

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

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

  • 基于JSON的配置文件(config.json):记录模型、层和其他可追踪对象的配置。
  • 基于H5的状态文件,例如model.weights.h5(对于整个模型),包含层的目录键及其权重。
  • JSON格式的元数据文件,存储如当前Keras版本等信息。

2、存储和加载模型

保存模型的方式如下

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')

2.1 设置

import numpy as np
import keras
from keras import ops

2.2 保存模型

本节讨论如何将整个模型保存到一个单一的文件中。该文件将包含:

  • 模型的架构/配置
  • 模型的权重值(在训练过程中学习得到的)
  • 模型的编译信息(如果调用了compile()方法)
  • 优化器及其状态(如果有的话,这允许你从上次中断的地方重新开始训练)

使用Keras的API进行保存操作

可以使用model.save()keras.models.save_model()(两者等效)来保存模型。之后,你可以使用keras.models.load_model()来重新加载模型。

在Keras 3中,唯一支持的格式是“Keras v3”格式,使用.keras扩展名。

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)
)

2.3 自定义对象的保存和加载

本节讨论在Keras保存和重新加载自定义层、函数和模型时的基本工作流程。

2.3.1 保存操作

当保存包含自定义对象的模型时,例如一个继承自Layer的子类,此时,就必须在对象类上定义一个get_config()方法。如果传递给自定义对象构造函数(__init__()方法)的参数不是Python对象(即除了基本类型如整数、字符串等以外的任何东西),那么你还需要在from_config()类方法中显式地反序列化这些参数。

get_config()方法用于返回一个表示层配置的字典。这个字典在保存模型时会与模型的配置一起存储,并且可以用于后续从配置重新创建该层。

from_config()类方法用于根据配置字典重新创建层实例。它应该使用字典中的值来设置层的属性,并返回一个新的层实例。

在自定义层和模型中使用get_config()from_config()方法时,你需要确保:

  • get_config()方法返回一个包含所有必要信息的字典,以便能够重新创建层或模型。
  • from_config()类方法能够使用get_config()返回的字典来重新创建层或模型的实例。

此外,如果在脚本中使用了自定义的损失函数、指标函数或优化器,并且它们被作为模型的编译参数(通过compile()方法传递),再保存时还需要确保这些函数可以在模型保存和重新加载时正确导入。这通常意味着需要将这些函数定义在单独的模块中,并在模型保存和加载的脚本中导入它们。

通过遵循这些步骤,可以确保包含自定义对象的Keras模型能够正确地保存和重新加载。

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)
2.3.2 加载操作

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

  • 注册自定义对象,
  • 在加载时直接传递自定义对象,或
  • 使用自定义对象作用域

以下是每种工作流程的示例:

注册自定义对象

这是推荐的方法,因为自定义对象注册大大简化了保存和加载代码。通过在自定义对象的类定义中添加 @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)
)

将自定义对象传递给load_model()
当加载一个包含自定义对象(如自定义层、自定义激活函数等)的模型时,如果这些自定义对象没有被全局注册,你可以通过在调用load_model()函数时直接传递它们作为参数来提供这些对象的定义。这样,Keras在加载模型时就能找到并正确解释这些自定义对象。

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)
)

使用自定义对象作用域

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

当在代码中定义了一个或多个自定义对象(如自定义层、自定义激活函数、自定义优化器等),并且这些对象没有通过全局注册(如使用@keras.saving.register_keras_serializable装饰器)或作为load_model函数的custom_objects参数传递时,可以使用自定义对象作用域来确保这些对象在加载模型时是可用的。

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)
)

2.4 模型序列化

本节讨论仅保存模型配置而不保存其状态的方法。模型的配置(或架构)指定了模型包含哪些层,以及这些层是如何连接的。如果拥有模型的配置,那么就可以创建一个具有全新初始化状态(没有权重或编译信息)的模型。

模型序列化用到的API

以下是可用的序列化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(): 从其配置中重新创建对象实例。
2.4.1 内存中的模型克隆

脚本可以通过keras.models.clone_model()在内存中克隆一个模型。这相当于先获取模型的配置,然后从其配置中重新创建模型(因此它不保留编译信息或层权重值)。

new_model = keras.models.clone_model(model)
2.4.2 get_config() 和 from_config()

get_config()from_config() 是 Keras 中用于序列化和反序列化模型或层的重要方法。当你想要保存模型或层的配置(即其结构和一些基本属性,但不包括权重或训练状态)时,这些方法是很有用的。

get_config()

get_config() 方法被定义在 Keras 的层(Layer)和模型(Model)类中。当你调用一个模型或层的 get_config() 方法时,它会返回一个 Python 字典,该字典包含了重建该模型或层所需的所有配置信息。这些信息通常包括层类型、层的名称、层的输入形状、激活函数等。

在自定义的 Keras 层或模型中,你应该重写 get_config() 方法,以确保它包含所有在 __init__() 方法中使用的参数。这是因为在反序列化时,from_config() 方法会使用这些参数来重新调用 __init__() 方法。

from_config(config)

from_config() 方法是一个类方法(使用 @classmethod 装饰器),它接受一个配置字典作为参数,并使用这个字典中的信息来重新创建一个模型或层的实例。在 Keras 的内置层和模型中,from_config() 方法已经被定义好了,但在自定义的层和模型中,你可能需要重写它以确保正确的行为。

在自定义的 from_config() 方法中,你通常会从传入的配置字典中提取参数,并使用这些参数来调用 __init__() 方法。这样,你就可以使用相同的配置信息来创建一个新的模型或层实例。

from keras.layers import Layer  
  
class CustomLayer(Layer):  
    def __init__(self, units=32, **kwargs):  
        super(CustomLayer, self).__init__(**kwargs)  
        self.units = units  
  
    def get_config(self):  
        config = super(CustomLayer, self).get_config()  
        config['units'] = self.units  
        return config  
  
    @classmethod  
    def from_config(cls, config):  
        return cls(**config)  
  
# 使用自定义层  
layer = CustomLayer(units=64)  
config = layer.get_config()  # 获取配置字典  
  
# 使用配置字典重新创建层  
new_layer = CustomLayer.from_config(config)  # 从配置中重建层

层和模型序列化的示例

layer = keras.layers.Dense(3, activation="relu")
layer_config = layer.get_config()
print(layer_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)
2.4.3 to_json() and keras.models.model_from_json()

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)
2.4.4 任意对象的序列化和反序列化

keras.saving.serialize_keras_object()keras.saving.deserialize_keras_object() 是通用的API,可以用于序列化或反序列化任何Keras对象以及任何自定义对象。它们是保存模型架构的基础,并且在Keras中所有序列化/反序列化调用的背后都使用了这些API。这些API为Keras对象提供了一个统一的序列化和反序列化机制,使得用户可以轻松地将模型、层、优化器、损失函数等保存到磁盘或从磁盘加载。

my_reg = keras.regularizers.L1(0.005)
config = keras.saving.serialize_keras_object(my_reg)
print(config)

请注意序列化格式中包含用于正确重构对象所需的所有信息:

  • module:包含Keras模块名称或其他用于识别对象来源的模块名称。
  • class_name:包含对象的类名。
  • config:包含用于重构对象所需的所有信息。
  • registered_name(对于自定义对象):对于自定义对象,可能需要一个注册名称来唯一标识它们。

有了这些信息,我们现在可以重构正则化器(regularizer)或其他任何Keras对象了。在Keras中,自定义对象(如自定义层、损失函数、正则化器等)需要在被序列化之前进行注册,以便在反序列化时能够正确地识别和重构它们。注册通常是通过将自定义对象的名称和构造函数添加到全局注册字典中完成的。在反序列化时,Keras会根据提供的moduleclass_nameregistered_name(如果有的话)来查找和调用正确的构造函数,并使用config字典中的参数来初始化对象。

new_reg = keras.saving.deserialize_keras_object(config)

2.5 模型权重保存

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

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

内存中权重转移的API
可以通过使用 get_weights()set_weights() 来在不同的对象之间复制权重:

keras.layers.Layer.get_weights():返回一个NumPy数组列表,包含权重值。
keras.layers.Layer.set_weights(weights):将模型权重设置为提供的值(作为NumPy数组)。

2.5.1 层间权重的传递

以下是一个在内存中将层的权重传送给另外一层的示例

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())
2.5.2 模型间的权重传递

在内存中,将一个具有兼容架构的模型的权重转移到另一个模型

# 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(np.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())
2.5.3 无状态层的情况

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

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())
2.5.4将权重保存到磁盘并加载回模型的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)

2.6 迁移学习示例

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

在迁移学习中,我们通常希望重用预训练模型的一部分(如特征提取器),并将其与新的分类器或其他类型的层结合使用。为了做到这一点,首先加载预训练模型的权重到该模型的一个实例中,然后复制或提取我们需要的部分到新模型中。

以下是一个简化的迁移学习流程示例:

  • 定义一个预训练模型的架构(比如VGG16、ResNet50等)。

  • 加载预训练权重到预训练模型的实例中,通常通过调用 load_weights() 方法。

  • 创建一个新的模型,该模型由预训练模型的一部分(如卷积基)和新添加的层(如全连接层)组成。

  • 将预训练模型中需要迁移的层的权重复制到新模型的对应层中。这可以通过使用 get_weights()set_weights() 方法来完成。

  • 编译并训练新模型,通常只训练新添加的层,而冻结预训练层的权重(通过将它们的 trainable 属性设置为 False)。

  • 使用新模型进行推理或进一步训练。

注意:在实际应用中,许多深度学习框架(如TensorFlow和Keras)提供了更高级别的API来处理迁移学习,使得这个过程更加简单和直接。例如,在Keras中,你可以使用 Model 类的 pop() 方法来移除顶层(通常是分类层),然后添加你自己的新层。然后,你可以调用 load_weights() 方法来加载预训练权重,并只训练新添加的层。

from tensorflow.keras.applications.mobilenet import MobileNet, preprocess_input, decode_predictions  
from tensorflow.keras.models import Model  
from tensorflow.keras.layers import Dense, GlobalAveragePooling2D  
from tensorflow.keras.optimizers import Adam  
  
# 加载预训练的MobileNet模型,但不包括顶部的全连接层  
base_model = MobileNet(weights='imagenet', include_top=False, input_shape=(224, 224, 3))  
  
# 添加全局平均池化层  
x = base_model.output  
x = GlobalAveragePooling2D()(x)  
  
# 添加自定义的全连接层(分类器)  
# 假设我们有10个类别  
predictions = Dense(10, activation='softmax')(x)  
  
# 创建新的模型,输入为base_model的输入,输出为predictions  
model = Model(inputs=base_model.input, outputs=predictions)  
  
# 冻结预训练模型的权重  
for layer in base_model.layers:  
    layer.trainable = False  
  
# 编译模型  
model.compile(optimizer=Adam(lr=0.0001), loss='categorical_crossentropy', metrics=['accuracy'])  
  
# 打印模型结构  
model.summary()  
  
# 如果你有预训练的权重需要加载到基础模型中(这里已经通过MobileNet(weights='imagenet', ...)加载了)  
# base_model.load_weights('path_to_your_pretrained_weights.h5')  
  
# 接下来,你可以用你的数据集训练这个模型,或者只训练顶部的分类器层  
# model.fit(x_train, y_train, epochs=10, batch_size=32)  
  
# 注意:在实际应用中,你可能还需要进行数据预处理(比如resize和normalize)  
# 并且将你的标签数据转换为one-hot编码或相应的分类格式

3 、总结

在Keras中,保存、序列化和导出模型是模型部署和复用的关键步骤。以下是这一工作的重要工作。

3.1 定义配置方法

相关的详细规格如下:

get_config() 方法应该返回一个可序列化为JSON的字典,以便与Keras架构和模型保存API兼容。

from_config(config)(一个类方法)应该根据提供的配置返回一个新的层或模型对象。默认实现是返回 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()
        # 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 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)

请注意,对于上面的 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`](/api/models/model_saving_apis/serialization_utils#deserializekerasobject-function) 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)

3.2 自定义对象的序列化方式

序列化格式具有一个特殊键,用于通过 @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)

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

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

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

3.3 操作步骤

以下是关于保存、序列化以及导出模型的一些基本操作步骤:

3.3.1. 保存模型

在Keras中,你可以使用model.save()方法来保存整个模型(包括其结构和权重)。这将生成一个HDF5文件(通常以.h5为后缀),其中包含了模型的所有信息。

model_save_path = "model_file_path.h5"
model.save(model_save_path)
3.3.2. 加载模型

要加载之前保存的模型,你可以使用keras.models.load_model()函数。

from keras.models import load_model
model = load_model(model_save_path)
3.3.3. 序列化模型结构(get_config()

如果你只想保存模型的结构(不包括其权重),你可以使用model.get_config()方法。这将返回一个字典,其中包含模型配置的所有信息,这些信息可以被序列化为JSON。

config = model.get_config()
3.3.4. 从配置中重新创建模型(from_config()

对于具有简单构造函数的模型(即其所有参数都是可序列化的,如字符串和整数),你可以直接使用from_config()类方法来从配置字典中重新创建模型。但是,如果你的模型构造函数接受更复杂的参数(如其他Keras层或模型),你可能需要在from_config()方法中进行额外的反序列化操作。

3.3.5. 导出模型权重

如果你只想保存模型的权重(不包括其结构),你可以使用model.save_weights()方法。这将生成一个HDF5文件,其中包含模型的权重信息。

model_weights_path = "model_weights_path.h5"
model.save_weights(model_weights_path)
3.3.6. 加载模型权重

要加载之前保存的模型权重,你可以使用model.load_weights()方法。

model.load_weights(model_weights_path)
3.3.7. 使用SavedModel格式保存模型

在TensorFlow 2.x中,推荐使用SavedModel格式来保存和加载模型。SavedModel是一种序列化格式,它使你可以保存整个TensorFlow程序(包括图、权重和元数据)。Keras默认使用SavedModel格式进行模型保存。

# TensorFlow 2.x默认使用SavedModel格式保存模型
model.save('my_model')

# 加载SavedModel格式的模型
new_model = tf.keras.models.load_model('my_model')
3.3.8. 导出为TFLite格式

如果你打算在移动或嵌入式设备上部署你的模型,你可能希望将其导出为TFLite格式。TFLite是TensorFlow Lite的缩写,它是TensorFlow的轻量级解决方案,用于移动和嵌入式设备。你可以使用TFLite Converter将Keras模型转换为TFLite格式。

注意:以上代码示例可能需要根据你的具体环境和模型结构进行调整。

  • 23
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MUKAMO

你的鼓励是我们创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值