TensorFlow 2.4 : ガイド : Keras :- Keras モデルをセーブしてロードする (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 01/24/2021
* 本ページは、TensorFlow org サイトの Guide – Keras の以下のページを翻訳した上で
適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
- お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
- Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
人工知能研究開発支援 | 人工知能研修サービス | テレワーク & オンライン授業を支援 |
PoC(概念実証)を失敗させないための支援 (本支援はセミナーに参加しアンケートに回答した方を対象としています。 |
◆ お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。
株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション |
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/ |
Facebook: https://www.facebook.com/ClassCatJP/ |
ガイド : Keras :- Keras モデルをセーブしてロードする
イントロダクション
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 形式
サンプル :
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 [==============================] - 1s 2ms/step - loss: 0.3164 INFO:tensorflow:Assets written to: my_model/assets 4/4 [==============================] - 0s 2ms/step - loss: 0.2706 <tensorflow.python.keras.callbacks.History at 0x7f4e56bc5978>
SavedModel が含むもの
model.save(‘my_model’) の呼び出しは my_model という名前のフォルダーを作成します、以下を含みます :
ls my_model
assets/ saved_model.pb variables/
モデル・アーキテクチャ、そして訓練 configuration (optimizer、損失とメトリクスを含みます) が saved_model.pb にストアされます。
重みは variables/ ディレクトリにセーブされます。
SavedModel 形式の詳細情報については、SavedModel ガイド (ディスク上の SavedModel 形式) を見てください。
SavedModel がカスタムオブジェクトをどのように処理するか
モデルとその層をセーブするとき、SavedModel 形式はクラス名、call 関数、損失、そして重み (そして実装されていれば config) をストアします。
call 関数はモデル/層の計算グラフを定義します。
モデル/層 config がないときは、call 関数はオリジナル・モデルのように存在するモデルを作成するために使用されます、これは訓練、評価できて推論のために利用できます。
それにもかかわらず、カスタム・モデルや層クラスを書くとき get_config と from_config メソッドを定義することは常に良い実践です。これは必要な場合後で計算を容易に更新することを可能にします。より多くの情報については カスタム・オブジェクト についてのセクションを見てください。
下は、config メソッドを上書きすることなく SavedModel 形式からカスタム層をロードするとき何が起きるかのサンプルです。
class CustomModel(keras.Model): def __init__(self, hidden_units): super(CustomModel, self).__init__() 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 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") # Delete the custom-defined model class to ensure that the loader does not have # access to it. del CustomModel loaded = keras.models.load_model("my_model") np.testing.assert_allclose(loaded(input_arr), outputs) print("Original model:", model) print("Loaded model:", loaded)
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. Original model: <__main__.CustomModel object at 0x7f4e56bb36d8> Loaded model: <tensorflow.python.keras.saving.saved_model.load.CustomModel object at 0x7f4e54b6d550>
上のサンプルで見られるように、ローダーはオリジナル・モデルのように動作する新しいモデルクラスを動的に作成します。
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 2ms/step - loss: 3.3511 4/4 [==============================] - 0s 2ms/step - loss: 3.2047 <tensorflow.python.keras.callbacks.History at 0x7f4e539fc470>
制限
SavedModel 形式と比較して、H5 ファイルには含まれない 2 つのものがあります :
- model.add_loss() & model.add_metric() を通して追加された 外部損失 & メトリクス は (SavedModel とは違い) セーブされません。貴方のモデル上でそのような損失 & メトリクスを持ちそして訓練を再開することを望む場合、モデルをロードした後これらの損失を追加し戻す必要があります。これは self.add_loss() & self.add_metric() を通して層の内部で作成された損失/メトリクスに適用されないことに注意してください。層がロードされる限り、これらの損失 & メトリクスは保持されます、何故ならばそれらは層の call メソッドの一部だからです。
- カスタム層のような カスタム・オブジェクトの計算グラフ はセーブされたファイルに含まれません。ロード時、Keras はモデルを再構築するためにこれらのオブジェクトの Python クラス/関数へのアクセスが必要です。カスタム・オブジェクト を見てください。
アーキテクチャをセーブする
モデルの configuration (or アーキテクチャ) はモデルが何の層を含むか、そしてこれらの層がどのように接続されるかを指定します (*)。モデルの configuration を持つ場合には、モデルは重みについて新たに初期化状態でそしてコンパイル情報なしに作成できます。
*これは (サブクラス化モデルではなく) functional or Sequential api を使用して定義されたモデルに適用されるだけであることに注意してください。
Sequential モデル or Functional API モデルの configuration
これらのタイプのモデルは層の明示的なグラフです : それらの configuration は構造化形式で常に利用可能です。
API
- get_config() と from_config()
- tf.keras.models.model_to_json() と tf.keras.models.model_from_json()
◆ get_config() と from_config()
config = model.get_config() の呼び出しはモデルの configuration を含む Python 辞書を返します。そして同じモデルが (Sequential モデルのために) Sequential.from_config(config) または (Functional 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)
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() と 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 2 to layer 1 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` for other # methods in the Status object. load_status.assert_consumed()
<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f2945948cf8>
◆ 形式 (= 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()
{'_CHECKPOINTABLE_OBJECT_GRAPH': tf.string, 'layer/var/.ATTRIBUTES/VARIABLE_VALUE': tf.int32, 'save_counter/.ATTRIBUTES/VARIABLE_VALUE': tf.int64}
◆ 転送学習サンプル
本質的には、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 0x7f2945923be0>
モデルを構築するために同じ API に固執することを一般に推奨します。Sequential と Functional あるいは Functional とサブクラス化等の間で切り替える場合、常に事前訓練モデルを再構築してそのモデルに事前訓練重みをロードします。
次の質問は、モデル・アーキテクチャが非常に異なる場合重みはどのようにセーブされて異なるモデルにロードできるか?です。その解は正確な層/変数をセーブしてリストアするために 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 0x7f29459231d0>
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 _________________________________________________________________
以上