儲存、序列化及匯出模型

作者: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://tensorflow.dev.org.tw/guide/function#controlling_retracing and https://tensorflow.dev.org.tw/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://tensorflow.dev.org.tw/guide/function#controlling_retracing and https://tensorflow.dev.org.tw/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)

Functional 模型範例

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() 方法)。然後可以透過 TF-Serving 提供此 TensorFlow SavedModel 成品,並且不再需要重新載入成品的所有原始模型程式碼 (包括自訂層) - 它是完全獨立的。

API

  • model.export(),將模型匯出為輕量 SavedModel 成品以進行推論
  • artifact.serve(),呼叫匯出成品的正向傳遞

用於自訂的較低層級 API

使用 .export() 進行簡單匯出

讓我們逐步完成使用 Functional 模型進行 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 物件。這表示 cls(**config) 的預設 from_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,因為這是自訂物件。