tf.keras : TF eager execution examples : MNIST (MLP)

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
 

以上