作者:Neel Kovelamudi, Francois Chollet
建立日期 2023/06/14
上次修改日期 2023/06/30
說明:儲存、序列化和匯出模型的完整指南。
一個 Keras 模型由多個組件組成
Keras API 將所有這些部分以統一的格式儲存在一起,並以 .keras
副檔名標記。這是一個 zip 壓縮檔,包含以下內容
model.weights.h5
(適用於整個模型),具有用於層及其權重的目錄鍵。讓我們來看看這是如何運作的。
如果您只有 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 keras
from keras import ops
本節介紹如何將整個模型儲存到單一檔案中。該檔案將包含
compile()
)您可以使用 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)
)
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 能夠在載入模型時識別該物件。
讓我們建立一個包含自訂層和自訂啟用函式的自訂模型來示範這一點。
範例
# 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 46ms/step - loss: 0.2571
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/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 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")
# 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 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 物件和任何自訂物件。它是儲存模型架構的基礎,並且是 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()
# 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(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())
無狀態層的情況
由於無狀態層不會變更權重的順序或數量,即使有額外/遺失的無狀態層,模型也可以具有相容的架構。
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())
可以透過呼叫 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: ['kernel', 'bias', 'kernel', 'bias']
Changing trainable status of one of the nested layers...
variables: ['kernel', 'bias', 'kernel', 'bias']
variable ordering changed: 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")
# 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) │ 50,240 │ ├─────────────────────────────────┼───────────────────────────┼────────────┤ │ dense_2 (Dense) │ (None, 64) │ 4,160 │ ├─────────────────────────────────┼───────────────────────────┼────────────┤ │ dense_3 (Dense) │ (None, 5) │ 325 │ └─────────────────────────────────┴───────────────────────────┴────────────┘
Total params: 54,725 (213.77 KB)
Trainable params: 54,725 (213.77 KB)
Non-trainable params: 0 (0.00 B)
規格
get_config()
應該回傳一個 JSON 可序列化的字典,以便與 Keras 架構和模型儲存 API 相容。from_config(config)
(一個 classmethod
) 應該回傳一個從 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)
{'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 物件。這表示 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`](/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)
序列化格式對於透過 @keras.saving.register_keras_serializable
註冊的自訂物件具有特殊鍵。這個 registered_name
鍵允許在載入/反序列化時輕鬆檢索,同時也允許使用者新增自訂命名。
讓我們看看從序列化我們在上面定義的自訂層 MyDense
中產生的 config。
範例:
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
。