ホーム » 「TensorFlow 2.4 Guide」タグがついた投稿

タグアーカイブ: TensorFlow 2.4 Guide

TensorFlow 2.4 : ガイド : モデルのセーブ :- SavedModel 形式を使用する

TensorFlow 2.4 : ガイド : モデルのセーブ :- SavedModel 形式を使用する (翻訳/解説)

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

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

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、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/

 

 

ガイド : モデルのセーブ :- SavedModel 形式を使用する

SavedModel は訓練パラメータ (i.e, tf.Variables) と計算を含む、完全な TensorFlow プログラムを含みます。それは実行するためのオリジナルのモデル構築コードを必要としません、それは TFLite, TensorFlow.js, TensorFlow ServingTensorFlow Hub で共有あるいは配備するために有用にします。

以下の API を使用して SavedModel 形式のモデルをセーブしてロードできます :

 

Keras から SavedModel を作成する

素早いイントロダクションとして、このセクションは事前訓練 Keras モデルをエクスポートしてそれで画像分類リクエストに役立てます。ガイドの残りは詳細を補足説明して SavedModel を作成するための他の方法を議論します。

import os
import tempfile

from matplotlib import pyplot as plt
import numpy as np
import tensorflow as tf

tmpdir = tempfile.mkdtemp()
physical_devices = tf.config.experimental.list_physical_devices('GPU')
for device in physical_devices:
  tf.config.experimental.set_memory_growth(device, True)
file = tf.keras.utils.get_file(
    "grace_hopper.jpg",
    "https://storage.googleapis.com/download.tensorflow.org/example_images/grace_hopper.jpg")
img = tf.keras.preprocessing.image.load_img(file, target_size=[224, 224])
plt.imshow(img)
plt.axis('off')
x = tf.keras.preprocessing.image.img_to_array(img)
x = tf.keras.applications.mobilenet.preprocess_input(
    x[tf.newaxis,...])
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/grace_hopper.jpg
65536/61306 [================================] - 0s 0us/step

Grace Hopper の画像を動作するサンプルとして、そして Keras の事前訓練された画像分類モデルを使用します、何故ならばそれは簡単に利用できるからです。カスタム・モデルもまた動作します、そして後で詳細にカバーされます。

labels_path = tf.keras.utils.get_file(
    'ImageNetLabels.txt',
    'https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt')
imagenet_labels = np.array(open(labels_path).read().splitlines())
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt
16384/10484 [==============================================] - 0s 0us/step
pretrained_model = tf.keras.applications.MobileNet()
result_before_save = pretrained_model(x)

decoded = imagenet_labels[np.argsort(result_before_save)[0,::-1][:5]+1]

print("Result before saving:\n", decoded)
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/mobilenet/mobilenet_1_0_224_tf.h5
17227776/17225924 [==============================] - 0s 0us/step
Result before saving:
 ['military uniform' 'bow tie' 'suit' 'bearskin' 'pickelhaube']

この画像に対する top 予測は「military uniform (軍服)」です。

mobilenet_save_path = os.path.join(tmpdir, "mobilenet/1/")
tf.saved_model.save(pretrained_model, mobilenet_save_path)
INFO:tensorflow:Assets written to: /tmp/tmpeu7g2o56/mobilenet/1/assets

save-path は TensorFlow Serving により使用される慣習に従います、そこでは最後のパス成分 (ここでは 1/) は貴方のモデルのためのバージョン番号です – それは TensorFlow Serving のようなツールに相対的な鮮度 (= freshness) について推論することを可能にします。tf.saved_model.load で SavedModel を Python にロードし戻して、そして Admiral Hopper の画像がどのように分類されるかを見ることができます。

loaded = tf.saved_model.load(mobilenet_save_path)
print(list(loaded.signatures.keys()))  # ["serving_default"]
['serving_default']

インポートされたシグネチャは常に辞書を返します。シグネチャ名と出力辞書キーをカスタマイズするためには、Specifying signatures during export を見てください。

infer = loaded.signatures["serving_default"]
print(infer.structured_outputs)
{'predictions': TensorSpec(shape=(None, 1000), dtype=tf.float32, name='predictions')}

SavedModel からの推論の実行はオリジナル・モデルと同じ結果を与えます。

labeling = infer(tf.constant(x))[pretrained_model.output_names[0]]

decoded = imagenet_labels[np.argsort(labeling)[0,::-1][:5]+1]

print("Result after saving and loading:\n", decoded)
Result after saving and loading:
 ['military uniform' 'bow tie' 'suit' 'bearskin' 'pickelhaube']

 

TensorFlow Serving で SavedModel を実行する

SavedModel は Python から利用可能ですが (それについてのより多くは下で)、プロダクション環境は典型的には Python コードを実行することなしに推論のために専用サービスを利用します。TensorFlow Serving を使用して SavedModel からセットアップすることは容易です。

end-to-end な tensorflow-serving サンプルについては TensorFlow Serving REST チュートリアル を見てください。

 

ディスク上の SavedModel 形式

SavedModel は、変数値とボキャブラリを含む、シリアライズされたシグネチャとそれらを実行するために必要な状態を含む辞書です。

ls {mobilenet_save_path}
assets/  saved_model.pb  variables/

saved_model.pb は実際の TensorFlow プログラム、あるいはモデル、そしてそれぞれ (tensor 入力を受け取り tensor 出力を生成する) 関数を識別する、名前付けられたシグネチャのセットをストアします。

SavedModel はモデルの複数のバージョン (= variants) を含むかも知れませんが (saved_model_cli への –tag_set フラグで識別される、複数の v1.MetaGraphDefs)、これは稀です。モデルの複数のバージョンを作成する API は tf.Estimator.experimental_export_all_saved_models、そして TensorFlow 1.x tf.saved_model.Builder を含みます。

saved_model_cli show --dir {mobilenet_save_path} --tag_set serve
The given SavedModel MetaGraphDef contains SignatureDefs with the following keys:
SignatureDef key: "__saved_model_init_op"
SignatureDef key: "serving_default"

variables ディレクトリは標準的な訓練チェックポイントを含みます (訓練チェックポイントへのガイド 参照)。

ls {mobilenet_save_path}/variables
variables.data-00000-of-00001  variables.index

assets ディレクトリは TensorFlow グラフにより使用されるファイルを含みます、例えば語彙テーブルを初期化するために使用されるテキストファイルです。それはこのサンプルでは使用されません。

SavedModel は TensorFlow グラフで使用されない任意のファイルのために assets.extra を持つかもしれません、例えば SavedModel で何を行なうかについての消費者のための情報です。TensorFlow 自身はこのディレクトリを使用しません。

 

カスタム・モデルをセーブする

tf.saved_model.save は tf.keras.Layer と tf.keras.Model のような、tf.Module とそのサブクラスのセーブをサポートします。

tf.Module をセーブしてリストアするサンプルを見ましょう。

class CustomModule(tf.Module):

  def __init__(self):
    super(CustomModule, self).__init__()
    self.v = tf.Variable(1.)

  @tf.function
  def __call__(self, x):
    print('Tracing with', x)
    return x * self.v

  @tf.function(input_signature=[tf.TensorSpec([], tf.float32)])
  def mutate(self, new_v):
    self.v.assign(new_v)

module = CustomModule()

tf.Module をセーブするとき、任意の tf.Variable 属性、tf.function でデコレートされたメソッド、そして再帰的な traversal (辿ること) を通して tf.Module がセーブされます (この再帰的 traversal についてのより多くは チェックポイント・チュートリアル 参照)。けれども、任意の Python 属性、関数とデータは失われます。これは tf.function がセーブされるとき、Python コードはセーブされないことを意味します。

Python コードがセーブされないのであれば、SavedModel は関数をどのようにリストアするかをどのように知るのでしょう?

簡潔に言えば、tf.function は ConcreteFunction (tf.Graph 回りの callable なラッパー) を生成するために Python コードをトレースすることにより動作します。tf.function をセーブするとき、実際には ConcreteFunction の tf.function のキャッシュをセーブしています。

tf.function と ConcreteFunctions の間の関係について更に学習するには、tf.function ガイド を見てください。

module_no_signatures_path = os.path.join(tmpdir, 'module_no_signatures')
module(tf.constant(0.))
print('Saving model...')
tf.saved_model.save(module, module_no_signatures_path)
Tracing with Tensor("x:0", shape=(), dtype=float32)
Saving model...
Tracing with Tensor("x:0", shape=(), dtype=float32)
INFO:tensorflow:Assets written to: /tmp/tmpxskcqu8t/module_no_signatures/assets

 

カスタム・モデルをロードして使用する

Python で SavedModel をロードするとき、総ての tf.Variable 属性、tf.function-decorated メソッド、と tf.Module はオリジナルのセーブされた tf.Module と同じオブジェクト構造でリストアされます。

imported = tf.saved_model.load(module_no_signatures_path)
assert imported(tf.constant(3.)).numpy() == 3
imported.mutate(tf.constant(2.))
assert imported(tf.constant(3.)).numpy() == 6

Python コードはセーブされていませんので、新しい入力シグネチャを伴う tf.function の呼び出しは失敗します :

imported(tf.constant([3.]))
ValueError: Could not find matching function to call for canonicalized inputs ((,), {}). Only existing signatures are [((TensorSpec(shape=(), dtype=tf.float32, name=u'x'),), {})].

 

基本的な再調整

変数オブジェクトは利用可能で、インポートされた関数を通して逆伝播できます。これは単純なケースでは SavedModel を再調整 (i.e. 再訓練) するためには十分です。

optimizer = tf.optimizers.SGD(0.05)

def train_step():
  with tf.GradientTape() as tape:
    loss = (10. - imported(tf.constant(2.))) ** 2
  variables = tape.watched_variables()
  grads = tape.gradient(loss, variables)
  optimizer.apply_gradients(zip(grads, variables))
  return loss
for _ in range(10):
  # "v" approaches 5, "loss" approaches 0
  print("loss={:.2f} v={:.2f}".format(train_step(), imported.v.numpy()))
loss=36.00 v=3.20
loss=12.96 v=3.92
loss=4.67 v=4.35
loss=1.68 v=4.61
loss=0.60 v=4.77
loss=0.22 v=4.86
loss=0.08 v=4.92
loss=0.03 v=4.95
loss=0.01 v=4.97
loss=0.00 v=4.98

 

一般的な再調整

Keras からの SavedModel は再調整のより進んだケースに対応するために plain __call__ よりも 多くの詳細 を提供します。TensorFlow は再調整の目的のために共有される SavedModel で、もし適用可能であれば、それらの以下を提供することを推奨します :

  • モデルが (バッチ正規化のように) forward パスが訓練と推論で異なるような dropout か他のテクニックを利用する場合、__call__ メソッドはオプションの、Python-値の training= 引数を取ります、これは False がデフォルトですが True に設定できます。
  • __call__ 属性の次に、対応する変数のリストを持つ .variable と .trainable_variable 属性があります。元々は訓練可能で再調整の間には凍結されることを意図した変数は .trainable_variables から除外されます。
  • 重み regularizer を層かサブモジュールの属性として表す Keras のようなフレームワークのために、.regularization_losses 属性もあり得ます。それは zero-argument 関数 (訳注: 引数を持たない関数) のリストを保持し、その後は合計損失への加算を意図しています。

最初の MobileNet サンプルに戻って、それらの幾つかを実際に見ることができます :

loaded = tf.saved_model.load(mobilenet_save_path)
print("MobileNet has {} trainable variables: {}, ...".format(
          len(loaded.trainable_variables),
          ", ".join([v.name for v in loaded.trainable_variables[:5]])))
MobileNet has 83 trainable variables: conv1/kernel:0, conv1_bn/gamma:0, conv1_bn/beta:0, conv_dw_1/depthwise_kernel:0, conv_dw_1_bn/gamma:0, ...
trainable_variable_ids = {id(v) for v in loaded.trainable_variables}
non_trainable_variables = [v for v in loaded.variables
                           if id(v) not in trainable_variable_ids]
print("MobileNet also has {} non-trainable variables: {}, ...".format(
          len(non_trainable_variables),
          ", ".join([v.name for v in non_trainable_variables[:3]])))
MobileNet also has 54 non-trainable variables: conv1_bn/moving_mean:0, conv1_bn/moving_variance:0, conv_dw_1_bn/moving_mean:0, ...

 

エクスポートの間にシグネチャを指定する

TensorFlow Serving と saved_model_cli のようなツールは SavedModel と相互作用できます。これらのツールがどの ConcreteFunction を使用するかを決定することを助けるために、サービング・シグネチャを指定する必要があります。 tf.keras.Model はシグネチャを自動的に指定しますが、私達のカスタム・モジュールのためにサービング・シグネチャを明示的に宣言しなければなりません。

デフォルトでは、シグネチャはカスタム tf.Module 内では宣言されません。

assert len(imported.signatures) == 0

サービング・シグネチャを宣言するため、signatures kwarg を使用して ConcreteFunction を指定します。単一のシグネチャを指定するとき、そのシグネチャ・キーは ‘serving_default’ です、これは定数 tf.saved_model.DEFAULT_SERVING_SIGNATURE_DEF_KEY としてセーブされます。

module_with_signature_path = os.path.join(tmpdir, 'module_with_signature')
call = module.__call__.get_concrete_function(tf.TensorSpec(None, tf.float32))
tf.saved_model.save(module, module_with_signature_path, signatures=call)
Tracing with Tensor("x:0", dtype=float32)
Tracing with Tensor("x:0", dtype=float32)
INFO:tensorflow:Assets written to: /tmp/tmpineuv5tu/module_with_signature/assets
imported_with_signatures = tf.saved_model.load(module_with_signature_path)
list(imported_with_signatures.signatures.keys())
['serving_default']

複数のシグネチャをエクスポートするには、シグネチャ・キーの辞書を ConcreteFunction に渡します。各シグネチャ・キーは一つの ConcreteFunction に対応します。

module_multiple_signatures_path = os.path.join(tmpdir, 'module_with_multiple_signatures')
signatures = {"serving_default": call,
              "array_input": module.__call__.get_concrete_function(tf.TensorSpec([None], tf.float32))}

tf.saved_model.save(module, module_multiple_signatures_path, signatures=signatures)
Tracing with Tensor("x:0", shape=(None,), dtype=float32)
Tracing with Tensor("x:0", shape=(None,), dtype=float32)
INFO:tensorflow:Assets written to: /tmp/tmpineuv5tu/module_with_multiple_signatures/assets
imported_with_multiple_signatures = tf.saved_model.load(module_multiple_signatures_path)
list(imported_with_multiple_signatures.signatures.keys())
['serving_default', 'array_input']

デフォルトでは、出力 tensor 名は output_0 のように、正しく包括的です。出力名を制御するには、貴方の tf.function を出力名を出力にマップする辞書を返すように変更します。入力名は Python 関数 arg 名に由来します。

class CustomModuleWithOutputName(tf.Module):
  def __init__(self):
    super(CustomModuleWithOutputName, self).__init__()
    self.v = tf.Variable(1.)

  @tf.function(input_signature=[tf.TensorSpec([], tf.float32)])
  def __call__(self, x):
    return {'custom_output_name': x * self.v}

module_output = CustomModuleWithOutputName()
call_output = module_output.__call__.get_concrete_function(tf.TensorSpec(None, tf.float32))
module_output_path = os.path.join(tmpdir, 'module_with_output_name')
tf.saved_model.save(module_output, module_output_path,
                    signatures={'serving_default': call_output})
INFO:tensorflow:Assets written to: /tmp/tmpineuv5tu/module_with_output_name/assets
imported_with_output_name = tf.saved_model.load(module_output_path)
imported_with_output_name.signatures['serving_default'].structured_outputs
{'custom_output_name': TensorSpec(shape=(), dtype=tf.float32, name='custom_output_name')}

 

C++ で SavedModel をロードする

SavedModel ローダ の C++ バージョンは SessionOptions と RunOptions を許容しながら、パスから SavedModel をロードするための API を提供します。ロードされるグラフに関連するタグを指定しなければなりません。SavedModel のロードされたバージョンは SavedModelBundle として参照されてそれがロードされたものの内で MetaGraphDef とセッションを含みます。

const string export_dir = ...
SavedModelBundle bundle;
...
LoadSavedModel(session_options, run_options, export_dir, {kSavedModelTagTrain},
               &bundle);

 

SavedModel コマンドライン・インターフェイスの詳細

SavedModel を調査して実行するために SavedModel コマンドライン・インターフェイス (CLI) を利用できます。例えば、モデルの SignatureDef を調べるために CLI を利用できます。CLI は入力 Tensor dtype と shape がモデルに適合することを素早く確認することを可能にします。更に、貴方のモデルをテストすることを望む場合、様々な形式 (例えば、Python 式) でサンプル入力を渡してから出力を取得することによりサニティ・チェックを行なうために CLI を使用できます。

 

SavedModel CLI をインストールする

大雑把に言えば、TensorFlow を次の 2 つの方法のいずれかでインストールできます :

  • 事前ビルドされた TensorFlow バイナリをインストールすることによって。
  • ソースコードから TensorFlow をビルドすることによって。

事前ビルドされた TensorFlow バイナリを通して TensorFlow をインストールした場合、SavedModel CLI は貴方のシステム上にパス名 bin/saved_model_cli で既にインストールされています。

ソースコードから TensorFlow を構築した場合、saved_model_cli をビルドするために次の追加のコマンドを実行しなければなりません :

$ bazel build tensorflow/python/tools:saved_model_cli

 

コマンドの概要

SavedModel CLI は SavedModel 上で以下の 2 つのコマンドをサポートします :

  • show, これは SavedModel から利用可能な計算を示します。
  • run, これは SavedModel から計算を実行します。

 

show コマンド

SavedModel は一つまたはそれ以上のモデル・バージョン (= variants) (技術的には、v1.MetaGraphDef) を含みます、(それらは) それらのタグ・セットで識別されます。モデルをサーブするため、各モデルバージョンにどの種類の SignatureDef がありそしてそれらの入力と出力が何かを知りたいと思うかもしれません。show コマンドは階層順序にある SavedModel の内容を貴方に調べさせます。ここにシンタックスがあります :

usage: saved_model_cli show [-h] --dir DIR [--all]
[--tag_set TAG_SET] [--signature_def SIGNATURE_DEF_KEY]

例えば、次のコマンドは SavedModel の総ての利用可能なタグセットを示します :

$ saved_model_cli show --dir /tmp/saved_model_dir
The given SavedModel contains the following tag-sets:
serve
serve, gpu

次のコマンドはタグセットのための総ての利用可能な SignatureDef キーを示します :

$ saved_model_cli show --dir /tmp/saved_model_dir --tag_set serve
The given SavedModel `MetaGraphDef` contains `SignatureDefs` with the
following keys:
SignatureDef key: "classify_x2_to_y3"
SignatureDef key: "classify_x_to_y"
SignatureDef key: "regress_x2_to_y3"
SignatureDef key: "regress_x_to_y"
SignatureDef key: "regress_x_to_y2"
SignatureDef key: "serving_default"

タグセットに複数のタグがある場合、総てのタグを指定しなければなりません、各タグはカンマで分離されます。例えば :

$ saved_model_cli show --dir /tmp/saved_model_dir --tag_set serve,gpu

特定の SignatureDef のための総ての入力と出力の TensorInfo を示すには、signature_def オプションに SignatureDef キーを渡します。これは、後で計算グラフを実行するために入力 tensor の tensor キー値, dtype と shape を知ることを望むとき非常に有用です。例えば :

$ saved_model_cli show --dir \
/tmp/saved_model_dir --tag_set serve --signature_def serving_default
The given SavedModel SignatureDef contains the following input(s):
  inputs['x'] tensor_info:
      dtype: DT_FLOAT
      shape: (-1, 1)
      name: x:0
The given SavedModel SignatureDef contains the following output(s):
  outputs['y'] tensor_info:
      dtype: DT_FLOAT
      shape: (-1, 1)
      name: y:0
Method name is: tensorflow/serving/predict

SavedModel の総ての利用可能な情報を示すには、–all オプションを使用します。例えば :

$ saved_model_cli show --dir /tmp/saved_model_dir --all
MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:

signature_def['classify_x2_to_y3']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['inputs'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 1)
        name: x2:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['scores'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 1)
        name: y3:0
  Method name is: tensorflow/serving/classify

...

signature_def['serving_default']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['x'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 1)
        name: x:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['y'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 1)
        name: y:0
  Method name is: tensorflow/serving/predict

 

run コマンド

グラフ計算を実行し、入力を渡してから出力を表示 (そしてオプションでセーブ) するために run コマンドを起動します。ここにシンタックスがあります :

usage: saved_model_cli run [-h] --dir DIR --tag_set TAG_SET --signature_def
                           SIGNATURE_DEF_KEY [--inputs INPUTS]
                           [--input_exprs INPUT_EXPRS]
                           [--input_examples INPUT_EXAMPLES] [--outdir OUTDIR]
                           [--overwrite] [--tf_debug]

run コマンドは入力をモデルに渡すために以下の 3 つの方法を提供します :

  • –inputs オプションはファイルの numpy ndarray を渡すことを可能にします。
  • –input_exprs オプションは Python 式を渡すことを可能にします。
  • –input_examples オプションは tf.train.Example を渡すことを可能にします。

 

–inputs

ファイルの入力データを渡すためには、–inputs オプションを指定します、これは次の一般的な形式を取ります :

--inputs <INPUTS>

ここで INPUTS は以下の形式のいずれかです :

  • <input_key>=<filename>
  • <input_key>=<filename>[<variable_name>]

複数の INPUTS を渡しても良いです。複数の入力を渡す場合には、INPUTS の各々を分離するためにセミコロンを使用します。

saved_model_cli は filename をロードするために numpy.load を使用します。filename は以下の形式のいずれかが許容されます :

  • .npy
  • .npz
  • pickle 形式

.npy ファイルは常に numpy ndarray を含みます。従って、.npy ファイルからロードするとき、内容は指定された入力 tensor に直接割当てられます。その .npy ファイルとともに variable_name を指定する場合、variable_name は無視されて警告が発行されます。

.npz (zip) ファイルからロードするとき、入力 tensor キーのためにロードする zip ファイル内の変数を識別するためにオプションで variable_name を指定しても良いです。variable_name を指定しない場合、SavedModel CLI は一つのファイルだけが zip ファイルに含まれているか確認してそれを指定された入力 tensor キーのためにロードします。

pickle ファイルからロードするとき、variable_name が角括弧で指定されない場合、pickle ファイル内が何であれ指定された入力 tensor キーに渡されます。そうでなければ、SavedModel CLI は辞書が pickle ファイルにストアされていると仮定してvariable_name に対応する値が使用されます。

 

–input_exprs

Python 式を通して入力を渡すためには、–input_exprs オプションを指定します。これは手近にデータファイルを持たないときに、依然として (モデルの SignatureDef の dtype と shape に一致する) 幾つかの単純な入力でモデルをサニティ・チェックすることを望むときのために有用であり得ます。例えば :

`<input_key>=[[1],[2],[3]]`

Python 式に加えて、numpy 関数を渡しても良いです。例えば :

`<input_key>=np.ones((32,32,3))`

 

–input_examples

入力として tf.train.Example を渡すためには、–input_examples オプションを指定します。各入力キーについて、それは辞書のリストを取ります、そこでは各辞書は tf.train.Examples のインスタンスです。辞書キーは特徴で値は各特徴に対する値リストです。例えば :

`<input_key>=[{"age":[22,24],"education":["BS","MS"]}]`

 

Save output

デフォルトでは、SavedModel CLI は出力を stdout に書きます。ディレクトリが –outdir オプションに渡される場合、出力は与えられたディレクトリ下で出力 tensor キーにちなんで名前付けられた .npy ファイルとしてセーブされます。

既存の出力ファイルに上書きするためには –overwrite を使用します。

 

以上




TensorFlow 2.4 : ガイド : モデルのセーブ :- 訓練チェックポイント

TensorFlow 2.4 : ガイド : モデルのセーブ :- 訓練チェックポイント (翻訳/解説)

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

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

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、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/

 

 

ガイド : モデルのセーブ :- 訓練チェックポイント

フレーズ「TensorFlow モデルをセーブする」は典型的には 2 つのことの一つを意味します :

  • チェックポイント, OR
  • SavedModel。

チェックポイントはモデルにより使用される総てのパラメータ (tf.Variable オブジェクト) の正確な値を捕捉します。チェックポイントはモデルにより定義された計算のどのような記述も含みません、そのため典型的には (セーブされたパラメータ値を利用する) ソースコードが利用可能であるときにだけ有用です。

他方、SavedModel 形式はパラメータ値 (チェックポイント) に加えてモデルにより定義された計算のシリアライズされた記述を含みます。この形式のモデルはモデルを作成したソースコードから独立です。そしてそれらは TensorFlow Serving, TensorFlow Lite, TensorFlow.js あるいは他のプログラミング言語 (C, C++, Java, Go, Rust, C# 等。TensorFlow API) のプログラムを通して配備のために適します。

このガイドはチェックポイントを書いて読むための API をカバーします。

 

セットアップ

import tensorflow as tf
class Net(tf.keras.Model):
  """A simple linear model."""

  def __init__(self):
    super(Net, self).__init__()
    self.l1 = tf.keras.layers.Dense(5)

  def call(self, x):
    return self.l1(x)
net = Net()

 

tf.keras 訓練 API からセーブする

セーブとリストアについては tf.keras ガイドを見てください。

tf.keras.Model.save_weights は TensorFlow チェックポイントをセーブします。

net.save_weights('easy_checkpoint')

 

チェックポイントを書く

TensorFlow モデルの永続的な状態は tf.Variable オブジェクトにストアされます。これらは直接構築できますが、しばしば tf.keras.layers or tf.keras.Model のような高位 API を通して作成されます。

変数を管理する最も容易な方法はそれらを Python オブジェクトに装着してから、それらのオブジェクトを参照することです。

tf.train.Checkpoint, tf.keras.layers.Layer, and tf.keras.Model のサブクラスはそれらの属性に割当てられた変数を自動的に追跡します。以下のサンプルは単純な線形モデルを構築してから、チェックポイントを書きます、これはモデルの変数の総てのための値を含みます。

Model.save_weights でモデル-チェックポイントを容易にセーブできます。

 

手動チェックポインティング

セットアップ

tf.train.Checkpoint の総ての特徴を実演する手助けをするために、toy データセットと最適化ステップを定義します :

def toy_dataset():
  inputs = tf.range(10.)[:, None]
  labels = inputs * 5. + tf.range(5.)[None, :]
  return tf.data.Dataset.from_tensor_slices(
    dict(x=inputs, y=labels)).repeat().batch(2)
def train_step(net, example, optimizer):
  """Trains `net` on `example` using `optimizer`."""
  with tf.GradientTape() as tape:
    output = net(example['x'])
    loss = tf.reduce_mean(tf.abs(output - example['y']))
  variables = net.trainable_variables
  gradients = tape.gradient(loss, variables)
  optimizer.apply_gradients(zip(gradients, variables))
  return loss

 

チェックポイント・オブジェクトを作成する

チェックポイントを手動で作成するために tf.train.Checkpoint オブジェクトを使用します、そこではチェックポイントすることを望むオブジェクトはオブジェクト上の属性として設定されます。

tf.train.CheckpointManager はまた複数のチェックポイントを管理するために役立つことができます。

opt = tf.keras.optimizers.Adam(0.1)
dataset = toy_dataset()
iterator = iter(dataset)
ckpt = tf.train.Checkpoint(step=tf.Variable(1), optimizer=opt, net=net, iterator=iterator)
manager = tf.train.CheckpointManager(ckpt, './tf_ckpts', max_to_keep=3)

 

モデルを訓練してチェックポイントする

次の訓練ループはモデルと optimizer のインスタンスを作成してから、それらを tf.train.Checkpoint オブジェクトに集めます。

それはデータの各バッチ上でループの訓練ステップを呼び出し、そして定期的にチェックポイントをディスクに書きます。

def train_and_checkpoint(net, manager):
  ckpt.restore(manager.latest_checkpoint)
  if manager.latest_checkpoint:
    print("Restored from {}".format(manager.latest_checkpoint))
  else:
    print("Initializing from scratch.")

  for _ in range(50):
    example = next(iterator)
    loss = train_step(net, example, opt)
    ckpt.step.assign_add(1)
    if int(ckpt.step) % 10 == 0:
      save_path = manager.save()
      print("Saved checkpoint for step {}: {}".format(int(ckpt.step), save_path))
      print("loss {:1.2f}".format(loss.numpy()))
train_and_checkpoint(net, manager)
Initializing from scratch.
Saved checkpoint for step 10: ./tf_ckpts/ckpt-1
loss 26.85
Saved checkpoint for step 20: ./tf_ckpts/ckpt-2
loss 20.27
Saved checkpoint for step 30: ./tf_ckpts/ckpt-3
loss 13.72
Saved checkpoint for step 40: ./tf_ckpts/ckpt-4
loss 7.30
Saved checkpoint for step 50: ./tf_ckpts/ckpt-5
loss 1.72

 

リストアして訓練を続ける

最初の訓練サイクルの後、新しいモデルとマネージャを渡して、しかし正確に貴方がやめたところで訓練を選択できます :

opt = tf.keras.optimizers.Adam(0.1)
net = Net()
dataset = toy_dataset()
iterator = iter(dataset)
ckpt = tf.train.Checkpoint(step=tf.Variable(1), optimizer=opt, net=net, iterator=iterator)
manager = tf.train.CheckpointManager(ckpt, './tf_ckpts', max_to_keep=3)

train_and_checkpoint(net, manager)
Restored from ./tf_ckpts/ckpt-5
Saved checkpoint for step 60: ./tf_ckpts/ckpt-6
loss 0.91
Saved checkpoint for step 70: ./tf_ckpts/ckpt-7
loss 0.90
Saved checkpoint for step 80: ./tf_ckpts/ckpt-8
loss 0.57
Saved checkpoint for step 90: ./tf_ckpts/ckpt-9
loss 0.49
Saved checkpoint for step 100: ./tf_ckpts/ckpt-10
loss 0.39

tf.train.CheckpointManager オブジェクトは古いチェックポイントを削除します。上ではそれは 3 つの最も最近のチェックポイントだけを保持するように configure されています。

print(manager.checkpoints)  # List the three remaining checkpoints
['./tf_ckpts/ckpt-8', './tf_ckpts/ckpt-9', './tf_ckpts/ckpt-10']

これらのパス, e.g. ‘./tf_ckpts/ckpt-10’, はディスク上のファイルではありません。代わりにそれらはインデックスファイルと、変数値を含む一つまたはそれ以上のデータファイルのためのプレフィクスです。これらのプレフィクスは単一のチェックポイント・ファイル (‘./tf_ckpts/checkpoint’) 内で一緒にグループ分けされます、そこで CheckpointManager はその状態をセーブします。

ls ./tf_ckpts
checkpoint                   ckpt-8.data-00000-of-00001  ckpt-9.index
ckpt-10.data-00000-of-00001  ckpt-8.index
ckpt-10.index                ckpt-9.data-00000-of-00001

 

ローディング機構

TensorFlow はロードされているオブジェクトから始めて、名前付けられたエッジを持つ有向グラフを辿ることにより変数をチェックポイントされた値に合わせます。エッジ名は典型的にはオブジェクトの属性名に由来します、例えば self.l1 = tf.keras.layers.Dense(5) 内の “l1” です。tf.train.Checkpoint は tf.train.Checkpoint(step=…) 内の “step” のように、そのキーワード引数名を使用します。

上のサンプルからの依存性グラフはこのようなものです :


optimizer は赤色、通常の変数は青色、そして optimizer スロット変数はオレンジ色にあります。他のノード — 例えば、tf.train.Checkpoint を表す — は黒色です。

スロット変数は optimizer の状態の一部ですが、特定の変数のために作成されます。例えば上の ‘m’ エッジはモメンタムに対応します、これは各変数のために Adam optimizer が追跡します。変数と optimizer の両者がセーブされる場合にスロット変数はチェックポイントにセーブされるだけですので、破線のエッジです。

tf.train.Checkpoint オブジェクト上で restore を呼び出すと要求された復元 (= restorations) をキューに入れて、チェックポイント・オブジェクトから一致するパスがあれば変数値をリストアします。例えば、ネットワークと層を通してそれへの一つのパスを再構築することにより上で定義したモデルから単にバイアスをロードできます。

to_restore = tf.Variable(tf.zeros([5]))
print(to_restore.numpy())  # All zeros
fake_layer = tf.train.Checkpoint(bias=to_restore)
fake_net = tf.train.Checkpoint(l1=fake_layer)
new_root = tf.train.Checkpoint(net=fake_net)
status = new_root.restore(tf.train.latest_checkpoint('./tf_ckpts/'))
print(to_restore.numpy())  # This gets the restored value.
[0. 0. 0. 0. 0.]
[-0.00426486  0.98928887  1.9369034   2.996623    3.9505417 ]

これらの新しいオブジェクトのための依存性グラフは貴方が上で書いた大きいチェックポイントの遥かに小さい部分グラフです。それはバイアスとチェックポイントに番号付けるために tf.train.Checkpoint が使用する save カウンターだけを含みます。

restore はオプションの assertion を持つ、status オブジェクトを返します。新しいチェックポイントで作成されたオブジェクトの総てがリストアされて、従って status.assert_existing_objects_matched はパスします。

status.assert_existing_objects_matched()
<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f525dea5b38>

チェックポイントには層のカーネルと optimizer の変数を含む、一致しない多くのオブジェクトがありますstatus.assert_consumed はチェックポイントとプログラムが正確に一致する場合にだけパスし、そしてここでは例外を上げます。

 

遅延復元 (= Delayed restorations)

TensorFlow の Layer オブジェクトは変数の作成をそれらの最初の呼び出し (入力 shape が利用可能なとき) まで遅延させるかもしれません。例えば、Dense 層のカーネルの shape は層の入力と出力 shape の両者に依拠し、そのためコンストラクタ引数として必要な出力 shape はそれ自身の上の変数を作成するために十分な情報ではありません。Layer の呼び出しはまた変数値を読みますので、restore は変数の作成とその最初の使用の間に発生しなければなりません。

この作法 (= idiom) をサポートするために、tf.train.Checkpoint は一致する変数をまだ持たない restore をキューイングします。

delayed_restore = tf.Variable(tf.zeros([1, 5]))
print(delayed_restore.numpy())  # Not restored; still zeros
fake_layer.kernel = delayed_restore
print(delayed_restore.numpy())  # Restored
[[0. 0. 0. 0. 0.]]
[[4.7385683 4.7148175 4.7513504 4.7783995 5.0485835]]

 

チェックポイントを手動で調べる

tf.train.load_checkpoint はチェックポイントの内容への低位アクセスを与える CheckpointReader を返します。それは各変数のキーからチェックポイントの各変数のための shape と dtype へのマッピングを含みます。変数のキーは上で表示されたグラフ内のような、そのオブジェクトパスです。

Note: チェックポイントへの高位構造はありません。それは変数のためのパスと値を知るだけで、モデル あるいはそれらがどのように接続されているかの概念は持ちません。

reader = tf.train.load_checkpoint('./tf_ckpts/')
shape_from_key = reader.get_variable_to_shape_map()
dtype_from_key = reader.get_variable_to_dtype_map()

sorted(shape_from_key.keys())
['_CHECKPOINTABLE_OBJECT_GRAPH',
 'iterator/.ATTRIBUTES/ITERATOR_STATE',
 'net/l1/bias/.ATTRIBUTES/VARIABLE_VALUE',
 'net/l1/bias/.OPTIMIZER_SLOT/optimizer/m/.ATTRIBUTES/VARIABLE_VALUE',
 'net/l1/bias/.OPTIMIZER_SLOT/optimizer/v/.ATTRIBUTES/VARIABLE_VALUE',
 'net/l1/kernel/.ATTRIBUTES/VARIABLE_VALUE',
 'net/l1/kernel/.OPTIMIZER_SLOT/optimizer/m/.ATTRIBUTES/VARIABLE_VALUE',
 'net/l1/kernel/.OPTIMIZER_SLOT/optimizer/v/.ATTRIBUTES/VARIABLE_VALUE',
 'optimizer/beta_1/.ATTRIBUTES/VARIABLE_VALUE',
 'optimizer/beta_2/.ATTRIBUTES/VARIABLE_VALUE',
 'optimizer/decay/.ATTRIBUTES/VARIABLE_VALUE',
 'optimizer/iter/.ATTRIBUTES/VARIABLE_VALUE',
 'optimizer/learning_rate/.ATTRIBUTES/VARIABLE_VALUE',
 'save_counter/.ATTRIBUTES/VARIABLE_VALUE',
 'step/.ATTRIBUTES/VARIABLE_VALUE']

そして net.l1.kernel の値に関心があれば次のコードで値を得ることができます :

key = 'net/l1/kernel/.ATTRIBUTES/VARIABLE_VALUE'

print("Shape:", shape_from_key[key])
print("Dtype:", dtype_from_key[key].name)
Shape: [1, 5]
Dtype: float32

それはまた get_tensor メソッドも提供し、変数の値を調査することを可能にします :

reader.get_tensor(key)
array([[4.7385683, 4.7148175, 4.7513504, 4.7783995, 5.0485835]],
      dtype=float32)

 

リストと辞書追跡

self.l1 = tf.keras.layers.Dense(5) のような直接的な属性割当てと同様に、リストと辞書を属性に割り当てるとそれらの内容を追跡します。

save = tf.train.Checkpoint()
save.listed = [tf.Variable(1.)]
save.listed.append(tf.Variable(2.))
save.mapped = {'one': save.listed[0]}
save.mapped['two'] = save.listed[1]
save_path = save.save('./tf_list_example')

restore = tf.train.Checkpoint()
v2 = tf.Variable(0.)
assert 0. == v2.numpy()  # Not restored yet
restore.mapped = {'two': v2}
restore.restore(save_path)
assert 2. == v2.numpy()

リストと辞書のためのラッパー・オブジェクトに気付くかもしれません。これらのラッパーは基礎的なデータ構造のためのチェックポイント可能なバージョンです。ちょうど属性ベースのローディングのように、これらのラッパーはそれらがコンテナに追加されるとすぐに変数の値をリストアします。

restore.listed = []
print(restore.listed)  # ListWrapper([])
v1 = tf.Variable(0.)
restore.listed.append(v1)  # Restores v1, from restore() in the previous cell
assert 1. == v1.numpy()
ListWrapper([])

同じ追跡が tf.keras.Model のサブクラスに自動的に適用され、そして層のリストを追跡するサンプルのために使用されるかもしれません。

 

まとめ

TensorFlow オブジェクトはそれらが使用する変数の値をセーブしてリストアするための容易な自動機構を提供しています。

 

以上



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 を訪ねてください。

 

以上



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