TensorFlow : Guide : Estimators : カスタム Estimator の作成 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
更新日時 : 07/14/2018
作成日時 : 03/28/2018
* TensorFlow 1.9 でドキュメント構成が変わりましたので調整しました。
* 本ページは、TensorFlow の本家サイトの Guide – Estimators – Creating Custom Estimators を翻訳した上で適宜、補足説明したものです:
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
このドキュメントはカスタム Estimator を紹介します。特に、このドキュメントはアイリス問題を解く際に pre-made Estimator DNNClassifier の挙動を模倣するカスタム Estimator をどのように作成するかを示します。アイリス問題の詳細は Pre-Made Estimators の章を見てください。
サンプル・コードをダウンロードしてアクセスするためには次の2つのコマンドを起動してください :
git clone https://github.com/tensorflow/models/ cd models/samples/core/get_started
このドキュメントでは custom_estimator.py を見ていきます。次のコマンドでそれを実行できます :
python custom_estimator.py
もし待ち遠しく感じるのであれば、custom_estimator.py を (同じディレクトリにある) premade_estimator.py と自由に比較・対比してください。
Pre-made vs. カスタム
次の図が示すように、pre-made Estimator は tf.estimator.Estimator 基底クラスのサブクラスで、一方でカスタム Estimator は tf.estimator.Estimator のインスタンスです :
Pre-made Estimator は完全に焼けて (= baked) います。けれども時には、Estimator の挙動についてより制御する必要があります。そこがカスタム Estimator が役に立つところです。殆どどのようなことを行なうためにもカスタム Estimator を作成できます。もし貴方が隠れ層にある普通ではない流儀で接続されることを望むのであれば、カスタム Estimator を書きましょう。もし貴方のモデルのために独特な メトリック を計算することを望むのであれば、カスタム Estimator を書きましょう。基本的には、貴方の特定の問題のために Estimator が最適化されることを望むのであれば、カスタム Estimator を書きましょう。
モデル関数 (または model_fn) は ML アルゴリズムを実装します。pre-made Estimator とカスタム Estimator で作業する間の唯一の違いは :
- pre-made Estimator では、貴方のために誰かが既にモデル関数を書いています。
- カスタム Estimator では、貴方がモデル関数を書かなければなりません。
貴方のモデル関数は、あらゆる種類の隠れ層とメトリクスを定義して、広い範囲のアルゴリズムを実装できるでしょう。入力関数のように、総てのモデル関数は入力パラメータの標準的なグループを受け取りそして出力値の標準的なグループを返さなければなりません。丁度入力関数が Dataset API を利用できるように、モデル関数は Layers API と Metrics API を利用できます。
アイリス問題をカスタム Estimator でどのように解くかを見てみましょう。簡単な覚書として — ここに模倣しようとしているアイリス・モデルの体系があります :
入力関数を書く
私達のカスタム Estimator 実装は iris_data.py からの pre-made Estimator 実装 と同じ入力関数を仕様します。すなわち :
def train_input_fn(features, labels, batch_size): """An input function for training""" # Convert the inputs to a Dataset. dataset = tf.data.Dataset.from_tensor_slices((dict(features), labels)) # Shuffle, repeat, and batch the examples. dataset = dataset.shuffle(1000).repeat().batch(batch_size) # Return the read end of the pipeline. return dataset.make_one_shot_iterator().get_next()
この入力関数は(features, labels) ペアのバッチを生成する入力パイプラインを構築します、そこでは特徴は辞書特徴です。
特徴カラムを作成する
Premade Estimators と 特徴カラム の章で詳述されましたように、モデルが各々の特徴をどのように使用するべきかを指定するために貴方のモデルの特徴カラムを定義しなければなりません。pre-made Estimator かカスタム Estimator のいずれかで作業するにせよ、貴方は同じ流儀で特徴カラムを定義します。
次のコードは各々の入力特徴のために単純な numeric_column を作成します、これは入力特徴の値はモデルへの入力として直接使用されるべきであることを示しています :
# Feature columns describe how to use the input. my_feature_columns = [] for key in train_x.keys(): my_feature_columns.append(tf.feature_column.numeric_column(key=key))
モデル関数を書く
私達が使用するモデル関数は次の呼び出しシグネチャ (= call signature) を持ちます :
def my_model_fn( features, # This is batch_features from input_fn labels, # This is batch_labels from input_fn mode, # An instance of tf.estimator.ModeKeys params): # Additional configuration
最初の2つの引数は入力関数から返される特徴とラベルのバッチです ; つまり、features と labels は貴方のモデルが使用するデータへのハンドルです。mode 引数は呼び出し元が training, predicting, あるいは evaluation を要求しているかを示します。
呼び出し元は params を Estimator のコンストラクタに渡すこともできます。コンストラクタに渡された任意の params は model_fn 上に順番に渡されます。custom_estimator.py では次の行群が estimator を作成してモデルを構成するために params を設定します。この構成ステップは Getting Started with TensorFlow で tf.estimator.DNNClassifier をどのように構成したかに類似しています。
classifier = tf.estimator.Estimator( model_fn=my_model, params={ 'feature_columns': my_feature_columns, # Two hidden layers of 10 nodes each. 'hidden_units': [10, 10], # The model must choose between 3 classes. 'n_classes': 3, })
典型的なモデル関数を実装するためには、次を行わなければなりません :
- モデルを定義する。
- 3つの異なるモードの各々のために追加の計算を指定します :
- 予測する
- 評価する
- 訓練する
モデルを定義する
基本的な深層ニューラルネットワーク・モデルは次の3つのセクションを定義しなければなりません :
入力層を定義する
model_fn の最初の行は特徴辞書と feature_columns を貴方のモデルのための入力に変換するために tf.feature_column.input_layer を呼び出します、次のようにです :
# Use `input_layer` to apply the feature columns. net = tf.feature_column.input_layer(features, params['feature_columns'])
前の行は貴方の特徴カラムで定義された変換を適用し、モデルの入力層を作成します。
隠れ層
もし貴方が深層ニューラルネットワークを作成している場合には、一つまたはそれ以上の隠れ層を定義しなければなりません。Layers API は、convolutional, pooling, と dropout 層を含む、総てのタイプの隠れ層を定義するための関数の豊富なセットを提供します。アイリスのためには、隠れ層を作成するために単に tf.layers.dense を呼び出します、これは params[‘hidden_layers’] で定義された次元を持ちます。dense 層では各ノードは前の層の総てのノードに接続されます。ここに関連するコードがあります :
# Build the hidden layers, sized according to the 'hidden_units' param. for units in params['hidden_units']: net = tf.layers.dense(net, units=units, activation=tf.nn.relu)
ここでの変数 net はネットワークの現在のトップ層を表します。最初の iteration の間、net は入力層を表します。各ループ iteration で tf.layers.dense は新しい層を作成して、これは、変数 net を使用して、その入力として前の層の出力を取ります。
2つの隠れ層を作成後、私達のネットワークは次のように見えます。単純化のため、図は各層の総てのユニットを表してはいません。
tf.layers.dense は、多数の正則化パラメータを設定するための機能を含む多くの追加の能力を提供することに注意してください。けれども単純化のために、他のパラメータのデフォルト値を単に受容します。
出力層
更にもう一度 tf.layers.dense を呼び出すことにより出力層を定義します、今回は活性化関数なしでです :
# Compute logits (1 per class). logits = tf.layers.dense(net, params['n_classes'], activation=None)
ここで、net は最後の隠れ層を表します。従って、層の完全な層は今では次のように接続されています :
出力層を定義するとき、units パラメータは出力の数を指定します。それで、units を params[‘n_classes’] に設定することで、モデルはクラス毎に一つの出力値を生成します。出力ベクトルの各要素は、それぞれアイリスの関連するクラス: セトサ, バージカラー, バージニカのために計算された、スコア、あるいは「ロジット」を含むでしょう。
後で、これらのロジットは tf.nn.softmax 関数により確率へと変換されるでしょう。
訓練、評価、そして予測を実装する
モデル関数を作成する最後のステップは予測、評価、そして訓練を実装する分岐コードを書くことです。
モデル関数は誰かが Estimator の train, evaluate, または predict メソッドを呼び出すときいつでも起動されます。モデル関数のためのシグネチャはこのように見えることを思い出してください :
def my_model_fn( features, # This is batch_features from input_fn labels, # This is batch_labels from input_fn mode, # An instance of tf.estimator.ModeKeys, see below params): # Additional configuration
3番目の引数, mode に注目しましょう。次のテーブルが示すように、誰かが train, evaluate, または predict を呼び出すとき、Estimator フレームワークは次のように設定された mode パラメータとともに貴方のモデル関数を起動します :
Estimator メソッド | Estimator モード |
train() | ModeKeys.TRAIN |
evaluate() | ModeKeys.EVAL |
predict() | ModeKeys.PREDICT |
例えば、classifier と命名したオブジェクトを生成するためにカスタム Estimator をインスタンス化することを仮定します。それから次の呼び出しを行ないます :
classifier = tf.estimator.Estimator(...) classifier.train(input_fn=lambda: my_input_fn(FILE_TRAIN, True, 500))
それから Estimator フレームワークは貴方のモデル関数を ModeKeys.TRAIN に設定された mode とともに呼び出します。
貴方のモデル関数は3つ総ての mode 値を処理するためのコードを提供しなければなりません。各 mode 値のために、貴方のコードは tf.estimator.EstimatorSpec のインスタンスを返さなければなりません、これは呼び出し元が必要とする情報を含みます。各モードを考察してみましょう。
予測する
Estimator の predict メソッドが呼び出されたとき、model_fn は mode = ModeKeys.PREDICT を受け取ります。この場合、モデル関数は予測を含む tf.estimator.EstimatorSpec を返さなければなりません。
モデルは予測を行なう前に訓練されていなければなりません。訓練されたモデルは、Estimator をインスタンス化したときに初期化された model_dir ディレクトリ内でディスク上にストアされます。
このモデルのために予測を生成するコードは次のようなものです :
# Compute predictions. predicted_classes = tf.argmax(logits, 1) if mode == tf.estimator.ModeKeys.PREDICT: predictions = { 'class_ids': predicted_classes[:, tf.newaxis], 'probabilities': tf.nn.softmax(logits), 'logits': logits, } return tf.estimator.EstimatorSpec(mode, predictions=predictions)
prediction 辞書は prediction モードで実行するとき貴方のモデルが返すもの総てを含みます。
predictions は次の3つのキー/値ペアを保持します :
- class_ids はこのサンプルに対するもっとも尤度の高い種のモデルの予測を表わすクラス id (0, 1, または 2) を保持します。
- probabilities は3つの確率 (このサンプルでは、0.02, 0.95, と 0.03) を保持します。
- logits は生のロジット値 (このサンプルでは、-1.3, 2.6, と -0.9) を保持します。
その辞書を tf.estimator.EstimatorSpec の predictions パラメータを通して呼び出し元に返します。Estimator の predict メソッドはこれらの辞書を生成します。
損失を計算する
訓練 と 評価 の両者のためにモデルの損失を計算する必要があります。これは最適化される 目的 (= objective) です。
tf.losses.sparse_softmax_cross_entropy を呼び出すことにより損失を計算できます。この関数により返される値は最も低く、およそ 0 で、(インデックス・ラベルにおける) 正しいクラスの確率は 1.0 近くです。正しいクラスの確率が減少するとき返される損失値は徐々により大きくなります。
この関数はバッチ全体に渡る平均を返します。
# Compute loss. loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)
評価する
Estimator の evaluate メソッドが呼び出されたとき、model_fn は mode = ModeKeys.EVAL を受け取ります。この場合、モデル関数は、モデルの損失とオプションで一つまたはそれ以上のメトリクスを含む tf.estimator.EstimatorSpec を返さなければなりません。
メトリクスを返すのははオプションですが、多くのカスタム Estimator は少なくとも一つのメトリックを返します。TensorFlow は一般的なメトリクスを計算するための Metrics モジュール tf.metrics を提供します。簡潔さのために、私達は accuracy を返すだけです。tf.metrics.accuracy 関数は私達の予測を真の値に対して、つまり、入力関数により提供されるラベルに対して比較します。tf.metrics.accuracy 関数はラベルと予測が同じ shape を持つことを要求します。ここに tf.metrics.accuracy への呼び出しがあります :
# Compute evaluation metrics. accuracy = tf.metrics.accuracy(labels=labels, predictions=predicted_classes, name='acc_op')
評価のために返される EstimatorSpec は典型的には次の情報を含みます :
- loss, これはモデルの損失です。
- eval_metric_ops、これはオプションのメトリクスの辞書です。
さて、ただ一つのメトリックを含む辞書を作成します。他のメトリクスを計算する場合には、同じ辞書に追加のキー/値ペアとしてそれらを追加します。それから、tf.estimator.EstimatorSpec の eval_metric_ops 引数に辞書を渡します。ここにコードがあります :
metrics = {'accuracy': accuracy} tf.summary.scalar('accuracy', accuracy[1]) if mode == tf.estimator.ModeKeys.EVAL: return tf.estimator.EstimatorSpec( mode, loss=loss, eval_metric_ops=metrics)
tf.summary.scalar は TRAIN と EVAL モードの両者において accuracy を TensorBoard で利用可能にします。(More on this later).
訓練する
Estimator の train メソッドが呼び出されるとき、model_fn は mode = ModeKeys.TRAIN で呼び出されます。この場合、モデル関数は損失と訓練演算を含む EstimatorSpec を返さなければなりません。
訓練演算の構築は optimizer を必要とするでしょう。私達は tf.train.AdagradOptimizer を使用します、何故ならばデフォルトで Adagrad をまた使用している DNNClassifier を模倣しているからです。tf.train パッケージは多くの他の optimizer を提供します — それらで自由に実験してください。
ここに optimizer を構築するコードがあります :
optimizer = tf.train.AdagradOptimizer(learning_rate=0.1)
次に、先に計算した損失上で optimizer の minimize メソッドを使用して訓練演算を構築します。
minimize メソッドはまた global_step パラメータを取ります。TensorFlow は (訓練実行をいつ終わらせるかを知るために) 処理された訓練ステップ数をカウントするためにこのパラメータを使用します。更に、global_step は TensorBoard グラフが正しく動作するために欠くことができません。単純に tf.train.get_global_step を呼び出して結果を minimize の global_step 引数に渡します。
ここにモデルを訓練するためのコードがあります :
train_op = optimizer.minimize(loss, global_step=tf.train.get_global_step())
訓練のために返される EstimatorSpec は次のフィールド集合を持たなければなりません :
- loss, これは損失関数の値を含みます。
- train_op, これは訓練ステップを実行します。
ここに EstimatorSpec を呼び出すコードがあります :
return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)
モデル関数は今では完成しています。
カスタム Estimator
Estimator 基底クラスを通してカスタム Estimator を次のようにインスタンス化します :
# Build 2 hidden layer DNN with 10, 10 units respectively. classifier = tf.estimator.Estimator( model_fn=my_model, params={ 'feature_columns': my_feature_columns, # Two hidden layers of 10 nodes each. 'hidden_units': [10, 10], # The model must choose between 3 classes. 'n_classes': 3, })
ここで params 辞書は DNNClassifier の key-word 引数と同じ目的に役立ちます ; つまり、params 辞書は model_fn のコードを変更することなしに貴方の Estimator を構成することを可能にします。
私達の Estimator を使用して訓練、評価して予測を生成するための残りのコードは Premade Estimators の章と同様です。
例えば、次の行はモデルを訓練します :
# Train the Model. classifier.train( input_fn=lambda:iris_data.train_input_fn(train_x, train_y, args.batch_size), steps=args.train_steps)
TensorBoard
貴方のカスタム Estimator のための訓練結果を TensorBoard で見ることができます。このレポートを見るためには、次のように TensorBoard をコマンドラインから開始します :
# Replace PATH with the actual path passed as model_dir tensorboard --logdir=PATH
それから、http://localhost:6006 にブラウズすることで TensorBoard をオープンします。
総ての pre-made Estimators は TensorBoard への多くの情報を自動的にロギングします。けれども、カスタム Estimator では、TensorBoard は一つのデフォルト・ログと貴方が TensorBoard にロギングするように明示的に伝えた情報を提供するだけです。貴方が単に作成しただけのカスタム Estimator については、TensorBoard は次を生成します :
簡単に言えば、3つのグラフが貴方に伝えるものがここにあります :
- global_step/sec: パフォーマンス・インジケータで、モデルが訓練されるときにどれだけのバッチが毎秒処理されたか (勾配更新) を示します。
- loss: レポートされる損失です。
- accuracy: 精度は次の2行により記録されます :
- eval_metric_ops={‘my_accuracy’: accuracy}), 評価の間。
- tf.summary.scalar(‘accuracy’, accuracy[1]), 訓練の間。
これらの tensorboard グラフは、貴方の optimizer の minimize メソッドに global_step を渡すことが重要である多くの理由の一つです。それなしではモデルはこれらのグラフのために x-軸を記録できません。
my_accuracy と loss グラフにおいて以下に注意してください :
- オレンジ色の線は訓練を表します。
- 青色のドットは評価を表します。
訓練の間、要約 (オレンジの線) はバッチが処理されるにつれて定期的に記録され、それはそれが何故 x-軸範囲に渡るグラフになるかです。
対照的に、評価は evaluate への各呼び出しのためにグラフ上の単一ポイントを生成します。このポイントは evaluation 呼び出し全体に渡る平均を含みます。これはグラフ上で幅を持ちません、何故ならばそれは (単一のチェックポイントから) 特定の訓練ステップにおいてモデル状態から全体的に評価されるからです。
下の図で示されるように、レポーティングを見ることができ、また左側のコントロールを使用して選択的に無効/有効にすることができます。
以上