Edited at

TensorFlow2.0を使ってMNISTをMLPで学習する


モチベーション


当面の取り組み予定

ディープラーニング基礎講座の課題をTensorFlow 2.0で書き直す。


今回の課題


  • MNISTデータセットを多層パーセプトロン(MLP)で学習する


    • 制約:全体の実行時間は60分以内

    • 元々の課題は「TensorFlowを使わずに」だったが、今回はTensorFlowの勉強を兼ねているため利用する

    • 元々の課題は精度も求められていたが、今回はTensorFlowの勉強に重きを置き、精度向上には取り組まない




参考


環境


  • Google Colaboratory

  • TensorFlow 2.0 Alpha


コード


TensorFlowのインストール

デフォルトではGoogle ColabにTensorFlow 2.0はインストールされていないため、自分でインストールします。今回はGPUを利用するためGPU版です。なお、初めにこのインストールを忘れ、import tensorflow as tfとしていると、TensorFlowインストール後にランタイムの再起動が必要になりますので、忘れずに初めにインストールしておきましょう。

!pip install tensorflow-gpu==2.0.0-alpha0


MNISTのダウンロードと加工

チュートリアルに載っているtensorflow_datasetsを利用します。as_supervisedオプションを付けると戻り値がタプルになって利用しやすそうだったため設定しています(デフォルトではディクショナリーになる)。

import tensorflow_datasets as tfds

import tensorflow as tf

dataset = tfds.load('mnist', as_supervised=True)
mnist_train, mnist_test = dataset['train'], dataset['test']

画像データの正規化、シャッフル、バッチ化を行います。shuffleの引数buffer_sizeはチュートリアル通り10,000にしていますが、訓練データのデータ数は60,000あります。データ数以上の値を設定していると確実にシャッフルされるとどこかに書いてありましたが、訓練データより少ないとどうなるのかはよくわかっていません。

def convert_types(image, label):

image = tf.cast(image, tf.float32)
image /= 255
return image, label

mnist_train = mnist_train.map(convert_types).shuffle(10000).batch(32)
mnist_test = mnist_test.map(convert_types).batch(32)


モデルの定義

こちらもチュートリアル通りKerasを使っています。Kerasを使わないモデル定義の方法はそのうち勉強します。あとこの場合、メソッド名をcallとする必要があるようです(後のコードで自分ではcallを読んでいないので)。Kerasの学習はfitだと思っていましたが・・・。これもそのうちわかるでしょう。

from tensorflow.keras.layers import Dense, Flatten, Conv2D

from tensorflow.keras import Model

class MyModel(Model):
def __init__(self):
super(MyModel, self).__init__()
self.flatten = Flatten() # (28, 28, 1) -> 784
self.d1 = Dense(128, activation = 'relu') # 784 -> 128
self.d2 = Dense(10, activation = 'softmax') # 128 -> 10

def call(self, x):
x = self.flatten(x)
x = self.d1(x)
return self.d2(x)

model = MyModel()

こちらもKerasを使っています。SparseCategoricalCrossentropy()はこれまで見たことがなかったのですが、ラベルがone hotエンコーディングされていない場合に使うといいのだそうです。今回のラベルはone hotエンコーディングされていませんので、SparseCategoricalCrossentropy()をそのまま使うことにしました(試しにone hotエンコーディングせずCategoricalCrossentropy()を使ってみましたがエラーになりました)。

loss_object = tf.keras.losses.SparseCategoricalCrossentropy()

optimizer = tf.keras.optimizers.Adam()


訓練の準備

ログ用のLossとAccuracyの定義です。

train_loss = tf.keras.metrics.Mean(name = 'train_loss')

train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name = 'train_accuracy')

test_loss = tf.keras.metrics.Mean(name = 'test_loss')
test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name = 'test_accuracy')

@tf.functionを付けると、その関数はグラフにコンパイルされ速く動作するそうです。詳細はこちらをご覧下さい。試しに時間を測定してみました。



  • @tf.functionあり:1.74分


  • @tf.functionなし:4.29分

なので、結構違いますね!基本的には@tf.functionを付けるようにしておいた方がよさそうです。

with tf.GradientTape() as tapeの中には微分を行う対象式を記述します。そして、その後のtape.gradientにより自動的に微分してくれるようです。

@tf.function

def train_step(image, label):
with tf.GradientTape() as tape:
predictions = model(image)
loss = loss_object(label, predictions)
gradients = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(gradients, model.trainable_variables))

train_loss(loss)
train_accuracy(label, predictions)

@tf.function
def test_step(image, label):
predictions = model(image)
t_loss = loss_object(label, predictions)

test_loss(t_loss)
test_accuracy(label, predictions)


学習

最後に学習です。このコードを簡単にするためにデータロード時にas_supervised=Trueとしていました。

EPOCHS = 5

for epoch in range(EPOCHS):
for image, label in mnist_train:
train_step(image, label)

for test_image, test_label in mnist_test:
test_step(test_image, test_label)

template = 'Epoch {}, Loss: {}, Accuracy: {}, Test Loss: {}, Test Accuracy: {}'
print(template.format(epoch + 1, train_loss.result(), train_accuracy.result() * 100, test_loss.result(), test_accuracy.result() * 100))

結果はこちらの通りです。

Epoch 1, Loss: 0.0035276836715638638, Accuracy: 99.88829803466797, Test Loss: 0.1231490969657898, Test Accuracy: 97.93000030517578

Epoch 2, Loss: 0.0034228067379444838, Accuracy: 99.892578125, Test Loss: 0.12411412596702576, Test Accuracy: 97.92285919189453
Epoch 3, Loss: 0.0034678690135478973, Accuracy: 99.89083099365234, Test Loss: 0.12539470195770264, Test Accuracy: 97.92533874511719
Epoch 4, Loss: 0.00351154338568449, Accuracy: 99.88870239257812, Test Loss: 0.12521344423294067, Test Accuracy: 97.94062042236328
Epoch 5, Loss: 0.0034353630617260933, Accuracy: 99.89129638671875, Test Loss: 0.12637655436992645, Test Accuracy: 97.93353271484375


所感


  • Placeholderやtf.Session()がなくなって、より直感的に書けるようになった


  • tf.kerasの出番が多い


  • tensorflow_datasetstf.GradientTape()などTensorFlow 2.0以前の関数の使い方が少しわかってよかった


GitHubのリポジトリ

こちらです。