チラ裏備忘録

情報整理

Tensorflow2 Subclassing APIの自分用メモ

参考:エキスパートのための TensorFlow 2.0 入門  |  TensorFlow Core
の一連の流れを自分用備忘録としてメモします.

インポート

import tensorflow as tf

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

MNISTデータのロード

mnist = tf.keras.datasets.mnist

(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

# Add a channels dimension
x_train = x_train[..., tf.newaxis]
x_test = x_test[..., tf.newaxis]

下2行は次元を追加しています.
(60000, 28, 28) → (60000, 28, 28, 1)

データの整形

# 画像データをtf.data.Datasetオブジェクトに変換
train_ds = tf.data.Dataset.from_tensor_slices((x_train, y_train)).shuffle(10000).batch(32)

# ラベルデータをtf.data.Datasetオブジェクトに変換
test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)

わざわざtf.data.Dataset.from_tensor_slicesに食わせているのは,Datasetオブジェクトに変換してshuffle()及びbatch()を適用するため.

shuffle()は文字通りデータをシャッフルしますが,含まれるデータ数以上の数値を入力することで完全なシャッフルが可能となります.

参考:TensorFlowのDataset.shuffleのbuffer_size - Qiita

尚,batch後にshuffleを実行すると,シャッフルはバッチ内でのみ行われ,ミニバッチの枠を超えて(例:ミニバッチAとミニバッチB間のデータでシャッフル)行われることはありません.

モデルの定義(Modelクラスのサブクラス化)

class MyModel(Model): # tensorflow.keras.Modelを継承
    def __init__(self):
        super(MyModel, self).__init__() # 親クラスのコンストラクタ実行(引数にクラス名忘れない)

        # 層を変数に格納する
        self.conv1 = Conv2D(32, 3, activation='relu')
        self.flatten = Flatten()
        self.d1 = Dense(128, activation='relu')
        self.d2 = Dense(10, activation='softmax')

    def call(self, x): # xはモデルへの入力データ
        # 構造(層の繋がり)を定義する
        x = self.conv1(x)
        x = self.flatten(x)
        x = self.d1(x)
        return self.d2(x)

# モデルのインスタンスを作成
model = MyModel()
メモ
  • tensorflow.keras.Modelを継承する
  • __init__内で,super(モデル名, self).__init__()
  • __init__内で,層を変数に格納する
  • call内で,構造(層の繋がり)を定義する
  • call(self, x)のxは入力データ

損失関数と最適化手法の決定

# 変数名はloss_objectとするのが通例らしい
loss_object = tf.keras.losses.SparseCategoricalCrossentropy() # 損失関数

optimizer = tf.keras.optimizers.Adam() # 最適化手法
sparse_categorical_crossentropyとcategorical_crossentropyの違いは?

sparse_categorical_crossentropy:ラベルが整数表記
categorical_crossentropy:ラベルがone-hot表記

参考:categorical_crossentropyとsparse_categorical_crossentropyの違い【Keras】 | 研究所で働くエンジニアのブログ

5/30追記: 上記の関係が逆になっていました。大変申し訳ありませんでした。

metrics

# 学習用
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')

metricsとは,モデルの評価指標です.
ここでは損失関数の値と分類の精度を指標としています.

学習用関数定義

@tf.function # デコレータ
def train_step(images, labels):
    with tf.GradientTape() as tape:
        predictions = model(images) # モデルに入力を与え,予測値を求める
        loss = loss_object(labels, predictions) # 損失関数にラベルと予測値を与え,lossを計算
    gradients = tape.gradient(loss, model.trainable_variables) # 自動微分で勾配を求める
    optimizer.apply_gradients(zip(gradients, model.trainable_variables)) # 勾配と学習用変数から,重みを更新

    train_loss(loss) # 損失関数の値を記録(Mean)
    train_accuracy(labels, predictions) # 精度を記録(SparseCategoricalAccuracy)

Model.trainable_variables…ネットワーク内の訓練可能な重みをリストで取得

tf.keras.metrics.Mean

Meanはその名の通り,与えられたデータの平均値を求めます.

# 平均を計算
m = tf.keras.metrics.Mean()
m([1,2,3,4,5]).numpy() # 3.0

# 値の蓄積
m.reset_states() # 初期化
_ = m.update_state([2,4,6,8,10])
_ = m.update_state([10, 20, 30, 40, 50]) # 計算の蓄積
m.result().numpy() # 18.0 = (2+4+6+8+10+10+20+30+40+50)/10

# 計算を適用する値の選択
m.reset_states()
_ = m.update_state([10, 20, 30, 40, 50], sample_weight=[1,1,0,0,0]) # 0に対応する値は計算されない
m.result().numpy() # 15.0

つまり,train_loss(loss)という部分では,loss_object(labels, predictions)から返ってきた32個(ミニバッチサイズ)のデータから成るlossを平均していると思われます(ミニバッチ学習).
(詳細な挙動について分かり次第追記します…)

tf.keras.metrics.SparseCategoricalAccuracy

モデルの精度を計算します.

m = tf.keras.metrics.SparseCategoricalAccuracy()
_ = m.update_state([[2],[1]], [[0.1, 0.9, 0.8], [0.05, 0.95, 0]])
m.result().numpy() # 0.5

つまり,train_accuracy(labels, predictions)の部分では,正解ラベルとモデルの出力(確率)を渡してミニバッチ単位での精度を求めています.

テスト用関数定義

学習用と殆ど変わりません.
データがテスト用のものに変わっただけです.

@tf.function
def test_step(images, labels):
    predictions = model(images)
    t_loss = loss_object(labels, predictions)

    test_loss(t_loss)
    test_accuracy(labels, predictions)
補足
    for images, labels in train_ds:
        train_step(images, labels)

    for test_images, test_labels in test_ds:
        test_step(test_images, test_labels)

すぐ下に記述されていますが,実際の学習では,上記のようにデータが与えられます.
train_dsは最初にbatch(32)でデータ分けをしていますから,
train_stepに与えられるimagesとlabelsの形状は,imagesが(32, 28, 28, 1),labelsが(32,)です(test_images,test_labelsも同様).

学習とテスト

EPOCHS = 5 # エポック数(※1エポック = 少なくとも一度は全てのデータを学習に使用した状態)

for epoch in range(EPOCHS): # 5エポック
    for images, labels in train_ds: # ミニバッチ学習
        train_step(images, labels) # 32個で1セットのデータ

    for test_images, test_labels in test_ds: # ミニバッチごとに精度を計算
        test_step(test_images, test_labels) # 32個で1セットのデータ

    # エポック数,loss,精度を表示
    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))
  
    # 次のエポック用にメトリクスをリセット
    train_loss.reset_states()
    train_accuracy.reset_states()
    test_loss.reset_states()
    test_accuracy.reset_states()

train_stepのループではデータ数60,000を32のミニバッチサイズで割るので,1,875回
test_stepのループではデータ数10,000を32のミニバッチサイズで割るので,約313回
ループが回ることになります.

注意点

Subclassingの書き方では,モデルの概要を表示するmodel.summary()は実行できません.
Subclassing APIは,Define-by-Runである(実行と同時にモデルが動的に生成される)ためです.