Keras : Ex-Tutorials : TensorFlow へのシンプルな I/F としての Keras

Keras : Ex-Tutorials : TensorFlow へのシンプルな I/F としての Keras (翻訳/解説)

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

* 本ページは、Keras 開発チーム推奨の外部チュートリアル・リソースの一つ : “Keras as a simplified interface to TensorFlow: tutorial” を翻訳した上でまとめ直して補足説明したものです:

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

 

TensorFlow tensor 上で Keras 層を呼び出す

定番の単純なサンプル – MNIST 手書き数字分類から始めましょう。
Keras Dense 層 (完全結合層) スタックを使用して TensorFlow 分類器を構築していきます。

最初に TensorFlow session を作成して Keras で登録することから始めます。これは Keras が内部的に作成した総ての変数を初期化するために登録した session を使用することを意味しています :

import tensorflow as tf
sess = tf.Session()

from keras import backend as K
K.set_session(sess)

さて MNIST モデルを作成するにあたり、placeholder を定義することで分類器が正確に TensorFlow で行なうように構築していくことが可能です :

# この placeholder は入力数字を flat ベクトルとして含みます
img = tf.placeholder(tf.float32, shape=(None, 784))

それからモデル定義プロセスをスピードアップするために Keras 層を使用することができます :

from keras.layers import Dense

# Keras 層は TensorFlow tensor 上で呼び出すことができます :
x = Dense(128, activation='relu')(img)  # 128 ユニットと ReLU 活性を持つ完全結合層
x = Dense(128, activation='relu')(x)
preds = Dense(10, activation='softmax')(x)  # 10 ユニットと softmax 活性を持つ出力層

ラベルのための placeholder と使用する損失関数を定義します :

labels = tf.placeholder(tf.float32, shape=(None, 10))

from keras.objectives import categorical_crossentropy
loss = tf.reduce_mean(categorical_crossentropy(labels, preds))

モデルを TensorFlow optimizer – tf.train.GradientDescentOptimizer で訓練します :

from tensorflow.examples.tutorials.mnist import input_data
mnist_data = input_data.read_data_sets('MNIST_data', one_hot=True)

train_step = tf.train.GradientDescentOptimizer(0.5).minimize(loss)

# 総ての変数を初期化します。
init_op = tf.global_variables_initializer()
sess.run(init_op)

# 訓練ループを実行します。
with sess.as_default():
    for i in range(100):
        batch = mnist_data.train.next_batch(50)
        train_step.run(feed_dict={img: batch[0],
                                  labels: batch[1]})

今モデルを評価できます :

from keras.metrics import categorical_accuracy as accuracy

acc_value = accuracy(labels, preds)
with sess.as_default():
    print acc_value.eval(feed_dict={img: mnist_data.test.images,
                                    labels: mnist_data.test.labels})

上の例では、Keras を幾つかの tensor 入力を幾つかの tensor 出力にマップする op を生成するための構文上のショートカットとしてのみ使用していて、それで総てです。最適化は Keras optimizer ではなく TensorFlow 本来の optimizer を通して成されます。(但し、損失関数は keras.objectives からインポートした categorical_crossentropy に tf.reduce_mean を適用していますが。)

TensorFlow 本来の optimizer と Keras optimizer の相対的なパフォーマンスについて補記しますと : モデルの “Keras 流儀” による最適化 vs. TensorFlow optimizer による最適化は少しだけスピードの違いがあります。幾分直感に反しますが、Keras が大抵の場合には 5-10 % 程度より高速に見えます。けれども Keras optimizer と TF 本来の optimizer のどちらを通してモデルを最適化してもこれらの差異は結局のところ、実際には重要ではないほどに十分に小さいです。

 

訓練とテストの間の異なる挙動

幾つかの Keras 層 (e.g. Dropout, BatchNormalization) は訓練時とテスト時で異なって挙動します。
layer.uses_learning_phase, boolean をプリントすることで層が “学習フェイズ” (train/test) を使用するか否かを識別できます : 層が訓練モードとテストモードで異なる挙動を持つ場合には True、そうでなければ False です。

モデルがそのような層を含む場合には、feed_dict の一部として学習フェイズの値を指定する必要があります、そうすればモデルは dropout 等を適用するか否かを知ります。

Keras learning phase (スカラー TensorFlow tensor) は Keras backend を通してアクセス可能です :

from keras import backend as K
print K.learning_phase()

Out:

Tensor("keras_learning_phase:0", shape=(), dtype=bool)

learning phase を使用するためには、単純に feed_dict に値 “1” (訓練モード) or “0” (テストモード) を渡します :

# 訓練モード
train_step.run(feed_dict={x: batch[0], labels: batch[1], K.learning_phase(): 1})

例えば、前の MNIST サンプルにどのように Dropout 層を追加するかがここにあります :

from keras.layers import Dropout
from keras import backend as K

img = tf.placeholder(tf.float32, shape=(None, 784))
labels = tf.placeholder(tf.float32, shape=(None, 10))

x = Dense(128, activation='relu')(img)
x = Dropout(0.5)(x)
x = Dense(128, activation='relu')(x)
x = Dropout(0.5)(x)
preds = Dense(10, activation='softmax')(x)

loss = tf.reduce_mean(categorical_crossentropy(labels, preds))

train_step = tf.train.GradientDescentOptimizer(0.5).minimize(loss)
with sess.as_default():
    for i in range(100):
        batch = mnist_data.train.next_batch(50)
        train_step.run(feed_dict={img: batch[0],
                                  labels: batch[1],
                                  K.learning_phase(): 1})

acc_value = accuracy(labels, preds)
with sess.as_default():
    print acc_value.eval(feed_dict={img: mnist_data.test.images,
                                    labels: mnist_data.test.labels,
                                    K.learning_phase(): 0})

 

名前スコープ、デバイス・スコープとの互換性

Keras 層とモデルは TensorFlow の名前スコープと完全に互換です。例えば、次のコード・スニペットを考えてください :

x = tf.placeholder(tf.float32, shape=(None, 20, 64))
with tf.name_scope('block1'):
    y = LSTM(32, name='mylstm')(x)

すると私達の LSTM 層の重みは block1/mylstm_W_i, block1/mylstm_U_i, etc… と名前付けられます。

同様に、デバイス・スコープも期待どおりに動作します :

with tf.device('/gpu:0'):
    x = tf.placeholder(tf.float32, shape=(None, 20, 64))
    y = LSTM(32)(x)  # LSTM 層の総ての ops/variables は GPU:0 上で存続します

 

グラフ・スコープとの互換性

TensorFlow グラフ・スコープの内側で定義する任意の Keras 層とモデルはその変数と演算の総てを指定されたグラフの一部として作成させます。例えば、次は期待どおりに動作します :

from keras.layers import LSTM
import tensorflow as tf

my_graph = tf.Graph()
with my_graph.as_default():
    x = tf.placeholder(tf.float32, shape=(None, 20, 64))
    y = LSTM(32)(x)  # LSTM 層の総ての ops/variables はグラフの一部として作成されます

 

変数スコープとの互換性

変数共有は、TensorFlow 変数スコープを通してではなく、同じ Keras 層 (or モデル) インスタンスを複数回呼び出すことを通して成されるべきです。TensorFlow 変数スコープは Keras 層やモデル上に効果を持ちません。

重み共有が Keras でどのように動作するかを素早く要約すると :
同じ層インスタンスやモデル・インスタンスを再利用することにより、その重みを共有します。

Keras での重み共有についての更なる情報については、Getting started : Keras functional API の重み共有セクションを見てください。

ここに単純なサンプルがあります :

# Keras 層をインスタンス化する
lstm = LSTM(32)

# 2 つの TF placeholders をインスタンス化します
x = tf.placeholder(tf.float32, shape=(None, 20, 64))
y = tf.placeholder(tf.float32, shape=(None, 20, 64))

# 2 つの tensor を *同じ* LSTM 重みでエンコードします
x_encoded = lstm(x)
y_encoded = lstm(y)

 

訓練可能な重みと状態 updates を集める

幾つかの Keras 層 (stateful RNNs と BatchNormalization 層) は各訓練ステップの一部として実行される必要がある内部更新を持ちます。それらは tensor タプルのリスト, layer.updates としてストアされます。それらのために (各訓練ステップで実行される) assign ops を生成すべきです。ここにサンプルがあります :

from keras.layers import BatchNormalization

layer = BatchNormalization()(x)

update_ops = []
for old_value, new_value in layer.updates:
    update_ops.append(tf.assign(old_value, new_value))

Keras モデル (Model インスタンスか Sequential インスタンス) を使用している場合には、model.udpates は同じように挙動する (そしてモデルの総ての基礎層の updates を集めます) ことに注意してください。

加えて、層の訓練可能な重みを明示的に集める必要があるケースでは、layer.trainable_weights (or model.trainable_weights) – TensorFlow Variable インスタンスのリストを通してそれを行なうことができます :

from keras.layers import Dense

layer = Dense(32)(x)  # 層をインスタンス化して呼び出します
print layer.trainable_weights  # TensorFlow Variables のリスト

これを知ることは TensorFlow optimizer に基づき貴方自身の訓練ルーチンを実装することを可能にします。

 

TensorFlow で Keras モデルを使用する

Keras Sequential モデルを TensorFlow ワークフロー内の使用のために変換する

TensorFlow プロジェクトで再利用することを臨む Keras Sequential モデルを見出したとします (例えば、事前訓練された重みを持つ VGG16 画像分類器 を考えてください)。どのように進めますか?

次の Keras モデルから始めて、それが入力として特定の TensorFlow tensor, my_input_tensor を取るために変更することを望むと仮定します。この入力 tensor は例えばデータ feeder op に、あるいは前の TensorFlow モデルの出力になり得ます。

# これは最初の Keras モデルです。
model = Sequential()
model.add(Dense(32, activation='relu', input_dim=784))
model.add(Dense(10, activation='softmax'))

カスタム TensorFlow placeholder の上に Sequential モデルを構築し始めるために単に keras.layers.InputLayer を使用して、それからその上にモデルの残りを構築する必要があるだけです :

from keras.layers import InputLayer

# これは変更した Keras モデルです
model = Sequential()
model.add(InputLayer(input_tensor=custom_input_tensor,
                     input_shape=(None, 784)))

# 前のようにモデルの残りを構築します
model.add(Dense(32, activation='relu'))
model.add(Dense(10, activation='softmax'))

この段階で、事前訓練された重みをロードするために model.load_weights(weights_file) を呼び出すことができます。

それから Sequential モデルの出力 tensor を集めることを多分望むでしょう :

output_tensor = model.output

今 output_tensor の上に
新しい TensorFlow op を追加することができます、等々。

 

TensorFlow tensor 上で Keras モデルを呼び出す

Keras モデルは層と同じように作用しますので、TensorFlow tensor 上で呼び出すことができます :

from keras.models import Sequential

model = Sequential()
model.add(Dense(32, activation='relu', input_dim=784))
model.add(Dense(10, activation='softmax'))

# これは動作します!
x = tf.placeholder(tf.float32, shape=(None, 784))
y = model(x)

Note Keras モデルを呼び出すことにより、そのアーキテクチャと重みの両者を再利用しています。tensor 上でモデルを呼び出しているとき、入力 tensor 上に新しい TF ops を作成し、そしてそれらの ops は既にモデルに存在している TF Variable インスタンスを再利用しています。

 

マルチ GPU と分散訓練

Keras モデルの部分を異なる GPU に割り当てる

TensorFlow デバイス・スコープは Keras 層とモデルと完全に互換ですので、グラフの特定の部分を異なる GPU にそれらを割り当てることが可能です。ここに単純なサンプルがあります :

with tf.device('/gpu:0'):
    x = tf.placeholder(tf.float32, shape=(None, 20, 64))
    y = LSTM(32)(x)  # all ops in the LSTM layer will live on GPU:0

with tf.device('/gpu:1'):
    x = tf.placeholder(tf.float32, shape=(None, 20, 64))
    y = LSTM(32)(x)  # all ops in the LSTM layer will live on GPU:1

LSTM 層により作成された変数は GPU 上には存在しないことに注意してください : 総ての TensorFlow 変数はそれらが作成されたデバイス・スコープからは独立に CPU 上に常に存在しています。TensorFlow は裏でデバイス間変数転送を扱います。

異なる GPU 上で同じモデルの複数のレプリカを (異なるレプリカに渡り同じ重みを共有しながら) 訓練することを望む場合、最初に一つのデバイス・スコープ下でモデル (または層) をインスタンス化して、それから異なる GPU デバイス・スコープで同じモデル・インスタンスを複数回呼び出すべきです、例えば次のようにです :

with tf.device('/cpu:0'):
    x = tf.placeholder(tf.float32, shape=(None, 784))

    # shared model living on CPU:0
    # it won't actually be run during training; it acts as an op template
    # and as a repository for shared variables
    model = Sequential()
    model.add(Dense(32, activation='relu', input_dim=784))
    model.add(Dense(10, activation='softmax'))

# replica 0
with tf.device('/gpu:0'):
    output_0 = model(x)  # all ops in the replica will live on GPU:0

# replica 1
with tf.device('/gpu:1'):
    output_1 = model(x)  # all ops in the replica will live on GPU:1

# merge outputs on CPU
with tf.device('/cpu:0'):
    preds = 0.5 * (output_0 + output_1)

# we only run the `preds` tensor, so that only the two
# replicas on GPU get run (plus the merge op on CPU)
output_value = sess.run([preds], feed_dict={x: data})

 

分散訓練

クラスタにリンクされた TF session を Keras で登録することにより TensorFlow 分散訓練を自明に利用することができます :

server = tf.train.Server.create_local_server()
sess = tf.Session(server.target)

from keras import backend as K
K.set_session(sess)

TensorFlow を分散設定で使用することについての更なる情報については、このチュートリアル を見てください。

 

TensorFlow-serving でモデルをエクスポートする

TensorFlow Serving は TensorFlow モデルをプロダクション設定でサーブするためのライブラリで、Google により開発されました。

任意の Keras モデルは TensorFlow-serving でエクスポート可能です (それが一つの入力と一つの出力を持つ限りは、これは TF-serving の制限です)、それが TensorFlow ワークフローの一部として訓練されているかにかかわらず。

ここにそれがどのように動作するかがあります。

もしグラフが Keras 学習フェイズ (訓練時とテスト時で異なる挙動) を利用している場合、モデルをエクスポートする前に本当に最初にやることはグラフに学習フェイズの値 (推定上, i.e. テストモードとして 0) をハードコードすることです。これは 1) Keras backend で定数 learning phase を登録して、そして 2) モデルを後で再構築します。

ここにこれらの 2 つの単純なステップが実際にあります :

from keras import backend as K

K.set_learning_phase(0)  # all new operations will be in test mode from now on

# serialize the model and get its weights, for quick re-building
config = previous_model.get_config()
weights = previous_model.get_weights()

# re-build a model where the learning phase is now hard-coded to 0
from keras.models import model_from_config
new_model = model_from_config(config)
new_model.set_weights(weights)

公式チュートリアル で見つかる手順をフォローすることにより、モデルをエクスポートするために TensorFlow-serving を使用できます :

from tensorflow_serving.session_bundle import exporter

export_path = ... # where to save the exported graph
export_version = ... # version number (integer)

saver = tf.train.Saver(sharded=True)
model_exporter = exporter.Exporter(saver)
signature = exporter.classification_signature(input_tensor=model.input,
                                              scores_tensor=model.output)
model_exporter.init(sess.graph.as_graph_def(),
                    default_graph_signature=signature)
model_exporter.export(export_path, tf.constant(export_version), sess)
 

以上