Keras 2 : ガイド : シリアル化とセーブ (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 10/27/2021 (keras 2.6.0)
* 本ページは、Keras の以下のドキュメントを翻訳した上で適宜、補足説明したものです:
- Serialization and saving (Author: Kathy Wu, Francois Chollet)
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
- 人工知能研究開発支援
- 人工知能研修サービス(経営者層向けオンサイト研修)
- テクニカルコンサルティングサービス
- 実証実験(プロトタイプ構築)
- アプリケーションへの実装
- 人工知能研修サービス
- PoC(概念実証)を失敗させないための支援
- テレワーク & オンライン授業を支援
- お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
- ウェビナー運用には弊社製品「ClassCat® Webinar」を利用しています。
◆ お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。
株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション |
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/ ; Facebook |
Keras 2 : ガイド : シリアル化とセーブ
イントロダクション
Keras モデルは複数のコンポーネントから成ります :
- アーキテクチャ、あるいは configuration、これはモデルがどの層を含むか、そしてそれらがどのように接続されるかを指定します。
- 重み値のセット (「モデルの状態」)。
- optimizer (モデルのコンパイルにより定義されます)。
- 損失とメトリクスのセット (モデルのコンパイルか add_loss() か add_metric() を呼び出すことで定義されます)。
Keras API はこれらのピースの総てをディスクに一度にセーブするか、それらの幾つかを選択的にセーブすることを可能にします :
- 総てを TensorFlow SavedModel 形式で (または古い Keras H5 形式で) 単一アーカイブにセーブします。これは標準的な実践です。
- アーキテクチャ / configuration だけをセーブします、典型的には JSON ファイルとして。
- 重み値だけをセーブします。これは一般にはモデルを訓練するときに使用されます。
これらのオプションの各々を見ましょう : 一つまたは他をいつ利用するのでしょう?そしてそれらはどのように動作するのでしょう?
モデルをセーブしてロードする方法
このガイドを読むために 10 秒だけを持つ場合、ここに貴方が知る必要があるものがあります。
Keras モデルをセーブする :
model = ... # Get model (Sequential, Functional Model, or Model subclass)
model.save('path/to/location')
モデルをロードし戻す :
from tensorflow import keras
model = keras.models.load_model('path/to/location')
さて、詳細を見ましょう。
セットアップ
import numpy as np
import tensorflow as tf
from tensorflow import keras
モデル全体のセーブ & ロード
モデル全体を単一のアーティファクトにセーブできます。それは以下を含みます :
- モデルのアーキテクチャ/config
- モデルの重み値 (それは訓練の間に学習されました)
- モデルのコンパイル情報 (compile() が呼び出された場合)
- optimizer とその状態、もしあれば (これは貴方がやめたところで訓練を再開することを可能にします)
API
- model.save() or tf.keras.models.save_model()
- tf.keras.models.load_model()
モデル全体をディスクにセーブするために使用できる 2 つの形式があります : TensorFlow SavedModel 形式、そして古い Keras H5 形式 です。推奨形式は SavedModel です。model.save() を使用するときそれはデフォルトです。
H5 形式に以下により切り替えることができます :
- save() に save_format=’h5′ を渡す。
- save() に .h5 or .keras で終わるファイル名を渡す。
SavedModel 形式
SavedModel はより包括的なセーブ形式で、モデル・アーキテクチャ, 重み, そして call 関数のトレースされた Tensorflow サブグラフを保存します。これは組込み層とカスタムオブジェクトの両方をリストアすることを 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="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')` creates a SavedModel folder `my_model`.
model.save("my_model")
# It can be used to reconstruct the model identically.
reconstructed_model = keras.models.load_model("my_model")
# Let's check:
np.testing.assert_allclose(
model.predict(test_input), reconstructed_model.predict(test_input)
)
# The reconstructed model is already compiled and has retained the optimizer
# state, so training can resume:
reconstructed_model.fit(test_input, test_target)
4/4 [==============================] - 0s 833us/step - loss: 0.2464 <tensorflow.python.keras.callbacks.History at 0x1511b87d0>
SavedModel が含むもの
model.save(‘my_model’) の呼び出しは my_model という名前のフォルダーを作成し、以下を含みます :
!ls my_model
assets keras_metadata.pb saved_model.pb variables
モデル・アーキテクチャ、そして訓練 configuration (optimizer、損失とメトリクスを含みます) が saved_model.pb にストアされます。重みは variables/ ディレクトリにセーブされます。
SavedModel 形式の詳細情報については、SavedModel ガイド (ディスク上の SavedModel 形式) を見てください。
SavedModel がカスタムオブジェクトを処理する方法
モデルとその層をセーブするとき、SavedModel 形式はクラス名、call 関数、損失、そして重み (そして実装されていれば config) をストアします。call 関数はモデル/層の計算グラフを定義します。
モデル/層 config がないときは、call 関数はオリジナル・モデルのように存在するモデルを作成するために使用されます、これは訓練、評価できて推論のために利用できます。
それにもかかわらず、カスタム・モデルや層クラスを書くとき get_config と from_config メソッドを定義することは常に良い実践です。これは必要な場合後で計算を容易に更新することを可能にします。詳細については カスタム・オブジェクト についてのセクションを見てください。
サンプル :
class CustomModel(keras.Model):
def __init__(self, hidden_units):
super(CustomModel, self).__init__()
self.hidden_units = hidden_units
self.dense_layers = [keras.layers.Dense(u) for u in hidden_units]
def call(self, inputs):
x = inputs
for layer in self.dense_layers:
x = layer(x)
return x
def get_config(self):
return {"hidden_units": self.hidden_units}
@classmethod
def from_config(cls, config):
return cls(**config)
model = CustomModel([16, 16, 10])
# Build the model by calling it
input_arr = tf.random.uniform((1, 5))
outputs = model(input_arr)
model.save("my_model")
# Option 1: Load with the custom_object argument.
loaded_1 = keras.models.load_model(
"my_model", custom_objects={"CustomModel": CustomModel}
)
# Option 2: Load without the CustomModel class.
# Delete the custom-defined model class to ensure that the loader does not have
# access to it.
del CustomModel
loaded_2 = keras.models.load_model("my_model")
np.testing.assert_allclose(loaded_1(input_arr), outputs)
np.testing.assert_allclose(loaded_2(input_arr), outputs)
print("Original model:", model)
print("Model Loaded with custom objects:", loaded_1)
print("Model loaded without the custom object class:", loaded_2)
INFO:tensorflow:Assets written to: my_model/assets WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually. WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually. Original model: <__main__.CustomModel object at 0x151ad0990> Model Loaded with custom objects: <__main__.CustomModel object at 0x151b03850> Model loaded without the custom object class: <tensorflow.python.keras.saving.saved_model.load.CustomModel object at 0x151bb0310>
最初にロードされたモデルは config と CustomModel クラスを使用してロードされています。2 番目のモデルは元のモデルのように動作するモデルクラスを動的に作成してロードされています。
SavedModel の Configure
TensoFlow 2.4 の新機能 : model.save に引数 save_traces が追加されました、これは SavedModel 関数 tracing をトグルすることを可能にします。関数は Keras がカスタムオブジェクトを元のクラス定義なしに再ロードすることを可能にするために保存されますので、save_traces=False の時には、総てのカスタムオブジェクトは get_config/from_config メソッドを定義していなければなりません。ロードするとき、カスタムオブジェクトは custom_objects 引数に渡さなければなりません。save_traces=False は SavedModel により使用されるディスク容量とセーブ時間を削減します。
Keras H5 形式
Keras はまた単一の HDF5 ファイルをセーブすることもサポートします、これはモデルのアーキテクチャ、重み値、そして compile() 情報を含みます。それは SavedModel の軽量な代替です。
サンプル :
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.h5')` creates a h5 file `my_model.h5`.
model.save("my_h5_model.h5")
# It can be used to reconstruct the model identically.
reconstructed_model = keras.models.load_model("my_h5_model.h5")
# Let's check:
np.testing.assert_allclose(
model.predict(test_input), reconstructed_model.predict(test_input)
)
# The reconstructed model is already compiled and has retained the optimizer
# state, so training can resume:
reconstructed_model.fit(test_input, test_target)
4/4 [==============================] - 0s 967us/step - loss: 0.8106 4/4 [==============================] - 0s 1ms/step - loss: 0.7184 <tensorflow.python.keras.callbacks.History at 0x151d5ac90>
制限
SavedModel 形式と比較して、H5 ファイルには含まれない 2 つのものがあります :
- model.add_loss() & model.add_metric() を通して追加された 外部損失 & メトリクス は (SavedModel とは違い) セーブされません。貴方のモデル上でそのような損失 & メトリクスを持ちそして訓練を再開することを望む場合、モデルをロードした後これらの損失を追加し戻す必要があります。これは self.add_loss() & self.add_metric() を通して層の内部で作成された損失/メトリクスに適用されないことに注意してください。層がロードされる限り、これらの損失 & メトリクスは保持されます、何故ならばそれらは層の call メソッドの一部だからです。
- カスタム層のような カスタム・オブジェクトの計算グラフ はセーブされたファイルに含まれません。ロード時、Keras はモデルを再構築するためにこれらのオブジェクトの Python クラス/関数へのアクセスが必要です。カスタム・オブジェクト を見てください。
アーキテクチャをセーブする
モデルの configuration (or アーキテクチャ) はモデルが何の層を含むか、そしてこれらの層がどのように接続されるかを指定します (*)。モデルの configuration を持つ場合には、モデルは重みについて新たに初期化状態でそしてコンパイル情報なしに作成できます。
*これは (サブクラス化モデルではなく) 関数型 or シーケンシャル api を使用して定義されたモデルに適用されるだけであることに注意してください。
シーケンシャル・モデル or 関数型 API モデルの configuration
これらのタイプのモデルは層の明示的なグラフです : それらの configuration は構造化形式で常に利用可能です。
API
- get_config() と from_config()
- tf.keras.models.model_to_json() and tf.keras.models.model_from_json()
◆ get_config() と from_config()
config = model.get_config() の呼び出しはモデルの configuration を含む Python 辞書を返します。そして同じモデルが (Sequential モデルのために) Sequential.from_config(config) または (関数型 API モデルのために) Model.from_config(config) を通して再構築できます。
同じワークフローが任意のシリアライズ可能な層のためにも動作します。
層サンプル :
layer = keras.layers.Dense(3, activation="relu")
layer_config = layer.get_config()
new_layer = keras.layers.Dense.from_config(layer_config)
Sequential モデル・サンプル :
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() と tf.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)
カスタム・オブジェクト
モデルと層
サブクラス化モデルと層のアーキテクチャはメソッド __init__ と call で定義されます。それらは Python バイトコードとして考えられて、それらは JSON-互換 config にはシリアライズできません — バイトコードを (e.g. pickle を通して) シリアライズを試すことはできるでしょうけど、それは全く安全ではなく貴方のモデルは異なるシステム上でロードできないことを意味します。
カスタム定義層を持つモデル、あるいはサブクラス化モデルをセーブ/ロードためには、get_config とオプションで from_config メソッドを上書きするべきです。更に、カスタム・オブジェクトを Keras がそれを知るように register を使用するべきです。
カスタム関数
カスタム定義関数 (e.g. 活性化損失や初期化) は get_config メソッドを必要としません。関数名がそれがカスタム・オブジェクトとして登録される限りはロードするために十分です。
TensorFlow グラフだけをロードする
Keras により生成された TensorFlow グラフをロードすることが可能です。それを行なう場合、どのような custom_objects も提供する必要がありません。それをこのように行なうことができます :
model.save("my_model")
tensorflow_graph = tf.saved_model.load("my_model")
x = np.random.uniform(size=(4, 32)).astype(np.float32)
predicted = tensorflow_graph(x).numpy()
INFO:tensorflow:Assets written to: my_model/assets
このメソッドは幾つかの欠点を持つことに注意してください :
- トレース可能性の理由のため、使用されたカスタム・オブジェクトへのアクセスを常に持つべきです。再作成できないモデルをプロダクションに配置することは望まないでしょう。
- tf.saved_model.load により返されるオブジェクトは Keras モデルではありません。そのため使いやすくはありません。例えば、.predict() や .fit() へのアクセスを持たないでしょう。
その使用は推奨されないとしても、貴方が困った状況にあれば、例えば、カスタム・オブジェクトのコードを失ったり、tf.keras.models.load_model() でモデルをロードする問題を持つ場合には、それは役立つ可能性があります。
tf.saved_model.load についてのページ でより多くを見つけることができます。
◆ config メソッドを定義する
仕様 :
- get_config は Keras アーキテクチャ- とモデルセーブ-API と互換であるために JSON-シリアライズ可能な辞書を返すべきです。
- from_config(config) (classmethod) は config から作成される新しい層やモデル・オブジェクトを返すべきです。デフォルト実装は cls(**config) を返します。
サンプル :
class CustomLayer(keras.layers.Layer):
def __init__(self, a):
self.var = tf.Variable(a, name="var_a")
def call(self, inputs, training=False):
if training:
return inputs * self.var
else:
return inputs
def get_config(self):
return {"a": self.var.numpy()}
# There's actually no need to define `from_config` here, since returning
# `cls(**config)` is the default behavior.
@classmethod
def from_config(cls, config):
return cls(**config)
layer = CustomLayer(5)
layer.var.assign(2)
serialized_layer = keras.layers.serialize(layer)
new_layer = keras.layers.deserialize(
serialized_layer, custom_objects={"CustomLayer": CustomLayer}
)
カスタム・オブジェクトを登録する
Keras は config をどのクラスが生成したかを記録します。上のサンプルからは、tf.keras.layers.serialize がカスタム層のシリアライズされた形式を生成します :
{'class_name': 'CustomLayer', 'config': {'a': 2}}
Keras は総ての組込み層、モデル、optimizer、そしてメトリック・クラスのマスターリストを保持します、これは from_config を呼び出す正しいクラスを見つけるために使用されます。クラスが見つけられない場合、エラーが上げられます (Value Error: Unknown layer)。このリストにカスタムクラスを登録するためには 2,3 の方法があります :
- loading 関数で custom_objects 引数を設定する (上のセクション “Defining the config methods” のサンプル参照)。
- tf.keras.utils.custom_object_scope or tf.keras.utils.CustomObjectScope
- tf.keras.utils.register_keras_serializable
カスタム層と関数サンプル
class CustomLayer(keras.layers.Layer):
def __init__(self, units=32, **kwargs):
super(CustomLayer, self).__init__(**kwargs)
self.units = units
def build(self, input_shape):
self.w = self.add_weight(
shape=(input_shape[-1], self.units),
initializer="random_normal",
trainable=True,
)
self.b = self.add_weight(
shape=(self.units,), initializer="random_normal", trainable=True
)
def call(self, inputs):
return tf.matmul(inputs, self.w) + self.b
def get_config(self):
config = super(CustomLayer, self).get_config()
config.update({"units": self.units})
return config
def custom_activation(x):
return tf.nn.tanh(x) ** 2
# Make a model with the CustomLayer and custom_activation
inputs = keras.Input((32,))
x = CustomLayer(32)(inputs)
outputs = keras.layers.Activation(custom_activation)(x)
model = keras.Model(inputs, outputs)
# Retrieve the config
config = model.get_config()
# At loading time, register the custom objects with a `custom_object_scope`:
custom_objects = {"CustomLayer": CustomLayer, "custom_activation": custom_activation}
with keras.utils.custom_object_scope(custom_objects):
new_model = keras.Model.from_config(config)
In-メモリのモデル複製
tf.keras.models.clone_model() を通してモデルの in-メモリ複製も行なうことができます。これは config を得てその config からモデルを再作成することに等値です (従ってそれはコンパイル情報や層重み値を保全しません)。
サンプル :
with keras.utils.custom_object_scope(custom_objects):
new_model = keras.models.clone_model(model)
モデルの重み値だけをセーブ & ロードする
モデルの重みだけをセーブ & ロードすることを選択できます。これは以下の場合に役立つ可能性があります :
- 推論のためだけにモデルを必要とする (場合) : この場合訓練を再開する必要はないので、コンパイル情報や optimizer 状態を必要としません。
- 貴方は転移学習を行なっている (場合) : この場合前のモデルの状態を再利用する新しいモデルを訓練していますので、前のモデルのコンパイル情報を必要としません。
in-メモリの重み転送 (= transfer) のための API
重みは get_weights と set_weights を使用して異なるオブジェクトの間でコピーできます :
- tf.keras.layers.Layer.get_weights() : numpy 配列のリストを返します。
- tf.keras.layers.Layer.set_weights() : 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(SubclassedModel, self).__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())
ステートレス層のケース
ステートレス層は重みの順序や数を変更しないので、モデルは追加/欠落 (= extra/missing) のステートレス層がある場合でさえ互換なアーキテクチャを持つことができます。
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 を呼び出すことにより重みは次の形式でディスクにセーブできます :
- TensorFlow チェックポイント
- HDF5
model.save_weights のためのデフォルト形式は TensorFlow チェックポイントです。セーブ形式を指定するには 2 つの方法があります :
- save_format 引数 : 値を save_format=”tf” か save_format=”h5″ に設定する。
- path 引数: path が .h5 か .hdf5 で終われば、HDF5 形式が使用されます。他のサフィックスは save_format が設定されない限りは TensorFlow チェックポイントという結果になります。
重みを in-メモリ numpy 配列として取得するオプションもあります。各 API は良い点と悪い点を持ち、これらは下で詳述されます。
TF チェックポイント形式
サンプル :
# 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("ckpt")
load_status = sequential_model.load_weights("ckpt")
# `assert_consumed` can be used as validation that all variable values have been
# restored from the checkpoint. See [`tf.train.Checkpoint.restore`](https://www.tensorflow.org/api_docs/python/tf/train/Checkpoint/restore) for other
# methods in the Status object.
load_status.assert_consumed()
◆ 形式 (= format) 詳細
TensorFlow チェックポイント形式はオブジェクト属性名を使用して重みをセーブしてリストアします。例えば、tf.keras.layers.Dense 層を考えます。層は 2 つの重みを含みます : dense.kernel と dense.bias です。層が tf 形式にセーブされるとき、結果としてのチェックポイントはキー “kernel” と “bias” そしてそれに対応する重み値を含みます。詳細については TF Checkpoint ガイドの “Loading mechanics” を見てください。
属性/グラフエッジは (変数の名前ではなく、) parent オブジェクトで使用された名前で命名されることに注意してください。下のサンプル CustomLayer を考えます。変数 CustomLayer.var はキーの一部として “var” でセーブされます、”var_a” ではありません。
class CustomLayer(keras.layers.Layer):
def __init__(self, a):
self.var = tf.Variable(a, name="var_a")
layer = CustomLayer(5)
layer_ckpt = tf.train.Checkpoint(layer=layer).save("custom_layer")
ckpt_reader = tf.train.load_checkpoint(layer_ckpt)
ckpt_reader.get_variable_to_dtype_map()
{'save_counter/.ATTRIBUTES/VARIABLE_VALUE': tf.int64, 'layer/var/.ATTRIBUTES/VARIABLE_VALUE': tf.int32, '_CHECKPOINTABLE_OBJECT_GRAPH': tf.string}
◆ 転送学習サンプル
基本的には、2 つのモデルが同じアーキテクチャを持つ限りは、それらは同じチェックポイントを共有できます。
サンプル :
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")
# Extract a portion of the functional model defined in the Setup section.
# The following lines produce a new model that excludes the final output
# layer of the functional model.
pretrained = keras.Model(
functional_model.inputs, functional_model.layers[-1].input, name="pretrained_model"
)
# Randomly assign "trained" weights.
for w in pretrained.weights:
w.assign(tf.random.normal(w.shape))
pretrained.save_weights("pretrained_ckpt")
pretrained.summary()
# Assume this is a separate program where only 'pretrained_ckpt' exists.
# Create a new functional model with a different output dimension.
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(5, name="predictions")(x)
model = keras.Model(inputs=inputs, outputs=outputs, name="new_model")
# Load the weights from pretrained_ckpt into model.
model.load_weights("pretrained_ckpt")
# Check that all of the pretrained weights have been loaded.
for a, b in zip(pretrained.weights, model.weights):
np.testing.assert_allclose(a.numpy(), b.numpy())
print("\n", "-" * 50)
model.summary()
# Example 2: Sequential model
# Recreate the pretrained model, and load the saved 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)
pretrained_model = keras.Model(inputs=inputs, outputs=x, name="pretrained")
# Sequential example:
model = keras.Sequential([pretrained_model, keras.layers.Dense(5, name="predictions")])
model.summary()
pretrained_model.load_weights("pretrained_ckpt")
# Warning! Calling `model.load_weights('pretrained_ckpt')` won't throw an error,
# but will *not* work as expected. If you inspect the weights, you'll see that
# none of the weights will have loaded. `pretrained_model.load_weights()` is the
# correct method to call.
Model: "pretrained_model" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= digits (InputLayer) [(None, 784)] 0 _________________________________________________________________ dense_1 (Dense) (None, 64) 50240 _________________________________________________________________ dense_2 (Dense) (None, 64) 4160 ================================================================= Total params: 54,400 Trainable params: 54,400 Non-trainable params: 0 _________________________________________________________________ -------------------------------------------------- Model: "new_model" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= digits (InputLayer) [(None, 784)] 0 _________________________________________________________________ dense_1 (Dense) (None, 64) 50240 _________________________________________________________________ dense_2 (Dense) (None, 64) 4160 _________________________________________________________________ predictions (Dense) (None, 5) 325 ================================================================= Total params: 54,725 Trainable params: 54,725 Non-trainable params: 0 _________________________________________________________________ Model: "sequential_3" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= pretrained (Functional) (None, 64) 54400 _________________________________________________________________ predictions (Dense) (None, 5) 325 ================================================================= Total params: 54,725 Trainable params: 54,725 Non-trainable params: 0 _________________________________________________________________ <tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x10e58f3d0>
モデルを構築するために同じ API に固執することを一般に推奨します。Sequential と関数型あるいは関数型とサブクラス化等の間で切り替える場合、常に事前訓練モデルを再構築してそのモデルに事前訓練重みをロードしてください。
次の質問は、モデル・アーキテクチャが非常に異なる場合重みはどのようにセーブされて異なるモデルにロードできるか?です。その解は正確な層/変数をセーブしてリストアするために tf.train.Checkpoint を使用することです。
サンプル :
# Create a subclassed model that essentially uses functional_model's first
# and last layers.
# First, save the weights of functional_model's first and last dense layers.
first_dense = functional_model.layers[1]
last_dense = functional_model.layers[-1]
ckpt_path = tf.train.Checkpoint(
dense=first_dense, kernel=last_dense.kernel, bias=last_dense.bias
).save("ckpt")
# Define the subclassed model.
class ContrivedModel(keras.Model):
def __init__(self):
super(ContrivedModel, self).__init__()
self.first_dense = keras.layers.Dense(64)
self.kernel = self.add_variable("kernel", shape=(64, 10))
self.bias = self.add_variable("bias", shape=(10,))
def call(self, inputs):
x = self.first_dense(inputs)
return tf.matmul(x, self.kernel) + self.bias
model = ContrivedModel()
# Call model on inputs to create the variables of the dense layer.
_ = model(tf.ones((1, 784)))
# Create a Checkpoint with the same structure as before, and load the weights.
tf.train.Checkpoint(
dense=model.first_dense, kernel=model.kernel, bias=model.bias
).restore(ckpt_path).assert_consumed()
<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x151ed1110>
HDF5 format
HDF5 形式は層名によりグループ分けされる重みを含みます。重みは、訓練可能な重みのリストを非訓練可能な重みのリストに連結することにより順序付けられたリストです (layer.weights と同じです)。こうして、モデルは、それが同じ層と訓練可能な状態をチェックポイントでセーブされたものとして持てば hdf5 チェックポイントを利用できます。
サンプル :
# 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("weights.h5")
sequential_model.load_weights("weights.h5")
モデルがネストされた層を含むとき layer.trainable の変更は異なる layer.weights 順序という結果になるかもしれないことに注意してください。
class NestedDenseLayer(keras.layers.Layer):
def __init__(self, units, name=None):
super(NestedDenseLayer, self).__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
◆ 転移学習サンプル
HDF5 から事前訓練済みの重みをロードするとき、重みをオリジナルのチェックポイントされたモデルにロードしてから望まれる重み/層を新しいモデルに抽出することが推奨されます。
サンプル :
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_6" _________________________________________________________________ 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: 54,725 Trainable params: 54,725 Non-trainable params: 0 _________________________________________________________________
以上