TensorFlow 2.4 : ガイド : 基本 – モジュール、層とモデルへのイントロダクション (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 12/28/2020
* 本ページは、TensorFlow org サイトの Guide – TensorFlow Basics の以下のページを翻訳した上で
適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、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/ |
ガイド : 基本 – モジュール、層とモデルへのイントロダクション
TensorFlow で機械学習を行なうためには、貴方はモデルを定義し、セーブし、そしてリストアする可能性が高いです。
モデルは、抽象的には :
- tensor 上で何某かを計算する関数 (forward パス)
- 訓練に応えて更新できる幾つかの変数
このガイドでは、どのように TensorFlow モデルが定義されるかを見るために Keras の水面下を進みます。これは TensorFlow がどのように変数とモデルを集めて、そしてそれらがどのようにセーブされてリストアされるかを見ることです。
セットアップ
import tensorflow as tf from datetime import datetime %load_ext tensorboard
TensorFlow でモデルと層を定義する
殆どのモデルは層から成ります。層は再利用可能な既知の数学的構造を持つ関数で再利用可能で訓練可能な変数を持ちます。TensorFlow では、Keras や Sonnet のような、層とモデルの最も高位な実装が同じ基礎的なクラス: tf.Module 上に構築されます。
ここにスカラー tensor 上で動作する非常に単純な tf.Module のサンプルがあります :
class SimpleModule(tf.Module): def __init__(self, name=None): super().__init__(name=name) self.a_variable = tf.Variable(5.0, name="train_me") self.non_trainable_variable = tf.Variable(5.0, trainable=False, name="do_not_train_me") def __call__(self, x): return self.a_variable * x + self.non_trainable_variable simple_module = SimpleModule(name="simple") simple_module(tf.constant(5.0))
<tf.Tensor: shape=(), dtype=float32, numpy=30.0>
モジュールと、延長戦上の、層は「オブジェクト」のための深層学習の専門用語です : それらは内部状態、そして状態を利用するメソッドを持ちます。
__call__ について Python callable のように振る舞うこと以外は特別なことはありません ; 貴方は貴方が望むどのような関数を伴うモデルをも起動できます。
再調整の間に層と変数を凍結することを含む、どのような理由のためにも変数の訓練可能性を有効と無効に設定することができます。
Note: tf.Module は tf.keras.layers.Layer と tf.keras.Model の両者のための基底クラスで、ここで見る総てはまた Keras で適用できます。歴史的な互換性の理由のために、Keras 層は変数をモジュールから集めませんので、貴方のモデルはモジュールだけか Keras 層だけを使用するべきです。けれども、下で示される変数を調べるためのメソッドはどちらの場合でも同じです。
tf.Module をサブクラス化することにより、このオブジェクトに割当てられた任意の tf.Variable や tf.Module インスタンスは自動的に集められます。これは変数をセーブしてロードし、tf.Module のコレクションを作成することも可能にします。
# All trainable variables print("trainable variables:", simple_module.trainable_variables) # Every variable print("all variables:", simple_module.variables)
trainable variables: (<tf.Variable 'train_me:0' shape=() dtype=float32, numpy=5.0>,) all variables: (<tf.Variable 'train_me:0' shape=() dtype=float32, numpy=5.0>, <tf.Variable 'do_not_train_me:0' shape=() dtype=float32, numpy=5.0>)
これはモジュールから作られた 2-層線形層モデルのサンプルです。
最初に dense (線形) 層です :
class Dense(tf.Module): def __init__(self, in_features, out_features, name=None): super().__init__(name=name) self.w = tf.Variable( tf.random.normal([in_features, out_features]), name='w') self.b = tf.Variable(tf.zeros([out_features]), name='b') def __call__(self, x): y = tf.matmul(x, self.w) + self.b return tf.nn.relu(y)
そしてそれから完全なモデルです、これは 2 層インスタンスを作成してそれらを適用します。
class SequentialModule(tf.Module): def __init__(self, name=None): super().__init__(name=name) self.dense_1 = Dense(in_features=3, out_features=3) self.dense_2 = Dense(in_features=3, out_features=2) def __call__(self, x): x = self.dense_1(x) return self.dense_2(x) # You have made a model! my_model = SequentialModule(name="the_model") # Call it, with random results print("Model results:", my_model(tf.constant([[2.0, 2.0, 2.0]])))
Model results: tf.Tensor([[0.4665389 5.2320476]], shape=(1, 2), dtype=float32)
tf.Module インスタンスはそれに割当てられた任意の tf.Variable や tf.Module インスタンスを、再帰的に、自動的に集めます。これは単一モデル・インスタンスを持つ tf.Module のコレクションを管理し、そしてモデル全体をセーブしてロードすることを可能にします。
print("Submodules:", my_model.submodules)
Submodules: (<__main__.Dense object at 0x7f1107f04908>, <__main__.Dense object at 0x7f1107f04208>)
for var in my_model.variables: print(var, "\n")
<tf.Variable 'b:0' shape=(3,) dtype=float32, numpy=array([0., 0., 0.], dtype=float32)> <tf.Variable 'w:0' shape=(3, 3) dtype=float32, numpy= array([[ 0.98472846, -0.6798039 , -1.1477611 ], [ 0.59508 , 2.6986682 , -0.74189097], [ 0.3933476 , 0.4434388 , -0.99572664]], dtype=float32)> <tf.Variable 'b:0' shape=(2,) dtype=float32, numpy=array([0., 0.], dtype=float32)> <tf.Variable 'w:0' shape=(3, 2) dtype=float32, numpy= array([[ 0.31658304, 0.5166663 ], [-0.15895618, 0.64840126], [ 0.02509804, 0.70377165]], dtype=float32)>
変数の作成を待つ
層への入力と出力の両者のサイズを定義しなければならないことにここで気付いたかもしれません。これは w が既知の shape を持ち割当てられるようにです。
変数作成を特定の入力 shape でモジュールが最初に呼び出されるまで遅延させることにより、入力サイズを前もって指定する必要はありません。
class FlexibleDenseModule(tf.Module): # Note: No need for `in+features` def __init__(self, out_features, name=None): super().__init__(name=name) self.is_built = False self.out_features = out_features def __call__(self, x): # Create variables on first call. if not self.is_built: self.w = tf.Variable( tf.random.normal([x.shape[-1], self.out_features]), name='w') self.b = tf.Variable(tf.zeros([self.out_features]), name='b') self.is_built = True y = tf.matmul(x, self.w) + self.b return tf.nn.relu(y)
# Used in a module class MySequentialModule(tf.Module): def __init__(self, name=None): super().__init__(name=name) self.dense_1 = FlexibleDenseModule(out_features=3) self.dense_2 = FlexibleDenseModule(out_features=2) def __call__(self, x): x = self.dense_1(x) return self.dense_2(x) my_model = MySequentialModule(name="the_model") print("Model results:", my_model(tf.constant([[2.0, 2.0, 2.0]])))
Model results: tf.Tensor([[8.965608 5.2192545]], shape=(1, 2), dtype=float32)
この柔軟性は何故 TensorFlow 層が、tf.keras.layers.Dense 内のように、しばしば (入力と出力サイズの両者ではなく) それらの出力のみの shape を指定する必要があるかです。
重みをセーブする
tf.Module を チェックポイント と SavedModel の両者としてセーブできます。
チェックポイントは単に重みです (つまり、モジュールとそのサブモジュール内の変数のセットの値です)。
chkp_path = "my_checkpoint" checkpoint = tf.train.Checkpoint(model=my_model) checkpoint.write(chkp_path) checkpoint.write(chkp_path)
my_checkpoint
チェックポイントは 2 つの種類のファイルから成ります — データそれ自身、それからメタデータのためのインデックスファイルです。インデックスファイルは何が実際にセーブされたかそしてチェックポイントの番号付けを追跡します、その一方でチェックポイントデータは変数値とそれらの属性検索パスを含みます。
ls my_checkpoint*
my_checkpoint.data-00000-of-00001 my_checkpoint.index
それら (= 変数のコレクション) を含む Python オブジェクトによりソートされた、変数のコレクション全体がセーブされていることを確かめるためにチェックポイント内を見ることができます。
tf.train.list_variables(chkp_path)
[('_CHECKPOINTABLE_OBJECT_GRAPH', []), ('model/dense_1/b/.ATTRIBUTES/VARIABLE_VALUE', [3]), ('model/dense_1/w/.ATTRIBUTES/VARIABLE_VALUE', [3, 3]), ('model/dense_2/b/.ATTRIBUTES/VARIABLE_VALUE', [2]), ('model/dense_2/w/.ATTRIBUTES/VARIABLE_VALUE', [3, 2])]
分散訓練 (複数マシン) の間それらはシャードできます、これが何故それらが番号付けられるかです (e.g., ‘00000-of-00001’)。この場合にはけれども、一つのシャードだけがあります。
モデルをロードし戻すとき、Python オブジェクトの値を上書きします。
new_model = MySequentialModule() new_checkpoint = tf.train.Checkpoint(model=new_model) new_checkpoint.restore("my_checkpoint") # Should be the same result as above new_model(tf.constant([[2.0, 2.0, 2.0]]))
<tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[8.965608 , 5.2192545]], dtype=float32)>
Note: チェックポイントは長い訓練ワークフローの心臓部ですので、tf.checkpoint.CheckpointManager はチェックポイント管理を遥かに容易にするヘルパークラスです。より詳細は ガイド を見てください。
関数をセーブする
TensorFlow Serving と TensorFlow Lite で見られるように、そして TensorFlow Hub から訓練されたモデルをダウンロードするときでさえ、 TensorFlow は元の Python オブジェクトなしでモデルを実行できます。
TensorFlow は オリジナルコードなし で、Python で記述された計算をどのように行なうかを知る必要があります。これを行なうため、グラフ を作成できます、これは前の ガイド で説明されています。
このグラフは関数を実装する、演算あるいは ops を含みます。
このコードがグラフとして動作するべきであることを示すために @tf.function デコレータを追加することにより上のモデルでグラフを定義できます。
class MySequentialModule(tf.Module): def __init__(self, name=None): super().__init__(name=name) self.dense_1 = Dense(in_features=3, out_features=3) self.dense_2 = Dense(in_features=3, out_features=2) @tf.function def __call__(self, x): x = self.dense_1(x) return self.dense_2(x) # You have made a model with a graph! my_model = MySequentialModule(name="the_model")
作成したモデルは前のものと正確に同じように動作します。関数に渡される各一意のシグネチャは分離したグラフを作成します。詳細は グラフガイド を見てください。
print(my_model([[2.0, 2.0, 2.0]])) print(my_model([[[2.0, 2.0, 2.0], [2.0, 2.0, 2.0]]]))
tf.Tensor([[0.8239192 1.0339663]], shape=(1, 2), dtype=float32) tf.Tensor( [[[0.8239192 1.0339663] [0.8239192 1.0339663]]], shape=(1, 2, 2), dtype=float32)
TensorBoard summary 内でそれをトレースすることでグラフを可視化できます。
# Set up logging. stamp = datetime.now().strftime("%Y%m%d-%H%M%S") logdir = "logs/func/%s" % stamp writer = tf.summary.create_file_writer(logdir) # Create a new model to get a fresh trace # Otherwise the summary will not see the graph. new_model = MySequentialModule() # Bracket the function call with # tf.summary.trace_on() and tf.summary.trace_export(). tf.summary.trace_on(graph=True) tf.profiler.experimental.start(logdir) # Call only one tf.function when tracing. z = print(new_model(tf.constant([[2.0, 2.0, 2.0]]))) with writer.as_default(): tf.summary.trace_export( name="my_func_trace", step=0, profiler_outdir=logdir)
tf.Tensor([[0.9635918 0. ]], shape=(1, 2), dtype=float32)
結果としてのトレースを見るために tensorboard を起動します :
%tensorboard --logdir logs/func
SavedModel を作成する
訓練されたモデルを完全に共有する推奨方法は SavedModel を使用することです。SavedModel は関数のコレクションと重みのコレクションの両者を含みます。
ちょうど作成されたモデルをセーブできます。
INFO:tensorflow:Assets written to: the_saved_model/assets
# Inspect the in the directory ls -l the_saved_model
total 24 drwxr-xr-x 2 root root 4096 Dec 27 03:27 assets -rw-r--r-- 1 root root 14139 Dec 27 03:37 saved_model.pb drwxr-xr-x 2 root root 4096 Dec 27 03:37 variables
# The variables/ directory contains a checkpoint of the variables ls -l the_saved_model/variables
total 8 -rw-r--r-- 1 root root 408 Dec 27 03:37 variables.data-00000-of-00001 -rw-r--r-- 1 root root 356 Dec 27 03:37 variables.index
saved_model.pb ファイルは functional tf.Graph を記述する プロトコルバッファ です。
モデルと層はこの表現からそれを作成したクラスのインスタンスを実際に作成することなしにロードできます。これは、大規模にサービス提供したりエッジデバイス上のような Python インタープリタを持たない (or 望まない) 状況や、元の Python コードが利用可能でないか、利用することが実際的でない状況で望まれます。
モデルを新しいオブジェクトとしてロードできます :
new_model = tf.saved_model.load("the_saved_model")
セーブされたモデルをロードすることから作成された、new_model は (どのようなクラス知識もない) 内部的な TensorFlow ユーザオブジェクトです。それはタイプ SequentialModule のものではありません。
isinstance(new_model, SequentialModule)
False
この新しいモデルは定義済みの入力シグネチャ上で動作します。このようにリストアされたモデルにより多くのシグネチャを追加することはできません。
print(my_model([[2.0, 2.0, 2.0]])) print(my_model([[[2.0, 2.0, 2.0], [2.0, 2.0, 2.0]]]))
tf.Tensor([[0.8239192 1.0339663]], shape=(1, 2), dtype=float32) tf.Tensor( [[[0.8239192 1.0339663] [0.8239192 1.0339663]]], shape=(1, 2, 2), dtype=float32)
このように、SavedModel を使用して、TensorFlow 重みと tf.Module を使用するグラフをセーブし、そしてそれらを再度ロードすることができます。
Keras モデルと層
ここまでの時点で、Keras の言及がないことに注意してください。tf.Module 上に貴方自身の高位 API を構築することができます。
このセクションでは、Keras がどのように tf.Module を使用するか調べます。Keras モデルへの完全はユーザガイドは Keras ガイド で見つけられます。
Keras 層
tf.keras.layers.Layer は総ての Keras 層の基底クラスで、そしてそれは tf.Module から継承しています。
単に親 (クラス) をスワップアウトしてから __call__ を call に変更することによりモジュールを Keras 層に変換できます :
class MyDense(tf.keras.layers.Layer): # Adding **kwargs to support base Keras layer arguemnts def __init__(self, in_features, out_features, **kwargs): super().__init__(**kwargs) # This will soon move to the build step; see below self.w = tf.Variable( tf.random.normal([in_features, out_features]), name='w') self.b = tf.Variable(tf.zeros([out_features]), name='b') def call(self, x): y = tf.matmul(x, self.w) + self.b return tf.nn.relu(y) simple_layer = MyDense(name="simple", in_features=3, out_features=3)
Keras 層はそれら自身の __call__ を持ち、これは次のセクションで説明される幾つかの bookkeeping (簿記) を行なってから、call() を呼び出します。機能に変更がないことを見るはずです。
simple_layer([[2.0, 2.0, 2.0]])
<tf.Tensor: shape=(1, 3), dtype=float32, numpy=array([[0.6693126, 4.2450995, 0. ]], dtype=float32)>
ビルドステップ
述べたように、入力 shape が確かなものになるまで変数の作成を待つことは多くの場合便利です。
Keras 層は特別なライフサイクル・ステップを装備しています、これは層をどのように定義するかの点でより多くの柔軟性を可能にします。これは build() 関数で定義されます。
build は正確に一度呼び出されます、そしてそれは入力の shape とともに呼び出されます。それは変数 (重み) を作成するために通常は使用されます。
上の MyDense 層を入力のサイズに柔軟であるように書き換えることができます。
class FlexibleDense(tf.keras.layers.Layer): # Note the added `**kwargs`, as Keras supports many arguments def __init__(self, out_features, **kwargs): super().__init__(**kwargs) self.out_features = out_features def build(self, input_shape): # Create the state of the layer (weights) self.w = tf.Variable( tf.random.normal([input_shape[-1], self.out_features]), name='w') self.b = tf.Variable(tf.zeros([self.out_features]), name='b') def call(self, inputs): # Defines the computation from inputs to outputs return tf.matmul(inputs, self.w) + self.b # Create the instance of the layer flexible_dense = FlexibleDense(out_features=3)
この時点では、モデルは構築 (= build) されていませんので、変数はありません。
flexible_dense.variables
[]
関数を呼び出すと適切なサイズの変数を割当てます。
# Call it, with predictably random results print("Model results:", flexible_dense(tf.constant([[2.0, 2.0, 2.0], [3.0, 3.0, 3.0]])))
Model results: tf.Tensor( [[-2.8541365 1.7678564 -3.6050391] [-4.281205 2.6517844 -5.4075584]], shape=(2, 3), dtype=float32)
flexible_dense.variables
[<tf.Variable 'flexible_dense/w:0' shape=(3, 3) dtype=float32, numpy= array([[-0.9561231 , -0.29272076, 0.8344338 ], [-0.3900787 , -0.05350868, -1.9528853 ], [-0.08086642, 1.2301576 , -0.684068 ]], dtype=float32)>, <tf.Variable 'flexible_dense/b:0' shape=(3,) dtype=float32, numpy=array([0., 0., 0.], dtype=float32)>]
build は一度呼び出されるだけですので、入力 shape が層の変数と互換である場合入力は拒絶されます。
try: print("Model results:", flexible_dense(tf.constant([[2.0, 2.0, 2.0, 2.0]]))) except tf.errors.InvalidArgumentError as e: print("Failed:", e)
Failed: Matrix size-incompatible: In[0]: [1,4], In[1]: [3,3] [Op:MatMul]
Keras 層は以下を含む数多くの特別な特徴を持ちます :
- オプションの損失
- メトリクスのためのサポート
- 訓練と推論利用の間で識別するためのオプションの訓練引数のための組込みサポート
- Python でモデル複製を可能にする configuration を正確にストアすることを可能にする get_config と from_config メソッド
カスタム層への 完全なガイド でそれらを読んでください。
Keras モデル
貴方のモデルをネストされた Keras 層として定義できます。
けれども、Keras はまた tf.keras.Model と呼ばれるフル機能のモデルクラスも提供します。それは tf.keras.layers.Layer から継承していますので、Keras モデルは Keras 層でそして同じ方法で利用され、ネストされ、そしてセーブされます。Keras モデルは、訓練、評価、ロード、セーブそして複数マシン上で訓練することさえ容易にする特別な機能を装備しています。
殆ど同一のコードで上から SequentialModule を定義することができて、再度 __call__ を call() に変換して親 (クラス) を変更します。
class MySequentialModel(tf.keras.Model): def __init__(self, name=None, **kwargs): super().__init__(**kwargs) self.dense_1 = FlexibleDense(out_features=3) self.dense_2 = FlexibleDense(out_features=2) def call(self, x): x = self.dense_1(x) return self.dense_2(x) # You have made a Keras model! my_sequential_model = MySequentialModel(name="the_model") # Call it on a tensor, with random results print("Model results:", my_sequential_model(tf.constant([[2.0, 2.0, 2.0]])))
Model results: tf.Tensor([[0.4714159 2.0135105]], shape=(1, 2), dtype=float32)
変数とサブモジュールを追跡することを含む、総ての同じ特徴が利用可能です。
my_sequential_model.variables
[<tf.Variable 'my_sequential_model/flexible_dense_1/w:0' shape=(3, 3) dtype=float32, numpy= array([[ 0.47678438, 1.6857429 , 2.330113 ], [ 0.14240924, 0.35983694, -1.6812501 ], [-0.89495224, -0.5529289 , -0.4358884 ]], dtype=float32)>, <tf.Variable 'my_sequential_model/flexible_dense_1/b:0' shape=(3,) dtype=float32, numpy=array([0., 0., 0.], dtype=float32)>, <tf.Variable 'my_sequential_model/flexible_dense_2/w:0' shape=(3, 2) dtype=float32, numpy= array([[ 0.14410608, -0.70226425], [ 0.36562452, 0.6488081 ], [-1.2691822 , -0.729403 ]], dtype=float32)>, <tf.Variable 'my_sequential_model/flexible_dense_2/b:0' shape=(2,) dtype=float32, numpy=array([0., 0.], dtype=float32)>]
my_sequential_model.submodules
(<__main__.FlexibleDense at 0x7fc42c0b2320>, <__main__.FlexibleDense at 0x7fc42c64ed68>)
tf.keras.Model の override は TensorFlow モデルを構築するための非常に Pythonic なアプローチです。他のフレームワークからモデルを移行している場合、これは非常に簡単であり得ます。
既存の層と入力の単純な集まりであるモデルを構築している場合、functional API を使用することにより時間と空間を節約できます、これはモデル再構築とアーキテクチャ回りで追加の特徴を装備しています。
ここに functional API による同じモデルがあります :
inputs = tf.keras.Input(shape=[3,]) x = FlexibleDense(3)(inputs) x = FlexibleDense(2)(x) my_functional_model = tf.keras.Model(inputs=inputs, outputs=x) my_functional_model.summary()
Model: "model_1" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_2 (InputLayer) [(None, 3)] 0 _________________________________________________________________ flexible_dense_5 (FlexibleDe (None, 3) 12 _________________________________________________________________ flexible_dense_6 (FlexibleDe (None, 2) 8 ================================================================= Total params: 20 Trainable params: 20 Non-trainable params: 0 _________________________________________________________________
my_functional_model(tf.constant([[2.0, 2.0, 2.0]]))
<tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[-1.0238204, 4.6910114]], dtype=float32)>
ここでの主要な差異は入力 shape が functional 構築プロセスの一部として前もって指定されることです。この場合 input_shape 引数は完全には指定されなくても構いません ; 幾つかの次元を None のままとすることができます。
Note: サブクラス化モデルでは input_shape や InputLayer を指定する必要はありません ; これらの引数と層は無視されます。
Keras モデルをセーブする
Keras モデルはチェックポイントすることができます、そしてそれは tf.Module と同じように見えます。
Keras モデルも tf.saved_models.save() でセーブできます、それらはモジュールであるからです。けれども、Keras モデルは便利なメソッドと他の機能を持ちます。
my_sequential_model.save("exname_of_file")
INFO:tensorflow:Assets written to: exname_of_file/assets
同様に容易に、それらはロードし戻すことができます。
reconstructed_model = tf.keras.models.load_model("exname_of_file")
WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.
Keras SavedModels はまたメトリック、損失と optimizer 状態もセーブします。
この再構築されたモデルは利用できて同じデータ上で呼び出されたとき同じ結果を生成します。
reconstructed_model(tf.constant([[2.0, 2.0, 2.0]]))
<tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[0.4714159, 2.0135105]], dtype=float32)>
特徴サポートのためのカスタム層のための configuration メソッドを提供することを含む、Keras モデルのセーブとシリアライゼーションについて知ることは更に多くあります。saving and serialization へのガイド を確認してください。
以上