ホーム » Keras

Keras」カテゴリーアーカイブ

TensorFlow 2.4 : ガイド : Keras :- Keras モデルをセーブしてロードする

TensorFlow 2.4 : ガイド : Keras :- Keras モデルをセーブしてロードする (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 01/24/2021

* 本ページは、TensorFlow org サイトの Guide – Keras の以下のページを翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

無料セミナー実施中 クラスキャット主催 人工知能 & ビジネス Web セミナー

人工知能とビジネスをテーマにウェビナー (WEB セミナー) を定期的に開催しています。スケジュールは弊社 公式 Web サイト でご確認頂けます。
  • お住まいの地域に関係なく 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

モデル全体をディスクにセーブするために使用できる 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()

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 の方法があります :

  1. loading 関数で custom_objects 引数を設定する (上のセクション “Defining the config methods” のサンプル参照)。
  2. tf.keras.utils.custom_object_scope or tf.keras.utils.CustomObjectScope
  3. 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 を使用して異なるオブジェクトの間でコピーできます :

下のサンプル。

 
メモリ内で、重みを一つの層から他方へ転送する

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 つの方法があります :

  1. save_format 引数 : 値を save_format=”tf” か save_format=”h5″ に設定する。
  2. 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
_________________________________________________________________
 

以上



TensorFlow 2.4 : ガイド : Keras :- 転移学習と再調整

TensorFlow 2.4 : ガイド : Keras :- 転移学習と再調整 (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 01/22/2021

* 本ページは、TensorFlow org サイトの Guide – Keras の以下のページを翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

無料セミナー実施中 クラスキャット主催 人工知能 & ビジネス Web セミナー

人工知能とビジネスをテーマにウェビナー (WEB セミナー) を定期的に開催しています。スケジュールは弊社 公式 Web サイト でご確認頂けます。
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
  • Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
クラスキャットは人工知能・テレワークに関する各種サービスを提供しております :

人工知能研究開発支援 人工知能研修サービス テレワーク & オンライン授業を支援
PoC(概念実証)を失敗させないための支援 (本支援はセミナーに参加しアンケートに回答した方を対象としています。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/
Facebook: https://www.facebook.com/ClassCatJP/

 

 

ガイド : Keras :- 転移学習と再調整

セットアップ

import numpy as np
import tensorflow as tf
from tensorflow import keras

 

イントロダクション

転移学習 は 1 つの問題上で学習された特徴を取り、そしてそれらを新しい、類似の問題上で活用することから成ります。例えば、アライグマ (= raccoon) を識別するために学習したモデルからの特徴はタヌキを識別することを意図したモデルを始動するために有用かもしれません。

転移学習はスクラッチから本格的な (= full-scale) モデルを訓練するには貴方のデータセットが少な過ぎるデータを持つようなタスクのために通常は行なわれます。

深層学習のコンテキストで転移学習の最も一般的な具現化は以下のワークフローです :

  1. 前に訓練されたモデルから層を取ります。
  2. 今後の訓練ラウンドの間にそれらが含む任意の情報を壊すことを避けるために、それらを凍結します。
  3. 凍結された層の上に幾つかの新しい、訓練可能な層を追加します。それらは古い特徴を新しいデータセット上の予測に変えるために学習します。
  4. 貴方のデータセット上で新しい層を訓練します。

最後の、オプションのステップは 再調整 で、これは上で得たモデル全体 (あるいはその一部) を解凍して、非常に低い学習率で新しいデータ上でそれを再訓練することから成ります。事前訓練された特徴を新しいデータに漸増的事前に適応させることによりこれは潜在的には意味のある改良を獲得できます。

最初に、Keras 訓練可能 API を詳細に調べます、これは殆どの転移学習 & 再調整ワークフソーの基礎となります。

そして、ImageNet データセット上で事前訓練されたモデルを取り、そしてそれを Kaggle “猫 vs 犬” 分類データセット上で再訓練することにより典型的なワークフローを実演します。

これは Deep Learning with Python と 2016 ブログ投稿 “building powerful image classification models using very little data” から採用されました。

 

層を凍結する : 訓練可能な属性を理解する

層 & モデルは 3 つの重み属性を持ちます :

  • weights は層の総ての重み変数のリストです。
  • trainable_weights は訓練の間に損失を最小化するために (勾配降下を通して) 更新されることを意図したそれらのリストです。
  • non_trainable_weights は訓練されることを意図していないそれらのリストです。典型的にはそれらは forward パスの間にモデルにより更新されます。

サンプル: Dense 層は 2 つの訓練可能な重み (カーネル & バイアス) を持ちます

layer = keras.layers.Dense(3)
layer.build((None, 4))  # Create the weights

print("weights:", len(layer.weights))
print("trainable_weights:", len(layer.trainable_weights))
print("non_trainable_weights:", len(layer.non_trainable_weights))
weights: 2
trainable_weights: 2
non_trainable_weights: 0

一般に、総ての重みは訓練可能な重みです。非訓練可能な重みを持つ唯一の組込み層は BatchNormalization です。訓練の間 その入力の平均と分散を追跡するために非訓練可能な重みを利用します。貴方自身のカスタム層で非訓練可能な重みをどのように使用するかを学習するためには、writing new layers from scratch へのガイド を見てください。

 
サンプル: BatchNormalization 層は 2 つの訓練可能な重みと 2 つの非訓練可能な重みを持つ。

layer = keras.layers.BatchNormalization()
layer.build((None, 4))  # Create the weights

print("weights:", len(layer.weights))
print("trainable_weights:", len(layer.trainable_weights))
print("non_trainable_weights:", len(layer.non_trainable_weights))
weights: 4
trainable_weights: 2
non_trainable_weights: 2

層 & モデルはまたブーリアン属性 trainable も持ちます。その値は変更可能です。layer.trainable を False を設定すると総ての層の重みを訓練可能から非訓練可能に移します。これは層を「凍結する (= freezing)」と呼称されます : (fit() で訓練するとき、あるいは勾配更新を適用するために trainable_weights に依拠する任意のカスタムループで訓練するときのいずれかの) 訓練の間に凍結された層の状態は更新されません。

 
サンプル: trainable を False に設定します

layer = keras.layers.Dense(3)
layer.build((None, 4))  # Create the weights
layer.trainable = False  # Freeze the layer

print("weights:", len(layer.weights))
print("trainable_weights:", len(layer.trainable_weights))
print("non_trainable_weights:", len(layer.non_trainable_weights))
weights: 2
trainable_weights: 0
non_trainable_weights: 2

訓練可能な重みが非訓練可能になるとき、その値は訓練の間にもはや更新されません。

# Make a model with 2 layers
layer1 = keras.layers.Dense(3, activation="relu")
layer2 = keras.layers.Dense(3, activation="sigmoid")
model = keras.Sequential([keras.Input(shape=(3,)), layer1, layer2])

# Freeze the first layer
layer1.trainable = False

# Keep a copy of the weights of layer1 for later reference
initial_layer1_weights_values = layer1.get_weights()

# Train the model
model.compile(optimizer="adam", loss="mse")
model.fit(np.random.random((2, 3)), np.random.random((2, 3)))

# Check that the weights of layer1 have not changed during training
final_layer1_weights_values = layer1.get_weights()
np.testing.assert_allclose(
    initial_layer1_weights_values[0], final_layer1_weights_values[0]
)
np.testing.assert_allclose(
    initial_layer1_weights_values[1], final_layer1_weights_values[1]
)
1/1 [==============================] - 1s 565ms/step - loss: 0.1359

layer.trainable 属性を layer.__call__() の引数 training と混同しないでください (これは層がその forward パスを推論モードか訓練モードのいずれで実行するべきかを制御します)。より多くの情報については、Keras FAQ を見てください。

 

trainable 属性の再帰的な設定

モデルか副層 (= sublayer) を持つ任意の層上で trainable = False を設定する場合、総ての子層 (= children layers) もまた非訓練になります。

サンプル:

inner_model = keras.Sequential(
    [
        keras.Input(shape=(3,)),
        keras.layers.Dense(3, activation="relu"),
        keras.layers.Dense(3, activation="relu"),
    ]
)

model = keras.Sequential(
    [keras.Input(shape=(3,)), inner_model, keras.layers.Dense(3, activation="sigmoid"),]
)

model.trainable = False  # Freeze the outer model

assert inner_model.trainable == False  # All layers in `model` are now frozen
assert inner_model.layers[0].trainable == False  # `trainable` is propagated recursively

 

典型的な転移学習ワークフロー

これは典型的な転移学習ワークフローが Keras でどのように実装できるかに導いてくれます :

  1. 基底モデルをインスタンス化して事前訓練された重みをその中にロードします。
  2. trainable = False を設定して基底モデルの総ての層を凍結します。
  3. 基底モデルからの一つ (or 幾つか) の層の出力の上に新しいモデルを作成します。
  4. 貴方の新しいデータセット上で新しいモデルを訓練します。

代替の、より軽量なワークフローはまた次のようなものであり得ることに注意してください :

  1. 基底モデルをインスタンス化して事前訓練された重みをその中にロードします。
  2. それを通して新しいデータセットを実行してそして基底モデルからの一つ (or 幾つか) の層の出力を記録します。これは 特徴抽出 と呼ばれます。
  3. その出力を新しい、より小さなモデルのための入力データとして利用します。

2 番目のワークフローの主要な優位点は基底モデルを (訓練のエポック毎に 1 度ではなく) 貴方のデータ上 1 度実行するだけであることです。従ってそれは遥かに高速で & 安価です。

けれども、その 2 番目のワークフローに伴う問題は、それは訓練の間に貴方の新しいモデルの入力データを動的に変更することを可能にしないことです、これは例えば、データ増強を行なうときに必要です。転移学習は典型的には貴方の新しいデータセットが完全な (= full-scale) モデルをスクラッチから訓練すためには非常に少ないデータを持つときタスクのために使用され、そしてそのようなシナリオではデータ増強は非常に重要です。そのため以下では、最初のワークフローにフォーカスします。

ここに最初のワークフローが Keras でどのようなものかがあります :

最初に、事前訓練された重みで基底モデルをインスタンス化します。

base_model = keras.applications.Xception(
    weights='imagenet',  # Load weights pre-trained on ImageNet.
    input_shape=(150, 150, 3),
    include_top=False)  # Do not include the ImageNet classifier at the top.
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/xception/xception_weights_tf_dim_ordering_tf_kernels_notop.h5
83689472/83683744 [==============================] - 1s 0us/step

それから、基底モデルを凍結します。

base_model.trainable = False

上に新しいモデルを作成します。

inputs = keras.Input(shape=(150, 150, 3))
# We make sure that the base_model is running in inference mode here,
# by passing `training=False`. This is important for fine-tuning, as you will
# learn in a few paragraphs.
x = base_model(inputs, training=False)
# Convert features of shape `base_model.output_shape[1:]` to vectors
x = keras.layers.GlobalAveragePooling2D()(x)
# A Dense classifier with a single unit (binary classification)
outputs = keras.layers.Dense(1)(x)
model = keras.Model(inputs, outputs)

新しいデータでモデルを訓練します。

model.compile(optimizer=keras.optimizers.Adam(),
              loss=keras.losses.BinaryCrossentropy(from_logits=True),
              metrics=[keras.metrics.BinaryAccuracy()])
model.fit(new_dataset, epochs=20, callbacks=..., validation_data=...)

 

再調整

貴方のモデルが新しいデータ上でひとたび収束したのであれば、基底モデルの総てまたは一部を解凍してそして非常に小さい学習率でモデル全体を end-to-end に再訓練することを試すができます。

これは増加的な (= incremental) 改良を潜在的に与えられるオプションの最後のステップです。それはまた潜在的に素早い overfitting に繋がる可能性があります — それに留意してください。

凍結された層を持つモデルが収束まで訓練された後だけにこのステップを行なうことが不可欠です。もしランダムに初期化された訓練可能な層を事前訓練された特徴を保持する訓練可能な層と混在させれば、ランダムに初期化された層は訓練の間に非常に大きな勾配更新を引き起こすでしょう、これは事前訓練された特徴を破壊します。

この段階で非常に低い学習率を利用することも重要です、何故ならば典型的には非常に小さいデータセット上で、訓練の最初のラウンドにおけるよりも遥かに大規模なモデルを訓練しているからです。その結果、大きな重み更新を適用する場合非常に迅速に overffitting する危険性があります。ここでは、漸増的な方法で事前訓練された重みを再適応させることを望むだけです。

これが基底モデル全体の再調整をどのように実装するかです :

# Unfreeze the base model
base_model.trainable = True

# It's important to recompile your model after you make any changes
# to the `trainable` attribute of any inner layer, so that your changes
# are take into account
model.compile(optimizer=keras.optimizers.Adam(1e-5),  # Very low learning rate
              loss=keras.losses.BinaryCrossentropy(from_logits=True),
              metrics=[keras.metrics.BinaryAccuracy()])

# Train end-to-end. Be careful to stop before you overfit!
model.fit(new_dataset, epochs=10, callbacks=..., validation_data=...)

 
compile() と trainable についての重要なノート

モデル上での compile() の呼び出しはそのモデルの動作を「凍結する」ことを意図しています。これはモデルがコンパイルされた時の訓練可能な属性値は再度 compile が呼び出されるまでそのモデルのライフタイムを通して保存されるべきであることを含蓄します。こうして、任意の訓練可能な値を変更する場合、貴方の変更が考慮ためにモデル上で compile() を再度呼び出すことを確実にしてください。

 
BatchNormalization 層についての重要なノート

多くの画像モデルは BatchNormalization 層を含みます。その層は考えうる総てにおいて特別なケースです。ここに留意すべき幾つかのことがあります。

  • BatchNormalization は訓練の間に更新されるべき 2 つの非訓練可能な重みを含みます。これらは入力の平均と分散を追跡する変数です。
  • bn_layer.trainable = False を設定するとき、BatchNormalization 層は推論モードで実行され、そしてその平均 & 分散統計値を更新しません。これは一般には他の層には当てはまりません、というのは 重みの訓練可能性 & 推論/訓練モードは 2 つの直交する概念だからです。しかし 2 つは BatchNormalization 層の場合には結び付けられます。
  • 再調整を行なうために BatchNormalization 層を含むモデルを解凍するとき、基底モデルを呼び出すとき training=False を渡して BatchNormalization 層を推論モードに保持するべきです。そうでないと非訓練可能な重みに適用される更新がモデルが学習したものを突然に破壊します。

このガイドの最後の end-to-end サンプルでこのパターンを実際に見ます。

 

カスタム訓練ループで転移学習 & 再調整

fit() の代わりに、貴方自身の低位訓練ループを使用している場合、ワークフローは本質的には同じままです。勾配更新を適用するときリスト model.trainable_weights を考慮することだけに注意すべきです :

# Create base model
base_model = keras.applications.Xception(
    weights='imagenet',
    input_shape=(150, 150, 3),
    include_top=False)
# Freeze base model
base_model.trainable = False

# Create new model on top.
inputs = keras.Input(shape=(150, 150, 3))
x = base_model(inputs, training=False)
x = keras.layers.GlobalAveragePooling2D()(x)
outputs = keras.layers.Dense(1)(x)
model = keras.Model(inputs, outputs)

loss_fn = keras.losses.BinaryCrossentropy(from_logits=True)
optimizer = keras.optimizers.Adam()

# Iterate over the batches of a dataset.
for inputs, targets in new_dataset:
    # Open a GradientTape.
    with tf.GradientTape() as tape:
        # Forward pass.
        predictions = model(inputs)
        # Compute the loss value for this batch.
        loss_value = loss_fn(targets, predictions)

    # Get gradients of loss wrt the *trainable* weights.
    gradients = tape.gradient(loss_value, model.trainable_weights)
    # Update the weights of the model.
    optimizer.apply_gradients(zip(gradients, model.trainable_weights))

Likewise for fine-tuning.

 

end-to-end サンプル:

猫 vs. 犬上で画像分類モデルを再調整します。

データセット

これらのコンセプトを固めるために、具体的な end-to-end 転移学習 & 再調整サンプルを通り抜けましょう。ImageNet 上で事前訓練された、Xception モデルをロードし、そしてそれを Kaggle “猫 vs. 犬” 分類データセット上で利用します。

 

データを得る

最初に、TFDS を使用して猫 vs. 犬データセットを取得しましょう。貴方自身のデータセットを持つ場合、クラス専用フォルダーにファイリングされた画像のセットから同様のラベル付けられたデータセット・オブジェクトを生成するために貴方は多分ユティリティ tf.keras.preprocessing.image_dataset_from_directory を使用することを望むでしょう。

転移学習は非常に小さいデータセットで作業するとき最も有用です。データセットを小さく保持するために、訓練のために元の訓練データ (25,000 画像) の 40%、検証のために 10%、そしてテストのために 10% を利用します。

import tensorflow_datasets as tfds

tfds.disable_progress_bar()

train_ds, validation_ds, test_ds = tfds.load(
    "cats_vs_dogs",
    # Reserve 10% for validation and 10% for test
    split=["train[:40%]", "train[40%:50%]", "train[50%:60%]"],
    as_supervised=True,  # Include labels
)

print("Number of training samples: %d" % tf.data.experimental.cardinality(train_ds))
print(
    "Number of validation samples: %d" % tf.data.experimental.cardinality(validation_ds)
)
print("Number of test samples: %d" % tf.data.experimental.cardinality(test_ds))
Number of training samples: 9305
Number of validation samples: 2326
Number of test samples: 2326

これらは訓練データセットの最初の 9 画像です — 見れるように、それらは総て異なるサイズです。

import matplotlib.pyplot as plt

plt.figure(figsize=(10, 10))
for i, (image, label) in enumerate(train_ds.take(9)):
    ax = plt.subplot(3, 3, i + 1)
    plt.imshow(image)
    plt.title(int(label))
    plt.axis("off")

ラベル 1 が「犬」でラベル 0 が「猫」であることも見れます。

 

データを標準化する

raw 画像は様々なサイズを持ちます。加えて、各ピクセルは 0 と 255 の間の 3 つの整数値 (RGB レベル値) から成ります。これはニューラルネットワークに供給するために素晴らしい適合ではありません。私達は 2 つのことを行なう必要があります :

  • 固定画像サイズに標準化する。150×150 を選択します。
  • ピクセル値を -1 と 1 の間に正規化する。モデル自身の一部として Normalization を使用してこれを行ないます。

一般に、既に前処理されたデータを取るモデルに対して、入力として raw データを取るモデルを開発することは良い実践です。その理由は、モデルが前処理されたデータを想定する場合、他で (web ブラウザ、モバイル app で) それを使用するためにモデルをエクスポートするたびに、正確に同じ前処理パイプラインを再実装する必要があるためです。それは非常に素早く非常に技巧的になります。そのためモデルに達する前に最小限の量の前処理を行なうべきです。

ここでは、データパイプラインで画像リサイズを行ない (何故ならば深層ニューラルネットワークはデータの連続的なバッチを処理するだけだからです)、そしてモデルの一部として入力値スケーリングを行ないます、それを作成するときに。

画像を 150×150 にリサイズしましょう :

size = (150, 150)

train_ds = train_ds.map(lambda x, y: (tf.image.resize(x, size), y))
validation_ds = validation_ds.map(lambda x, y: (tf.image.resize(x, size), y))
test_ds = test_ds.map(lambda x, y: (tf.image.resize(x, size), y))

更に、データをバッチ化してローディング・スピードを最適化するためにキャッシングと先取り (= prefetching) を利用しましょう。

batch_size = 32

train_ds = train_ds.cache().batch(batch_size).prefetch(buffer_size=10)
validation_ds = validation_ds.cache().batch(batch_size).prefetch(buffer_size=10)
test_ds = test_ds.cache().batch(batch_size).prefetch(buffer_size=10)

 

ランダムデータ増強を使用する

大規模な画像データセットを持たないとき、ランダムな水平反転や小さなランダム回転のような、サンプルの多様性を人工的に導入することは良い実践です。これは overfitting をスローダウンさせる一方で訓練データの様々な様相にモデルを晒す役に立ちます。

from tensorflow import keras
from tensorflow.keras import layers

data_augmentation = keras.Sequential(
    [
        layers.experimental.preprocessing.RandomFlip("horizontal"),
        layers.experimental.preprocessing.RandomRotation(0.1),
    ]
)

様々なランダム変換の後に最初のバッチの最初の画像がどのように見えるか可視化しましょう :

import numpy as np

for images, labels in train_ds.take(1):
    plt.figure(figsize=(10, 10))
    first_image = images[0]
    for i in range(9):
        ax = plt.subplot(3, 3, i + 1)
        augmented_image = data_augmentation(
            tf.expand_dims(first_image, 0), training=True
        )
        plt.imshow(augmented_image[0].numpy().astype("int32"))
        plt.title(int(labels[i]))
        plt.axis("off")

 

モデルを構築する

今は先に説明したブループリントに従ってモデルを構築しましょう。

以下に注意してください :

  • (初期化時に [0, 255] 範囲内の) 入力値を [-1, 1] 範囲にスケールするために Normalization 層を追加します。
  • 正則化のために、分類層の前に Dropout 層を追加します。
  • 基底モデルを呼び出すときに、それが推論モードで動作するように training=False を渡すことを確実にします、その結果 batchnorm 統計値は再調整のために基底モデルを解凍した後でさえ更新されえません。
base_model = keras.applications.Xception(
    weights="imagenet",  # Load weights pre-trained on ImageNet.
    input_shape=(150, 150, 3),
    include_top=False,
)  # Do not include the ImageNet classifier at the top.

# Freeze the base_model
base_model.trainable = False

# Create new model on top
inputs = keras.Input(shape=(150, 150, 3))
x = data_augmentation(inputs)  # Apply random data augmentation

# Pre-trained Xception weights requires that input be normalized
# from (0, 255) to a range (-1., +1.), the normalization layer
# does the following, outputs = (inputs - mean) / sqrt(var)
norm_layer = keras.layers.experimental.preprocessing.Normalization()
mean = np.array([127.5] * 3)
var = mean ** 2
# Scale inputs to [-1, +1]
x = norm_layer(x)
norm_layer.set_weights([mean, var])

# The base model contains batchnorm layers. We want to keep them in inference mode
# when we unfreeze the base model for fine-tuning, so we make sure that the
# base_model is running in inference mode here.
x = base_model(x, training=False)
x = keras.layers.GlobalAveragePooling2D()(x)
x = keras.layers.Dropout(0.2)(x)  # Regularize with dropout
outputs = keras.layers.Dense(1)(x)
model = keras.Model(inputs, outputs)

model.summary()
Model: "model_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_12 (InputLayer)        [(None, 150, 150, 3)]     0         
_________________________________________________________________
sequential_6 (Sequential)    (None, 150, 150, 3)       0         
_________________________________________________________________
normalization (Normalization (None, 150, 150, 3)       7         
_________________________________________________________________
xception (Functional)        (None, 5, 5, 2048)        20861480  
_________________________________________________________________
global_average_pooling2d_2 ( (None, 2048)              0         
_________________________________________________________________
dropout (Dropout)            (None, 2048)              0         
_________________________________________________________________
dense_16 (Dense)             (None, 1)                 2049      
=================================================================
Total params: 20,863,536
Trainable params: 2,049
Non-trainable params: 20,861,487
_________________________________________________________________

 

トップ層を訓練する

model.compile(
    optimizer=keras.optimizers.Adam(),
    loss=keras.losses.BinaryCrossentropy(from_logits=True),
    metrics=[keras.metrics.BinaryAccuracy()],
)

epochs = 20
model.fit(train_ds, epochs=epochs, validation_data=validation_ds)
Epoch 1/20
291/291 [==============================] - 26s 80ms/step - loss: 0.0948 - binary_accuracy: 0.9621 - val_loss: 0.0689 - val_binary_accuracy: 0.9738
Epoch 2/20
291/291 [==============================] - 22s 77ms/step - loss: 0.0891 - binary_accuracy: 0.9634 - val_loss: 0.0700 - val_binary_accuracy: 0.9746
Epoch 3/20
291/291 [==============================] - 22s 77ms/step - loss: 0.0850 - binary_accuracy: 0.9659 - val_loss: 0.0684 - val_binary_accuracy: 0.9751
Epoch 4/20
291/291 [==============================] - 23s 77ms/step - loss: 0.0860 - binary_accuracy: 0.9675 - val_loss: 0.0701 - val_binary_accuracy: 0.9738
Epoch 5/20
291/291 [==============================] - 23s 78ms/step - loss: 0.0955 - binary_accuracy: 0.9628 - val_loss: 0.0809 - val_binary_accuracy: 0.9678
Epoch 6/20
291/291 [==============================] - 23s 78ms/step - loss: 0.0942 - binary_accuracy: 0.9616 - val_loss: 0.0711 - val_binary_accuracy: 0.9742
Epoch 7/20
291/291 [==============================] - 23s 78ms/step - loss: 0.0962 - binary_accuracy: 0.9616 - val_loss: 0.0738 - val_binary_accuracy: 0.9716
Epoch 8/20
291/291 [==============================] - 22s 77ms/step - loss: 0.0858 - binary_accuracy: 0.9654 - val_loss: 0.0707 - val_binary_accuracy: 0.9716
Epoch 9/20
291/291 [==============================] - 23s 78ms/step - loss: 0.0940 - binary_accuracy: 0.9613 - val_loss: 0.0730 - val_binary_accuracy: 0.9703
Epoch 10/20
291/291 [==============================] - 23s 78ms/step - loss: 0.0944 - binary_accuracy: 0.9616 - val_loss: 0.0814 - val_binary_accuracy: 0.9699
Epoch 11/20
291/291 [==============================] - 23s 78ms/step - loss: 0.0937 - binary_accuracy: 0.9617 - val_loss: 0.0730 - val_binary_accuracy: 0.9699
Epoch 12/20
291/291 [==============================] - 23s 77ms/step - loss: 0.0907 - binary_accuracy: 0.9646 - val_loss: 0.0774 - val_binary_accuracy: 0.9690
Epoch 13/20
291/291 [==============================] - 23s 78ms/step - loss: 0.0974 - binary_accuracy: 0.9604 - val_loss: 0.0713 - val_binary_accuracy: 0.9742
Epoch 14/20
291/291 [==============================] - 23s 78ms/step - loss: 0.0921 - binary_accuracy: 0.9616 - val_loss: 0.0723 - val_binary_accuracy: 0.9725
Epoch 15/20
291/291 [==============================] - 23s 77ms/step - loss: 0.0910 - binary_accuracy: 0.9625 - val_loss: 0.0723 - val_binary_accuracy: 0.9712
Epoch 16/20
291/291 [==============================] - 23s 78ms/step - loss: 0.0953 - binary_accuracy: 0.9631 - val_loss: 0.0745 - val_binary_accuracy: 0.9721
Epoch 17/20
291/291 [==============================] - 23s 78ms/step - loss: 0.0865 - binary_accuracy: 0.9656 - val_loss: 0.0731 - val_binary_accuracy: 0.9733
Epoch 18/20
291/291 [==============================] - 23s 77ms/step - loss: 0.0833 - binary_accuracy: 0.9662 - val_loss: 0.0748 - val_binary_accuracy: 0.9725
Epoch 19/20
291/291 [==============================] - 23s 78ms/step - loss: 0.0974 - binary_accuracy: 0.9589 - val_loss: 0.0761 - val_binary_accuracy: 0.9708
Epoch 20/20
291/291 [==============================] - 22s 77ms/step - loss: 0.0990 - binary_accuracy: 0.9613 - val_loss: 0.0904 - val_binary_accuracy: 0.9686
CPU times: user 2min 17s, sys: 17.9 s, total: 2min 34s
Wall time: 7min 34s

 

モデル全体の再調整のラウンドを行なう

最後に、基底モデルを解凍してそしてモデル全体を低い学習率で end-to-end に訓練しましょう。

重要なことは、基底モデルは訓練可能になりますが、それは依然として推論モードで動作していることです、何故ならばモデルを構築するときにそれを呼び出すとき training=False を渡したからです。これは、バッチ正規化層内部ではバッチ統計値を更新しないことを意味しています。それらが行なうのであれば、それらはそこまでにモデルにより学習された表現上で大暴れします (= wreck havoc)。

# Unfreeze the base_model. Note that it keeps running in inference mode
# since we passed `training=False` when calling it. This means that
# the batchnorm layers will not update their batch statistics.
# This prevents the batchnorm layers from undoing all the training
# we've done so far.
base_model.trainable = True
model.summary()

model.compile(
    optimizer=keras.optimizers.Adam(1e-5),  # Low learning rate
    loss=keras.losses.BinaryCrossentropy(from_logits=True),
    metrics=[keras.metrics.BinaryAccuracy()],
)

epochs = 10
model.fit(train_ds, epochs=epochs, validation_data=validation_ds)
Model: "model_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_7 (InputLayer)         [(None, 150, 150, 3)]     0         
_________________________________________________________________
sequential_3 (Sequential)    (None, 150, 150, 3)       0         
_________________________________________________________________
normalization (Normalization (None, 150, 150, 3)       7         
_________________________________________________________________
xception (Functional)        (None, 5, 5, 2048)        20861480  
_________________________________________________________________
global_average_pooling2d_1 ( (None, 2048)              0         
_________________________________________________________________
dropout (Dropout)            (None, 2048)              0         
_________________________________________________________________
dense_8 (Dense)              (None, 1)                 2049      
=================================================================
Total params: 20,863,536
Trainable params: 20,809,001
Non-trainable params: 54,535
_________________________________________________________________
Epoch 1/10
291/291 [==============================] - 89s 291ms/step - loss: 0.0808 - binary_accuracy: 0.9661 - val_loss: 0.0584 - val_binary_accuracy: 0.9772
Epoch 2/10
291/291 [==============================] - 86s 296ms/step - loss: 0.0623 - binary_accuracy: 0.9777 - val_loss: 0.0530 - val_binary_accuracy: 0.9781
Epoch 3/10
291/291 [==============================] - 87s 298ms/step - loss: 0.0485 - binary_accuracy: 0.9806 - val_loss: 0.0469 - val_binary_accuracy: 0.9794
Epoch 4/10
291/291 [==============================] - 89s 304ms/step - loss: 0.0337 - binary_accuracy: 0.9874 - val_loss: 0.0445 - val_binary_accuracy: 0.9789
Epoch 5/10
291/291 [==============================] - 88s 303ms/step - loss: 0.0269 - binary_accuracy: 0.9875 - val_loss: 0.0414 - val_binary_accuracy: 0.9837
Epoch 6/10
291/291 [==============================] - 88s 303ms/step - loss: 0.0264 - binary_accuracy: 0.9909 - val_loss: 0.0482 - val_binary_accuracy: 0.9819
Epoch 7/10
291/291 [==============================] - 87s 301ms/step - loss: 0.0239 - binary_accuracy: 0.9918 - val_loss: 0.0431 - val_binary_accuracy: 0.9824
Epoch 8/10
291/291 [==============================] - 87s 301ms/step - loss: 0.0176 - binary_accuracy: 0.9925 - val_loss: 0.0445 - val_binary_accuracy: 0.9837
Epoch 9/10
291/291 [==============================] - 87s 300ms/step - loss: 0.0163 - binary_accuracy: 0.9942 - val_loss: 0.0454 - val_binary_accuracy: 0.9828
Epoch 10/10
291/291 [==============================] - 87s 300ms/step - loss: 0.0110 - binary_accuracy: 0.9962 - val_loss: 0.0428 - val_binary_accuracy: 0.9832
<tensorflow.python.keras.callbacks.History at 0x7fda3008a278>
 

以上



TensorFlow 2.4 : ガイド : Keras :- 貴方自身のコールバックを書く

TensorFlow 2.4 : ガイド : Keras :- 貴方自身のコールバックを書く (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 01/18/2021

* 本ページは、TensorFlow org サイトの Guide – Keras の以下のページを翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

無料セミナー実施中 クラスキャット主催 人工知能 & ビジネス Web セミナー

人工知能とビジネスをテーマにウェビナー (WEB セミナー) を定期的に開催しています。スケジュールは弊社 公式 Web サイト でご確認頂けます。
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
  • Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
クラスキャットは人工知能・テレワークに関する各種サービスを提供しております :

人工知能研究開発支援 人工知能研修サービス テレワーク & オンライン授業を支援
PoC(概念実証)を失敗させないための支援 (本支援はセミナーに参加しアンケートに回答した方を対象としています。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/
Facebook: https://www.facebook.com/ClassCatJP/

 

ガイド : Keras :- 貴方自身のコールバックを書く

イントロダクション

コールバックは 訓練、評価や推論の間の Keras モデルの動作をカスタマイズするためのパワフルなツールです。サンプルは訓練進捗と結果が TensorBoard でエクスポートされて可視化できる tf.keras.callbacks.TensorBoard や、訓練の間にモデルを定期的にセーブする tf.keras.callbacks.ModelCheckpoint を含みます。

このガイドでは、Keras コールバックが何か、それは何ができるか、そして貴方自身のものをどのように構築できるかを学習します。貴方を始めさせるために単純なコールバック・アプリケーションの幾つかのデモを提供します。

 

セットアップ

import tensorflow as tf
from tensorflow import keras

 

Keras コールバック概要

総てのコールバックは keras.callbacks.Callback クラスをサブクラス化して、そして訓練、テストと予測の様々なステージで呼び出されるメソッドのセットを override します。コールバックは訓練の間のモデルの内部状態と統計上のビューを得るために有用です。

コールバックのリストを (キーワード引数 callbacks として) 以下のモデル・メソッドに渡すことができます :

 

コールバック・メソッドの概要

グローバル・メソッド

on_(train|test|predict)_begin(self, logs=None)

fit/evaluate/predict の最初に呼び出されます。

on_(train|test|predict)_end(self, logs=None)

fit/evaluate/predict の最後に呼び出されます。

 

訓練/テスト/予測のためのバッチレベル・メソッド

on_(train|test|predict)_batch_begin(self, batch, logs=None)

訓練/テスト/予測の間のバッチを処理するすぐ前に呼び出されます。

on_(train|test|predict)_batch_end(self, batch, logs=None)

バッチを訓練/テスト/予測する最後に呼び出されます。このメソッド内では、logs はメトリクス結果を含む辞書です。

 

エポックレベル・メソッド (訓練 only)

on_epoch_begin(self, epoch, logs=None)

訓練の間にエポックの最初に呼び出されます。

on_epoch_end(self, epoch, logs=None)

訓練の間にエポックの最後に呼び出されます。

 

基本的なサンプル

具体的なサンプルを見ましょう。始めるために、tensorflow をインポートして単純な Sequential Keras モデルを定義しましょう :

# Define the Keras model to add callbacks to
def get_model():
    model = keras.Sequential()
    model.add(keras.layers.Dense(1, input_dim=784))
    model.compile(
        optimizer=keras.optimizers.RMSprop(learning_rate=0.1),
        loss="mean_squared_error",
        metrics=["mean_absolute_error"],
    )
    return model

それから訓練とテストのために Keras datasets API から MNIST データをロードします :

# Load example MNIST data and pre-process it
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train = x_train.reshape(-1, 784).astype("float32") / 255.0
x_test = x_test.reshape(-1, 784).astype("float32") / 255.0

# Limit the data to 1000 samples
x_train = x_train[:1000]
y_train = y_train[:1000]
x_test = x_test[:1000]
y_test = y_test[:1000]

今は、以下をログ出力する単純なカスタム・コールバックを定義します :

  • fit/evaluate/predict の最初と最後
  • 各エポックの最初 & 最後
  • 各訓練バッチの最初 & 最後
  • 各評価 (テスト) バッチの最初 & 最後
  • 各推論 (予測) バッチの最初 & 最後
class CustomCallback(keras.callbacks.Callback):
    def on_train_begin(self, logs=None):
        keys = list(logs.keys())
        print("Starting training; got log keys: {}".format(keys))

    def on_train_end(self, logs=None):
        keys = list(logs.keys())
        print("Stop training; got log keys: {}".format(keys))

    def on_epoch_begin(self, epoch, logs=None):
        keys = list(logs.keys())
        print("Start epoch {} of training; got log keys: {}".format(epoch, keys))

    def on_epoch_end(self, epoch, logs=None):
        keys = list(logs.keys())
        print("End epoch {} of training; got log keys: {}".format(epoch, keys))

    def on_test_begin(self, logs=None):
        keys = list(logs.keys())
        print("Start testing; got log keys: {}".format(keys))

    def on_test_end(self, logs=None):
        keys = list(logs.keys())
        print("Stop testing; got log keys: {}".format(keys))

    def on_predict_begin(self, logs=None):
        keys = list(logs.keys())
        print("Start predicting; got log keys: {}".format(keys))

    def on_predict_end(self, logs=None):
        keys = list(logs.keys())
        print("Stop predicting; got log keys: {}".format(keys))

    def on_train_batch_begin(self, batch, logs=None):
        keys = list(logs.keys())
        print("...Training: start of batch {}; got log keys: {}".format(batch, keys))

    def on_train_batch_end(self, batch, logs=None):
        keys = list(logs.keys())
        print("...Training: end of batch {}; got log keys: {}".format(batch, keys))

    def on_test_batch_begin(self, batch, logs=None):
        keys = list(logs.keys())
        print("...Evaluating: start of batch {}; got log keys: {}".format(batch, keys))

    def on_test_batch_end(self, batch, logs=None):
        keys = list(logs.keys())
        print("...Evaluating: end of batch {}; got log keys: {}".format(batch, keys))

    def on_predict_batch_begin(self, batch, logs=None):
        keys = list(logs.keys())
        print("...Predicting: start of batch {}; got log keys: {}".format(batch, keys))

    def on_predict_batch_end(self, batch, logs=None):
        keys = list(logs.keys())
        print("...Predicting: end of batch {}; got log keys: {}".format(batch, keys))

Let’s try it out:

model = get_model()
model.fit(
    x_train,
    y_train,
    batch_size=128,
    epochs=1,
    verbose=0,
    validation_split=0.5,
    callbacks=[CustomCallback()],
)

res = model.evaluate(
    x_test, y_test, batch_size=128, verbose=0, callbacks=[CustomCallback()]
)

res = model.predict(x_test, batch_size=128, callbacks=[CustomCallback()])
Starting training; got log keys: []
Start epoch 0 of training; got log keys: []
...Training: start of batch 0; got log keys: []
...Training: end of batch 0; got log keys: ['loss', 'mean_absolute_error']
...Training: start of batch 1; got log keys: []
...Training: end of batch 1; got log keys: ['loss', 'mean_absolute_error']
...Training: start of batch 2; got log keys: []
...Training: end of batch 2; got log keys: ['loss', 'mean_absolute_error']
...Training: start of batch 3; got log keys: []
...Training: end of batch 3; got log keys: ['loss', 'mean_absolute_error']
Start testing; got log keys: []
...Evaluating: start of batch 0; got log keys: []
...Evaluating: end of batch 0; got log keys: ['loss', 'mean_absolute_error']
...Evaluating: start of batch 1; got log keys: []
...Evaluating: end of batch 1; got log keys: ['loss', 'mean_absolute_error']
...Evaluating: start of batch 2; got log keys: []
...Evaluating: end of batch 2; got log keys: ['loss', 'mean_absolute_error']
...Evaluating: start of batch 3; got log keys: []
...Evaluating: end of batch 3; got log keys: ['loss', 'mean_absolute_error']
Stop testing; got log keys: ['loss', 'mean_absolute_error']
End epoch 0 of training; got log keys: ['loss', 'mean_absolute_error', 'val_loss', 'val_mean_absolute_error']
Stop training; got log keys: ['loss', 'mean_absolute_error', 'val_loss', 'val_mean_absolute_error']
Start testing; got log keys: []
...Evaluating: start of batch 0; got log keys: []
...Evaluating: end of batch 0; got log keys: ['loss', 'mean_absolute_error']
...Evaluating: start of batch 1; got log keys: []
...Evaluating: end of batch 1; got log keys: ['loss', 'mean_absolute_error']
...Evaluating: start of batch 2; got log keys: []
...Evaluating: end of batch 2; got log keys: ['loss', 'mean_absolute_error']
...Evaluating: start of batch 3; got log keys: []
...Evaluating: end of batch 3; got log keys: ['loss', 'mean_absolute_error']
...Evaluating: start of batch 4; got log keys: []
...Evaluating: end of batch 4; got log keys: ['loss', 'mean_absolute_error']
...Evaluating: start of batch 5; got log keys: []
...Evaluating: end of batch 5; got log keys: ['loss', 'mean_absolute_error']
...Evaluating: start of batch 6; got log keys: []
...Evaluating: end of batch 6; got log keys: ['loss', 'mean_absolute_error']
...Evaluating: start of batch 7; got log keys: []
...Evaluating: end of batch 7; got log keys: ['loss', 'mean_absolute_error']
Stop testing; got log keys: ['loss', 'mean_absolute_error']
Start predicting; got log keys: []
...Predicting: start of batch 0; got log keys: []
...Predicting: end of batch 0; got log keys: ['outputs']
...Predicting: start of batch 1; got log keys: []
...Predicting: end of batch 1; got log keys: ['outputs']
...Predicting: start of batch 2; got log keys: []
...Predicting: end of batch 2; got log keys: ['outputs']
...Predicting: start of batch 3; got log keys: []
...Predicting: end of batch 3; got log keys: ['outputs']
...Predicting: start of batch 4; got log keys: []
...Predicting: end of batch 4; got log keys: ['outputs']
...Predicting: start of batch 5; got log keys: []
...Predicting: end of batch 5; got log keys: ['outputs']
...Predicting: start of batch 6; got log keys: []
...Predicting: end of batch 6; got log keys: ['outputs']
...Predicting: start of batch 7; got log keys: []
...Predicting: end of batch 7; got log keys: ['outputs']
Stop predicting; got log keys: []

 

logs 辞書の使用方法

logs 辞書はバッチかエポックの最後の損失値と総てのメトリックを含みます。サンプルは損失と平均絶対誤差 (MAE) を含みます。

class LossAndErrorPrintingCallback(keras.callbacks.Callback):
    def on_train_batch_end(self, batch, logs=None):
        print("For batch {}, loss is {:7.2f}.".format(batch, logs["loss"]))

    def on_test_batch_end(self, batch, logs=None):
        print("For batch {}, loss is {:7.2f}.".format(batch, logs["loss"]))

    def on_epoch_end(self, epoch, logs=None):
        print(
            "The average loss for epoch {} is {:7.2f} "
            "and mean absolute error is {:7.2f}.".format(
                epoch, logs["loss"], logs["mean_absolute_error"]
            )
        )


model = get_model()
model.fit(
    x_train,
    y_train,
    batch_size=128,
    epochs=2,
    verbose=0,
    callbacks=[LossAndErrorPrintingCallback()],
)

res = model.evaluate(
    x_test,
    y_test,
    batch_size=128,
    verbose=0,
    callbacks=[LossAndErrorPrintingCallback()],
)
For batch 0, loss is   28.35.
For batch 1, loss is  495.31.
For batch 2, loss is  338.58.
For batch 3, loss is  256.09.
For batch 4, loss is  206.61.
For batch 5, loss is  173.36.
For batch 6, loss is  149.41.
For batch 7, loss is  134.53.
The average loss for epoch 0 is  134.53 and mean absolute error is    6.17.
For batch 0, loss is    5.50.
For batch 1, loss is    5.29.
For batch 2, loss is    5.42.
For batch 3, loss is    5.37.
For batch 4, loss is    5.01.
For batch 5, loss is    4.77.
For batch 6, loss is    4.67.
For batch 7, loss is    4.74.
The average loss for epoch 1 is    4.74 and mean absolute error is    1.78.
For batch 0, loss is    5.57.
For batch 1, loss is    5.10.
For batch 2, loss is    5.00.
For batch 3, loss is    4.99.
For batch 4, loss is    5.14.
For batch 5, loss is    5.18.
For batch 6, loss is    5.13.
For batch 7, loss is    5.07.

 

self.model の使用方法

これらのメソッドの一つが呼び出されるとき、ログ情報を受け取ることに加えて、コールバックは訓練/評価/推論の現在のラウンドに関連するモデルへのアクセスを持ちます : self.model です。

コールバック内で self.model で行なうことができることの幾つかがあります :

  • 訓練を直ちに中断するために self.model.stop_training = True を設定します。
  • self.model.optimizer.learning_rate のような、(self.model.optimizer として利用可能な) optimizer のハイパーパラメータを変化させます。
  • 定期的な間隔でモデルをセーブします。
  • 訓練の間の正当性チェックとして使用するため、各エポックの最後で幾つかのテストサンプル上 model.predict() の出力を記録します。
  • 時間につれてモデルが何を学習しているか監視するため、各エポックの最後に中間特徴の可視化を抽出します。
  • 等々。

これを 2, 3 のサンプルで実際に見ましょう。

 

Keras コールバック・アプリケーションのサンプル

最小損失における Early Stopping

最初の例はコールバックの作成を示します、これは損失の最小に達したとき属性 self.model.stop_training (boolean) を設定することにより訓練を停止します。オプションで、ローカル最小に達した後で停止する前に幾つのエポックを待つべきかを指定するための引数 patience を貴方は提供できます。

tf.keras.callbacks.EarlyStopping はより完全で一般的な実装を提供します。

import numpy as np


class EarlyStoppingAtMinLoss(keras.callbacks.Callback):
    """Stop training when the loss is at its min, i.e. the loss stops decreasing.

  Arguments:
      patience: Number of epochs to wait after min has been hit. After this
      number of no improvement, training stops.
  """

    def __init__(self, patience=0):
        super(EarlyStoppingAtMinLoss, self).__init__()
        self.patience = patience
        # best_weights to store the weights at which the minimum loss occurs.
        self.best_weights = None

    def on_train_begin(self, logs=None):
        # The number of epoch it has waited when loss is no longer minimum.
        self.wait = 0
        # The epoch the training stops at.
        self.stopped_epoch = 0
        # Initialize the best as infinity.
        self.best = np.Inf

    def on_epoch_end(self, epoch, logs=None):
        current = logs.get("loss")
        if np.less(current, self.best):
            self.best = current
            self.wait = 0
            # Record the best weights if current results is better (less).
            self.best_weights = self.model.get_weights()
        else:
            self.wait += 1
            if self.wait >= self.patience:
                self.stopped_epoch = epoch
                self.model.stop_training = True
                print("Restoring model weights from the end of the best epoch.")
                self.model.set_weights(self.best_weights)

    def on_train_end(self, logs=None):
        if self.stopped_epoch > 0:
            print("Epoch %05d: early stopping" % (self.stopped_epoch + 1))


model = get_model()
model.fit(
    x_train,
    y_train,
    batch_size=64,
    steps_per_epoch=5,
    epochs=30,
    verbose=0,
    callbacks=[LossAndErrorPrintingCallback(), EarlyStoppingAtMinLoss()],
)
For batch 0, loss is   31.46.
For batch 1, loss is  431.85.
For batch 2, loss is  299.28.
For batch 3, loss is  227.94.
For batch 4, loss is  184.11.
The average loss for epoch 0 is  184.11 and mean absolute error is    8.47.
For batch 0, loss is    7.22.
For batch 1, loss is    6.92.
For batch 2, loss is    6.74.
For batch 3, loss is    6.27.
For batch 4, loss is    6.40.
The average loss for epoch 1 is    6.40 and mean absolute error is    2.04.
For batch 0, loss is    5.56.
For batch 1, loss is    6.04.
For batch 2, loss is    5.59.
For batch 3, loss is    5.10.
For batch 4, loss is    4.92.
The average loss for epoch 2 is    4.92 and mean absolute error is    1.79.
For batch 0, loss is    5.98.
For batch 1, loss is    4.58.
For batch 2, loss is    6.00.
For batch 3, loss is    8.78.
For batch 4, loss is   13.73.
The average loss for epoch 3 is   13.73 and mean absolute error is    2.96.
Restoring model weights from the end of the best epoch.
Epoch 00004: early stopping
<tensorflow.python.keras.callbacks.History at 0x7f77f173fe48>

 

学習率スケジューリング

この例では、カスタム・コールバックが訓練の過程において optimizer の学習率を動的に変更するためにどのように使用できるかを示しています。

より一般的な実装については callbacks.LearningRateScheduler を見てください。

class CustomLearningRateScheduler(keras.callbacks.Callback):
    """Learning rate scheduler which sets the learning rate according to schedule.

  Arguments:
      schedule: a function that takes an epoch index
          (integer, indexed from 0) and current learning rate
          as inputs and returns a new learning rate as output (float).
  """

    def __init__(self, schedule):
        super(CustomLearningRateScheduler, self).__init__()
        self.schedule = schedule

    def on_epoch_begin(self, epoch, logs=None):
        if not hasattr(self.model.optimizer, "lr"):
            raise ValueError('Optimizer must have a "lr" attribute.')
        # Get the current learning rate from model's optimizer.
        lr = float(tf.keras.backend.get_value(self.model.optimizer.learning_rate))
        # Call schedule function to get the scheduled learning rate.
        scheduled_lr = self.schedule(epoch, lr)
        # Set the value back to the optimizer before this epoch starts
        tf.keras.backend.set_value(self.model.optimizer.lr, scheduled_lr)
        print("\nEpoch %05d: Learning rate is %6.4f." % (epoch, scheduled_lr))


LR_SCHEDULE = [
    # (epoch to start, learning rate) tuples
    (3, 0.05),
    (6, 0.01),
    (9, 0.005),
    (12, 0.001),
]


def lr_schedule(epoch, lr):
    """Helper function to retrieve the scheduled learning rate based on epoch."""
    if epoch < LR_SCHEDULE[0][0] or epoch > LR_SCHEDULE[-1][0]:
        return lr
    for i in range(len(LR_SCHEDULE)):
        if epoch == LR_SCHEDULE[i][0]:
            return LR_SCHEDULE[i][1]
    return lr


model = get_model()
model.fit(
    x_train,
    y_train,
    batch_size=64,
    steps_per_epoch=5,
    epochs=15,
    verbose=0,
    callbacks=[
        LossAndErrorPrintingCallback(),
        CustomLearningRateScheduler(lr_schedule),
    ],
)
Epoch 00000: Learning rate is 0.1000.
For batch 0, loss is   32.28.
For batch 1, loss is  477.32.
For batch 2, loss is  327.87.
For batch 3, loss is  248.14.
For batch 4, loss is  199.64.
The average loss for epoch 0 is  199.64 and mean absolute error is    8.51.

Epoch 00001: Learning rate is 0.1000.
For batch 0, loss is    7.94.
For batch 1, loss is    6.56.
For batch 2, loss is    6.11.
For batch 3, loss is    5.98.
For batch 4, loss is    5.82.
The average loss for epoch 1 is    5.82 and mean absolute error is    1.97.

Epoch 00002: Learning rate is 0.1000.
For batch 0, loss is    5.04.
For batch 1, loss is    4.87.
For batch 2, loss is    5.04.
For batch 3, loss is    4.71.
For batch 4, loss is    4.68.
The average loss for epoch 2 is    4.68 and mean absolute error is    1.76.

Epoch 00003: Learning rate is 0.0500.
For batch 0, loss is    5.27.
For batch 1, loss is    4.22.
For batch 2, loss is    4.10.
For batch 3, loss is    4.00.
For batch 4, loss is    3.93.
The average loss for epoch 3 is    3.93 and mean absolute error is    1.59.

Epoch 00004: Learning rate is 0.0500.
For batch 0, loss is    3.29.
For batch 1, loss is    3.94.
For batch 2, loss is    3.96.
For batch 3, loss is    4.24.
For batch 4, loss is    4.32.
The average loss for epoch 4 is    4.32 and mean absolute error is    1.63.

Epoch 00005: Learning rate is 0.0500.
For batch 0, loss is    4.12.
For batch 1, loss is    4.32.
For batch 2, loss is    4.80.
For batch 3, loss is    5.20.
For batch 4, loss is    5.33.
The average loss for epoch 5 is    5.33 and mean absolute error is    1.84.

Epoch 00006: Learning rate is 0.0100.
For batch 0, loss is    5.05.
For batch 1, loss is    4.55.
For batch 2, loss is    3.81.
For batch 3, loss is    3.58.
For batch 4, loss is    3.53.
The average loss for epoch 6 is    3.53 and mean absolute error is    1.47.

Epoch 00007: Learning rate is 0.0100.
For batch 0, loss is    3.30.
For batch 1, loss is    4.03.
For batch 2, loss is    3.57.
For batch 3, loss is    3.48.
For batch 4, loss is    3.39.
The average loss for epoch 7 is    3.39 and mean absolute error is    1.45.

Epoch 00008: Learning rate is 0.0100.
For batch 0, loss is    2.76.
For batch 1, loss is    3.08.
For batch 2, loss is    2.95.
For batch 3, loss is    3.00.
For batch 4, loss is    3.16.
The average loss for epoch 8 is    3.16 and mean absolute error is    1.39.

Epoch 00009: Learning rate is 0.0050.
For batch 0, loss is    3.62.
For batch 1, loss is    4.01.
For batch 2, loss is    4.27.
For batch 3, loss is    3.95.
For batch 4, loss is    3.71.
The average loss for epoch 9 is    3.71 and mean absolute error is    1.48.

Epoch 00010: Learning rate is 0.0050.
For batch 0, loss is    3.41.
For batch 1, loss is    2.97.
For batch 2, loss is    2.78.
For batch 3, loss is    2.98.
For batch 4, loss is    3.01.
The average loss for epoch 10 is    3.01 and mean absolute error is    1.34.

Epoch 00011: Learning rate is 0.0050.
For batch 0, loss is    3.48.
For batch 1, loss is    3.39.
For batch 2, loss is    3.24.
For batch 3, loss is    3.20.
For batch 4, loss is    3.43.
The average loss for epoch 11 is    3.43 and mean absolute error is    1.44.

Epoch 00012: Learning rate is 0.0010.
For batch 0, loss is    3.49.
For batch 1, loss is    3.63.
For batch 2, loss is    3.23.
For batch 3, loss is    3.22.
For batch 4, loss is    3.19.
The average loss for epoch 12 is    3.19 and mean absolute error is    1.37.

Epoch 00013: Learning rate is 0.0010.
For batch 0, loss is    2.18.
For batch 1, loss is    2.91.
For batch 2, loss is    2.87.
For batch 3, loss is    3.00.
For batch 4, loss is    3.16.
The average loss for epoch 13 is    3.16 and mean absolute error is    1.34.

Epoch 00014: Learning rate is 0.0010.
For batch 0, loss is    2.55.
For batch 1, loss is    3.37.
For batch 2, loss is    3.27.
For batch 3, loss is    3.41.
For batch 4, loss is    3.29.
The average loss for epoch 14 is    3.29 and mean absolute error is    1.39.
<tensorflow.python.keras.callbacks.History at 0x7f77f1683b38>

 

組込み Keras コールバック

API doc を読むことにより既存の Keras コールバックを確実に調べてください。アプリケーションは CSV へのロギング、モデルのセーブ、TensorBoard 内の可視化、そしてそれ以上を含みます!

 

以上



TensorFlow 2.4 : ガイド : Keras :- Keras でマスキングとパディング

TensorFlow 2.4 : ガイド : Keras :- Keras でマスキングとパディング (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 01/17/2021

* 本ページは、TensorFlow org サイトの Guide – Keras の以下のページを翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

無料セミナー実施中 クラスキャット主催 人工知能 & ビジネス Web セミナー

人工知能とビジネスをテーマにウェビナー (WEB セミナー) を定期的に開催しています。スケジュールは弊社 公式 Web サイト でご確認頂けます。
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
  • Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
クラスキャットは人工知能・テレワークに関する各種サービスを提供しております :

人工知能研究開発支援 人工知能研修サービス テレワーク & オンライン授業を支援
PoC(概念実証)を失敗させないための支援 (本支援はセミナーに参加しアンケートに回答した方を対象としています。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/
Facebook: https://www.facebook.com/ClassCatJP/

 

ガイド : Keras :- Keras でマスキングとパディング

セットアップ

import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

 

イントロダクション

マスキング はシークエンス処理層に入力のある時間ステップは欠損していて、そのためにデータを処理するときにスキップされるべきであることを伝える方法です。

パディング はマスキングの特殊な形式で、そこではマスクされるステップはシークエンスの最初か最後です。パディングはシークエンスデータを連続するバッチにエンコードするための必要性に由来します : バッチの総てのシークエンスを与えられた標準的な長さにするためには、幾つかのシークエンスをパッドしたり切り詰める (= truncate) 必要あります。

Let’s take a close look.

 

シークエンス・データをパディング

シークエンス・データを処理するとき、個々のサンプルが異なる長さを持つことは非常に一般的です。次のサンプルを考えます (単語としてトークン化されたテキスト) :

[
  ["Hello", "world", "!"],
  ["How", "are", "you", "doing", "today"],
  ["The", "weather", "will", "be", "nice", "tomorrow"],
]

語彙検索の後、データは整数としてベクトル化されるかもしれません、例えば :

[
  [83, 91, 1, 645, 1253, 927],
  [73, 8, 3215, 55, 927],
  [71, 1331, 4231]
]

データはネストされたリストで、そこでは個々のサンプルはそれぞれ長さ 3, 5 と 6 を持ちます。深層学習モデルのための入力データは (この場合 e.g. (batch_size, 6, vocab_size) の shape の) 単一の tensorでなければならないので、最長の項目よりも短いサンプルはあるプレースホルダー値でパディングされる必要があります (代替的に、短いサンプルをパッドする前に長いサンプルを切る詰めるかもしれません)。

Keras は共通の長さに Python リストを切り詰めてパッドするユティリティ関数を提供します : tf.keras.preprocessing.sequence.pad_sequences です。

raw_inputs = [
    [711, 632, 71],
    [73, 8, 3215, 55, 927],
    [83, 91, 1, 645, 1253, 927],
]

# By default, this will pad using 0s; it is configurable via the
# "value" parameter.
# Note that you could "pre" padding (at the beginning) or
# "post" padding (at the end).
# We recommend using "post" padding when working with RNN layers
# (in order to be able to use the
# CuDNN implementation of the layers).
padded_inputs = tf.keras.preprocessing.sequence.pad_sequences(
    raw_inputs, padding="post"
)
print(padded_inputs)
[[ 711  632   71    0    0    0]
 [  73    8 3215   55  927    0]
 [  83   91    1  645 1253  927]]

 

マスキング

総てのサンプルが一様な (= uniform) 長さを持つ今、モデルはデータのある部分は実際にはパディングで無視されるべきであることを知らされなければなりません。そのメカニズムが マスキング です。

Keras モデルで入力マスクを導入するには 3 つの方法があります :

  • keras.layers.Masking 層を追加する。
  • keras.layers.Embedding 層を mask_zero=True で configure する。
  • mask 引数を手動で渡します、この引数をサポートする層を呼び出すとき (e.g. RNN 層)。

 

マスク生成層 : Embedding と Masking

内部的には、これらの層はマスク tensor (shape (batch, sequence_length) の 2D tensor) を作成し、そしてそれを Masking か Embedding 層により返される tensor 出力に装着します。

embedding = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)
masked_output = embedding(padded_inputs)

print(masked_output._keras_mask)

masking_layer = layers.Masking()
# Simulate the embedding lookup by expanding the 2D input to 3D,
# with embedding dimension of 10.
unmasked_embedding = tf.cast(
    tf.tile(tf.expand_dims(padded_inputs, axis=-1), [1, 1, 10]), tf.float32
)

masked_embedding = masking_layer(unmasked_embedding)
print(masked_embedding._keras_mask)
tf.Tensor(
[[ True  True  True False False False]
 [ True  True  True  True  True False]
 [ True  True  True  True  True  True]], shape=(3, 6), dtype=bool)
tf.Tensor(
[[ True  True  True False False False]
 [ True  True  True  True  True False]
 [ True  True  True  True  True  True]], shape=(3, 6), dtype=bool)

プリントされた結果から見れるように、マスクは shape (batch_size, sequence_length) を持つ 2D ブーリアン tensor で、そこでは各個々の False エントリは対応する時間ステップは処理の間無視されるべきであることを示します。

 

Functional API と Sequential API のマスク伝播

Functional API か Sequential API を使用するとき、Embedding か Masking 層により生成されたマスクはそれらを使用できる任意の層 (例えば、RNN 層) に対してネットワークを通して伝播されます。Keras は入力に対応するマスクを自動的に取得してそれをどのように使用するかを知る任意の層に渡します。

例えば、次の Sequential モデルでは、LSTM 層は自動的にマスクを受け取ります、それはそれがパッドされた値を無視することを意味します :

model = keras.Sequential(
    [layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True), layers.LSTM(32),]
)

これはまた次の Functional API モデルにも当てはまります :

inputs = keras.Input(shape=(None,), dtype="int32")
x = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)(inputs)
outputs = layers.LSTM(32)(x)

model = keras.Model(inputs, outputs)

 

マスク tensor を直接的に層に渡す

(LSTM 層のような) マスクを処理できる層は __call__ メソッドで mask 引数を持ちます。

一方で、マスクを生成する層 (e.g. Embedding) は呼び出せる compute_mask(input, previous_mask) メソッドを公開します。

こうして、マスク生成層の compute_mask() メソッドの出力をマスク消費層の __call__ メソッドに渡すことができます、このようにです :

class MyLayer(layers.Layer):
    def __init__(self, **kwargs):
        super(MyLayer, self).__init__(**kwargs)
        self.embedding = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)
        self.lstm = layers.LSTM(32)

    def call(self, inputs):
        x = self.embedding(inputs)
        # Note that you could also prepare a `mask` tensor manually.
        # It only needs to be a boolean tensor
        # with the right shape, i.e. (batch_size, timesteps).
        mask = self.embedding.compute_mask(inputs)
        output = self.lstm(x, mask=mask)  # The layer will ignore the masked values
        return output


layer = MyLayer()
x = np.random.random((32, 10)) * 100
x = x.astype("int32")
layer(x)
<tf.Tensor: shape=(32, 32), dtype=float32, numpy=
array([[ 5.17572416e-03, -2.62944121e-03, -1.35105345e-02, ...,
        -4.60889516e-03,  5.47572691e-03,  4.72571421e-03],
       [ 1.64351589e-03, -4.22070269e-03, -5.25859464e-03, ...,
         2.60032900e-03,  1.49587099e-03, -4.24058782e-03],
       [-4.32722829e-03,  1.06376233e-02,  1.42529635e-02, ...,
         2.60018371e-03,  7.20684696e-03,  1.94990877e-02],
       ...,
       [ 4.70825378e-03, -1.18944524e-02, -1.36345504e-02, ...,
        -1.12335877e-02,  3.96840260e-05, -3.31538590e-03],
       [-1.87381962e-03,  4.51480271e-03, -1.13129790e-03, ...,
         4.01536841e-03,  1.47789170e-03,  7.81966280e-03],
       [-6.34184107e-03, -1.26939616e-03,  2.88227363e-03, ...,
        -4.33679670e-03, -6.08236482e-03, -4.22849880e-05]], dtype=float32)>

 

貴方のカスタム層でマスキングをサポートする

時に貴方は (Embedding のような) マスクを生成する層や、現在のマスクを変更する必要がある層を書く必要があるかもしれません。

例えば、時間次元上で連結する Concatenate 層のような、入力とは異なる時間次元を持つ tensor を生成する任意の層は現在のマスクを変更する必要があります、その結果下流の層はマスクされた時間ステップを正しく考慮に入れることができます。

これを行なうために、貴方の層は layer.compute_mask() メソッドを実装するべきです、これは入力と現在のマスクが与えられたとき新しいマスクを生成します。

ここに現在のマスクを変更する必要がある TemporalSplit 層のサンプルがあります。

class TemporalSplit(keras.layers.Layer):
    """Split the input tensor into 2 tensors along the time dimension."""

    def call(self, inputs):
        # Expect the input to be 3D and mask to be 2D, split the input tensor into 2
        # subtensors along the time axis (axis 1).
        return tf.split(inputs, 2, axis=1)

    def compute_mask(self, inputs, mask=None):
        # Also split the mask into 2 if it presents.
        if mask is None:
            return None
        return tf.split(mask, 2, axis=1)


first_half, second_half = TemporalSplit()(masked_embedding)
print(first_half._keras_mask)
print(second_half._keras_mask)
tf.Tensor(
[[ True  True  True]
 [ True  True  True]
 [ True  True  True]], shape=(3, 3), dtype=bool)
tf.Tensor(
[[False False False]
 [ True  True False]
 [ True  True  True]], shape=(3, 3), dtype=bool)

ここに入力値からマスクを生成できる CustomEmbedding 層のもう一つのサンプルがあります :

class CustomEmbedding(keras.layers.Layer):
    def __init__(self, input_dim, output_dim, mask_zero=False, **kwargs):
        super(CustomEmbedding, self).__init__(**kwargs)
        self.input_dim = input_dim
        self.output_dim = output_dim
        self.mask_zero = mask_zero

    def build(self, input_shape):
        self.embeddings = self.add_weight(
            shape=(self.input_dim, self.output_dim),
            initializer="random_normal",
            dtype="float32",
        )

    def call(self, inputs):
        return tf.nn.embedding_lookup(self.embeddings, inputs)

    def compute_mask(self, inputs, mask=None):
        if not self.mask_zero:
            return None
        return tf.not_equal(inputs, 0)


layer = CustomEmbedding(10, 32, mask_zero=True)
x = np.random.random((3, 10)) * 9
x = x.astype("int32")

y = layer(x)
mask = layer.compute_mask(x)

print(mask)
tf.Tensor(
[[ True  True  True  True  True  True  True False  True  True]
 [ True  True False  True  True  True  True  True False  True]
 [False  True False  True False  True  True  True  True  True]], shape=(3, 10), dtype=bool)

 

互換な層上でマスク伝播にオプトインする

殆どの層は時間次元を変更しませんので、現在のマスクを変更する必要はありません。けれども、それらは依然として現在のマスクを、変更されないまま、次の層に 伝播 できることを望むかもしれません。これはオプトイン (= opt-in) 動作です。デフォルトでは、カスタム層は現在のマスクを壊します (何故ならばフレームワークはマスクの伝播が行なうに安全であるか識別する方法を持たないからです)。

時間次元を変更しないカスタム層を持つ場合、そしてそれに現在の入力マスクを伝播できることを望む場合、層コンストラクタで self.supports_masking = True を設定するべきです。この場合、compute_mask() のデフォルト動作は単に現在のマスクを通過することです。

ここにマスク伝播のためにホワイトリストに登録された層のサンプルがあります :

class MyActivation(keras.layers.Layer):
    def __init__(self, **kwargs):
        super(MyActivation, self).__init__(**kwargs)
        # Signal that the layer is safe for mask propagation
        self.supports_masking = True

    def call(self, inputs):
        return tf.nn.relu(inputs)

今では (Embedding のような) マスク生成層と (LSTM のような) マスク消費層を媒介するこのカスタム層を使用できて、そしてそれは一緒にマスクを渡します、その結果それはマスク消費層に到達します。

inputs = keras.Input(shape=(None,), dtype="int32")
x = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)(inputs)
x = MyActivation()(x)  # Will pass the mask along
print("Mask found:", x._keras_mask)
outputs = layers.LSTM(32)(x)  # Will receive the mask

model = keras.Model(inputs, outputs)
Mask found: KerasTensor(type_spec=TensorSpec(shape=(None, None), dtype=tf.bool, name=None), name='Placeholder_1:0')

 

マスク情報を必要とする層を書く

いくつかの層はマスク消費者です : それらは call で mask 引数を受け取りそしてそれを特定の時間ステップをスキップするかどうかを決定するために使用します。

そのような層を書くために、単純に call シグネチャで mask=None 引数を追加できます。入力に関連するマスクはそれが利用可能なときにいつでも層に渡されます。

下の単純なサンプルがここにあります : 層は入力シークエンスの時間次元に渡り softmax を計算する一方で、マスクされた時間ステップは破棄します。

class TemporalSoftmax(keras.layers.Layer):
    def call(self, inputs, mask=None):
        broadcast_float_mask = tf.expand_dims(tf.cast(mask, "float32"), -1)
        inputs_exp = tf.exp(inputs) * broadcast_float_mask
        inputs_sum = tf.reduce_sum(inputs * broadcast_float_mask, axis=1, keepdims=True)
        return inputs_exp / inputs_sum


inputs = keras.Input(shape=(None,), dtype="int32")
x = layers.Embedding(input_dim=10, output_dim=32, mask_zero=True)(inputs)
x = layers.Dense(1)(x)
outputs = TemporalSoftmax()(x)

model = keras.Model(inputs, outputs)
y = model(np.random.randint(0, 10, size=(32, 100)), np.random.random((32, 100, 1)))

 

要点

それが Keras でマスキングについて知る必要があることの総てです。まとめると :

  • 「マスキング」は層がシークエンス入力である時間ステップをいつスキップ/無視するかをどのように知ることができるかです。
  • 幾つかの層はマスク生成者 (-generator) です : Embedding は (mask_zero=True の場合) 入力値からマスクを生成できて、Masking 層もそれができます。
  • 幾つかの層はマスク消費者 (-consumer) です : それらは __call__ メソッドで mask 引数を公開します。これは RNN 層が当てはまります。
  • Functional API と Sequential API では、マスク情報は自動的に伝播されます。
  • スタンドアロンな方法で層を使用するとき、層に mask 引数を手動で渡すことができます。
  • 現在のマスクを変更する層を容易に書くことができます、それは新しいマスクを生成するか、入力に関連するマスクを消費します。
 

以上



TensorFlow 2.4 : ガイド : Keras :- 訓練ループをスクラッチから書く

TensorFlow 2.0 : ガイド : Keras :- 訓練ループをスクラッチから書く (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 01/16/2021

* 本ページは、TensorFlow org サイトの Guide – Keras の以下のページを翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

無料セミナー実施中 クラスキャット主催 人工知能 & ビジネス Web セミナー

人工知能とビジネスをテーマにウェビナー (WEB セミナー) を定期的に開催しています。スケジュールは弊社 公式 Web サイト でご確認頂けます。
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
  • Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
クラスキャットは人工知能・テレワークに関する各種サービスを提供しております :

人工知能研究開発支援 人工知能研修サービス テレワーク & オンライン授業を支援
PoC(概念実証)を失敗させないための支援 (本支援はセミナーに参加しアンケートに回答した方を対象としています。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/
Facebook: https://www.facebook.com/ClassCatJP/

 

ガイド : Keras :- 訓練ループをスクラッチから書く

セットアップ

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import numpy as np

 

イントロダクション

Keras はデフォルトの訓練と評価ループ、fit() と evaluate() を提供します、それらの使用方法はガイド 組込みメソッドで訓練と評価 でカバーされます。

(例えば、fit() を使用して GAN を訓練するために) fit() の便利さを依然として活用しながら貴方のモデルの学習アルゴリズムをカスタマイズすることを望む場合、Model クラスをサブクラス化して貴方自身の train_step() メソッドを実装できます、これは fit() の間に繰り返し呼び出されます。これはガイド Model.fit で起きることをカスタマイズする でカバーされます。

今は、訓練 & 評価に渡り非常に低位な制御を望む場合、貴方自身の訓練 & 評価ループをスクラッチから書くべきです。このガイドはこれについてです。

 

GradientTape を使用する : 最初の end-to-end サンプル

GradientTape スコープ内でのモデルの呼び出しは損失値に関する層の訓練可能な重みの勾配を取得することを可能にします。optimizer インスタンスを使用して、これらの変数 (これらは model.trainable_weights を使用して取得できます) を更新するためにこれらの勾配を使用できます。

単純な MNIST モデルを考えましょう :

inputs = keras.Input(shape=(784,), name="digits")
x1 = layers.Dense(64, activation="relu")(inputs)
x2 = layers.Dense(64, activation="relu")(x1)
outputs = layers.Dense(10, name="predictions")(x2)
model = keras.Model(inputs=inputs, outputs=outputs)

カスタム訓練ループでミニバッチ勾配を使用してそれを訓練しましょう。

最初に、optimizer、損失関数、そしてデータセットを必要とします :

# Instantiate an optimizer.
optimizer = keras.optimizers.SGD(learning_rate=1e-3)
# Instantiate a loss function.
loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)

# Prepare the training dataset.
batch_size = 64
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train = np.reshape(x_train, (-1, 784))
x_test = np.reshape(x_test, (-1, 784))

# Reserve 10,000 samples for validation.
x_val = x_train[-10000:]
y_val = y_train[-10000:]
x_train = x_train[:-10000]
y_train = y_train[:-10000]

# Prepare the training dataset.
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(batch_size)

# Prepare the validation dataset.
val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))
val_dataset = val_dataset.batch(batch_size)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step

ここに私達の訓練ループがあります :

  • エポックに渡り反復する for ループを開始します。
  • 各エポックについて、データセットに渡り、バッチで反復する for ループを開始します。
  • 各バッチについて、GradientTape() スコープを開始します。
  • このスコープ内で、model (forward パス) を呼び出して損失を計算します。
  • スコープの外で、損失に関するモデルの重みの勾配を取得します。
  • 最後に、勾配に基づいてモデルの重みを更新するために optimizer を使用します。
epochs = 2
for epoch in range(epochs):
    print("\nStart of epoch %d" % (epoch,))

    # Iterate over the batches of the dataset.
    for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):

        # Open a GradientTape to record the operations run
        # during the forward pass, which enables auto-differentiation.
        with tf.GradientTape() as tape:

            # Run the forward pass of the layer.
            # The operations that the layer applies
            # to its inputs are going to be recorded
            # on the GradientTape.
            logits = model(x_batch_train, training=True)  # Logits for this minibatch

            # Compute the loss value for this minibatch.
            loss_value = loss_fn(y_batch_train, logits)

        # Use the gradient tape to automatically retrieve
        # the gradients of the trainable variables with respect to the loss.
        grads = tape.gradient(loss_value, model.trainable_weights)

        # Run one step of gradient descent by updating
        # the value of the variables to minimize the loss.
        optimizer.apply_gradients(zip(grads, model.trainable_weights))

        # Log every 200 batches.
        if step % 200 == 0:
            print(
                "Training loss (for one batch) at step %d: %.4f"
                % (step, float(loss_value))
            )
            print("Seen so far: %s samples" % ((step + 1) * 64))
Start of epoch 0
Training loss (for one batch) at step 0: 96.9620
Seen so far: 64 samples
Training loss (for one batch) at step 200: 1.8153
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 1.1619
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 1.1577
Seen so far: 38464 samples

Start of epoch 1
Training loss (for one batch) at step 0: 0.8497
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.6811
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 1.0251
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.6824
Seen so far: 38464 samples

 

メトリクスの低位処理

この基本的なループにメトリクス監視を追加しましょう。

スクラッチから書かれたそのような訓練ループで組込みメトリクス (or 貴方が書いたカスタムメトリクス) を容易に再利用できます。ここにフローがあります :

  • ループの開始でメトリックをインスタンス化します。
  • 各バッチ後に metric.update_state() を呼び出します。
  • メトリックの現在の値を表示する必要があるとき metric.result() を呼び出します。
  • メトリックの状態をクリアする必要があるとき (典型的にはエポックの最後) metric.reset_states() を呼び出します。

各エポックの最後に検証データ上 SparseCategoricalAccuracy を計算するためにこの知識を利用しましょう :

# Get model
inputs = keras.Input(shape=(784,), name="digits")
x = layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = layers.Dense(10, name="predictions")(x)
model = keras.Model(inputs=inputs, outputs=outputs)

# Instantiate an optimizer to train the model.
optimizer = keras.optimizers.SGD(learning_rate=1e-3)
# Instantiate a loss function.
loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)

# Prepare the metrics.
train_acc_metric = keras.metrics.SparseCategoricalAccuracy()
val_acc_metric = keras.metrics.SparseCategoricalAccuracy()

ここに私達の訓練 & 評価ループがあります :

import time

epochs = 2
for epoch in range(epochs):
    print("\nStart of epoch %d" % (epoch,))
    start_time = time.time()

    # Iterate over the batches of the dataset.
    for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):
        with tf.GradientTape() as tape:
            logits = model(x_batch_train, training=True)
            loss_value = loss_fn(y_batch_train, logits)
        grads = tape.gradient(loss_value, model.trainable_weights)
        optimizer.apply_gradients(zip(grads, model.trainable_weights))

        # Update training metric.
        train_acc_metric.update_state(y_batch_train, logits)

        # Log every 200 batches.
        if step % 200 == 0:
            print(
                "Training loss (for one batch) at step %d: %.4f"
                % (step, float(loss_value))
            )
            print("Seen so far: %d samples" % ((step + 1) * 64))

    # Display metrics at the end of each epoch.
    train_acc = train_acc_metric.result()
    print("Training acc over epoch: %.4f" % (float(train_acc),))

    # Reset training metrics at the end of each epoch
    train_acc_metric.reset_states()

    # Run a validation loop at the end of each epoch.
    for x_batch_val, y_batch_val in val_dataset:
        val_logits = model(x_batch_val, training=False)
        # Update val metrics
        val_acc_metric.update_state(y_batch_val, val_logits)
    val_acc = val_acc_metric.result()
    val_acc_metric.reset_states()
    print("Validation acc: %.4f" % (float(val_acc),))
    print("Time taken: %.2fs" % (time.time() - start_time))
Start of epoch 0
Training loss (for one batch) at step 0: 112.9925
Seen so far: 64 samples
Training loss (for one batch) at step 200: 1.3940
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 1.2486
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 1.9907
Seen so far: 38464 samples
Training acc over epoch: 0.6960
Validation acc: 0.7993
Time taken: 5.56s

Start of epoch 1
Training loss (for one batch) at step 0: 0.5860
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.9344
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.5471
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.6339
Seen so far: 38464 samples
Training acc over epoch: 0.8266
Validation acc: 0.8626
Time taken: 5.49s

 

貴方の訓練ステップを tf.function でスピードアップする

TensorFlow 2.0 のデフォルトのランタイムは eager 実行 です。そのようなものとして、上の訓練ループは eagerly に実行されます。

これはデバッグのためには素晴らしいですが、グラフコンパイルは明らかなパフォーマンス優位を持ちます。貴方の計算を静的グラフとして記述することはフレームワークがグローバルなパフォーマンス最適化を適用することを有効にします。これはフレームワークが (次に何が来るかの知識なしに) 1 つの演算を他の 1 つの後に貪欲に実行するように制約されているときには不可能です。

入力として tensor を取る任意の関数を静的グラフにコンパイルできます。その上に @tf.function デコレータを単に追加します、このようにです :

@tf.function
def train_step(x, y):
    with tf.GradientTape() as tape:
        logits = model(x, training=True)
        loss_value = loss_fn(y, logits)
    grads = tape.gradient(loss_value, model.trainable_weights)
    optimizer.apply_gradients(zip(grads, model.trainable_weights))
    train_acc_metric.update_state(y, logits)
    return loss_value

評価ステップでも同じことを行ないましょう :

@tf.function
def test_step(x, y):
    val_logits = model(x, training=False)
    val_acc_metric.update_state(y, val_logits)

今は、訓練ループをこのコンパイルされた訓練ステップで再実行しましょう :

import time

epochs = 2
for epoch in range(epochs):
    print("\nStart of epoch %d" % (epoch,))
    start_time = time.time()

    # Iterate over the batches of the dataset.
    for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):
        loss_value = train_step(x_batch_train, y_batch_train)

        # Log every 200 batches.
        if step % 200 == 0:
            print(
                "Training loss (for one batch) at step %d: %.4f"
                % (step, float(loss_value))
            )
            print("Seen so far: %d samples" % ((step + 1) * 64))

    # Display metrics at the end of each epoch.
    train_acc = train_acc_metric.result()
    print("Training acc over epoch: %.4f" % (float(train_acc),))

    # Reset training metrics at the end of each epoch
    train_acc_metric.reset_states()

    # Run a validation loop at the end of each epoch.
    for x_batch_val, y_batch_val in val_dataset:
        test_step(x_batch_val, y_batch_val)

    val_acc = val_acc_metric.result()
    val_acc_metric.reset_states()
    print("Validation acc: %.4f" % (float(val_acc),))
    print("Time taken: %.2fs" % (time.time() - start_time))
Start of epoch 0
Training loss (for one batch) at step 0: 0.8467
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.4002
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.7801
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.3799
Seen so far: 38464 samples
Training acc over epoch: 0.8683
Validation acc: 0.8857
Time taken: 1.38s

Start of epoch 1
Training loss (for one batch) at step 0: 0.7573
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.3093
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.3223
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.7105
Seen so far: 38464 samples
Training acc over epoch: 0.8869
Validation acc: 0.8948
Time taken: 1.03s

Much faster, isn’t it?

 

モデルにより追跡される損失の低位処理

層 & モデルは self.add_loss(value) を呼び出す層により forward パスの間に作成された任意の損失を再帰的に追跡します。スカラー損失値の結果としてのリストは forward パスの最後にプロパティ model.losses を通して利用可能です。

これらの損失成分を使用することを望む場合、訓練ステップ内でそれらを合計してそれらを主要損失に追加するべきです。

この層を考えます、これは activity 正則化損失を作成します :

class ActivityRegularizationLayer(layers.Layer):
    def call(self, inputs):
        self.add_loss(1e-2 * tf.reduce_sum(inputs))
        return inputs

それを使用する実際に単純なモデルを構築しましょう :

inputs = keras.Input(shape=(784,), name="digits")
x = layers.Dense(64, activation="relu")(inputs)
# Insert activity regularization as a layer
x = ActivityRegularizationLayer()(x)
x = layers.Dense(64, activation="relu")(x)
outputs = layers.Dense(10, name="predictions")(x)

model = keras.Model(inputs=inputs, outputs=outputs)

ここに今は訓練ステップがどのように見えるべきかがあります :

@tf.function
def train_step(x, y):
    with tf.GradientTape() as tape:
        logits = model(x, training=True)
        loss_value = loss_fn(y, logits)
        # Add any extra losses created during the forward pass.
        loss_value += sum(model.losses)
    grads = tape.gradient(loss_value, model.trainable_weights)
    optimizer.apply_gradients(zip(grads, model.trainable_weights))
    train_acc_metric.update_state(y, logits)
    return loss_value

 

要約

今では組込み訓練ループを使用することと貴方自身のものをスクラッチから書くことについて知るべき (存在する) 総てのことを貴方は知っています。

結論として、ここに単純な end-to-end サンプルがあります、これはこのガイドで学習した総てを一緒に結び付けています : MNIST 数字上で訓練された DCGAN です。

 

End-to-end サンプル : スクラッチからの GAN 訓練ループ

貴方は敵対的生成ネットワーク (GAN, Generative Adversarial Networks) に馴染みがあるかもしれません。GAN は画像の訓練データセットの潜在的分布を学習することにより (画像の「潜在的空間 (= latent space)」)、殆どリアルに見える新しい画像を生成できます。

GAN は 2 つのパートから成ります : “generator” モデル、これは潜在的空間のポイントを画像空間のポイントにマップし、そして “discrimintor” モデル、分類器は (訓練データセットからの) リアル画像と (generator ネットワークの出力) フェイク画像の間の違いを識別できます。

GAN 訓練はこのようなものです :

1) discriminator を訓練する。- 潜在的空間のランダムポイントのバッチをサンプリングします。- ポイントを “generator” モデルを通してフェイク画像に変えます。- リアル画像のバッチを得てそれらを生成された画像と結合します。- 生成された (画像) vs. リアル画像を分類するために “discriminator” モデルを訓練します。

2) generator を訓練する。- 潜在的空間のランダムポイントをサンプリングします。- ポイントを “generator” ネットワークを通してフェイク画像に変えます。- リアル画像のバッチを得てそれらを生成された画像と結合します。- discriminator を「騙して (= fool)」そしてフェイク画像をリアルとして分類するように “generator” モデルを訓練します。

GAN がどのように動作するかの遥かに詳細な概要については、Deep Learning with Python を見てください。

この訓練ループを実装しましょう。最初に、フェイク vs リアル数字を分類することを意図した discriminator を作成します :

discriminator = keras.Sequential(
    [
        keras.Input(shape=(28, 28, 1)),
        layers.Conv2D(64, (3, 3), strides=(2, 2), padding="same"),
        layers.LeakyReLU(alpha=0.2),
        layers.Conv2D(128, (3, 3), strides=(2, 2), padding="same"),
        layers.LeakyReLU(alpha=0.2),
        layers.GlobalMaxPooling2D(),
        layers.Dense(1),
    ],
    name="discriminator",
)
discriminator.summary()
Model: "discriminator"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 14, 14, 64)        640       
_________________________________________________________________
leaky_re_lu (LeakyReLU)      (None, 14, 14, 64)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 7, 7, 128)         73856     
_________________________________________________________________
leaky_re_lu_1 (LeakyReLU)    (None, 7, 7, 128)         0         
_________________________________________________________________
global_max_pooling2d (Global (None, 128)               0         
_________________________________________________________________
dense_4 (Dense)              (None, 1)                 129       
=================================================================
Total params: 74,625
Trainable params: 74,625
Non-trainable params: 0
_________________________________________________________________

そして generator ネットワークを生成しましょう、それは潜在ベクトルを (MNIST 数字を表す) shape (28, 28, 1) の出力に変えます :

latent_dim = 128

generator = keras.Sequential(
    [
        keras.Input(shape=(latent_dim,)),
        # We want to generate 128 coefficients to reshape into a 7x7x128 map
        layers.Dense(7 * 7 * 128),
        layers.LeakyReLU(alpha=0.2),
        layers.Reshape((7, 7, 128)),
        layers.Conv2DTranspose(128, (4, 4), strides=(2, 2), padding="same"),
        layers.LeakyReLU(alpha=0.2),
        layers.Conv2DTranspose(128, (4, 4), strides=(2, 2), padding="same"),
        layers.LeakyReLU(alpha=0.2),
        layers.Conv2D(1, (7, 7), padding="same", activation="sigmoid"),
    ],
    name="generator",
)

ここにキービット (= key bit) があります : 訓練ループです。見れるようにそれは非常に簡単です。訓練ステップ関数は 17 行だけを取ります。

# Instantiate one optimizer for the discriminator and another for the generator.
d_optimizer = keras.optimizers.Adam(learning_rate=0.0003)
g_optimizer = keras.optimizers.Adam(learning_rate=0.0004)

# Instantiate a loss function.
loss_fn = keras.losses.BinaryCrossentropy(from_logits=True)


@tf.function
def train_step(real_images):
    # Sample random points in the latent space
    random_latent_vectors = tf.random.normal(shape=(batch_size, latent_dim))
    # Decode them to fake images
    generated_images = generator(random_latent_vectors)
    # Combine them with real images
    combined_images = tf.concat([generated_images, real_images], axis=0)

    # Assemble labels discriminating real from fake images
    labels = tf.concat(
        [tf.ones((batch_size, 1)), tf.zeros((real_images.shape[0], 1))], axis=0
    )
    # Add random noise to the labels - important trick!
    labels += 0.05 * tf.random.uniform(labels.shape)

    # Train the discriminator
    with tf.GradientTape() as tape:
        predictions = discriminator(combined_images)
        d_loss = loss_fn(labels, predictions)
    grads = tape.gradient(d_loss, discriminator.trainable_weights)
    d_optimizer.apply_gradients(zip(grads, discriminator.trainable_weights))

    # Sample random points in the latent space
    random_latent_vectors = tf.random.normal(shape=(batch_size, latent_dim))
    # Assemble labels that say "all real images"
    misleading_labels = tf.zeros((batch_size, 1))

    # Train the generator (note that we should *not* update the weights
    # of the discriminator)!
    with tf.GradientTape() as tape:
        predictions = discriminator(generator(random_latent_vectors))
        g_loss = loss_fn(misleading_labels, predictions)
    grads = tape.gradient(g_loss, generator.trainable_weights)
    g_optimizer.apply_gradients(zip(grads, generator.trainable_weights))
    return d_loss, g_loss, generated_images

画像のバッチ上で train_step を繰り返し呼び出すことにより、私達の GAN を訓練しましょう。discriminator と generator は convnet (畳込みネットワーク) ですので、このコードを GPU 上で実行することを望むはずです。

import os

# Prepare the dataset. We use both the training & test MNIST digits.
batch_size = 64
(x_train, _), (x_test, _) = keras.datasets.mnist.load_data()
all_digits = np.concatenate([x_train, x_test])
all_digits = all_digits.astype("float32") / 255.0
all_digits = np.reshape(all_digits, (-1, 28, 28, 1))
dataset = tf.data.Dataset.from_tensor_slices(all_digits)
dataset = dataset.shuffle(buffer_size=1024).batch(batch_size)

epochs = 1  # In practice you need at least 20 epochs to generate nice digits.
save_dir = "./"

for epoch in range(epochs):
    print("\nStart epoch", epoch)

    for step, real_images in enumerate(dataset):
        # Train the discriminator & generator on one batch of real images.
        d_loss, g_loss, generated_images = train_step(real_images)

        # Logging.
        if step % 200 == 0:
            # Print metrics
            print("discriminator loss at step %d: %.2f" % (step, d_loss))
            print("adversarial loss at step %d: %.2f" % (step, g_loss))

            # Save one generated image
            img = tf.keras.preprocessing.image.array_to_img(
                generated_images[0] * 255.0, scale=False
            )
            img.save(os.path.join(save_dir, "generated_img" + str(step) + ".png"))

        # To limit execution time we stop after 10 steps.
        # Remove the lines below to actually train the model!
        if step > 10:
            break
Start epoch 0
discriminator loss at step 0: 0.70
adversarial loss at step 0: 0.68

That’s it! Colab GPU 上で単なる ~30s の訓練後に見栄えの良いフェイク MNIST 数字を得るでしょう。

 

以上



TensorFlow 2.4 : ガイド : Keras :- Model.fit で起きることをカスタマイズする

TensorFlow 2.0 : ガイド : Keras :- Model.fit で起きることをカスタマイズする (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 01/15/2021

* 本ページは、TensorFlow org サイトの Guide – Keras の以下のページを翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

無料セミナー実施中 クラスキャット主催 人工知能 & ビジネス Web セミナー

人工知能とビジネスをテーマにウェビナー (WEB セミナー) を定期的に開催しています。スケジュールは弊社 公式 Web サイト でご確認頂けます。
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
  • Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
クラスキャットは人工知能・テレワークに関する各種サービスを提供しております :

人工知能研究開発支援 人工知能研修サービス テレワーク & オンライン授業を支援
PoC(概念実証)を失敗させないための支援 (本支援はセミナーに参加しアンケートに回答した方を対象としています。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/
Facebook: https://www.facebook.com/ClassCatJP/

 

ガイド : Keras :- Model.fit で起きることをカスタマイズする

イントロダクション

教師あり学習を行なっているとき、fit() を使用できて総てがスムースに動作します。

貴方自身の訓練ループをスクラッチから書く必要があるとき、GradientTape を利用して総ての小さな詳細を制御できます。

しかしカスタム訓練アルゴリズムを必要としながら、コールバック、組込みの分散サポート、あるはステップ融合 (= fusing) のような fit() の便利な特徴から依然として恩恵を受けることを望む場合にはどうでしょう?

Keras の中心的な原理は 複雑さの進歩的な (= progressive) 公開 です。貴方は低位ワークフローに漸進的に進むことができるはずです。高位機能が貴方のユースケースに正確には適合しない場合、崖から落ちるべきではありません。高位の同等の便利さを維持しながら詳細に渡る制御を得ることができるはずです。

fit() が行なうことをカスタマイズする必要があるとき、Model クラスの訓練ステップ関数を override する べきです。これはデータの総てのバッチのために fit() により呼び出される関数です。それから通常のように fit() を呼び出すことができます — そしてそれは貴方自身の学習アルゴリズムを実行しているでしょう。

このパターンはモデルを Functional API で構築することを妨げないことに注意してください。貴方がシーケンシャル・モデル、Functional API モデル、あるいはサブクラス化モデルを構築していようが、これを行なうことができます。

それがどのように動作するか見ましょう。

 

セットアップ

TensorFlow 2.2 かそれ以後を必要とします。

import tensorflow as tf
from tensorflow import keras

 

最初の単純なサンプル

単純なサンプルから始めましょう :

  • keras.Model をサブクラス化する新しいクラスを作成します。
  • メソッド train_step(self, data) を単に override します。
  • (損失を含む) メトリック名をそれらの現在の値にマップする辞書を返します。

入力引数 data は訓練データとして fit に渡されるものです :

  • fit(x, y, …) を呼び出すことにより、Numpy 配列を渡す場合には、データはタプル (x, y) です。
  • fit(dataset, …) を呼び出すことにより、tf.data.Dataset を渡す場合には、データは各バッチでデータセットにより yield されるものになります。

train_step メソッドの本体で、既に馴染みがあるものに類似した通常の訓練更新を実装します。重要なことは、self.compiled_loss を通して損失を計算する ことです、これは compile() に渡された損失関数をラップします。

同様に、compile() に渡されたメトリクスの状態を更新するために self.compiled_metrics.update_state(y, y_pred) を呼び出し、そしてそれらの現在の値を取得するために最後に self.metrics からの結果を問い合わせます。

class CustomModel(keras.Model):
    def train_step(self, data):
        # Unpack the data. Its structure depends on your model and
        # on what you pass to `fit()`.
        x, y = data

        with tf.GradientTape() as tape:
            y_pred = self(x, training=True)  # Forward pass
            # Compute the loss value
            # (the loss function is configured in `compile()`)
            loss = self.compiled_loss(y, y_pred, regularization_losses=self.losses)

        # Compute gradients
        trainable_vars = self.trainable_variables
        gradients = tape.gradient(loss, trainable_vars)
        # Update weights
        self.optimizer.apply_gradients(zip(gradients, trainable_vars))
        # Update metrics (includes the metric that tracks the loss)
        self.compiled_metrics.update_state(y, y_pred)
        # Return a dict mapping metric names to current value
        return {m.name: m.result() for m in self.metrics}

Let’s try this out:

import numpy as np

# Construct and compile an instance of CustomModel
inputs = keras.Input(shape=(32,))
outputs = keras.layers.Dense(1)(inputs)
model = CustomModel(inputs, outputs)
model.compile(optimizer="adam", loss="mse", metrics=["mae"])

# Just use `fit` as usual
x = np.random.random((1000, 32))
y = np.random.random((1000, 1))
model.fit(x, y, epochs=3)
Epoch 1/3
32/32 [==============================] - 1s 1ms/step - loss: 0.4041 - mae: 0.5120
Epoch 2/3
32/32 [==============================] - 0s 958us/step - loss: 0.2610 - mae: 0.4111
Epoch 3/3
32/32 [==============================] - 0s 914us/step - loss: 0.2933 - mae: 0.4311
<tensorflow.python.keras.callbacks.History at 0x7f1e109407b8>

 

低位に進む

自然に、compile() で損失関数を渡すことを単にスキップし、そして代わりに train_step で手動で総てを行なうことができるでしょう。メトリクスのためのようにです。

ここに低位サンプルがあります、それは optimizer を configure するために compile() を使用するだけです :

  • 損失と MAE スコアを追跡するために Metric インスタンスを作成することから始めます。
  • (update_state() を呼び出して) これらのメトリクスの状態を更新するカスタム train_step() を実装してから、進捗バーにより表示されて任意のコールバックに渡される (result() を通して) 現在の平均値を返すようにそれらに問い合わせます。
  • 各エポック間でメトリクス上 reset_states() を呼び出す必要があることに注意してください!そうでなければ result() は訓練の開始からの平均を返すでしょう、その一方で通常はエポック毎平均で作業します。ありがたいことに、フレームワークはそれを私達のために行なうことができます : モデルの metrics プロパティでリセットすることを望む任意のメトリックを単にリストします。モデルは、各 fit() エポックの最初か evaluate() への呼び出しの最初にここでリストされた任意のオブジェクト上で reset_states を呼び出します。
loss_tracker = keras.metrics.Mean(name="loss")
mae_metric = keras.metrics.MeanAbsoluteError(name="mae")


class CustomModel(keras.Model):
    def train_step(self, data):
        x, y = data

        with tf.GradientTape() as tape:
            y_pred = self(x, training=True)  # Forward pass
            # Compute our own loss
            loss = keras.losses.mean_squared_error(y, y_pred)

        # Compute gradients
        trainable_vars = self.trainable_variables
        gradients = tape.gradient(loss, trainable_vars)

        # Update weights
        self.optimizer.apply_gradients(zip(gradients, trainable_vars))

        # Compute our own metrics
        loss_tracker.update_state(loss)
        mae_metric.update_state(y, y_pred)
        return {"loss": loss_tracker.result(), "mae": mae_metric.result()}

    @property
    def metrics(self):
        # We list our `Metric` objects here so that `reset_states()` can be
        # called automatically at the start of each epoch
        # or at the start of `evaluate()`.
        # If you don't implement this property, you have to call
        # `reset_states()` yourself at the time of your choosing.
        return [loss_tracker, mae_metric]


# Construct an instance of CustomModel
inputs = keras.Input(shape=(32,))
outputs = keras.layers.Dense(1)(inputs)
model = CustomModel(inputs, outputs)

# We don't passs a loss or metrics here.
model.compile(optimizer="adam")

# Just use `fit` as usual -- you can use callbacks, etc.
x = np.random.random((1000, 32))
y = np.random.random((1000, 1))
model.fit(x, y, epochs=5)
Epoch 1/5
32/32 [==============================] - 0s 992us/step - loss: 0.3279 - mae: 0.4593
Epoch 2/5
32/32 [==============================] - 0s 876us/step - loss: 0.2278 - mae: 0.3866
Epoch 3/5
32/32 [==============================] - 0s 899us/step - loss: 0.2207 - mae: 0.3808
Epoch 4/5
32/32 [==============================] - 0s 969us/step - loss: 0.2131 - mae: 0.3746
Epoch 5/5
32/32 [==============================] - 0s 978us/step - loss: 0.2058 - mae: 0.3681

 

sample_weight & class_weight をサポートする

最初の基本的なサンプルはサンプル重み付けにどのような言及もしていないことに気付いたかもしれません。fit() 引数 sample_weight と class_weight をサポートすることを望む場合、単純に以下を行なうでしょう :

  • data 引数から sample_weight をアンパックする
  • それを compiled_loss & compiled_metrics に渡す (もちろん、損失 & メトリクスのために compile() に依拠しない場合、単にそれを手動で適用することもできるでしょう)
  • That’s it. That’s the list.
class CustomModel(keras.Model):
    def train_step(self, data):
        # Unpack the data. Its structure depends on your model and
        # on what you pass to `fit()`.
        if len(data) == 3:
            x, y, sample_weight = data
        else:
            x, y = data

        with tf.GradientTape() as tape:
            y_pred = self(x, training=True)  # Forward pass
            # Compute the loss value.
            # The loss function is configured in `compile()`.
            loss = self.compiled_loss(
                y,
                y_pred,
                sample_weight=sample_weight,
                regularization_losses=self.losses,
            )

        # Compute gradients
        trainable_vars = self.trainable_variables
        gradients = tape.gradient(loss, trainable_vars)

        # Update weights
        self.optimizer.apply_gradients(zip(gradients, trainable_vars))

        # Update the metrics.
        # Metrics are configured in `compile()`.
        self.compiled_metrics.update_state(y, y_pred, sample_weight=sample_weight)

        # Return a dict mapping metric names to current value.
        # Note that it will include the loss (tracked in self.metrics).
        return {m.name: m.result() for m in self.metrics}


# Construct and compile an instance of CustomModel
inputs = keras.Input(shape=(32,))
outputs = keras.layers.Dense(1)(inputs)
model = CustomModel(inputs, outputs)
model.compile(optimizer="adam", loss="mse", metrics=["mae"])

# You can now use sample_weight argument
x = np.random.random((1000, 32))
y = np.random.random((1000, 1))
sw = np.random.random((1000, 1))
model.fit(x, y, sample_weight=sw, epochs=3)
Epoch 1/3
32/32 [==============================] - 0s 1ms/step - loss: 1.1263 - mae: 1.3582
Epoch 2/3
32/32 [==============================] - 0s 973us/step - loss: 0.5116 - mae: 0.8744
Epoch 3/3
32/32 [==============================] - 0s 902us/step - loss: 0.2700 - mae: 0.5867
<tensorflow.python.keras.callbacks.History at 0x7f1e0c8ce3c8>

 

貴方自身の評価ステップを提供する

model.evaluate() への呼び出しのために同じことを行なうことを望むとすればどうでしょう?それならば正確に同じ方法で test_step を override するでしょう。ここにそれがどのように見えるかがあります :

class CustomModel(keras.Model):
    def test_step(self, data):
        # Unpack the data
        x, y = data
        # Compute predictions
        y_pred = self(x, training=False)
        # Updates the metrics tracking the loss
        self.compiled_loss(y, y_pred, regularization_losses=self.losses)
        # Update the metrics.
        self.compiled_metrics.update_state(y, y_pred)
        # Return a dict mapping metric names to current value.
        # Note that it will include the loss (tracked in self.metrics).
        return {m.name: m.result() for m in self.metrics}


# Construct an instance of CustomModel
inputs = keras.Input(shape=(32,))
outputs = keras.layers.Dense(1)(inputs)
model = CustomModel(inputs, outputs)
model.compile(loss="mse", metrics=["mae"])

# Evaluate with our custom test_step
x = np.random.random((1000, 32))
y = np.random.random((1000, 1))
model.evaluate(x, y)
32/32 [==============================] - 0s 857us/step - loss: 0.4283 - mae: 0.5305
[0.41645824909210205, 0.5266892313957214]

 

仕上げる : end-to-end GAN サンプル

貴方が丁度学習した総てを活用する end-to-end サンプルをウォークスルーしましょう。

以下を考えましょう :

  • 28x28x1 画像を生成することを意図した generator ネットワーク。
  • 28x28x1 画像を 2 つのクラス (“fake” と “real”) に分類することを意図した discriminator ネットワーク。
  • 各々に 1 つの optimizer。
  • discriminator を訓練するための損失関数。
from tensorflow.keras import layers

# Create the discriminator
discriminator = keras.Sequential(
    [
        keras.Input(shape=(28, 28, 1)),
        layers.Conv2D(64, (3, 3), strides=(2, 2), padding="same"),
        layers.LeakyReLU(alpha=0.2),
        layers.Conv2D(128, (3, 3), strides=(2, 2), padding="same"),
        layers.LeakyReLU(alpha=0.2),
        layers.GlobalMaxPooling2D(),
        layers.Dense(1),
    ],
    name="discriminator",
)

# Create the generator
latent_dim = 128
generator = keras.Sequential(
    [
        keras.Input(shape=(latent_dim,)),
        # We want to generate 128 coefficients to reshape into a 7x7x128 map
        layers.Dense(7 * 7 * 128),
        layers.LeakyReLU(alpha=0.2),
        layers.Reshape((7, 7, 128)),
        layers.Conv2DTranspose(128, (4, 4), strides=(2, 2), padding="same"),
        layers.LeakyReLU(alpha=0.2),
        layers.Conv2DTranspose(128, (4, 4), strides=(2, 2), padding="same"),
        layers.LeakyReLU(alpha=0.2),
        layers.Conv2D(1, (7, 7), padding="same", activation="sigmoid"),
    ],
    name="generator",
)

ここに機能完全な GAN クラスがあります、それ自身のシグネチャを使用するために compile() を override し、そして train_step の 17 行で GAN アルゴリズム全体を実装しています :

class GAN(keras.Model):
    def __init__(self, discriminator, generator, latent_dim):
        super(GAN, self).__init__()
        self.discriminator = discriminator
        self.generator = generator
        self.latent_dim = latent_dim

    def compile(self, d_optimizer, g_optimizer, loss_fn):
        super(GAN, self).compile()
        self.d_optimizer = d_optimizer
        self.g_optimizer = g_optimizer
        self.loss_fn = loss_fn

    def train_step(self, real_images):
        if isinstance(real_images, tuple):
            real_images = real_images[0]
        # Sample random points in the latent space
        batch_size = tf.shape(real_images)[0]
        random_latent_vectors = tf.random.normal(shape=(batch_size, self.latent_dim))

        # Decode them to fake images
        generated_images = self.generator(random_latent_vectors)

        # Combine them with real images
        combined_images = tf.concat([generated_images, real_images], axis=0)

        # Assemble labels discriminating real from fake images
        labels = tf.concat(
            [tf.ones((batch_size, 1)), tf.zeros((batch_size, 1))], axis=0
        )
        # Add random noise to the labels - important trick!
        labels += 0.05 * tf.random.uniform(tf.shape(labels))

        # Train the discriminator
        with tf.GradientTape() as tape:
            predictions = self.discriminator(combined_images)
            d_loss = self.loss_fn(labels, predictions)
        grads = tape.gradient(d_loss, self.discriminator.trainable_weights)
        self.d_optimizer.apply_gradients(
            zip(grads, self.discriminator.trainable_weights)
        )

        # Sample random points in the latent space
        random_latent_vectors = tf.random.normal(shape=(batch_size, self.latent_dim))

        # Assemble labels that say "all real images"
        misleading_labels = tf.zeros((batch_size, 1))

        # Train the generator (note that we should *not* update the weights
        # of the discriminator)!
        with tf.GradientTape() as tape:
            predictions = self.discriminator(self.generator(random_latent_vectors))
            g_loss = self.loss_fn(misleading_labels, predictions)
        grads = tape.gradient(g_loss, self.generator.trainable_weights)
        self.g_optimizer.apply_gradients(zip(grads, self.generator.trainable_weights))
        return {"d_loss": d_loss, "g_loss": g_loss}

それを試運転しましょう :

# Prepare the dataset. We use both the training & test MNIST digits.
batch_size = 64
(x_train, _), (x_test, _) = keras.datasets.mnist.load_data()
all_digits = np.concatenate([x_train, x_test])
all_digits = all_digits.astype("float32") / 255.0
all_digits = np.reshape(all_digits, (-1, 28, 28, 1))
dataset = tf.data.Dataset.from_tensor_slices(all_digits)
dataset = dataset.shuffle(buffer_size=1024).batch(batch_size)

gan = GAN(discriminator=discriminator, generator=generator, latent_dim=latent_dim)
gan.compile(
    d_optimizer=keras.optimizers.Adam(learning_rate=0.0003),
    g_optimizer=keras.optimizers.Adam(learning_rate=0.0003),
    loss_fn=keras.losses.BinaryCrossentropy(from_logits=True),
)

# To limit the execution time, we only train on 100 batches. You can train on
# the entire dataset. You will need about 20 epochs to get nice results.
gan.fit(dataset.take(100), epochs=1)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
100/100 [==============================] - 9s 24ms/step - d_loss: 0.4919 - g_loss: 0.8347
<tensorflow.python.keras.callbacks.History at 0x7f8b38026780>

The ideas behind deep learning are simple, so why should their implementation be painful?

 

以上



TensorFlow 2.4 : ガイド : Keras :- 前処理層で作業する

TensorFlow 2.0 : ガイド : Keras :- 前処理層で作業する (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 01/14/2021

* 本ページは、TensorFlow org サイトの Guide – Keras の以下のページを翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

無料セミナー実施中 クラスキャット主催 人工知能 & ビジネス Web セミナー

人工知能とビジネスをテーマにウェビナー (WEB セミナー) を定期的に開催しています。スケジュールは弊社 公式 Web サイト でご確認頂けます。
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
  • Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
クラスキャットは人工知能・テレワークに関する各種サービスを提供しております :

人工知能研究開発支援 人工知能研修サービス テレワーク & オンライン授業を支援
PoC(概念実証)を失敗させないための支援 (本支援はセミナーに参加しアンケートに回答した方を対象としています。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/
Facebook: https://www.facebook.com/ClassCatJP/

 

ガイド : Keras :- 前処理層で作業する

Keras 前処理層

Keras 前処理層 API は開発者に Keras-native 入力処理パイプラインを構築することを可能にします。これらの入力処理パイプラインは Keras モデルと直接結び付けられ、そして Keras SavedModel の一部としてエクスポートされた、非-Keras ワークフローにおける独立した前処理コードとして利用できます。

Keras 前処理層で、真に end-to-end なモデル : raw 画像や raw 構造化データを入力として受け取るモデル ; 特徴正規化やそれら自身の上の特徴値インデキシングを処理するモデルを構築してエクスポートできます。

 

利用可能な前処理層

Core 前処理層

  • TextVectorization 層: raw 文字列を Embedding 層や Dense 層で読めるエンコードされた表現に変えます。
  • Normalization 層: 入力特徴の特徴単位の正規化を遂行します。

 

構造化データ前処理層

これらの層は構造化データ・エンコーディングと特徴エンジニアリングのためです。

  • CategoryEncoding 層: 整数カテゴリカル特徴を one-hot, マルチ-hot あるいは TF-IDF dense 表現に変えます。
  • Hashing 層: 「ハッシュトリック (= hashing trick)」としても知られる、カテゴリカル特徴ハッシングを遂行します。
  • Discretization (離散化) 層: 連続数値特徴を整数カテゴリカル特徴に変えます。
  • StringLookup 層: 文字列カテゴリカル値を整数インデックスに変えます。
  • IntegerLookup 層: 整数カテゴリカル値を整数インデックスに変えます。
  • CategoryCrossing 層: カテゴリカル特徴を共起 (= co-occurrence) 特徴に結びつけます。 E.g. 特徴値 “a” と “b” を持つ場合、それは組合せ特徴 “a と b は同時に存在する” を提供できます。

 

画像前処理層

これらの層は画像モデルの入力を標準化するためです。

  • Resizing 層: 画像のバッチをターゲットサイズにリサイズする。
  • Rescaling 層: 画像のバッチの値をリスケールしてオフセットする (e.g. [0, 255] 範囲の入力から [0, 1] 範囲の入力に進める)。
  • CenterCrop 層: 画像のバッチである場合に中心クロップを返す。

 

画像データ増強層

これらの層は画像のバッチにランダムな増強変換を適用します。それらは訓練の間だけアクティブです。

  • RandomCrop 層
  • RandomFlip 層
  • RandomTranslation 層
  • RandomRotation 層
  • RandomZoom 層
  • RandomHeight 層
  • RandomWidth 層

 

adapt() メソッド

幾つかの前処理層は訓練データのサンプルに基づいて計算されなければならない内部状態を持ちます。stateful な前処理層リストは :

  • TextVectorization: 文字列トークンと整数インデックスの間のマッピングを保持します。
  • Normalization: 特徴の平均と標準偏差を保持します。
  • StringLookup と IntegerLookup: 入力値と出力インデックスの間のマッピングを保持します。
  • CategoryEncoding: 入力値のインデックスを保持します。
  • Discretization (離散化) : 値バケット境界についての情報を保持します。

重要なことは、これらの層は 非-訓練可能 であることです (訓練可能ではありません)。それらの状態は訓練の間に設定されません ; それは 訓練の前 に設定されなければなりません、”adaptation (適応)” と呼ばれるステップです。

adapt() メソッドを通して、それを訓練データに晒すことにより前処理層の状態を設定します :

import numpy as np
import tensorflow as tf
from tensorflow.keras.layers.experimental import preprocessing

data = np.array([[0.1, 0.2, 0.3], [0.8, 0.9, 1.0], [1.5, 1.6, 1.7],])
layer = preprocessing.Normalization()
layer.adapt(data)
normalized_data = layer(data)

print("Features mean: %.2f" % (normalized_data.numpy().mean()))
print("Features std: %.2f" % (normalized_data.numpy().std()))
Features mean: 0.00
Features std: 1.00

adapt() メソッドは NumPy 配列か tf.data.Dataset オブジェクトのいずれかを取ります。StringLookup と TextVectorization の場合には、文字列のリストを渡すこともできます :

data = [
    "ξεῖν᾽, ἦ τοι μὲν ὄνειροι ἀμήχανοι ἀκριτόμυθοι",
    "γίγνοντ᾽, οὐδέ τι πάντα τελείεται ἀνθρώποισι.",
    "δοιαὶ γάρ τε πύλαι ἀμενηνῶν εἰσὶν ὀνείρων:",
    "αἱ μὲν γὰρ κεράεσσι τετεύχαται, αἱ δ᾽ ἐλέφαντι:",
    "τῶν οἳ μέν κ᾽ ἔλθωσι διὰ πριστοῦ ἐλέφαντος,",
    "οἵ ῥ᾽ ἐλεφαίρονται, ἔπε᾽ ἀκράαντα φέροντες:",
    "οἱ δὲ διὰ ξεστῶν κεράων ἔλθωσι θύραζε,",
    "οἵ ῥ᾽ ἔτυμα κραίνουσι, βροτῶν ὅτε κέν τις ἴδηται.",
]
layer = preprocessing.TextVectorization()
layer.adapt(data)
vectorized_text = layer(data)
print(vectorized_text)
tf.Tensor(
[[37 12 25  5  9 20 21  0  0]
 [51 34 27 33 29 18  0  0  0]
 [49 52 30 31 19 46 10  0  0]
 [ 7  5 50 43 28  7 47 17  0]
 [24 35 39 40  3  6 32 16  0]
 [ 4  2 15 14 22 23  0  0  0]
 [36 48  6 38 42  3 45  0  0]
 [ 4  2 13 41 53  8 44 26 11]], shape=(8, 9), dtype=int64)

加えて、適応可能な (= adaptable) 層はコンストラクタ引数か重み割当てを通して状態を直接設定するためのオプションを常に公開しています。意図された状態値が層構築時に知られていたり、adapt() 呼び出しの外側で計算される場合、それらは層の内部計算に依拠することなく設定できます。例えば、TextVectorization, StringLookup や IntegerLookup 層のための外部語彙ファイルが既に存在する場合、それらは語彙ファイルへのパスを層のコンストラクタ引数に渡すことにより検索テーブルに直接ロードできます。

ここに事前計算された語彙で StringLookup 層をインスタンス化するサンプルがあります :

vocab = ["a", "b", "c", "d"]
data = tf.constant([["a", "c", "d"], ["d", "z", "b"]])
layer = preprocessing.StringLookup(vocabulary=vocab)
vectorized_data = layer(data)
print(vectorized_data)
tf.Tensor(
[[2 4 5]
 [5 1 3]], shape=(2, 3), dtype=int64)

 

モデルの前かモデルの内部でデータを処理する

前処理層を使用できるである 2 つの方法があります :

オプション 1: このように、それらをモデルの一部にします :

inputs = keras.Input(shape=input_shape)
x = preprocessing_layer(inputs)
outputs = rest_of_the_model(x)
model = keras.Model(inputs, outputs)

このオプションでは、モデルの残りの実行と同期して、前処理はデバイス上で発生します、それは GPU アクセラレーションから恩恵を受けることを意味します。GPU 上で訓練している場合、これは Normalization (正規化) 層と、総ての画像前処理とデータ増強層のために最善なオプションです。

 
オプション 2: 前処理されたデータのバッチを yield するデータセットを得るために、それを貴方の tf.data.Dataset に適用します、このようにです :

dataset = dataset.map(
  lambda x, y: (preprocessing_layer(x), y))

このオプションでは、前処理は CPU 上で非同期に発生し、そしてモデルに進む前にバッファリングされます。

これは TextVectorization 、そして総ての構造化データ前処理層のために最善なオプションです。それはまた CPU 上で訓練していて画像前処理層を使用する場合に良いオプションであり得ます。

 

推論時にモデルの内部で前処理を行なうメリット

オプション 2 で進める場合でさえ、前処理層を含む、推論-only end-to-end モデルを後でエクスポートすることを望むかもしれません。これを行なう主要なメリットはそれが モデルを可搬にして 訓練/サービングのねじれ (= skew) を減じる ことに役立つからです。

総てのデータ前処理がモデルの一部であるとき、他の人は各特徴がエンコードされて正規化されることをどのように想定されているかを知らなければならないことなく貴方のモデルをロードして利用できます。貴方の推論モデルは raw 画像や raw 構造化データを処理することができて、モデルのユーザに例えばテキストのために使用されるトークン化スキーム、カテゴリカル特徴のために使用されるインデキシング・スキーム、画像ピクセル値が [-1, +1] か [0, 1] に正規化されるかどうか、等々の詳細を知ることを要求しません。これは貴方がモデルを TensorFlow.js のような他のランタイムにエクスポートしている場合に特にパワフルです : JavaScript での前処理パイプラインを再実装しなくても構いません。

初期時に前処理層を tf.data パイプラインに置く場合、前処理をパッケージ化する推論モデルをエクスポートできます。単純に前処理層と訓練モデルを連鎖する新しいモデルをインスタンス化します :

inputs = keras.Input(shape=input_shape)
x = preprocessing_layer(inputs)
outputs = training_model(x)
infernece_model = keras.Model(inputs, outputs)

 

クイック・レシピ

画像データ増強 (デバイス上)

画像データ増強層は訓練の間だけアクティブであることに注意してください (Dropout 層と同様に)。

from tensorflow import keras
from tensorflow.keras import layers

# Create a data augmentation stage with horizontal flipping, rotations, zooms
data_augmentation = keras.Sequential(
    [
        preprocessing.RandomFlip("horizontal"),
        preprocessing.RandomRotation(0.1),
        preprocessing.RandomZoom(0.1),
    ]
)

# Create a model that includes the augmentation stage
input_shape = (32, 32, 3)
classes = 10
inputs = keras.Input(shape=input_shape)
# Augment images
x = data_augmentation(inputs)
# Rescale image values to [0, 1]
x = preprocessing.Rescaling(1.0 / 255)(x)
# Add the rest of the model
outputs = keras.applications.ResNet50(
    weights=None, input_shape=input_shape, classes=classes
)(x)
model = keras.Model(inputs, outputs)

サンプル image classification from scratch で実践の同様のセットアップを見ることができます。

 

数値特徴を正規化する

# Load some data
(x_train, y_train), _ = keras.datasets.cifar10.load_data()
x_train = x_train.reshape((len(x_train), -1))
input_shape = x_train.shape[1:]
classes = 10

# Create a Normalization layer and set its internal state using the training data
normalizer = preprocessing.Normalization()
normalizer.adapt(x_train)

# Create a model that include the normalization layer
inputs = keras.Input(shape=input_shape)
x = normalizer(inputs)
outputs = layers.Dense(classes, activation="softmax")(x)
model = keras.Model(inputs, outputs)

# Train the model
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy")
model.fit(x_train, y_train)
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170500096/170498071 [==============================] - 6s 0us/step
1563/1563 [==============================] - 4s 2ms/step - loss: 2.1744
<tensorflow.python.keras.callbacks.History at 0x7f4497e53780>

 

one-hot エンコーディングを通して文字列カテゴリカル特徴をエンコードする

# Define some toy data
data = tf.constant(["a", "b", "c", "b", "c", "a"])

# Use StringLookup to build an index of the feature values
indexer = preprocessing.StringLookup()
indexer.adapt(data)

# Use CategoryEncoding to encode the integer indices to a one-hot vector
encoder = preprocessing.CategoryEncoding(output_mode="binary")
encoder.adapt(indexer(data))

# Convert new test data (which includes unknown feature values)
test_data = tf.constant(["a", "b", "c", "d", "e", ""])
encoded_data = encoder(indexer(test_data))
print(encoded_data)
tf.Tensor(
[[0. 0. 0. 0. 1.]
 [0. 0. 0. 1. 0.]
 [0. 0. 1. 0. 0.]
 [0. 1. 0. 0. 0.]
 [0. 1. 0. 0. 0.]
 [1. 0. 0. 0. 0.]], shape=(6, 5), dtype=float32)

インデックス 0 は欠損値 (これは空文字列 “” として指定するべきです) のために予約されていて、インデックス 1 は out-of-vocabulary (語彙外の) 値 (adapt() の間に見られなかった値) のために予約されていることに注意してください。StringLookup の mask_token と oov_token コンストラクタ引数を使用してこれを configure できます。

サンプル structured data classification from scratch で実践で StringLookup と CategoryEncoding 層を見ることができます。

 

one-hot エンコーディングを通して整数カテゴリカル特徴をエンコードする

# Define some toy data
data = tf.constant([10, 20, 20, 10, 30, 0])

# Use IntegerLookup to build an index of the feature values
indexer = preprocessing.IntegerLookup()
indexer.adapt(data)

# Use CategoryEncoding to encode the integer indices to a one-hot vector
encoder = preprocessing.CategoryEncoding(output_mode="binary")
encoder.adapt(indexer(data))

# Convert new test data (which includes unknown feature values)
test_data = tf.constant([10, 10, 20, 50, 60, 0])
encoded_data = encoder(indexer(test_data))
print(encoded_data)
tf.Tensor(
[[0. 0. 0. 1. 0.]
 [0. 0. 0. 1. 0.]
 [0. 0. 1. 0. 0.]
 [0. 1. 0. 0. 0.]
 [0. 1. 0. 0. 0.]
 [1. 0. 0. 0. 0.]], shape=(6, 5), dtype=float32)

インデックス 0 は欠損値 (これは値 0 として指定するべきです) のために予約されていて、インデックス 1 は out-of-vocabulary (語彙外の) 値 (adapt() の間に見られなかった値) のために予約されていることに注意してください。IntegerLookup の mask_value と oov_token コンストラクタ引数を使用してこれを configure できます。

サンプル structured data classification from scratch で実践で IntegerLookup と CategoryEncoding 層を見ることができます。

 

整数カテゴリカル特徴にハッシュトリックを適用する

データで各値が数回だけ現れるような、 (10e3 かそれ以上のオーダーで) 多くの異なる値を取れるカテゴリカル特徴を持つ場合、それは特徴値をインデックスして one-hot エンコードすることは実現困難で効果的ではありません。代わりに、「ハッシュトリック (= hashing trick)」を適用することは良い考えです : 値を固定サイズのベクトルにハッシュ化します。これは特徴空間のサイズを管理可能にし、明示的なインデキシングの必要性を取り除きます。

# Sample data: 10,000 random integers with values between 0 and 100,000
data = np.random.randint(0, 100000, size=(10000, 1))

# Use the Hashing layer to hash the values to the range [0, 64]
hasher = preprocessing.Hashing(num_bins=64, salt=1337)

# Use the CategoryEncoding layer to one-hot encode the hashed values
encoder = preprocessing.CategoryEncoding(max_tokens=64, output_mode="binary")
encoded_data = encoder(hasher(data))
print(encoded_data.shape)
(10000, 64)

 

テキストをトークン・インデックスのシークエンスとしてエンコードする

これは Embedding 層に渡されるテキストをどのように前処理するかです。

# Define some text data to adapt the layer
data = tf.constant(
    [
        "The Brain is wider than the Sky",
        "For put them side by side",
        "The one the other will contain",
        "With ease and You beside",
    ]
)
# Instantiate TextVectorization with "int" output_mode
text_vectorizer = preprocessing.TextVectorization(output_mode="int")
# Index the vocabulary via `adapt()`
text_vectorizer.adapt(data)

# You can retrieve the vocabulary we indexed via get_vocabulary()
vocab = text_vectorizer.get_vocabulary()
print("Vocabulary:", vocab)

# Create an Embedding + LSTM model
inputs = keras.Input(shape=(1,), dtype="string")
x = text_vectorizer(inputs)
x = layers.Embedding(input_dim=len(vocab), output_dim=64)(x)
outputs = layers.LSTM(1)(x)
model = keras.Model(inputs, outputs)

# Call the model on test data (which includes unknown tokens)
test_data = tf.constant(["The Brain is deeper than the sea"])
test_output = model(test_data)
Vocabulary: ['', '[UNK]', 'the', 'side', 'you', 'with', 'will', 'wider', 'them', 'than', 'sky', 'put', 'other', 'one', 'is', 'for', 'ease', 'contain', 'by', 'brain', 'beside', 'and']

サンプル text classification from scratch で Embedding モードと結び付けられた、TextVectorization を実践で見ることができます。

そのようなモデルを訓練するとき、ベスト・パフォーマンスのためには、入力パイプラインの一部として TextVectorization 層を使用するべきであることに注意してください (これは上のテキスト分類サンプルで行なったことです)。

 

テキストをマルチ-hot エンコーディングで ngram の密行列としてエンコードする

これは Dense 層に渡されるテキストをどのように前処理するべきかです。

# Define some text data to adapt the layer
data = tf.constant(
    [
        "The Brain is wider than the Sky",
        "For put them side by side",
        "The one the other will contain",
        "With ease and You beside",
    ]
)
# Instantiate TextVectorization with "binary" output_mode (multi-hot)
# and ngrams=2 (index all bigrams)
text_vectorizer = preprocessing.TextVectorization(output_mode="binary", ngrams=2)
# Index the bigrams via `adapt()`
text_vectorizer.adapt(data)

print(
    "Encoded text:\n",
    text_vectorizer(["The Brain is deeper than the sea"]).numpy(),
    "\n",
)

# Create a Dense model
inputs = keras.Input(shape=(1,), dtype="string")
x = text_vectorizer(inputs)
outputs = layers.Dense(1)(x)
model = keras.Model(inputs, outputs)

# Call the model on test data (which includes unknown tokens)
test_data = tf.constant(["The Brain is deeper than the sea"])
test_output = model(test_data)

print("Model output:", test_output)
Encoded text:
 [[1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 1. 1. 0. 0. 0. 0. 0.
  0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 1. 1. 0. 0. 0.]] 

Model output: tf.Tensor([[-0.69113374]], shape=(1, 1), dtype=float32)

 

テキストを TF-IDF 重み付けで ngram の密行列としてエンコードする

これはテキストを Dense 層に渡す前に前処理する代替の方法です。

# Define some text data to adapt the layer
data = tf.constant(
    [
        "The Brain is wider than the Sky",
        "For put them side by side",
        "The one the other will contain",
        "With ease and You beside",
    ]
)
# Instantiate TextVectorization with "tf-idf" output_mode
# (multi-hot with TF-IDF weighting) and ngrams=2 (index all bigrams)
text_vectorizer = preprocessing.TextVectorization(output_mode="tf-idf", ngrams=2)
# Index the bigrams and learn the TF-IDF weights via `adapt()`
text_vectorizer.adapt(data)

print(
    "Encoded text:\n",
    text_vectorizer(["The Brain is deeper than the sea"]).numpy(),
    "\n",
)

# Create a Dense model
inputs = keras.Input(shape=(1,), dtype="string")
x = text_vectorizer(inputs)
outputs = layers.Dense(1)(x)
model = keras.Model(inputs, outputs)

# Call the model on test data (which includes unknown tokens)
test_data = tf.constant(["The Brain is deeper than the sea"])
test_output = model(test_data)
print("Model output:", test_output)
Encoded text:
 [[8.04719   1.6945957 0.        0.        0.        0.        0.
  0.        0.        0.        0.        0.        0.        0.
  0.        0.        1.0986123 1.0986123 1.0986123 0.        0.
  0.        0.        0.        0.        0.        0.        0.
  1.0986123 0.        0.        0.        0.        0.        0.
  0.        1.0986123 1.0986123 0.        0.        0.       ]] 

Model output: tf.Tensor([[-1.5733842]], shape=(1, 1), dtype=float32)
 

以上



TensorFlow 2.4 : ガイド : Keras :- Keras でリカレント・ニューラルネットワーク (RNN)

TensorFlow 2.0 : ガイド : Keras :- Keras でリカレント・ニューラルネットワーク (RNN) (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 01/13/2021

* 本ページは、TensorFlow org サイトの Guide – Keras の以下のページを翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

無料セミナー実施中 クラスキャット主催 人工知能 & ビジネス Web セミナー

人工知能とビジネスをテーマにウェビナー (WEB セミナー) を定期的に開催しています。スケジュールは弊社 公式 Web サイト でご確認頂けます。
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
  • Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
クラスキャットは人工知能・テレワークに関する各種サービスを提供しております :

人工知能研究開発支援 人工知能研修サービス テレワーク & オンライン授業を支援
PoC(概念実証)を失敗させないための支援 (本支援はセミナーに参加しアンケートに回答した方を対象としています。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/
Facebook: https://www.facebook.com/ClassCatJP/

 

 

ガイド : Keras :- Keras でリカレント・ニューラルネットワーク (RNN)

イントロダクション

リカレント・ニューラルネットワーク (RNN) は、時系列や自然言語のようなシークエンス・データをモデル化するためのパワフルなニューラルネットワークのクラスです。

模式的には (= schematically)、RNN 層はシークエンスの時間ステップに渡り反復するために for ループを使用し、一方でそれがそこまでに見た時間ステップについての情報をエンコードする内部状態を保持します。

Keras RNN API は以下に焦点を絞って設計されています :

  • 利用の容易さ: 組込み keras.layers.RNN, keras.layers.LSTM, keras.layers.GRU 層は難しい configuration 選択を行わなければならないことなくリカレント・モデルを素早く構築することを可能にします。
  • カスタマイズの容易さ: カスタム動作を持つ貴方自身の RNN セル層 (for ループの内側部) を定義し、そしてそれを一般的な keras.layers.RNN 層 (for ループ自身) で使用することもできます。これは異なる研究アイデアを最小限のコードで柔軟な方法で素早くプロトタイピングすることを可能にします。

 

セットアップ

import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

 

組込み RNN 層 : 単純なサンプル

Keras には 3 つの組込み RNN 層があります :

  1. keras.layers.SimpleRNN, 完全結合 RNN そこでは前の時間ステップからの出力は次の時間ステップに供給されます。
  2. keras.layers.GRU, 最初に Cho et al., 2014 内で提案されました。
  3. keras.layers.LSTM, 最初に Hochreiter & Schmidhuber, 1997 で提案されました。

2015 年初期に、Keras は LSTM と GRU の最初の再利用可能なオープンソース Python 実装を持ちました。

ここに Sequential モデルの単純なサンプルがあります、これは整数のシークエンスを処理し、各整数を 64-次元ベクトルに埋め込み、それから LSTM 層を使用してベクトルのシークエンスを処理します。

model = keras.Sequential()
# Add an Embedding layer expecting input vocab of size 1000, and
# output embedding dimension of size 64.
model.add(layers.Embedding(input_dim=1000, output_dim=64))

# Add a LSTM layer with 128 internal units.
model.add(layers.LSTM(128))

# Add a Dense layer with 10 units.
model.add(layers.Dense(10))

model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (None, None, 64)          64000     
_________________________________________________________________
lstm (LSTM)                  (None, 128)               98816     
_________________________________________________________________
dense (Dense)                (None, 10)                1290      
=================================================================
Total params: 164,106
Trainable params: 164,106
Non-trainable params: 0
_________________________________________________________________

組込み RNN は幾つかの有用な特徴をサポートします :

  • dropout と recurrent_dropout 引数を通した、リカレント・ドロップアウト
  • go_backwards 引数を通した、入力シークエンスを反対に処理する機能
  • unroll 引数を通した、ループ展開 (= unrolloing) (これは CPU 上で短いシークエンスを処理するとき大きなスピードアップに繋がる可能性があります)
  • … 等々。

より多くの情報については、RNN API ドキュメント を見てください。

 

出力と状態

デフォルトでは、RNN 層の出力はサンプル毎に単一のベクトルを含みます。このベクトルは最後の時間ステップに対応する RNN セル出力で、入力シークエンス全体についての情報を含みます。この出力の shape は (batch_size, units) です、ここで units は層のコンストラクタに渡される units 引数に対応します。

RNN 層はまた return_sequences=True を設定する場合、各サンプルに対する出力の全体のシークエンス (1 ベクトル per 時間ステップ per サンプル) を返すこともできます。この出力の shape は (batch_size, timesteps, units) です。

model = keras.Sequential()
model.add(layers.Embedding(input_dim=1000, output_dim=64))

# The output of GRU will be a 3D tensor of shape (batch_size, timesteps, 256)
model.add(layers.GRU(256, return_sequences=True))

# The output of SimpleRNN will be a 2D tensor of shape (batch_size, 128)
model.add(layers.SimpleRNN(128))

model.add(layers.Dense(10))

model.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_1 (Embedding)      (None, None, 64)          64000     
_________________________________________________________________
gru (GRU)                    (None, None, 256)         247296    
_________________________________________________________________
simple_rnn (SimpleRNN)       (None, 128)               49280     
_________________________________________________________________
dense_1 (Dense)              (None, 10)                1290      
=================================================================
Total params: 361,866
Trainable params: 361,866
Non-trainable params: 0
_________________________________________________________________

加えて、RNN 層はその最終的な内部状態を返すことができます。返された状態は後で RNN 実行を再開するためにか、もう一つの RNN を初期化する ために利用できます。この設定は一般にエンコーダ・デコーダ sequence-to-sequence モデルで使用され、そこではエンコーダ最終状態はデコーダの初期状態として使用されます。

RNN 層をその内部状態を返すように configure するには、層を作成するとき return_state パラメータを True に設定します。LSTM は 2 つの状態 tensor を持ちますが、GRU は一つだけを持つことに注意してください。

層の初期状態を configure するためには、単に層を追加のキーワード引数 initial_state で呼び出します。状態の shape は下のサンプルのように、層の unit サイズに一致する必要があります。

encoder_vocab = 1000
decoder_vocab = 2000

encoder_input = layers.Input(shape=(None,))
encoder_embedded = layers.Embedding(input_dim=encoder_vocab, output_dim=64)(
    encoder_input
)

# Return states in addition to output
output, state_h, state_c = layers.LSTM(64, return_state=True, name="encoder")(
    encoder_embedded
)
encoder_state = [state_h, state_c]

decoder_input = layers.Input(shape=(None,))
decoder_embedded = layers.Embedding(input_dim=decoder_vocab, output_dim=64)(
    decoder_input
)

# Pass the 2 states to a new LSTM layer, as initial state
decoder_output = layers.LSTM(64, name="decoder")(
    decoder_embedded, initial_state=encoder_state
)
output = layers.Dense(10)(decoder_output)

model = keras.Model([encoder_input, decoder_input], output)
model.summary()
Model: "model"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_1 (InputLayer)            [(None, None)]       0                                            
__________________________________________________________________________________________________
input_2 (InputLayer)            [(None, None)]       0                                            
__________________________________________________________________________________________________
embedding_2 (Embedding)         (None, None, 64)     64000       input_1[0][0]                    
__________________________________________________________________________________________________
embedding_3 (Embedding)         (None, None, 64)     128000      input_2[0][0]                    
__________________________________________________________________________________________________
encoder (LSTM)                  [(None, 64), (None,  33024       embedding_2[0][0]                
__________________________________________________________________________________________________
decoder (LSTM)                  (None, 64)           33024       embedding_3[0][0]                
                                                                 encoder[0][1]                    
                                                                 encoder[0][2]                    
__________________________________________________________________________________________________
dense_2 (Dense)                 (None, 10)           650         decoder[0][0]                    
==================================================================================================
Total params: 258,698
Trainable params: 258,698
Non-trainable params: 0
__________________________________________________________________________________________________

 

RNN 層と RNN セル

組込み RNN 層に加えて、RNN API はまたセルレベル API も提供します。入力シークエンスのバッチ全体を処理する RNN 層と違い、RNN セルは単一の時間ステップだけを処理します。

セルは RNN 層の for ループの内側にあります。keras.layers.RNN 層の内側でのセルのラッピングはシークエンスのバッチを処理できる層を与えます, e.g. RNN(LSTMCell(10))。

数学的には、RNN(LSTMCell(10)) は LSTM (10) と同じ結果を生成します。実際に、TF v1.x のこの層の実装は単に対応する RNN セルを作成してそれを RNN 層でラッピングしていました。けれども組込み GRU と LSTM 層の使用は CuDNN の使用を有効にしますのでより良いパフォーマンスを見るかもしれません。

3 つの組込み RNN セルがあり、それらの各々は適合する RNN 層に対応します。

セル抽象は、一般的な keras.layers.RNN クラスと一緒に、貴方の研究のためにカスタム RNN アーキテクチャを実装することを非常に容易にします。

 

交差バッチ statefulness

非常に長いシークエンス (多分無限大) を処理するとき、交差バッチ statefulness のパターンを使用することを望むかもしれません。

通常は、RNN 層の内部状態はそれが新しいバッチを見るたびにリセットされます (i.e. 層により見られる総てのサンプルは過去 (のもの) から独立的であると仮定されています)。層は与えられたサンプルを処理する間、状態を維持するだけです。

けれども非常に長いシークエンスを持つ場合、それらをより短いシークエンスに分解して (層の状態をリセットすることなく) それらの短いシークエンスを RNN 層にシーケンシャルに供給することは有用です。このようにして、層は、それが一度に一つの部分シークエンスを見ているだけですが、シークエンス全体についての情報を保持できます。

コンストラクタで stateful=True を設定することによりこれを行なうことができます。

もしシークエンス s = [t0, t1, … t1546, t1547] を持つ場合、それを例えば次のように分割できます :

s1 = [t0, t1, ... t100]
s2 = [t101, ... t201]
...
s16 = [t1501, ... t1547]

それからそれを次を通して処理するでしょう :

lstm_layer = layers.LSTM(64, stateful=True)
for s in sub_sequences:
  output = lstm_layer(s)

状態をクリアすることを望むとき、layer.reset_states() を使用できます。

Note: このセットアップでは、与えられたバッチ内のサンプル i は前のバッチ内のサンプル i の続きであることが仮定されています。これは総てのバッチはサンプルの同じ数 (バッチサイズ) を含むべきであることを意味します。例えば、もしバッチが [sequence_A_from_t0_to_t100, sequence_B_from_t0_to_t100] を含む場合、次のバッチは [sequence_A_from_t101_to_t200, sequence_B_from_t101_to_t200] を含むべきです。

ここに完全なサンプルがあります :

paragraph1 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph2 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph3 = np.random.random((20, 10, 50)).astype(np.float32)

lstm_layer = layers.LSTM(64, stateful=True)
output = lstm_layer(paragraph1)
output = lstm_layer(paragraph2)
output = lstm_layer(paragraph3)

# reset_states() will reset the cached state to the original initial_state.
# If no initial_state was provided, zero-states will be used by default.
lstm_layer.reset_states()

 

RNN 状態再利用

RNN 層の記録された状態は layer.weights() には含まれません。RNN 層からの状態を再利用したい場合には、layer.states により状態値を取得できてそしてそれを new_layer(inputs, initial_state=layer.states) のような Keras functional API かモデルサブクラス化を通して新しい層のための初期状態としてとして利用できます。

シーケンシャル・モデルはこの場合には使用されないかもしれないことに注意してください、何故ならばそれは単一入力と出力を持つ層だけをサポートするからで、初期状態の特別な入力はここでは使用することを不可能にします。

paragraph1 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph2 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph3 = np.random.random((20, 10, 50)).astype(np.float32)

lstm_layer = layers.LSTM(64, stateful=True)
output = lstm_layer(paragraph1)
output = lstm_layer(paragraph2)

existing_state = lstm_layer.states

new_lstm_layer = layers.LSTM(64)
new_output = new_lstm_layer(paragraph3, initial_state=existing_state)

 

双方向 RNN

時系列以外のシークエンス (e.g. テキスト) については、RNN モデルはそれが最初から最後までシークエンスを処理するだけでなく反対にも処理する場合により良く遂行できることが多いです。例えば、センテンスの次の単語を予測するためには、その前に来る単語だけでなく、単語回りのコンテキストを持つことがしばしば有用です。

Keras はそのような双方向 RNN を構築するための容易な API を提供します : keras.layers.Bidirectional ラッパーです。

model = keras.Sequential()

model.add(
    layers.Bidirectional(layers.LSTM(64, return_sequences=True), input_shape=(5, 10))
)
model.add(layers.Bidirectional(layers.LSTM(32)))
model.add(layers.Dense(10))

model.summary()
Model: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
bidirectional (Bidirectional (None, 5, 128)            38400     
_________________________________________________________________
bidirectional_1 (Bidirection (None, 64)                41216     
_________________________________________________________________
dense_3 (Dense)              (None, 10)                650       
=================================================================
Total params: 80,266
Trainable params: 80,266
Non-trainable params: 0
_________________________________________________________________

内部的には、Bidirectional は渡された RNN 層をコピーして新たにコピーされた層の go_backwards フィールドを反転します、その結果それは入力を反対の順序で処理します。

Bidirectional RNN の出力はデフォルトでは forward 層出力と backward 層出力の合計です。異なるマージ動作, e.g. 連結を必要とする場合には、Bidirectional ラッパー・コンストラクタで merge_mode パラメータを変更します。Bidirectional についてのより詳細は、API doc を確認してください。

 

パフォーマンス最適化と CuDNN カーネル

TensorFlow 2.0 では、組込み LSTM と GRU 層は GPU が利用可能なときデフォルトで CuDNN カーネルを活用するように更新されました。この変更で、以前の keras.layers.CuDNNLSTM/CuDNNGRU 層は deprecated となり、そして貴方はモデルをそれが動作するハードウェアについて心配することなく構築できます。

CuDNN カーネルはある仮定とともに構築されますので、組込み LSTM や GRU 層のデフォルトを変更する場合、これは層が CuDNN カーネルを利用できないことを意味します。例えば :

  • 活性化関数を tanh から他のものに変更する。
  • recurrent_activation 関数を sigmoid から他のものに変更する。
  • recurrent_dropout > 0 を使用する。
  • unroll を True に設定する、これは LSTM/GRU に内側の tf.while_loop を unrolled (展開されない) “for” ループに分解します。
  • use_bias を False に設定する。
  • 入力データが厳密に右パディングされないときマスキングを使用する (マスクが厳密に右パディングされたデータに対応する場合、CuDNN は依然として使用できます。これは最も一般的なケースです)。

制約の詳細なリストについては、LSTMGRU 層のためのドキュメントを見てください。

 

利用可能なとき CuDNN カーネルを使用する

パフォーマンスの違いを実演するために単純な LSTM モデルを構築しましょう。

入力シークエンスとして MNIST 数字の行のシークエンスを使用します (ピクセルの各行を時間ステップとして扱います)、そして数字のラベルを予測します。

batch_size = 64
# Each MNIST image batch is a tensor of shape (batch_size, 28, 28).
# Each input sequence will be of size (28, 28) (height is treated like time).
input_dim = 28

units = 64
output_size = 10  # labels are from 0 to 9

# Build the RNN model
def build_model(allow_cudnn_kernel=True):
    # CuDNN is only available at the layer level, and not at the cell level.
    # This means `LSTM(units)` will use the CuDNN kernel,
    # while RNN(LSTMCell(units)) will run on non-CuDNN kernel.
    if allow_cudnn_kernel:
        # The LSTM layer with default options uses CuDNN.
        lstm_layer = keras.layers.LSTM(units, input_shape=(None, input_dim))
    else:
        # Wrapping a LSTMCell in a RNN layer will not use CuDNN.
        lstm_layer = keras.layers.RNN(
            keras.layers.LSTMCell(units), input_shape=(None, input_dim)
        )
    model = keras.models.Sequential(
        [
            lstm_layer,
            keras.layers.BatchNormalization(),
            keras.layers.Dense(output_size),
        ]
    )
    return model

MNIST データセットをロードしましょう :

mnist = keras.datasets.mnist

(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
sample, sample_label = x_train[0], y_train[0]

モデルインスタンスを作成してそれを訓練しましょう。

モデルのための損失関数として sparse_categorical_crossentropy を選択します。モデルの出力は [batch_size, 10] の shape を持ちます。モデルのためのターゲットは整数ベクトルで、整数の各々は 0 から 9 の範囲にあります。

model = build_model(allow_cudnn_kernel=True)

model.compile(
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer="sgd",
    metrics=["accuracy"],
)


model.fit(
    x_train, y_train, validation_data=(x_test, y_test), batch_size=batch_size, epochs=1
)

(without GPU)

938/938 [==============================] - 24s 23ms/step - loss: 1.3442 - accuracy: 0.5640 - val_loss: 0.5411 - val_accuracy: 0.8282
<tensorflow.python.keras.callbacks.History at 0x7f01bf72aba8>

(with GPU)

938/938 [==============================] - 8s 5ms/step - loss: 1.3380 - accuracy: 0.5641 - val_loss: 0.5984 - val_accuracy: 0.8100

今は、CuDNN カーネルを使用しないモデルと比較しましょう :

noncudnn_model = build_model(allow_cudnn_kernel=False)
noncudnn_model.set_weights(model.get_weights())
noncudnn_model.compile(
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer="sgd",
    metrics=["accuracy"],
)
noncudnn_model.fit(
    x_train, y_train, validation_data=(x_test, y_test), batch_size=batch_size, epochs=1
)

(without GPU)

938/938 [==============================] - 22s 22ms/step - loss: 0.4696 - accuracy: 0.8580 - val_loss: 0.3728 - val_accuracy: 0.8801

(with GPU)

938/938 [==============================] - 45s 47ms/step - loss: 0.5087 - accuracy: 0.8424 - val_loss: 0.3288 - val_accuracy: 0.8975

NVIDIA GPU and CuDNN がインストールされたマシン上で実行するとき、CuDNN でビルドされたモデルは通常の TensorFlow カーネルを使用するモデルと比較して訓練が遥かに高速です。

同じ CuDNN-有効モデルはまた CPU-only 環境で推論を実行するためにも使用できます。下の tf.device アノテーションは単にデバイス配置を強制します。モデルは GPU が利用可能でない場合にデフォルトで CPU 上で動作します。

最早貴方がその上で実行しているハードウェアについて心配する必要は単純にありません。Isn’t that pretty cool?

import matplotlib.pyplot as plt

with tf.device("CPU:0"):
    cpu_model = build_model(allow_cudnn_kernel=True)
    cpu_model.set_weights(model.get_weights())
    result = tf.argmax(cpu_model.predict_on_batch(tf.expand_dims(sample, 0)), axis=1)
    print(
        "Predicted result is: %s, target result is: %s" % (result.numpy(), sample_label)
    )
    plt.imshow(sample, cmap=plt.get_cmap("gray"))
Predicted result is: [3], target result is: 5

 

リスト/辞書入力、またはネストされた入力を持つ RNN

ネストされた構造は実装者に単一の時間ステップ内により多くの情報を含めることを可能にします。例えば、ビデオフレームは音声とビデオ入力を同時に持てるでしょう。この場合のデータ shape は次のようなものであり得ます :

[batch, timestep, {“video”: [height, width, channel], “audio”: [frequency]}]

もう一つの例では、手書きデータは (ペンの) 圧力情報に加えて、ペンの現在の位置のための座標 x と y の両者を持てるでしょう。従ってデータ表現は次のようなものでしょう :

[batch, timestep, {“location”: [x, y], “pressure”: [force]}]

以下のコードはそのような構造化入力を受け取るカスタム RNN セルをどのように構築するかの例を提供します。

 

ネストされた入力/出力をサポートするカスタムセルを定義する

貴方自身の層を書くことの詳細については Making new Layers & Models via subclassing を見てください。

class NestedCell(keras.layers.Layer):
    def __init__(self, unit_1, unit_2, unit_3, **kwargs):
        self.unit_1 = unit_1
        self.unit_2 = unit_2
        self.unit_3 = unit_3
        self.state_size = [tf.TensorShape([unit_1]), tf.TensorShape([unit_2, unit_3])]
        self.output_size = [tf.TensorShape([unit_1]), tf.TensorShape([unit_2, unit_3])]
        super(NestedCell, self).__init__(**kwargs)

    def build(self, input_shapes):
        # expect input_shape to contain 2 items, [(batch, i1), (batch, i2, i3)]
        i1 = input_shapes[0][1]
        i2 = input_shapes[1][1]
        i3 = input_shapes[1][2]

        self.kernel_1 = self.add_weight(
            shape=(i1, self.unit_1), initializer="uniform", name="kernel_1"
        )
        self.kernel_2_3 = self.add_weight(
            shape=(i2, i3, self.unit_2, self.unit_3),
            initializer="uniform",
            name="kernel_2_3",
        )

    def call(self, inputs, states):
        # inputs should be in [(batch, input_1), (batch, input_2, input_3)]
        # state should be in shape [(batch, unit_1), (batch, unit_2, unit_3)]
        input_1, input_2 = tf.nest.flatten(inputs)
        s1, s2 = states

        output_1 = tf.matmul(input_1, self.kernel_1)
        output_2_3 = tf.einsum("bij,ijkl->bkl", input_2, self.kernel_2_3)
        state_1 = s1 + output_1
        state_2_3 = s2 + output_2_3

        output = (output_1, output_2_3)
        new_states = (state_1, state_2_3)

        return output, new_states

    def get_config(self):
        return {"unit_1": self.unit_1, "unit_2": unit_2, "unit_3": self.unit_3}

 

ネストされた入力/出力を持つ RNN モデルを構築する

keras.layers.RNN 層と丁度定義したカスタムセルを使用する Keras モデルを構築しましょう。

unit_1 = 10
unit_2 = 20
unit_3 = 30

i1 = 32
i2 = 64
i3 = 32
batch_size = 64
num_batches = 10
timestep = 50

cell = NestedCell(unit_1, unit_2, unit_3)
rnn = keras.layers.RNN(cell)

input_1 = keras.Input((None, i1))
input_2 = keras.Input((None, i2, i3))

outputs = rnn((input_1, input_2))

model = keras.models.Model([input_1, input_2], outputs)

model.compile(optimizer="adam", loss="mse", metrics=["accuracy"])

 

ランダムに生成されたデータでモデルを訓練する

このモデルのための良い候補データセットがありませんので、実演のためにランダムな Numpy データを使用します。

input_1_data = np.random.random((batch_size * num_batches, timestep, i1))
input_2_data = np.random.random((batch_size * num_batches, timestep, i2, i3))
target_1_data = np.random.random((batch_size * num_batches, unit_1))
target_2_data = np.random.random((batch_size * num_batches, unit_2, unit_3))
input_data = [input_1_data, input_2_data]
target_data = [target_1_data, target_2_data]

model.fit(input_data, target_data, batch_size=batch_size)
10/10 [==============================] - 1s 36ms/step - loss: 0.8854 - rnn_1_loss: 0.3126 - rnn_1_1_loss: 0.5728 - rnn_1_accuracy: 0.0854 - rnn_1_1_accuracy: 0.0333
<tensorflow.python.keras.callbacks.History at 0x7fb7fa0c2ac8>

Keras keras.layers.RNN 層では、貴方はシークエンス内の個々のステップのための数学ロジックを定義することが想定されるだけです、そして keras.layers.RNN 層は貴方のためにシークエンス反復を処理します。それは新しい種類の RNN (e.g. LSTM 変種) を素早くプロトタイピングする非常にパワフルな方法です。

より詳細については、API doc を訪ねてください。

 

以上



TensorFlow 2.4 : ガイド : Keras :- サブクラス化で新しい層とモデルを作成する

TensorFlow 2.0 : ガイド : Keras :- サブクラス化で新しい層とモデルを作成する (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 01/12/2021

* 本ページは、TensorFlow org サイトの Guide – Keras の以下のページを翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

無料セミナー実施中 クラスキャット主催 人工知能 & ビジネス Web セミナー

人工知能とビジネスをテーマにウェビナー (WEB セミナー) を定期的に開催しています。スケジュールは弊社 公式 Web サイト でご確認頂けます。
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
  • Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
クラスキャットは人工知能・テレワークに関する各種サービスを提供しております :

人工知能研究開発支援 人工知能研修サービス テレワーク & オンライン授業を支援
PoC(概念実証)を失敗させないための支援 (本支援はセミナーに参加しアンケートに回答した方を対象としています。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/
Facebook: https://www.facebook.com/ClassCatJP/

 

ガイド : Keras :- サブクラス化で新しい層とモデルを作成する

セットアップ

import tensorflow as tf
from tensorflow import keras

 

Layer クラス : 状態 (重み) の組合せと幾つかの計算

Keras の中心的な抽象の一つは Layer クラスです。層は状態 (層の「重み」) と入力から出力への変換 (“call”、層の forward パス) をカプセル化します。

ここに密結合の層があります。それは状態: 変数 w と b を持ちます。

class Linear(keras.layers.Layer):
    def __init__(self, units=32, input_dim=32):
        super(Linear, self).__init__()
        w_init = tf.random_normal_initializer()
        self.w = tf.Variable(
            initial_value=w_init(shape=(input_dim, units), dtype="float32"),
            trainable=True,
        )
        b_init = tf.zeros_initializer()
        self.b = tf.Variable(
            initial_value=b_init(shape=(units,), dtype="float32"), trainable=True
        )

    def call(self, inputs):
        return tf.matmul(inputs, self.w) + self.b

まさに Python 関数のように、それを幾つかの tensor 入力上で呼び出すことにより層を利用できるでしょう。

x = tf.ones((2, 2))
linear_layer = Linear(4, 2)
y = linear_layer(x)
print(y)
tf.Tensor(
[[-0.00070209 -0.00715558 -0.03840686 -0.0268244 ]
 [-0.00070209 -0.00715558 -0.03840686 -0.0268244 ]], shape=(2, 4), dtype=float32)

重み w と b は (その上で) 層の属性として設定された層により自動的に追跡されることに注意してください :

assert linear_layer.weights == [linear_layer.w, linear_layer.b]

重みを層に追加するための素早いショートカットへのアクセスも持つことに注意してください : add_weight メソッドです :

class Linear(keras.layers.Layer):
    def __init__(self, units=32, input_dim=32):
        super(Linear, self).__init__()
        self.w = self.add_weight(
            shape=(input_dim, units), initializer="random_normal", trainable=True
        )
        self.b = self.add_weight(shape=(units,), initializer="zeros", trainable=True)

    def call(self, inputs):
        return tf.matmul(inputs, self.w) + self.b


x = tf.ones((2, 2))
linear_layer = Linear(4, 2)
y = linear_layer(x)
print(y)
tf.Tensor(
[[ 0.03699157 -0.01076736  0.08286229  0.0686149 ]
 [ 0.03699157 -0.01076736  0.08286229  0.0686149 ]], shape=(2, 4), dtype=float32)

 

層は非訓練可能な重みを持つことができます

訓練可能な重みの他に、層に非訓練可能な重みを追加することもできます。そのような重みは、層を訓練しているとき、逆伝播の間は考慮されないことを意図しています。

ここに非訓練可能な重みをどのように追加して使用するかがあります :

class ComputeSum(keras.layers.Layer):
    def __init__(self, input_dim):
        super(ComputeSum, self).__init__()
        self.total = tf.Variable(initial_value=tf.zeros((input_dim,)), trainable=False)

    def call(self, inputs):
        self.total.assign_add(tf.reduce_sum(inputs, axis=0))
        return self.total


x = tf.ones((2, 2))
my_sum = ComputeSum(2)
y = my_sum(x)
print(y.numpy())
y = my_sum(x)
print(y.numpy())
[2. 2.]
[4. 4.]

それは layer.weights の一部ですが、それは非訓練可能な重みとしてカテゴライズされます :

print("weights:", len(my_sum.weights))
print("non-trainable weights:", len(my_sum.non_trainable_weights))

# It's not included in the trainable weights:
print("trainable_weights:", my_sum.trainable_weights)
weights: 1
non-trainable weights: 1
trainable_weights: []

 

ベストプラクティス: 入力の shape が知れるまで重み作成を遅延する

上の Linear 層は __init__ で重み w と b の shape を計算するために使用される input_dim 引数を取りました :

class Linear(keras.layers.Layer):
    def __init__(self, units=32, input_dim=32):
        super(Linear, self).__init__()
        self.w = self.add_weight(
            shape=(input_dim, units), initializer="random_normal", trainable=True
        )
        self.b = self.add_weight(shape=(units,), initializer="zeros", trainable=True)

    def call(self, inputs):
        return tf.matmul(inputs, self.w) + self.b

多くの場合、貴方は入力のサイズを前もって知らないかもしれません、そしてその値が知れたときに重みを遅れて作成したいでしょう、時に層をインスタンス化した後に。

Keras API では、層重みを層の build(self, inputs_shape) メソッドで作成することを推奨します。このようにです :

class Linear(keras.layers.Layer):
    def __init__(self, units=32):
        super(Linear, self).__init__()
        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

層の __call__ メソッドは最初にそれが呼び出されたときに build を自動的に実行します。今貴方は lazy で従って容易に利用できる層を持ちます :

# At instantiation, we don't know on what inputs this is going to get called
linear_layer = Linear(32)

# The layer's weights are created dynamically the first time the layer is called
y = linear_layer(x)

 

層は再帰的に構成可能です

Layer インスタンスを他の層の属性として割り当てる場合、外側の層は内側の層の重みを追跡し始めます。

そのような部分層 (= sublayer) は __init__ メソッドで作成することを勧めます (何故ならば部分層は典型的には build メソッドを持つので、それらは外側の層が構築される (= built) ときに構築されるからです)。

# Let's assume we are reusing the Linear class
# with a `build` method that we defined above.


class MLPBlock(keras.layers.Layer):
    def __init__(self):
        super(MLPBlock, self).__init__()
        self.linear_1 = Linear(32)
        self.linear_2 = Linear(32)
        self.linear_3 = Linear(1)

    def call(self, inputs):
        x = self.linear_1(inputs)
        x = tf.nn.relu(x)
        x = self.linear_2(x)
        x = tf.nn.relu(x)
        return self.linear_3(x)


mlp = MLPBlock()
y = mlp(tf.ones(shape=(3, 64)))  # The first call to the `mlp` will create the weights
print("weights:", len(mlp.weights))
print("trainable weights:", len(mlp.trainable_weights))
weights: 6
trainable weights: 6

 

add_loss() メソッド

層の call メソッドを書くとき、貴方の訓練ループを書くときに、後で使用することを望む損失 tensor を作成できます。これは self.add_loss(value) を呼び出すことで行なうことができます :

# A layer that creates an activity regularization loss
class ActivityRegularizationLayer(keras.layers.Layer):
    def __init__(self, rate=1e-2):
        super(ActivityRegularizationLayer, self).__init__()
        self.rate = rate

    def call(self, inputs):
        self.add_loss(self.rate * tf.reduce_sum(inputs))
        return inputs

(任意の内側の層で作成されたものを含む) これらの損失は layer.losses を通して取得できます。このプロパティは top-level 層への総ての __call__ の開始でリセットされますので、layer.losses は最後の forward パスの間に作成された損失値を常に含みます。

class OuterLayer(keras.layers.Layer):
    def __init__(self):
        super(OuterLayer, self).__init__()
        self.activity_reg = ActivityRegularizationLayer(1e-2)

    def call(self, inputs):
        return self.activity_reg(inputs)


layer = OuterLayer()
assert len(layer.losses) == 0  # No losses yet since the layer has never been called

_ = layer(tf.zeros(1, 1))
assert len(layer.losses) == 1  # We created one loss value

# `layer.losses` gets reset at the start of each __call__
_ = layer(tf.zeros(1, 1))
assert len(layer.losses) == 1  # This is the loss created during the call above

加えて、loss プロパティはまた任意の内側の層の重みのために作成された正則化損失を含みます :

class OuterLayerWithKernelRegularizer(keras.layers.Layer):
    def __init__(self):
        super(OuterLayerWithKernelRegularizer, self).__init__()
        self.dense = keras.layers.Dense(
            32, kernel_regularizer=tf.keras.regularizers.l2(1e-3)
        )

    def call(self, inputs):
        return self.dense(inputs)


layer = OuterLayerWithKernelRegularizer()
_ = layer(tf.zeros((1, 1)))

# This is `1e-3 * sum(layer.dense.kernel ** 2)`,
# created by the `kernel_regularizer` above.
print(layer.losses)
<tf.Tensor: shape=(), dtype=float32, numpy=0.001832671>]

これらの損失は訓練ループを書くときに考慮されることを意図しています、このようにです :

# Instantiate an optimizer.
optimizer = tf.keras.optimizers.SGD(learning_rate=1e-3)
loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)

# Iterate over the batches of a dataset.
for x_batch_train, y_batch_train in train_dataset:
  with tf.GradientTape() as tape:
    logits = layer(x_batch_train)  # Logits for this minibatch
    # Loss value for this minibatch
    loss_value = loss_fn(y_batch_train, logits)
    # Add extra losses created during this forward pass:
    loss_value += sum(model.losses)

  grads = tape.gradient(loss_value, model.trainable_weights)
  optimizer.apply_gradients(zip(grads, model.trainable_weights))

訓練ループを書くことについての詳細なガイドについては、writing a training loop from scratch へのガイド を見てください。

これらの損失はまた fit() でシームレスに動作します (もしあれば、それらは自動的に合計されて main 損失に追加されます) :

import numpy as np

inputs = keras.Input(shape=(3,))
outputs = ActivityRegularizationLayer()(inputs)
model = keras.Model(inputs, outputs)

# If there is a loss passed in `compile`, thee regularization
# losses get added to it
model.compile(optimizer="adam", loss="mse")
model.fit(np.random.random((2, 3)), np.random.random((2, 3)))

# It's also possible not to pass any loss in `compile`,
# since the model already has a loss to minimize, via the `add_loss`
# call during the forward pass!
model.compile(optimizer="adam")
model.fit(np.random.random((2, 3)), np.random.random((2, 3)))
1/1 [==============================] - 0s 180ms/step - loss: 0.2410
1/1 [==============================] - 0s 55ms/step - loss: 0.0372
<tensorflow.python.keras.callbacks.History at 0x7f8c38baf7b8>

 

add_metric() メソッド

add_loss() と同様に、層はまた訓練の間の数量 (= quantity) の移動平均を追跡するために add_metric() メソッドを持ちます。

次の層を考えます : “ロジスティック endpoint” 層です。それは入力として予測 & ターゲットを取り、add_loss() を通して追跡した損失を計算し、そして add_metric() を通して追跡した精度スカラーを計算します。

class LogisticEndpoint(keras.layers.Layer):
    def __init__(self, name=None):
        super(LogisticEndpoint, self).__init__(name=name)
        self.loss_fn = keras.losses.BinaryCrossentropy(from_logits=True)
        self.accuracy_fn = keras.metrics.BinaryAccuracy()

    def call(self, targets, logits, sample_weights=None):
        # Compute the training-time loss value and add it
        # to the layer using `self.add_loss()`.
        loss = self.loss_fn(targets, logits, sample_weights)
        self.add_loss(loss)

        # Log accuracy as a metric and add it
        # to the layer using `self.add_metric()`.
        acc = self.accuracy_fn(targets, logits, sample_weights)
        self.add_metric(acc, name="accuracy")

        # Return the inference-time prediction tensor (for `.predict()`).
        return tf.nn.softmax(logits)

このように追跡されたメトリクスは layer.metrics を通してアクセス可能です :

layer = LogisticEndpoint()

targets = tf.ones((2, 2))
logits = tf.ones((2, 2))
y = layer(targets, logits)

print("layer.metrics:", layer.metrics)
print("current accuracy value:", float(layer.metrics[0].result()))
layer.metrics: [<tensorflow.python.keras.metrics.BinaryAccuracy object at 0x7eff1154b4a8>]
current accuracy value: 1.0

丁度 add_loss() のためのように、これらのメトリクスは fit() により追跡されます :

inputs = keras.Input(shape=(3,), name="inputs")
targets = keras.Input(shape=(10,), name="targets")
logits = keras.layers.Dense(10)(inputs)
predictions = LogisticEndpoint(name="predictions")(logits, targets)

model = keras.Model(inputs=[inputs, targets], outputs=predictions)
model.compile(optimizer="adam")

data = {
    "inputs": np.random.random((3, 3)),
    "targets": np.random.random((3, 10)),
}
model.fit(data)
1/1 [==============================] - 0s 299ms/step - loss: 1.0361 - binary_accuracy: 0.0000e+00
<tensorflow.python.keras.callbacks.History at 0x7eff10cffac8>

 

オプションで層上でシリアライゼーションを有効にできます

貴方のカスタム層が Functional モデル の一部としてシリアライズ可能であることが必要ならば、オプションで get_config メソッドを実装できます :

class Linear(keras.layers.Layer):
    def __init__(self, units=32):
        super(Linear, self).__init__()
        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):
        return {"units": self.units}


# Now you can recreate the layer from its config:
layer = Linear(64)
config = layer.get_config()
print(config)
new_layer = Linear.from_config(config)
{'units': 64}

基底 Layer クラスの __init__ メソッドは幾つかのキーワード引数、特に nameと dtype を取ることに注意してください。これらの引数を __init__ で親クラスに渡してそれらを層 config に含めることは良い実践です :

class Linear(keras.layers.Layer):
    def __init__(self, units=32, **kwargs):
        super(Linear, 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(Linear, self).get_config()
        config.update({"units": self.units})
        return config


layer = Linear(64)
config = layer.get_config()
print(config)
new_layer = Linear.from_config(config)
{'name': 'linear_14', 'trainable': True, 'dtype': 'float32', 'units': 64}

層をその config からデシリアライズするときにより柔軟性を必要とする場合には、from_config クラス・メソッドをオーバーライドすることもできます。これは from_config の基底実装です :

def from_config(cls, config):
  return cls(**config)

シリアライゼーションとセービングについて更に学習するためには、完全な Saving and Serializing Models へのガイド を見てください。

 

call メソッドの特権的な training 引数

幾つかの層、特に BatchNormalization 層と Dropout 層は訓練と推論の間で異なる動作を持ちます。そのような層のために、call メソッドで training (ブーリアン) 引数を公開することは標準的な実践です。

call でこの引数を公開することで、組込み訓練と評価ループ (e.g. fit) に訓練と推論で層を正しく使用することを可能にします。

class CustomDropout(keras.layers.Layer):
    def __init__(self, rate, **kwargs):
        super(CustomDropout, self).__init__(**kwargs)
        self.rate = rate

    def call(self, inputs, training=None):
        if training:
            return tf.nn.dropout(inputs, rate=self.rate)
        return inputs

 

call() メソッドの特権的な mask 引数

call() によりサポートされる他の特権的な引数は mask 引数です。

総ての Keras RNN 層でそれを見つけるでしょう。mask は時系列データを処理するとき特定の入力時間ステップをスキップするために使用されるブーリアン tensor (入力の時間ステップ毎に 1 つのブーリアン値) です。

Keras は mask が前の層により生成されるとき、それをサポートする層のために正しい mask 引数を __call__() に自動的に渡します。mask-生成層は mask_zero=True で configure された Embedding 層、そして Masking 層です。

マスキングについてと masking-enabled 層をどのように書くかついて学習するには、ガイド “understanding padding and masking” を確認してください。

 

Model クラス

一般に、内側の計算ブロックを定義するために Layer クラスを使用し、そして外側のモデル — 貴方が訓練するオブジェクト — を定義するために Model クラスを使用します。

例えば、ResNet 50 モデルでは、Layer をサブクラス化した幾つかの ResNet ブロックと、ResNet 50 ネットワーク全体を取り囲む単一のモデルを持つでしょう。

Model クラスは Layer と同じ API を持ちますが、次の違いがあります :

  • それは組み込み訓練、評価と予測ループを公開します (model.fit(), model.evaluate(), model.predict())。
  • それはその内側の層のリストを model.layers プロパティを通して公開します。
  • それはセービングとシリアライゼーション API を公開します (save(), save_weights()…)

実際上、Layer クラス は著述で (「畳込み層」や「リカレント層」内の) 「層 (= layer)」や (「ResNet ブロック」や「Inception ブロック」内の) 「ブロック”」として言及するものに対応しています。

その一方で、Model クラス は著述で (「深層学習モデル」内の)「モデル」や (「深層ニューラルネットワーク」内の)「ネットワーク」として言及されるものに対応しています。

そしてもし貴方が疑問に思うのであれば、「Layer クラスか Model クラスを使用するべきか?」、自問してください : その上で fit() を呼び出す必要がありますか? その上で save() を呼び出す必要がありますか?もしそうであれば、Model で進めてください。そうでないなら (貴方のクラスがより大きなシステム内の単なるブロックであるか貴方自身で訓練 & セービングコードを書いているのであれば)、Layer を使用してください。

例えば、上の mini-resnet サンプルを取り、それを (fit() で訓練可能で save_weights でセーブできるような) Model を構築するために使用できるでしょう :

class ResNet(tf.keras.Model):

    def __init__(self):
        super(ResNet, self).__init__()
        self.block_1 = ResNetBlock()
        self.block_2 = ResNetBlock()
        self.global_pool = layers.GlobalAveragePooling2D()
        self.classifier = Dense(num_classes)

    def call(self, inputs):
        x = self.block_1(inputs)
        x = self.block_2(x)
        x = self.global_pool(x)
        return self.classifier(x)


resnet = ResNet()
dataset = ...
resnet.fit(dataset, epochs=10)
resnet.save(filepath)

 

総てを一つにまとめる : end-to-end サンプル

ここに貴方がここまでに学習したものがあります :

  • 層は (__init__ か build で作成された) 状態と (call 内で定義された) ある計算をカプセル化します。
  • 層は新しい、より大きな計算ブロックを作成するために再帰的にネストできます。
  • 層は add_loss() と add_metric() を通して、損失 (典型的には正則化損失) とメトリクスを作成して追跡できます。
  • 外側のコンテナ、貴方が訓練したいものはモデルです。モデルはちょうど層のようなものですが、追加の訓練とシリアライゼーション・ユティリティを伴います。

これら総てのものを end-to-end サンプルにまとめましょう : 変分オートエンコーダ (VAE) を実装していきます。それを MNIST 数字の上で訓練します。

私達の VAE は Model のサブクラスで、 Layer をサブクラス化した層のネストされた構成として構築されます。それは正則化損失 (KL ダイバージェンス) をフィーチャーします。

from tensorflow.keras import layers


class Sampling(layers.Layer):
    """Uses (z_mean, z_log_var) to sample z, the vector encoding a digit."""

    def call(self, inputs):
        z_mean, z_log_var = inputs
        batch = tf.shape(z_mean)[0]
        dim = tf.shape(z_mean)[1]
        epsilon = tf.keras.backend.random_normal(shape=(batch, dim))
        return z_mean + tf.exp(0.5 * z_log_var) * epsilon


class Encoder(layers.Layer):
    """Maps MNIST digits to a triplet (z_mean, z_log_var, z)."""

    def __init__(self, latent_dim=32, intermediate_dim=64, name="encoder", **kwargs):
        super(Encoder, self).__init__(name=name, **kwargs)
        self.dense_proj = layers.Dense(intermediate_dim, activation="relu")
        self.dense_mean = layers.Dense(latent_dim)
        self.dense_log_var = layers.Dense(latent_dim)
        self.sampling = Sampling()

    def call(self, inputs):
        x = self.dense_proj(inputs)
        z_mean = self.dense_mean(x)
        z_log_var = self.dense_log_var(x)
        z = self.sampling((z_mean, z_log_var))
        return z_mean, z_log_var, z


class Decoder(layers.Layer):
    """Converts z, the encoded digit vector, back into a readable digit."""

    def __init__(self, original_dim, intermediate_dim=64, name="decoder", **kwargs):
        super(Decoder, self).__init__(name=name, **kwargs)
        self.dense_proj = layers.Dense(intermediate_dim, activation="relu")
        self.dense_output = layers.Dense(original_dim, activation="sigmoid")

    def call(self, inputs):
        x = self.dense_proj(inputs)
        return self.dense_output(x)


class VariationalAutoEncoder(keras.Model):
    """Combines the encoder and decoder into an end-to-end model for training."""

    def __init__(
        self,
        original_dim,
        intermediate_dim=64,
        latent_dim=32,
        name="autoencoder",
        **kwargs
    ):
        super(VariationalAutoEncoder, self).__init__(name=name, **kwargs)
        self.original_dim = original_dim
        self.encoder = Encoder(latent_dim=latent_dim, intermediate_dim=intermediate_dim)
        self.decoder = Decoder(original_dim, intermediate_dim=intermediate_dim)

    def call(self, inputs):
        z_mean, z_log_var, z = self.encoder(inputs)
        reconstructed = self.decoder(z)
        # Add KL divergence regularization loss.
        kl_loss = -0.5 * tf.reduce_mean(
            z_log_var - tf.square(z_mean) - tf.exp(z_log_var) + 1
        )
        self.add_loss(kl_loss)
        return reconstructed

MNIST 上の単純な訓練ループを書きましょう :

original_dim = 784
vae = VariationalAutoEncoder(original_dim, 64, 32)

optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)
mse_loss_fn = tf.keras.losses.MeanSquaredError()

loss_metric = tf.keras.metrics.Mean()

(x_train, _), _ = tf.keras.datasets.mnist.load_data()
x_train = x_train.reshape(60000, 784).astype("float32") / 255

train_dataset = tf.data.Dataset.from_tensor_slices(x_train)
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

epochs = 2

# Iterate over epochs.
for epoch in range(epochs):
    print("Start of epoch %d" % (epoch,))

    # Iterate over the batches of the dataset.
    for step, x_batch_train in enumerate(train_dataset):
        with tf.GradientTape() as tape:
            reconstructed = vae(x_batch_train)
            # Compute reconstruction loss
            loss = mse_loss_fn(x_batch_train, reconstructed)
            loss += sum(vae.losses)  # Add KLD regularization loss

        grads = tape.gradient(loss, vae.trainable_weights)
        optimizer.apply_gradients(zip(grads, vae.trainable_weights))

        loss_metric(loss)

        if step % 100 == 0:
            print("step %d: mean loss = %.4f" % (step, loss_metric.result()))
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
Start of epoch 0
step 0: mean loss = 0.3348
step 100: mean loss = 0.1249
step 200: mean loss = 0.0988
step 300: mean loss = 0.0889
step 400: mean loss = 0.0840
step 500: mean loss = 0.0807
step 600: mean loss = 0.0786
step 700: mean loss = 0.0770
step 800: mean loss = 0.0759
step 900: mean loss = 0.0749
Start of epoch 1
step 0: mean loss = 0.0746
step 100: mean loss = 0.0739
step 200: mean loss = 0.0734
step 300: mean loss = 0.0730
step 400: mean loss = 0.0726
step 500: mean loss = 0.0722
step 600: mean loss = 0.0720
step 700: mean loss = 0.0717
step 800: mean loss = 0.0714
step 900: mean loss = 0.0712

VAE は Model をサブクラス化していますから、それは組み込み訓練ループをフィーチャーすることに注意してください。従ってそれをこのように訓練することもできたでしょう :

vae = VariationalAutoEncoder(784, 64, 32)

optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)

vae.compile(optimizer, loss=tf.keras.losses.MeanSquaredError())
vae.fit(x_train, x_train, epochs=2, batch_size=64)
Epoch 1/2
938/938 [==============================] - 4s 3ms/step - loss: 0.0953
Epoch 2/2
938/938 [==============================] - 3s 3ms/step - loss: 0.0675
<tensorflow.python.keras.callbacks.History at 0x7fee90279828>

 

オブジェクト指向開発を越えて : Functional API

このサンプルは貴方にとってオブジェクト指向開発に過ぎたでしょうか?貴方はまた Functional API を使用してモデルを構築することもできます。重要なことは、一つのスタイルか別のものの選択は他のスタイルで書かれたコンポーネントを活用することを妨げないことです : 貴方は常に上手く組み合わせることができます。

例えば、下の Functional API サンプルは上のサンプルで定義したのと同じ Sampling 層を再利用しています :

original_dim = 784
intermediate_dim = 64
latent_dim = 32

# Define encoder model.
original_inputs = tf.keras.Input(shape=(original_dim,), name="encoder_input")
x = layers.Dense(intermediate_dim, activation="relu")(original_inputs)
z_mean = layers.Dense(latent_dim, name="z_mean")(x)
z_log_var = layers.Dense(latent_dim, name="z_log_var")(x)
z = Sampling()((z_mean, z_log_var))
encoder = tf.keras.Model(inputs=original_inputs, outputs=z, name="encoder")

# Define decoder model.
latent_inputs = tf.keras.Input(shape=(latent_dim,), name="z_sampling")
x = layers.Dense(intermediate_dim, activation="relu")(latent_inputs)
outputs = layers.Dense(original_dim, activation="sigmoid")(x)
decoder = tf.keras.Model(inputs=latent_inputs, outputs=outputs, name="decoder")

# Define VAE model.
outputs = decoder(z)
vae = tf.keras.Model(inputs=original_inputs, outputs=outputs, name="vae")

# Add KL divergence regularization loss.
kl_loss = -0.5 * tf.reduce_mean(z_log_var - tf.square(z_mean) - tf.exp(z_log_var) + 1)
vae.add_loss(kl_loss)

# Train.
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)
vae.compile(optimizer, loss=tf.keras.losses.MeanSquaredError())
vae.fit(x_train, x_train, epochs=3, batch_size=64)
Epoch 1/3
938/938 [==============================] - 3s 3ms/step - loss: 0.0948
Epoch 2/3
938/938 [==============================] - 3s 3ms/step - loss: 0.0677
Epoch 3/3
938/938 [==============================] - 3s 3ms/step - loss: 0.0674
<tensorflow.python.keras.callbacks.History at 0x7fee8f847710>
 

以上



TensorFlow 2.4 : ガイド : Keras :- 組込みメソッドで訓練と評価

TensorFlow 2.0 : ガイド : Keras :- 組込みメソッドで訓練と評価 (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 01/11/2021

* 本ページは、TensorFlow org サイトの Guide – Keras の以下のページを翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

無料セミナー実施中 クラスキャット主催 人工知能 & ビジネス Web セミナー

人工知能とビジネスをテーマにウェビナー (WEB セミナー) を定期的に開催しています。スケジュールは弊社 公式 Web サイト でご確認頂けます。
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
  • Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
クラスキャットは人工知能・テレワークに関する各種サービスを提供しております :

人工知能研究開発支援 人工知能研修サービス テレワーク & オンライン授業を支援
PoC(概念実証)を失敗させないための支援 (本支援はセミナーに参加しアンケートに回答した方を対象としています。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/
Facebook: https://www.facebook.com/ClassCatJP/

 

 

ガイド : Keras :- 組込みメソッドで訓練と評価

セットアップ

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

 

イントロダクション

このガイドは (model.fit(), model.evaluate(), model.predict() のような) 訓練 & 検証のための組込み API を使用するときの訓練、評価と予測 (推論) モデルをカバーします。

貴方自身の訓練ステップ関数を指定する間に fit() を活用することに関心がある場合には、”customizing what happens in fit()” を見てください。

貴方自身の訓練 & 評価ループをスクラッチから書くことに関心がある場合には、”スクラッチから訓練ループを書く” を見てください。

一般に、組み込みループを使用していようが貴方自身のものを書いていようが、モデル訓練 & 評価は総ての種類の Keras モデル — シーケンシャル・モデル、Functional API で構築されたモデル、そしてモデル・サブクラス化を通してスクラッチから書かれたモデル — に渡り厳密に同じように動作します。

このガイドは分散訓練はカバーしません。分散訓練については、マルチ-gpu & 分散訓練へのガイド を見てください。

 

API 概要: 最初の end-to-end サンプル

データをモデルの組み込み訓練ループに渡すとき、(もし貴方のデータが小さくてメモリに収まるのであれば) Numpy 配列tf.data Dataset オブジェクトを使用するべきです。次の幾つかのパラグラフでは、optimizer、損失そしてメトリクスをどのように使用するかを実演するために、MNIST データセットを Numpy 配列として使用します。

次のモデルを考えましょう (ここでは、Functional API で構築しますが、それはシーケンシャル・モデルやサブクラス化モデルでもかまいません) :

inputs = keras.Input(shape=(784,), name="digits")
x = layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = layers.Dense(10, activation="softmax", name="predictions")(x)

model = keras.Model(inputs=inputs, outputs=outputs)

ここに典型的な end-to-end ワークフローがどのようなものかがあり、以下から成ります :

  • 訓練
  • 元の訓練データから生成された取り置いたセット上の検証
  • テストデータ上の評価

このサンプルのために MNIST データを使用します。

(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()

# Preprocess the data (these are NumPy arrays)
x_train = x_train.reshape(60000, 784).astype("float32") / 255
x_test = x_test.reshape(10000, 784).astype("float32") / 255

y_train = y_train.astype("float32")
y_test = y_test.astype("float32")

# Reserve 10,000 samples for validation
x_val = x_train[-10000:]
y_val = y_train[-10000:]
x_train = x_train[:-10000]
y_train = y_train[:-10000]

訓練 configuration を指定します (optimizer、損失、メトリクス) :

model.compile(
    optimizer=keras.optimizers.RMSprop(),  # Optimizer
    # Loss function to minimize
    loss=keras.losses.SparseCategoricalCrossentropy(),
    # List of metrics to monitor
    metrics=[keras.metrics.SparseCategoricalAccuracy()],
)

fit() を呼び出します、これはデータをサイズ “batch_size” の “batches” にスライスし、そして与えられた数の “epochs” のためにデータセット全体に渡り繰り返し反復します。

print("Fit model on training data")
history = model.fit(
    x_train,
    y_train,
    batch_size=64,
    epochs=2,
    # We pass some validation for
    # monitoring validation loss and metrics
    # at the end of each epoch
    validation_data=(x_val, y_val),
)
Fit model on training data
Epoch 1/2
782/782 [==============================] - 3s 3ms/step - loss: 0.5805 - sparse_categorical_accuracy: 0.8396 - val_loss: 0.1783 - val_sparse_categorical_accuracy: 0.9477
Epoch 2/2
782/782 [==============================] - 2s 2ms/step - loss: 0.1698 - sparse_categorical_accuracy: 0.9495 - val_loss: 0.1314 - val_sparse_categorical_accuracy: 0.9606

返された “history” オブジェクトは訓練の間の損失値とメトリック値の記録を保持しています :

history.history
{'loss': [0.34305039048194885, 0.15996208786964417],
 'sparse_categorical_accuracy': [0.9029399752616882, 0.9515600204467773],
 'val_loss': [0.17833450436592102, 0.1313990205526352],
 'val_sparse_categorical_accuracy': [0.947700023651123, 0.9606000185012817]}

evaluate() を通してテストデータ上でモデルを評価します :

# Evaluate the model on the test data using `evaluate`
print("Evaluate on test data")
results = model.evaluate(x_test, y_test, batch_size=128)
print("test loss, test acc:", results)

# Generate predictions (probabilities -- the output of the last layer)
# on new data using `predict`
print("Generate predictions for 3 samples")
predictions = model.predict(x_test[:3])
print("predictions shape:", predictions.shape)
Evaluate on test data
79/79 [==============================] - 0s 1ms/step - loss: 0.1306 - sparse_categorical_accuracy: 0.9597
test loss, test acc: [0.13056731224060059, 0.9596999883651733]
Generate predictions for 3 samples
predictions shape: (3, 10)

今は、このワークフローの各ピースを詳細にレビューしましょう。

 

compile() メソッド: 損失、メトリクス、そして optimizer を指定する

fit でモデルを訓練するためには、損失関数、optimizer そしてオプションで監視するための幾つかのメトリクスを指定する必要があります。

これらをモデルに compile() メソッドへの引数として渡します :

model.compile(
    optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
    loss=keras.losses.SparseCategoricalCrossentropy(),
    metrics=[keras.metrics.SparseCategoricalAccuracy()],
)

metrics 引数はリストであるべきです — 貴方のモデルは任意の数のメトリクスを持つことができます。

貴方のモデルがマルチ出力を持つ場合、各出力に対して異なる損失とメトリクスを指定できて、そしてモデルのトータル損失への各出力の寄与を調節できます。これについての更なる詳細をセクション「データをマルチ入力、マルチ出力モデルに渡す」で見つけるでしょう。

デフォルト設定で満足できる場合には、多くの場合 optimizer、損失とメトリクスはショートカットとして文字列識別子で指定できることに注意してください :

model.compile(
    optimizer="rmsprop",
    loss="sparse_categorical_crossentropy",
    metrics=["sparse_categorical_accuracy"],
)

後で再利用するために、モデル定義と compile ステップを関数内に配置しましょう ; それらをこのガイドの異なるサンプルに渡り幾度か呼び出します。

def get_uncompiled_model():
    inputs = keras.Input(shape=(784,), name="digits")
    x = layers.Dense(64, activation="relu", name="dense_1")(inputs)
    x = layers.Dense(64, activation="relu", name="dense_2")(x)
    outputs = layers.Dense(10, activation="softmax", name="predictions")(x)
    model = keras.Model(inputs=inputs, outputs=outputs)
    return model


def get_compiled_model():
    model = get_uncompiled_model()
    model.compile(
        optimizer="rmsprop",
        loss="sparse_categorical_crossentropy",
        metrics=["sparse_categorical_accuracy"],
    )
    return model

 
多くの組み込み optimizer、損失とメトリクスが利用可能です

一般に、貴方自身の損失、メトリクスや optimizer をスクラッチから作成しなくてもかまいません、何故ならば貴方が必要なものは既に Keras API の一部でありがちだからです :

Optimizers :

  • SGD() (with or without モメンタム)
  • RMSprop()
  • Adam()
  • 等々。

損失 :

  • MeanSquaredError()
  • KLDivergence()
  • CosineSimilarity()
  • 等々。

メトリクス :

  • AUC()
  • Precision()
  • Recall()
  • 等々

 

カスタム損失

Keras でカスタム損失を提供するためには 2 つの方法があります。最初のサンプルは入力 y_true と y_pred を受け取る関数を作成します。次のサンプルは実データと予測の間の平均二乗誤差を計算する損失関数を示します :

def custom_mean_squared_error(y_true, y_pred):
    return tf.math.reduce_mean(tf.square(y_true - y_pred))


model = get_uncompiled_model()
model.compile(optimizer=keras.optimizers.Adam(), loss=custom_mean_squared_error)

# We need to one-hot encode the labels to use MSE
y_train_one_hot = tf.one_hot(y_train, depth=10)
model.fit(x_train, y_train_one_hot, batch_size=64, epochs=1)
782/782 [==============================] - 2s 2ms/step - loss: 0.0281
<tensorflow.python.keras.callbacks.History at 0x7f01164a8978>

y_true と y_pred の他のパラメータを取る損失関数を必要とする場合、tf.keras.losses.Loss クラスをサブクラス化して次の 2 つのメソッドを実装することができます :

  • __init__(self) : 損失関数の呼び出しの間に渡すためのパラメータを受け取ります。
  • call(self, y_true, y_pred) : モデルの損失を計算するためにターゲット (y_true) とモデル予測 (y_pred) を使用します。

貴方は平均二乗誤差を使用することを望むとしましょう、但し 0.5 から遠い予測値に刺激を与えないような (= de-incentivize) 追加項とともに (categorical ターゲットは one-hot エンコードされて 0 と 1 の間の値を取るものと仮定します)。これはモデルが確信し過ぎないようにインセンティブを作成します、これは overfitting を減じるのに役立つかもしれません (それが動作するかは試すまで分かりません!)。

ここにそれをどのように行なうかがあります :

class CustomMSE(keras.losses.Loss):
    def __init__(self, regularization_factor=0.1, name="custom_mse"):
        super().__init__(name=name)
        self.regularization_factor = regularization_factor

    def call(self, y_true, y_pred):
        mse = tf.math.reduce_mean(tf.square(y_true - y_pred))
        reg = tf.math.reduce_mean(tf.square(0.5 - y_pred))
        return mse + reg * self.regularization_factor


model = get_uncompiled_model()
model.compile(optimizer=keras.optimizers.Adam(), loss=CustomMSE())

y_train_one_hot = tf.one_hot(y_train, depth=10)
model.fit(x_train, y_train_one_hot, batch_size=64, epochs=1)
782/782 [==============================] - 2s 2ms/step - loss: 0.0489
<tensorflow.python.keras.callbacks.History at 0x7f0115bd5828>

 

カスタム・メトリクス

API の一部ではないメトリックを必要とする場合、tf.keras.metrics.Metric クラスをサブクラス化することによりカスタム・メトリクスを容易に作成できます。4 つのメソッドを実装する必要があります :

  • __init__(self), そこでは貴方のメトリックのための状態変数を作成します。
  • update_state(self, y_true, y_pred, sample_weight=None), これは状態変数を更新するためにターゲット y_true と モデル予測 y_pred を使用します。
  • result(self), これは最終結果を計算するために状態変数を使用します。
  • reset_states(self), これはメトリックの状態を再初期化します。

状態更新と結果の計算は (それぞれ update_state() と result() で) 別々にしておきます、何故ならばある場合には、結果計算は非常に高価であるかもしれないためで、それは定期的にだけ行われるでしょう。

ここに CatgoricalTruePositives メトリックをどのように実装するかを示す単純なサンプルがあります、それは与えられたクラスに属するものとして正しく分類されたサンプルが幾つかを数えます :

class CategoricalTruePositives(keras.metrics.Metric):
    def __init__(self, name="categorical_true_positives", **kwargs):
        super(CategoricalTruePositives, self).__init__(name=name, **kwargs)
        self.true_positives = self.add_weight(name="ctp", initializer="zeros")

    def update_state(self, y_true, y_pred, sample_weight=None):
        y_pred = tf.reshape(tf.argmax(y_pred, axis=1), shape=(-1, 1))
        values = tf.cast(y_true, "int32") == tf.cast(y_pred, "int32")
        values = tf.cast(values, "float32")
        if sample_weight is not None:
            sample_weight = tf.cast(sample_weight, "float32")
            values = tf.multiply(values, sample_weight)
        self.true_positives.assign_add(tf.reduce_sum(values))

    def result(self):
        return self.true_positives

    def reset_states(self):
        # The state of the metric will be reset at the start of each epoch.
        self.true_positives.assign(0.0)


model = get_uncompiled_model()
model.compile(
    optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
    loss=keras.losses.SparseCategoricalCrossentropy(),
    metrics=[CategoricalTruePositives()],
)
model.fit(x_train, y_train, batch_size=64, epochs=3)
Epoch 1/3
782/782 [==============================] - 2s 2ms/step - loss: 0.5990 - categorical_true_positives: 21949.4393
Epoch 2/3
782/782 [==============================] - 2s 2ms/step - loss: 0.1754 - categorical_true_positives: 23804.2822
Epoch 3/3
782/782 [==============================] - 2s 2ms/step - loss: 0.1215 - categorical_true_positives: 24171.2644
<tensorflow.python.keras.callbacks.History at 0x7fd152f26e80>

 

標準的なシグネチャに当てはまらない損失とメトリクスを扱う

損失とメトリクスの圧倒的な大部分は y_true と y_pred から計算できます、ここで y_pred はモデルの出力です。しかし総てではありません。例えば、正則化損失は層の活性を必要とするだけかもしれません (この場合ターゲットはありません)、そしてこの活性はモデル出力ではないかもしれません。

そのような場合、カスタム層の call メソッドの内側から self.add_loss(loss_value) を呼び出すことができます。このように追加された損失は訓練の間は “main” 損失に追加されます (compile() に渡された 1 つ)。ここに activity 正則化を追加する単純なサンプルがあります (activity 正則化は総ての Keras 層で組込みであることに注意してください — この層は具体的な例を提供する目的のためだけです) :

class ActivityRegularizationLayer(layers.Layer):
    def call(self, inputs):
        self.add_loss(tf.reduce_sum(inputs) * 0.1)
        return inputs  # Pass-through layer.


inputs = keras.Input(shape=(784,), name="digits")
x = layers.Dense(64, activation="relu", name="dense_1")(inputs)

# Insert activity regularization as a layer
x = ActivityRegularizationLayer()(x)

x = layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = layers.Dense(10, name="predictions")(x)

model = keras.Model(inputs=inputs, outputs=outputs)
model.compile(
    optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
)

# The displayed loss will be much higher than before
# due to the regularization component.
model.fit(x_train, y_train, batch_size=64, epochs=1)
782/782 [==============================] - 2s 2ms/step - loss: 3.6940
<tensorflow.python.keras.callbacks.History at 0x7fd152623780>

add_metric() を使用して、logging メトリック値に対しても同じことを行うことができます :

class MetricLoggingLayer(layers.Layer):
    def call(self, inputs):
        # The `aggregation` argument defines
        # how to aggregate the per-batch values
        # over each epoch:
        # in this case we simply average them.
        self.add_metric(
            keras.backend.std(inputs), name="std_of_activation", aggregation="mean"
        )
        return inputs  # Pass-through layer.


inputs = keras.Input(shape=(784,), name="digits")
x = layers.Dense(64, activation="relu", name="dense_1")(inputs)

# Insert std logging as a layer.
x = MetricLoggingLayer()(x)

x = layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = layers.Dense(10, name="predictions")(x)

model = keras.Model(inputs=inputs, outputs=outputs)
model.compile(
    optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
)
model.fit(x_train, y_train, batch_size=64, epochs=1)
782/782 [==============================] - 2s 2ms/step - loss: 0.5606 - std_of_activation: 0.9663
<tensorflow.python.keras.callbacks.History at 0x7fd15149e6d8>

Functional API では、model.add_loss(loss_tensor) か model.add_metric(metric_tensor, name, aggregation) を呼び出すこともできます。

ここに単純なサンプルがあります :

inputs = keras.Input(shape=(784,), name="digits")
x1 = layers.Dense(64, activation="relu", name="dense_1")(inputs)
x2 = layers.Dense(64, activation="relu", name="dense_2")(x1)
outputs = layers.Dense(10, name="predictions")(x2)
model = keras.Model(inputs=inputs, outputs=outputs)

model.add_loss(tf.reduce_sum(x1) * 0.1)

model.add_metric(keras.backend.std(x1), name="std_of_activation", aggregation="mean")

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
)
model.fit(x_train, y_train, batch_size=64, epochs=1)
782/782 [==============================] - 2s 2ms/step - loss: 3.7107 - std_of_activation: 0.0021
<tensorflow.python.keras.callbacks.History at 0x7fd150b28668>

add_loss() を通して損失を渡すとき損失関数なしで compile() を呼び出すことが可能になることに注意してください、何故ならばモデルは既に最小化するための損失を持っているからです。

次の LogisticEndpoint 層を考えます : それは入力として targets & logits を取り、そしてそれは add_loss() を通して交差エントロピー損失を追跡します。それはまた add_metric() を通して分類精度も追跡します。

class LogisticEndpoint(keras.layers.Layer):
    def __init__(self, name=None):
        super(LogisticEndpoint, self).__init__(name=name)
        self.loss_fn = keras.losses.BinaryCrossentropy(from_logits=True)
        self.accuracy_fn = keras.metrics.BinaryAccuracy()

    def call(self, targets, logits, sample_weights=None):
        # Compute the training-time loss value and add it
        # to the layer using `self.add_loss()`.
        loss = self.loss_fn(targets, logits, sample_weights)
        self.add_loss(loss)

        # Log accuracy as a metric and add it
        # to the layer using `self.add_metric()`.
        acc = self.accuracy_fn(targets, logits, sample_weights)
        self.add_metric(acc, name="accuracy")

        # Return the inference-time prediction tensor (for `.predict()`).
        return tf.nn.softmax(logits)

それを 2 つの入力 (入力データ & ターゲット) を持つ (loss 引数なしでコンパイルされた) モデルで使用することができます、このようにです :

import numpy as np

inputs = keras.Input(shape=(3,), name="inputs")
targets = keras.Input(shape=(10,), name="targets")
logits = keras.layers.Dense(10)(inputs)
predictions = LogisticEndpoint(name="predictions")(logits, targets)

model = keras.Model(inputs=[inputs, targets], outputs=predictions)
model.compile(optimizer="adam")  # No loss argument!

data = {
    "inputs": np.random.random((3, 3)),
    "targets": np.random.random((3, 10)),
}
model.fit(data)
1/1 [==============================] - 0s 355ms/step - loss: 0.8829 - binary_accuracy: 0.0000e+00

マルチ入力モデルの訓練についてより多くの情報のためには、セクション Passing data to multi-input, multi-output models を見てください。

 

検証取り置き (= holdout) セットを自動的に分離する

貴方が見た最初の end-to-end サンプルでは、各エポックの終わりに検証損失と検証メトリクスを評価するために Numpy 配列のタプル (x_val, y_val) をモデルに渡すために validation_data 引数を使用しました。

ここにもう一つのオプションがあります : 引数 validation_split は検証のために訓練データの一部を自動的に取っておくことを可能にします。引数値は検証のために取っておかれるデータの割合を表しますので、それは 0 より高く 1 より低い数字に設定されるべきです。例えば、validation_split=0.2 は「検証のためにデータの 20% を使用する」ことを意味し、そして validation_split=0.6 は「検証のためにデータの 60% を使用する」ことを意味します。

検証が計算される方法は任意のシャッフルの前に、fit コールにより受け取った配列の最後の x% サンプルを取ります。

Numpy データによる訓練のときに validation_split を使用できるだけであることに注意してください。

model = get_compiled_model()
model.fit(x_train, y_train, batch_size=64, validation_split=0.2, epochs=1)
625/625 [==============================] - 2s 3ms/step - loss: 0.6469 - sparse_categorical_accuracy: 0.8200 - val_loss: 0.2360 - val_sparse_categorical_accuracy: 0.9282
<tensorflow.python.keras.callbacks.History at 0x7fd14d1d42e8>

 

tf.data Datasets からの訓練 & 評価

前の 2, 3 のパラグラフでは、損失、メトリクスと optimizer をどのように扱うかを見ました、そしてデータが Numpy 配列として渡されるとき、fit で validation_data と validation_split 引数をどのように使用するかを見ました。

今は貴方のデータが tf.data Dataset の形式で現れる場合を見ましょう。

tf.data API はデータを高速でスケーラブルな方法でロードして前処理するための TensorFlow 2.0 のユティリティのセットです。

Datasets の作成についての完全なガイドは、tf.data ドキュメント を見てください。

Dataset インスタンスをメソッド fit(), evaluate() と predict() に直接的に渡すことができます :

model = get_compiled_model()

# First, let's create a training Dataset instance.
# For the sake of our example, we'll use the same MNIST data as before.
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
# Shuffle and slice the dataset.
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

# Now we get a test dataset.
test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test))
test_dataset = test_dataset.batch(64)

# Since the dataset already takes care of batching,
# we don't pass a `batch_size` argument.
model.fit(train_dataset, epochs=3)

# You can also evaluate or predict on a dataset.
print("Evaluate")
result = model.evaluate(test_dataset)
dict(zip(model.metrics_names, result))
Epoch 1/3
782/782 [==============================] - 2s 3ms/step - loss: 0.5574 - sparse_categorical_accuracy: 0.8441
Epoch 2/3
782/782 [==============================] - 2s 3ms/step - loss: 0.1744 - sparse_categorical_accuracy: 0.9499
Epoch 3/3
782/782 [==============================] - 2s 3ms/step - loss: 0.1216 - sparse_categorical_accuracy: 0.9639
Evaluate
157/157 [==============================] - 0s 1ms/step - loss: 0.1193 - sparse_categorical_accuracy: 0.9641
{'loss': 0.11928322911262512,
 'sparse_categorical_accuracy': 0.9641000032424927}

Dataset は各エポックの最後にリセットされますので、それは次のエポックで再利用できることに注意してください。

このデータセットから特定の数のバッチ上でだけ訓練を実行することを望む場合、steps_per_epoch 引数を渡すことができます、これは次のエポックに移る前にこの Dataset を使用してモデルが幾つの訓練ステップを実行するべきかを指定します。

これを行なう場合、dataset は各エポックの最後にリセットされません、代わりに次のバッチを単にドローし続けます。dataset はやがてデータを使い果たします (それが無限ループな dataset でない限りは)。

model = get_compiled_model()

# Prepare the training dataset
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

# Only use the 100 batches per epoch (that's 64 * 100 samples)
model.fit(train_dataset, epochs=3, steps_per_epoch=100)
Epoch 1/3
100/100 [==============================] - 1s 3ms/step - loss: 1.2767 - sparse_categorical_accuracy: 0.6370
Epoch 2/3
100/100 [==============================] - 0s 3ms/step - loss: 0.4023 - sparse_categorical_accuracy: 0.8871
Epoch 3/3
100/100 [==============================] - 0s 3ms/step - loss: 0.3542 - sparse_categorical_accuracy: 0.8964
<tensorflow.python.keras.callbacks.History at 0x7fd14d6df4e0>

 

検証 dataset を使用する

Dataset インスタンスを fit の validation_data 引数として渡すことができます :

model = get_compiled_model()

# Prepare the training dataset
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

# Prepare the validation dataset
val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))
val_dataset = val_dataset.batch(64)

model.fit(train_dataset, epochs=1, validation_data=val_dataset)
782/782 [==============================] - 3s 3ms/step - loss: 0.5455 - sparse_categorical_accuracy: 0.8495 - val_loss: 0.2034 - val_sparse_categorical_accuracy: 0.9388
<tensorflow.python.keras.callbacks.History at 0x7fd15710acf8>

各エポックの最後に、モデルは検証 Dataset に渡り反復して検証損失と検証メトリクスを計算します。

この Dataset から特定の数のバッチ上でだけ検証を実行することを望む場合、validation_steps 引数を渡すことができます、これはモデルが検証を中断して次のエポックに進む前に検証 Dataset で幾つの検証ステップを実行するべきかを指定します :

model = get_compiled_model()

# Prepare the training dataset
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

# Prepare the validation dataset
val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))
val_dataset = val_dataset.batch(64)

model.fit(
    train_dataset,
    epochs=1,
    # Only run validation using the first 10 batches of the dataset
    # using the `validation_steps` argument
    validation_data=val_dataset,
    validation_steps=10,
)
782/782 [==============================] - 3s 3ms/step - loss: 0.5584 - sparse_categorical_accuracy: 0.8487 - val_loss: 0.3266 - val_sparse_categorical_accuracy: 0.9187
<tensorflow.python.keras.callbacks.History at 0x7fd15702b780>

検証 Dataset は各使用の後にリセットされることに注意してください (その結果エポックからエポックへ同じサンプル上で常に評価しています)。

Dataset オブジェクトから訓練するときには (訓練データから取り置いたセットを生成する) 引数 validation_split はサポートされません、何故ならばこの機能は (Dataset API では一般に可能ではない) dataset のサンプルをインデックスする機能を必要とするからです。

 

サポートされる他の入力フォーマット

Numpy 配列、eager tensor と TensorFlow Dataset の他にも、Pandas データフレームを使用したりバッチを yield する Python ジェネレータから Keras モデルを訓練することが可能です。

特に、keras.utils.Sequence クラスは (マルチプロセッシング-aware でシャッフル可能な) Python データ・ジェネレータを構築するための単純なインターフェイスを提供します。

一般に、以下を利用することを勧めます :

  • 貴方のデータが小さくてメモリに収まるのであれば NumPy 入力データ
  • 大規模なデータセットを持ち分散訓練を行なうことを必要とするのであれば Dataset オブジェクト
  • 大規模なデータセットを持ち TensorFlow では成せない多くのカスタム Python-側処理を行なう必要があれば Sequence オブジェクト (e.g. データロードや前処理のために外部ライブラリに依拠する場合)。

 

入力として keras.utils.Sequence オブジェクトを使用する

keras.utils.Sequence は 2 つの重要な特性を持つ Python ジェネレータを得るためにサブクラス化できるユティリティです :

  • それはマルチプロセッシングと上手く動作します。
  • それはシャッフルできます (e.g. fit() で shuffle=True を渡すとき)。

シークエンスは 2 つのメソッドを実装しなければなりません :

  • __getitem__
  • __len__

メソッド __getitem__ は完全なバッチを返すべきです。エポック間でデータセットを変更することを望む場合、on_epoch_end を実装しても良いです。

ここに素早いサンプルがあります :

from skimage.io import imread
from skimage.transform import resize
import numpy as np

# Here, `filenames` is list of path to the images
# and `labels` are the associated labels.

class CIFAR10Sequence(Sequence):
    def __init__(self, filenames, labels, batch_size):
        self.filenames, self.labels = filenames, labels
        self.batch_size = batch_size

    def __len__(self):
        return int(np.ceil(len(self.filenames) / float(self.batch_size)))

    def __getitem__(self, idx):
        batch_x = self.filenames[idx * self.batch_size:(idx + 1) * self.batch_size]
        batch_y = self.labels[idx * self.batch_size:(idx + 1) * self.batch_size]
        return np.array([
            resize(imread(filename), (200, 200))
               for filename in batch_x]), np.array(batch_y)

sequence = CIFAR10Sequence(filenames, labels, batch_size)
model.fit(sequence, epochs=10)

 

サンプル重み付けとクラス重み付けを使用する

デフォルト設定ではサンプルの重みはデータセットにおけるその頻度により決定されます。サンプル頻度からは独立して、データを重み付ける 2 つのメソッドがあります :

  • クラス重み
  • サンプル重み

 

クラス重み

これは Model.fit() に class_weight 引数への辞書を渡すことにより設定されます。この辞書はクラス・インデックスをこのクラスに属するサンプルのために使用されるべき重みにマップします。

これは再サンプリングすることなしにクラスの平衡を保つためや、特定のくクラスにより多くの重要度を与えるモデルを訓練するために利用できます。

例えば、貴方のデータでクラス “0” がクラス “1” の半分で表現されるのであれば、Model.fit(…, class_weight={0: 1., 1: 0.5}) を使用できるでしょう。

ここに NumPy サンプルがあります、そこではクラス #5 (これは MNIST データセットの数字 “5” です) の正しい分類により多くの重要性を与えるためにクラス重みかサンプル重みを使用します。

import numpy as np

class_weight = {
    0: 1.0,
    1: 1.0,
    2: 1.0,
    3: 1.0,
    4: 1.0,
    # Set weight "2" for class "5",
    # making this class 2x more important
    5: 2.0,
    6: 1.0,
    7: 1.0,
    8: 1.0,
    9: 1.0,
}

print("Fit with class weight")
model = get_compiled_model()
model.fit(x_train, y_train, class_weight=class_weight, batch_size=64, epochs=1)
Fit with class weight
782/782 [==============================] - 2s 2ms/step - loss: 0.6064 - sparse_categorical_accuracy: 0.8448

 

サンプル重み

極め細かい制御のため、あるいは分類器を構築していない場合、”sample weights” を利用できます。

  • NumPy データから訓練するとき: Model.fit() に sample_weight 引数を渡す。
  • tf.data か任意の他の種類の iterator から訓練するとき: (input_batch, label_batch, sample_weight_batch) タプルを yield します。

“sample weights” 配列は、トータル損失を計算するときにバッチの各サンプルがどのくらいの重みを持つべきであるかを指定する数字の配列です。それは一般に不均衡な分類問題で使用されます (滅多に見られないクラスにより大きい重みを与えるアイデアです)。

使用される重みが ones か zeros であるとき、配列は損失関数のためのマスクとして使用できます (特定のサンプルのトータル損失への寄与を完全に捨てます)。

sample_weight = np.ones(shape=(len(y_train),))
sample_weight[y_train == 5] = 2.0

print("Fit with sample weight")
model = get_compiled_model()
model.fit(x_train, y_train, sample_weight=sample_weight, batch_size=64, epochs=1)
Fit with sample weight
782/782 [==============================] - 2s 2ms/step - loss: 0.6247 - sparse_categorical_accuracy: 0.8420
<tensorflow.python.keras.callbacks.History at 0x7f316a017c88>

ここに適合している Dataset サンプルがあります :

sample_weight = np.ones(shape=(len(y_train),))
sample_weight[y_train == 5] = 2.0

# Create a Dataset that includes sample weights
# (3rd element in the return tuple).
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train, sample_weight))

# Shuffle and slice the dataset.
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

model = get_compiled_model()
model.fit(train_dataset, epochs=1)
782/782 [==============================] - 2s 2ms/step - loss: 0.6229 - sparse_categorical_accuracy: 0.8383
<tensorflow.python.keras.callbacks.History at 0x7f3169f36208>

 

データをマルチ入力、マルチ出力モデルに渡す

前のサンプルでは、単一の入力 (shape (764,) の tensor) と単一の出力 (shape (10,) の予測 tensor) を持つモデルを考えていました。しかしマルチ入力や出力を持つモデルについてはどうでしょう?

次のモデルを考えます、これは shape (32, 32, 3) (それは (高さ、幅、チャネル)) の画像入力と shape (None, 10) (それは (時間ステップ, 特徴)) の時系列入力を持ちます。私達のモデルはこれらの入力の組み合わせから計算された 2 つの出力を持ちます : (shape (1,) の) 「スコア」と (shape (5,) の) 5 クラスに渡る確率分布です。

image_input = keras.Input(shape=(32, 32, 3), name="img_input")
timeseries_input = keras.Input(shape=(None, 10), name="ts_input")

x1 = layers.Conv2D(3, 3)(image_input)
x1 = layers.GlobalMaxPooling2D()(x1)

x2 = layers.Conv1D(3, 3)(timeseries_input)
x2 = layers.GlobalMaxPooling1D()(x2)

x = layers.concatenate([x1, x2])

score_output = layers.Dense(1, name="score_output")(x)
class_output = layers.Dense(5, name="class_output")(x)

model = keras.Model(
    inputs=[image_input, timeseries_input], outputs=[score_output, class_output]
)

このモデルをプロットしましょう、そうすれば私達がここで何をしているかを明瞭に見ることができます (プロットで示される shape はサンプル毎 shape ではなくバッチ shape であることに注意してください)。

keras.utils.plot_model(model, "multi_input_and_output_model.png", show_shapes=True)

 
コンパイル時、損失関数をリストとして渡すことにより、異なる出力に対して異なる損失を指定できます :

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss=[keras.losses.MeanSquaredError(), keras.losses.CategoricalCrossentropy()],
)

モデルに単一の損失関数を渡すだけの場合には、同じ損失関数が総ての出力に適用されるでしょう (それはここでは適切ではありません)。

メトリクスについても同様です :

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss=[keras.losses.MeanSquaredError(), keras.losses.CategoricalCrossentropy()],
    metrics=[
        [
            keras.metrics.MeanAbsolutePercentageError(),
            keras.metrics.MeanAbsoluteError(),
        ],
        [keras.metrics.CategoricalAccuracy()],
    ],
)

出力層に名前を与えましたので、辞書を通して出力毎の損失とメトリクスを指定することもできるでしょう :

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss={
        "score_output": keras.losses.MeanSquaredError(),
        "class_output": keras.losses.CategoricalCrossentropy(),
    },
    metrics={
        "score_output": [
            keras.metrics.MeanAbsolutePercentageError(),
            keras.metrics.MeanAbsoluteError(),
        ],
        "class_output": [keras.metrics.CategoricalAccuracy()],
    },
)

2 出力以上を持つ場合、明示的な名前と辞書の使用を勧めます。

loss_weights 引数を使用して、異なる出力固有の損失に異なる重みを与えることも可能です (例えば、私達の例の「スコア」損失にクラス損失の 2 倍の重要性を与えることにより、特権を与えることを望むかもしれません) :

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss={
        "score_output": keras.losses.MeanSquaredError(),
        "class_output": keras.losses.CategoricalCrossentropy(),
    },
    metrics={
        "score_output": [
            keras.metrics.MeanAbsolutePercentageError(),
            keras.metrics.MeanAbsoluteError(),
        ],
        "class_output": [keras.metrics.CategoricalAccuracy()],
    },
    loss_weights={"score_output": 2.0, "class_output": 1.0},
)

これらの出力が訓練のためではなく予測のためであれば、特定の出力のための損失を計算しないことも選択できるでしょう :

# List loss version
model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss=[None, keras.losses.CategoricalCrossentropy()],
)

# Or dict loss version
model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss={"class_output": keras.losses.CategoricalCrossentropy()},
)

マルチ入力やマルチ出力モデルに fit でデータを渡すことは compile で損失関数を指定するのと同様な方法で動作します : (損失関数を受け取った出力への 1:1 マッピングを持つ) Numpy 配列のリスト出力名を Numpy 配列にマップする辞書 を渡すことができます。

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss=[keras.losses.MeanSquaredError(), keras.losses.CategoricalCrossentropy()],
)

# Generate dummy NumPy data
img_data = np.random.random_sample(size=(100, 32, 32, 3))
ts_data = np.random.random_sample(size=(100, 20, 10))
score_targets = np.random.random_sample(size=(100, 1))
class_targets = np.random.random_sample(size=(100, 5))

# Fit on lists
model.fit([img_data, ts_data], [score_targets, class_targets], batch_size=32, epochs=1)

# Alternatively, fit on dicts
model.fit(
    {"img_input": img_data, "ts_input": ts_data},
    {"score_output": score_targets, "class_output": class_targets},
    batch_size=32,
    epochs=1,
)
4/4 [==============================] - 1s 9ms/step - loss: 13.5903 - score_output_loss: 0.7376 - class_output_loss: 12.8527
4/4 [==============================] - 0s 9ms/step - loss: 11.6027 - score_output_loss: 0.5326 - class_output_loss: 11.0702
<tensorflow.python.keras.callbacks.History at 0x7f31532ea0f0>

ここに Dataset のユースケースがあります : Numpy 配列のために行なったことと同様です、Dataset は辞書のタプルを返すべきです。

train_dataset = tf.data.Dataset.from_tensor_slices(
    (
        {"img_input": img_data, "ts_input": ts_data},
        {"score_output": score_targets, "class_output": class_targets},
    )
)
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

model.fit(train_dataset, epochs=1)
2/2 [==============================] - 0s 15ms/step - loss: 11.3703 - score_output_loss: 0.4201 - class_output_loss: 10.9502
<tensorflow.python.keras.callbacks.History at 0x7f315ad6ac18>

 

コールバックを使用する

Keras の Callbacks は訓練の間に異なるポイント (エポックの最初、バッチの最後、エポックの最後, etc.) で呼び出されるオブジェクトでそれは次のような動作を実装するために使用できます :

  • 訓練の間に異なるポイントで検証を行なう (組込みのエポック毎検証を越えて)
  • 定期の間隔であるいはそれが特定の精度しきい値を超えたときにモデルをチェックポイントする
  • 訓練が頭打ちになったように見えるときにモデルの学習率を変更する
  • 訓練が頭打ちになったように見えるときに top 層の再調整を行なう
  • 訓練が終わるときあるいは特定のパフォーマンスしきい値を超えたところで電子メールかインスタントメッセージ通知を送る
  • 等々。

Callback は fit への呼び出しにリストとして渡すことができます :

model = get_compiled_model()

callbacks = [
    keras.callbacks.EarlyStopping(
        # Stop training when `val_loss` is no longer improving
        monitor="val_loss",
        # "no longer improving" being defined as "no better than 1e-2 less"
        min_delta=1e-2,
        # "no longer improving" being further defined as "for at least 2 epochs"
        patience=2,
        verbose=1,
    )
]
model.fit(
    x_train,
    y_train,
    epochs=20,
    batch_size=64,
    callbacks=callbacks,
    validation_split=0.2,
)
Epoch 1/20
625/625 [==============================] - 2s 2ms/step - loss: 0.6304 - sparse_categorical_accuracy: 0.8218 - val_loss: 0.2454 - val_sparse_categorical_accuracy: 0.9256
Epoch 2/20
625/625 [==============================] - 1s 2ms/step - loss: 0.1954 - sparse_categorical_accuracy: 0.9425 - val_loss: 0.1763 - val_sparse_categorical_accuracy: 0.9456
Epoch 3/20
625/625 [==============================] - 1s 2ms/step - loss: 0.1329 - sparse_categorical_accuracy: 0.9613 - val_loss: 0.1528 - val_sparse_categorical_accuracy: 0.9534
Epoch 4/20
625/625 [==============================] - 1s 2ms/step - loss: 0.1053 - sparse_categorical_accuracy: 0.9676 - val_loss: 0.1541 - val_sparse_categorical_accuracy: 0.9553
Epoch 5/20
625/625 [==============================] - 1s 2ms/step - loss: 0.0799 - sparse_categorical_accuracy: 0.9752 - val_loss: 0.1464 - val_sparse_categorical_accuracy: 0.9562
Epoch 00005: early stopping
<tensorflow.python.keras.callbacks.History at 0x7fe1e08bb518>

 

多くの組込みコールバックが利用可能です

  • ModelCheckpoint: 定期的にモデルをセーブする。
  • EarlyStopping: 訓練がもはや検証メトリクスを改善しないときに訓練を停止する。
  • TensorBoard: TensorBoard で可視化できるモデルログを定期的に書く (セクション「可視化」で更なる詳細)。
  • CSVLogger: 損失とメトリクス・データを CSV ファイルにストリームする。
  • etc.

完全なリストについては callbacks ドキュメント を見てください。

 

貴方自身のコールバックを書く

基底クラス keras.callbacks.Callback を拡張することでカスタム callback を作成できます。callback はクラス・プロパティ self.model を通してその関連するモデルへのアクセスを持ちます。

カスタムコールバックを書くへの完全なガイド を必ず読んでください。

ここに訓練の間にバッチ毎損失値のリストをセーブする単純な例があります :

class LossHistory(keras.callbacks.Callback):
    def on_train_begin(self, logs):
        self.per_batch_losses = []

    def on_batch_end(self, batch, logs):
        self.per_batch_losses.append(logs.get("loss"))

 

モデルをチェックポイントする

比較的大規模なデータセット上でモデルを訓練しているとき、貴方のモデルのチェックポイントをしばしばセーブすることは重要です。

これを成すための最も容易な方法は ModelCheckpoint コールバックによるものです :

model = get_compiled_model()

callbacks = [
    keras.callbacks.ModelCheckpoint(
        # Path where to save the model
        # The two parameters below mean that we will overwrite
        # the current checkpoint if and only if
        # the `val_loss` score has improved.
        # The saved model name will include the current epoch.
        filepath="mymodel_{epoch}",
        save_best_only=True,  # Only save a model if `val_loss` has improved.
        monitor="val_loss",
        verbose=1,
    )
]
model.fit(
    x_train, y_train, epochs=2, batch_size=64, callbacks=callbacks, validation_split=0.2
)
poch 1/2
625/625 [==============================] - 2s 3ms/step - loss: 0.6299 - sparse_categorical_accuracy: 0.8272 - val_loss: 0.2785 - val_sparse_categorical_accuracy: 0.9163

Epoch 00001: val_loss improved from inf to 0.27849, saving model to mymodel_1
INFO:tensorflow:Assets written to: mymodel_1/assets
Epoch 2/2
625/625 [==============================] - 1s 2ms/step - loss: 0.1858 - sparse_categorical_accuracy: 0.9474 - val_loss: 0.1806 - val_sparse_categorical_accuracy: 0.9468

Epoch 00002: val_loss improved from 0.27849 to 0.18056, saving model to mymodel_2
INFO:tensorflow:Assets written to: mymodel_2/assets
<tensorflow.python.keras.callbacks.History at 0x7fe1e06de208>

ModelCheckpoint コールバックはフォールトトレランスを実装するために利用できます : 訓練がランダムに中断される場合にモデルの最後のセーブされた状態から訓練を再開する機能です。ここに基本的なサンプルがあります :

import os

# Prepare a directory to store all the checkpoints.
checkpoint_dir = "./ckpt"
if not os.path.exists(checkpoint_dir):
    os.makedirs(checkpoint_dir)


def make_or_restore_model():
    # Either restore the latest model, or create a fresh one
    # if there is no checkpoint available.
    checkpoints = [checkpoint_dir + "/" + name for name in os.listdir(checkpoint_dir)]
    if checkpoints:
        latest_checkpoint = max(checkpoints, key=os.path.getctime)
        print("Restoring from", latest_checkpoint)
        return keras.models.load_model(latest_checkpoint)
    print("Creating a new model")
    return get_compiled_model()


model = make_or_restore_model()
callbacks = [
    # This callback saves a SavedModel every 100 batches.
    # We include the training loss in the saved model name.
    keras.callbacks.ModelCheckpoint(
        filepath=checkpoint_dir + "/ckpt-loss={loss:.2f}", save_freq=100
    )
]
model.fit(x_train, y_train, epochs=1, callbacks=callbacks)
Creating a new model
  69/1563 [>.............................] - ETA: 2s - loss: 1.6135 - sparse_categorical_accuracy: 0.5407INFO:tensorflow:Assets written to: ./ckpt/ckpt-loss=0.98/assets
 168/1563 [==>...........................] - ETA: 7s - loss: 1.2051 - sparse_categorical_accuracy: 0.6650INFO:tensorflow:Assets written to: ./ckpt/ckpt-loss=0.71/assets
 267/1563 [====>.........................] - ETA: 7s - loss: 1.0127 - sparse_categorical_accuracy: 0.7184INFO:tensorflow:Assets written to: ./ckpt/ckpt-loss=0.59/assets
 365/1563 [======>.......................] - ETA: 6s - loss: 0.8969 - sparse_categorical_accuracy: 0.7499INFO:tensorflow:Assets written to: ./ckpt/ckpt-loss=0.53/assets
 499/1563 [========>.....................] - ETA: 5s - loss: 0.7950 - sparse_categorical_accuracy: 0.7773INFO:tensorflow:Assets written to: ./ckpt/ckpt-loss=0.49/assets
 568/1563 [=========>....................] - ETA: 5s - loss: 0.7562 - sparse_categorical_accuracy: 0.7876INFO:tensorflow:Assets written to: ./ckpt/ckpt-loss=0.45/assets
 694/1563 [============>.................] - ETA: 4s - loss: 0.6999 - sparse_categorical_accuracy: 0.8026INFO:tensorflow:Assets written to: ./ckpt/ckpt-loss=0.43/assets
 768/1563 [=============>................] - ETA: 4s - loss: 0.6730 - sparse_categorical_accuracy: 0.8099INFO:tensorflow:Assets written to: ./ckpt/ckpt-loss=0.41/assets
 868/1563 [===============>..............] - ETA: 3s - loss: 0.6418 - sparse_categorical_accuracy: 0.8183INFO:tensorflow:Assets written to: ./ckpt/ckpt-loss=0.39/assets
1000/1563 [==================>...........] - ETA: 3s - loss: 0.6074 - sparse_categorical_accuracy: 0.8276INFO:tensorflow:Assets written to: ./ckpt/ckpt-loss=0.37/assets
1100/1563 [====================>.........] - ETA: 2s - loss: 0.5852 - sparse_categorical_accuracy: 0.8336INFO:tensorflow:Assets written to: ./ckpt/ckpt-loss=0.36/assets
1169/1563 [=====================>........] - ETA: 2s - loss: 0.5715 - sparse_categorical_accuracy: 0.8374INFO:tensorflow:Assets written to: ./ckpt/ckpt-loss=0.35/assets
1268/1563 [=======================>......] - ETA: 1s - loss: 0.5537 - sparse_categorical_accuracy: 0.8423INFO:tensorflow:Assets written to: ./ckpt/ckpt-loss=0.33/assets
1369/1563 [=========================>....] - ETA: 1s - loss: 0.5374 - sparse_categorical_accuracy: 0.8468INFO:tensorflow:Assets written to: ./ckpt/ckpt-loss=0.33/assets
1496/1563 [===========================>..] - ETA: 0s - loss: 0.5191 - sparse_categorical_accuracy: 0.8518INFO:tensorflow:Assets written to: ./ckpt/ckpt-loss=0.32/assets
1563/1563 [==============================] - 9s 6ms/step - loss: 0.5102 - sparse_categorical_accuracy: 0.8543
<tensorflow.python.keras.callbacks.History at 0x7fe1e035b358>

モデルをセーブしてリストアするための貴方自身のコールバックを書くこともできます。

シリアライゼーションとセービングについての完全なガイドは、Saving and Serializing Models へのガイドを見てください。

 

学習率スケジュールを使用する

深層学習モデルを訓練するときの共通のパターンは訓練が進むにつれて学習率を徐々に減じることです。これは一般に「学習率減衰 (= decay)」として知られています。

学習率減衰は静的 (現在のエポックか現在のバッチインデックスの関数として、前もって固定) でも動的 (モデルの現在の動作に呼応、特に検証損失) でもあり得るでしょう。

 

スケジュールを optimizer に渡す

スケジュール・オブジェクトを optimizer の learning_rate 引数として渡すことにより静的学習率減衰スケジュールは容易に使用できます :

initial_learning_rate = 0.1
lr_schedule = keras.optimizers.schedules.ExponentialDecay(
    initial_learning_rate, decay_steps=100000, decay_rate=0.96, staircase=True
)

optimizer = keras.optimizers.RMSprop(learning_rate=lr_schedule)

幾つかの組込みスケジュールが利用可能です : ExponentialDecay, PiecewiseConstantDecay, PolynomialDecay そして InverseTimeDecay.

 

動的学習率スケジュールを実装するためにコールバックを使用する

動的学習率スケジュール (例えば、検証損失がもはや改善しないときに学習率を減じます) はこれらのスケジュール・オブジェクトでは達成できません、何故ならば optimizer は検証メトリクスへのアクセスを持たないからです。

けれども、コールバックは検証メトリクスを含む、総てのメトリクスへのアクセスを持ちます!こうして optimizer の現在の学習率を変更するコールバックを使用してこのパターンを達成できます。実際に、これは ReduceLROnPlateau コールバックとして組込みでさえあります。

 

訓練の間に損失とメトリクスを可視化する

訓練の間に貴方のモデルを監視する最善の方法は TensorBoard を使用することです、これは以下を提供する、貴方がローカルで実行できるブラウザ・ベースのアプリケーションです :

  • 訓練と評価のための損失とメトリクスのライブプロット
  • (オプションで) 層活性のヒストグラムの可視化
  • (オプションで) Embedding 層で学習された埋め込み空間の 3D 可視化

TensorFlow を pip でインストールしたのであれば、次のコマンドラインから TensorBoard を起動できるはずです :

tensorboard --logdir=/full_path_to_your_logs

 

TensorBoard コールバックを使用する

Keras モデルと fit メソッドで TensorBoard を使用する最も容易な方法は TensorBoard コールバックです。

最も単純なケースでは、コールバックにログを書くことを望む場所を単に指定します、それで十分です :

keras.callbacks.TensorBoard(
    log_dir="/full_path_to_your_logs",
    histogram_freq=0,  # How often to log histogram visualizations
    embeddings_freq=0,  # How often to log embedding visualizations
    update_freq="epoch",
)  # How often to write logs (default: once per epoch)

TensorBoard callback は、embeddings, histograms をロギングするか否か、そしてどのくらいの頻度でログを書くかを含む、多くの有用なオプションを持ちます :

keras.callbacks.TensorBoard(
  log_dir='/full_path_to_your_logs',
  histogram_freq=0,  # How often to log histogram visualizations
  embeddings_freq=0,  # How often to log embedding visualizations
  update_freq='epoch')  # How often to write logs (default: once per epoch)
<tensorflow.python.keras.callbacks.TensorBoard at 0x7fe1e0781ef0>
 

以上



AI導入支援 #2 ウェビナー

スモールスタートを可能としたAI導入支援   Vol.2
[無料 WEB セミナー] [詳細]
「画像認識 AI PoC スターターパック」の紹介
既に AI 技術を実ビジネスで活用し、成果を上げている日本企業も多く存在しており、競争優位なビジネスを展開しております。
しかしながら AI を導入したくとも PoC (概念実証) だけでも高額な費用がかかり取組めていない企業も少なくないようです。A I導入時には欠かせない PoC を手軽にしかも短期間で認知度を確認可能とするサービの紹介と共に、AI 技術の特性と具体的な導入プロセスに加え運用時のポイントについても解説いたします。
日時:2021年10月13日(水)
会場:WEBセミナー
共催:クラスキャット、日本FLOW(株)
後援:働き方改革推進コンソーシアム
参加費: 無料 (事前登録制)
人工知能開発支援
◆ クラスキャットは 人工知能研究開発支援 サービスを提供しています :
  • テクニカルコンサルティングサービス
  • 実証実験 (プロトタイプ構築)
  • アプリケーションへの実装
  • 人工知能研修サービス
◆ お問合せ先 ◆
(株)クラスキャット
セールス・インフォメーション
E-Mail:sales-info@classcat.com