tf.keras TF eager execution examples : MNIST (MLP)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 07/22/2018 (TF 1.9)
* 本ページは、github 上の keras-team/keras/examples を参照しています:
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
tf.keras & TF Eager execution の実装サンプルはまだ少ないので自作して提供していきます :
- マルチバックエンド用 Keras (i.e. オリジナル Keras) で提供されているサンプルを題材にします。
- Eager excution を有効にします。
- モデル構成には tf.keras モジュールを使用します。
MNIST MLP
まずは最も基本的な MNIST MLP 用のスクリプトを基に、Eager execution を有効にした上でモデルは tf.keras で書き換えます。
Eager execution
最初に eager execution を有効にします。これはスクリプトの冒頭で行なう必要があります。
import tensorflow as tf tf.enable_eager_execution()
MNIST データロードと前処理
ハイパーパラメータの基本設定 :
batch_size = 128 num_classes = 10 epochs = 20
MNIST データセットをダウンロードしてロードしてくれる keras.datasets.mnist は tf.keras モジュールでもそのまま利用可能です。
MLP モデルですから画像データを reshape する際に構造化する必要はなく、784 = 28 * 28 と平坦化しておきます。また画像データをまとめて 255 で除算して簡単に正規化しておきます :
# the data, split between train and test sets (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data() x_train = x_train.reshape(60000, 784) x_test = x_test.reshape(10000, 784) x_train = x_train.astype('float32') x_test = x_test.astype('float32') x_train /= 255 x_test /= 255 print(x_train.shape[0], 'train samples') print(x_test.shape[0], 'test samples')
ラベルは one-hot ベクトルに変換しますが、ここでも keras.utils.to_categorical は tf.keras モジュールでも利用可能です :
# convert class vectors to binary class matrices y_train = tf.keras.utils.to_categorical(y_train, num_classes) y_test = tf.keras.utils.to_categorical(y_test, num_classes)
モデル作成
モデルを構成する方法は幾つかありますが、良く使用される方法を 3 つあげておきます。
汎用的には tf.keras.Model クラスを利用しますが、単純な層のスタックであれば Sequential モデルで十分です。
Sequential モデル
Sequential モデルを作成するために、層をリストで渡すことができます :
model = tf.keras.Sequential([ tf.keras.layers.Dense(512, activation=tf.nn.relu, input_shape=(784,)), tf.keras.layers.Dropout(0.2), tf.keras.layers.Dense(512, activation=tf.nn.relu), tf.keras.layers.Dropout(0.2), tf.keras.layers.Dense(num_classes, activation=tf.nn.softmax) ]) model.summary()
activation として tf.nn モジュールから活性化関数を選択していますが、これは (元のサンプルスクリプトのように) 文字列 ‘relu’, ‘softmax’ で代用することもできます。
Functional API
層は Functional API 形式で呼び出すことが可能で、tf.keras.Model クラスで束ねることができます :
input_image = tf.keras.layers.Input(shape=(784,)) x = tf.keras.layers.Dense(512, activation=tf.nn.relu)(input_image) x = tf.keras.layers.Dropout(0.2)(x) x = tf.keras.layers.Dense(512, activation=tf.nn.relu)(x) x = tf.keras.layers.Dropout(0.2)(x) output = tf.keras.layers.Dense(num_classes, activation=tf.nn.softmax)(x) model = tf.keras.Model(inputs=input_image, outputs=output) model.summary()
tf.keras.Model の継承
Eager execution モードなので命令型フレーバーにするならば、tf.keras.Model を拡張して __init__() と call() メソッドを実装すれば良いです。PyTorch の __init__(), forwad() の実装と酷似しています :
class MLPModel(tf.keras.Model): def __init__(self): super(MLPModel, self).__init__() self.dense1 = tf.keras.layers.Dense(512, activation=tf.nn.relu) self.dense2 = tf.keras.layers.Dense(512, activation=tf.nn.relu) self.dense3 = tf.keras.layers.Dense(10, activation=tf.nn.softmax) self.dropout1 = tf.keras.layers.Dropout(0.2) self.dropout2 = tf.keras.layers.Dropout(0.2) def call(self, inputs, training=False): x = self.dense1(inputs) if training: x = self.dropout1(x) x = self.dense2(x) if training: x = self.dropout2(x) return self.dense3(x) model = MLPModel()
訓練
.compile() & .fit() メソッドは optimizer を tf.train モジュールから選択することに注意すれば、普通に使えます :
model.compile( loss='categorical_crossentropy', #optimizer=tf.train.AdamOptimizer(), optimizer=tf.train.RMSPropOptimizer(learning_rate=0.01), metrics=['accuracy'])
history = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1, validation_data=(x_test, y_test))
実行時出力です :
60000 train samples 10000 test samples Train on 60000 samples, validate on 10000 samples Epoch 1/20 60000/60000 [==============================] - 6s 103us/step - loss: 0.6950 - acc: 0.8052 - val_loss: 0.3752 - val_acc: 0.9147 Epoch 2/20 60000/60000 [==============================] - 6s 99us/step - loss: 0.3426 - acc: 0.9295 - val_loss: 0.2074 - val_acc: 0.9551 Epoch 3/20 60000/60000 [==============================] - 6s 101us/step - loss: 0.3332 - acc: 0.9400 - val_loss: 0.2842 - val_acc: 0.9416 Epoch 4/20 60000/60000 [==============================] - 6s 99us/step - loss: 0.3405 - acc: 0.9452 - val_loss: 0.3667 - val_acc: 0.9412 Epoch 5/20 60000/60000 [==============================] - 6s 100us/step - loss: 0.3521 - acc: 0.9493 - val_loss: 0.2807 - val_acc: 0.9600 Epoch 6/20 60000/60000 [==============================] - 6s 100us/step - loss: 0.3481 - acc: 0.9520 - val_loss: 0.2932 - val_acc: 0.9638 Epoch 7/20 60000/60000 [==============================] - 6s 99us/step - loss: 0.3712 - acc: 0.9509 - val_loss: 0.3284 - val_acc: 0.9583 Epoch 8/20 60000/60000 [==============================] - 6s 100us/step - loss: 0.3824 - acc: 0.9556 - val_loss: 0.3402 - val_acc: 0.9602 Epoch 9/20 60000/60000 [==============================] - 6s 100us/step - loss: 0.3862 - acc: 0.9578 - val_loss: 0.3519 - val_acc: 0.9667 Epoch 10/20 60000/60000 [==============================] - 6s 99us/step - loss: 0.3726 - acc: 0.9549 - val_loss: 0.2642 - val_acc: 0.9680 Epoch 11/20 60000/60000 [==============================] - 6s 99us/step - loss: 0.3957 - acc: 0.9592 - val_loss: 0.2871 - val_acc: 0.9685 Epoch 12/20 60000/60000 [==============================] - 6s 100us/step - loss: 0.4098 - acc: 0.9600 - val_loss: 0.3668 - val_acc: 0.9650 Epoch 13/20 60000/60000 [==============================] - 6s 99us/step - loss: 0.3929 - acc: 0.9612 - val_loss: 0.3900 - val_acc: 0.9615 Epoch 14/20 60000/60000 [==============================] - 6s 99us/step - loss: 0.4462 - acc: 0.9627 - val_loss: 0.3784 - val_acc: 0.9685 Epoch 15/20 60000/60000 [==============================] - 6s 99us/step - loss: 0.4461 - acc: 0.9625 - val_loss: 0.3849 - val_acc: 0.9683 Epoch 16/20 60000/60000 [==============================] - 6s 100us/step - loss: 0.4318 - acc: 0.9634 - val_loss: 0.3443 - val_acc: 0.9708 Epoch 17/20 60000/60000 [==============================] - 6s 98us/step - loss: 0.4125 - acc: 0.9649 - val_loss: 0.4137 - val_acc: 0.9666 Epoch 18/20 60000/60000 [==============================] - 6s 98us/step - loss: 0.4240 - acc: 0.9630 - val_loss: 0.4306 - val_acc: 0.9663 Epoch 19/20 60000/60000 [==============================] - 6s 98us/step - loss: 0.4540 - acc: 0.9639 - val_loss: 0.3546 - val_acc: 0.9713 Epoch 20/20 60000/60000 [==============================] - 6s 101us/step - loss: 0.4426 - acc: 0.9659 - val_loss: 0.5093 - val_acc: 0.9649
評価
score = model.evaluate(x_test, y_test, verbose=0) print('Test loss:', score[0]) print('Test accuracy:', score[1])
Test loss: 0.5092759206473165 Test accuracy: 0.9649
Eager execution の無効化
ここまでは、特に Eager execution 依存コードはありませんので、無効化してもそのまま動作します。
MNIST ConvNet
次に (MLP ではなく) ConvNet 用のスクリプトです :
Eager execution
最初に eager execution を有効にします。これはスクリプトの冒頭で行なう必要があります。
import numpy as np import tensorflow as tf tfe = tf.contrib.eager tf.enable_eager_execution()
MNIST データロードと前処理
ハイパーパラメータの基本設定 :
batch_size = 128 num_classes = 10 epochs = 20
MNIST データセットをダウンロードしてロードしてくれる keras.datasets.mnist は tf.keras モジュールでもそのまま利用可能です。
MLP モデルですから画像データを reshape する際に構造化する必要はなく、784 = 28 * 28 と平坦化しておきます。また画像データをまとめて 255 で除算して簡単に正規化しておきます :
# the data, split between train and test sets (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data() x_train = x_train.reshape(60000, 784) x_test = x_test.reshape(10000, 784) x_train = x_train.astype('float32') x_test = x_test.astype('float32') x_train /= 255 x_test /= 255 print(x_train.shape[0], 'train samples') print(x_test.shape[0], 'test samples')
ラベルは one-hot ベクトルに変換しますが、ここでも keras.utils.to_categorical は tf.keras モジュールでも利用可能です :
# convert class vectors to binary class matrices y_train = tf.keras.utils.to_categorical(y_train, num_classes) y_test = tf.keras.utils.to_categorical(y_test, num_classes)
.from_tensor_slices() メソッド経由でデータセットにラップしておきます :
ds = tf.data.Dataset.from_tensor_slices((x_train, y_train)).shuffle(batch_size*2).batch(batch_size)
モデル作成
モデルを構成する方法は幾つかありますが、良く使用される方法を 3 つあげておきます。
汎用的には tf.keras.Model クラスを利用しますが、単純な層のスタックであれば Sequential モデルで十分です。
Sequential モデル
Sequential モデルを作成するために、層をリストで渡すことができます :
model = tf.keras.Sequential([ tf.keras.layers.Dense(512, activation=tf.nn.relu, input_shape=(784,)), tf.keras.layers.Dropout(0.2), tf.keras.layers.Dense(512, activation=tf.nn.relu), tf.keras.layers.Dropout(0.2), tf.keras.layers.Dense(num_classes, activation=tf.nn.softmax) ]) model.summary()
activation として tf.nn モジュールから活性化関数を選択していますが、これは (元のサンプルスクリプトのように) 文字列 ‘relu’, ‘softmax’ で代用することもできます。
Functional API
層は Functional API 形式で呼び出すことが可能で、tf.keras.Model クラスで束ねることができます :
input_image = tf.keras.layers.Input(shape=(784,)) x = tf.keras.layers.Dense(512, activation=tf.nn.relu)(input_image) x = tf.keras.layers.Dropout(0.2)(x) x = tf.keras.layers.Dense(512, activation=tf.nn.relu)(x) x = tf.keras.layers.Dropout(0.2)(x) output = tf.keras.layers.Dense(num_classes, activation=tf.nn.softmax)(x) model = tf.keras.Model(inputs=input_image, outputs=output) model.summary()
tf.keras.Model の継承
Eager execution モードなので命令型フレーバーにするならば、tf.keras.Model を拡張して __init__() と call() メソッドを実装すれば良いです。PyTorch の __init__(), forwad() の実装と酷似しています :
class MLPModel(tf.keras.Model): def __init__(self): super(MLPModel, self).__init__() self.dense1 = tf.keras.layers.Dense(512, activation=tf.nn.relu) self.dense2 = tf.keras.layers.Dense(512, activation=tf.nn.relu) self.dense3 = tf.keras.layers.Dense(10, activation=tf.nn.softmax) self.dropout1 = tf.keras.layers.Dropout(0.2) self.dropout2 = tf.keras.layers.Dropout(0.2) def call(self, inputs, training=False): x = self.dense1(inputs) if training: x = self.dropout1(x) x = self.dense2(x) if training: x = self.dropout2(x) return self.dense3(x) model = MLPModel()
訓練
.compile() & .fit() メソッドを利用する代わりに、eager execution らしいコーディングをしてみます。
損失関数
tf.keras でも categorical_crossentropy が利用可能です :
def loss(model, x, y, training=False): y_ = model(x, training) return tf.keras.losses.categorical_crossentropy (y, y_)
optimizer
optimizer は tf.train モジュールのものを利用します :
optimizer = tf.train.AdamOptimizer(learning_rate=0.001) #optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
評価
各エポックの最後にテスト・データセットを使用して評価します。
単純に予測が正解したカウントを数えて正解率としています :
labels_gt = tf.argmax(y_test, axis=1).numpy() def eval (model): predicts = model(x_test) # training = False labels_predicted = tf.argmax(predicts, axis=1) correct_count = tf.squeeze(tf.where(tf.equal(labels_gt, labels_predicted))) return len(correct_count.numpy())/len(labels_gt)
訓練ループ
GradientTape() を使用して勾配計算を行ない、勾配をモデル・パラメータに適用します。
損失の平均は tfe.metrics.Mean() を使用しています :
for e in range(1, epochs+1): epoch_loss_avg = tfe.metrics.Mean() for (steps, (x, y)) in enumerate(ds, 1): with tf.GradientTape() as tape: loss_value = loss(model, x, y, True) gradients = tape.gradient(loss_value, model.variables) optimizer.apply_gradients(zip(gradients, model.variables), global_step=tf.train.get_or_create_global_step()) epoch_loss_avg(loss_value) val_acc = eval (model) print("epoch: %2d ; train loss: %.4f ; val acc: %.4f" % (e, epoch_loss_avg.result(), val_acc))
実行時出力です :
60000 train samples 10000 test samples epoch: 1 ; train loss: 0.2331 ; val acc: 0.9613 epoch: 2 ; train loss: 0.0844 ; val acc: 0.9606 epoch: 3 ; train loss: 0.0518 ; val acc: 0.9636 epoch: 4 ; train loss: 0.0341 ; val acc: 0.9723 epoch: 5 ; train loss: 0.0253 ; val acc: 0.9776 epoch: 6 ; train loss: 0.0226 ; val acc: 0.9746 epoch: 7 ; train loss: 0.0183 ; val acc: 0.9776 epoch: 8 ; train loss: 0.0188 ; val acc: 0.9793 epoch: 9 ; train loss: 0.0161 ; val acc: 0.9788 epoch: 10 ; train loss: 0.0133 ; val acc: 0.9781 epoch: 11 ; train loss: 0.0132 ; val acc: 0.9759 epoch: 12 ; train loss: 0.0105 ; val acc: 0.9791 epoch: 13 ; train loss: 0.0120 ; val acc: 0.9801 epoch: 14 ; train loss: 0.0084 ; val acc: 0.9777 epoch: 15 ; train loss: 0.0098 ; val acc: 0.9797 epoch: 16 ; train loss: 0.0091 ; val acc: 0.9800 epoch: 17 ; train loss: 0.0086 ; val acc: 0.9812 epoch: 18 ; train loss: 0.0090 ; val acc: 0.9822 epoch: 19 ; train loss: 0.0083 ; val acc: 0.9797 epoch: 20 ; train loss: 0.0065 ; val acc: 0.9760
以上