tensorflow2 Subclassing APIとGradientTapeで2クラス分類!【tensorflow】

前回はSubclassing APIとGradientTapeを使ってかなりシンプルな線形回帰モデルの実装を行いました!

しかし、前回のモデルのパラメータはy = ax + bのaとbの2つみでDeep Neural Networkの実装の主役になるDenseレイヤーなどのパーツを全く使用しませんでした!また、前回は訓練データセットとテストデータセットの分割も行わなかったので実践的な内容ではありませんでした。

そこで今回、題材はシンプルなままで前回よりも実践的な操作(Denseレイヤーの使用や訓練データセットとテストデータセットの分割)を含んだ内容を扱ってみます!

まず、今回使用するライブラリをインポートします!

import tensorflow as tf
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle
import matplotlib.pyplot as plt
from matplotlib import collections
import seaborn as sns
print(tf.__version__)
# 2.5.0

データセットの生成

今回は2つだけの次元をもつ特徴量で、(x0, x1)=(3,2)を中心として正規分布するクラス0と(x0, x1)=(7,6)を中心として正規分布するクラス1をそれぞれ500個ずつ生成します!

この各点がどちらのクラスに所属するかを予測するモデルを作成していきます!

input_dim = 2
n_sample_per_class = 500

x0 = np.random.randn(n_sample_per_class, input_dim) + np.array([3, 2])
x1 = np.random.randn(n_sample_per_class, input_dim) + np.array([7, 6])

t0 = np.array([[0] for _ in range(n_sample_per_class)])
t1 = np.array([[1] for _ in range(n_sample_per_class)])

x = np.vstack([x0, x1])
t = np.vstack([t0, t1])

x = x.astype('float32')
t = t.astype('float32')

print(f"x.shape: {x.shape}, t.shape: {t.shape}")
# x.shape: (1000, 2), t.shape: (1000, 1)
sns.scatterplot(x=x[:, 0], y=x[:, 1], hue=t[:,0])

訓練データセットと検証データセットへの分割

生成したデータの80%を訓練用データセットに、残りの20%を検証用データセットに割り当てます!

これにはsklearnのtrain_test_split関数を使用します!

x_train, x_validation, t_train, t_validation = train_test_split(x, t, test_size=0.2)
sns.scatterplot(x=x_train[:, 0], y=x_train[:, 1], hue=t_train[:,0])
sns.scatterplot(x=x_validation[:, 0], y=x_validation[:, 1], hue=t_validation[:,0])

 

 

訓練用データ、検証用データそれぞれの分布は上のようになっています!

モデルの実装

今回使用する特徴量は二次元で、しかもかなり明瞭に2クラスに分離しているのでニューラルネットワークを用いた予測をするまでもないくらいに簡単なタスクです!しかし今回はニューラルネットワークの実装の練習ということで書いてみたいと思います!

class MyClassificationModel(tf.keras.Model):
    def __init__(self):
        super(MyClassificationModel, self).__init__()
        self.hidden_layer1 = tf.keras.layers.Dense(10, activation='relu')
        self.hidden_layer2 = tf.keras.layers.Dense(10, activation='relu')
        self.output_layer = tf.keras.layers.Dense(1, activation='sigmoid')

    def call(self, input):
        x = self.hidden_layer1(input)
        x = self.hidden_layer2(x)
        output = self.output_layer(x)
        return output

 

隠れ層2つを通して最後にSigmoidを介してクラス1に属する可能性を出力するモデルを実装しました!前回の線形回帰と同様に学習するパラメータを含む部分を__init__関数内で定義して、call 関数で予測値の出力までの演算(フォーワードパス)を定義しました!

それぞれの隠れ層はノード数10の全結合層です!活性化関数にはReLuを用いました!このようにしてニューラルネットワークを実装する際には__init__関数内でそのモデルを構成するレイヤーを定義していきます!

call関数の中ではself.hidden_layer1 → self.hidden_layer2 → self.output_layerという順番で入力を処理していくかなりシンプルなニューラルネットワークを定義しました!

 

訓練ステップの実装

モデルが実装できたので、今度はモデルを訓練するための関数を定義します!それがloss関数とtrain_step関数です!

loss関数はシンプルに損失関数を予測値と正解ラベルから計算する関数です!

train_step関数は1バッチ分のデータ(今回は128サンプルを1バッチに設定、後述)を用いてモデルの訓練を行ってパラメータを更新するという操作を行う関数です!上記の1バッチ分のデータによる訓練を1ステップと呼ぶのでtrain_stepという関数名になっています!

train_stepはtensorflowで内部的に使用される関数名でもあるのでそれを参考にしてこの関数名にしています!

ちなみにtf.keras.Modelクラスはtrain_stepメソッドが定義されていて、model.fit()での処理では内部的にこのtrain_stepメソッドが使用されています!このtrain_stepメソッドを上書きして独自の関数を定義することもできます!(詳しくはこちら

今回はModelクラスのメソッドとしてではなく、通常の関数としてtrain_stepを定

義して使用することにします!

bce = tf.keras.losses.BinaryCrossentropy()
def loss(t, y):
    return bce(t, y)

optimizer = tf.keras.optimizers.SGD(learning_rate=0.1)
def train_step(x, t):
    with tf.GradientTape() as tape:
        outputs = model(x)
        tmp_loss = loss(t, outputs)
        gradients = tape.gradient(
            tmp_loss,
            model.trainable_variables
        )
    optimizer.apply_gradients(zip(gradients, model.trainable_variables))
    return tmp_loss

 

前回と同様にtf.GradientTape内でモデルによる予測値の出力と、予測値と正解ラベルから損失関数を計算する演算を行います!

そしてtf.GradientTape節を抜けて、optimizer.apply_gradientsでモデルのパラメータの更新を行います!

1エポックの訓練の実装

さて、1ステップの訓練を行う関数が定義できたので、これを用いて1エポック(全ての訓練データを用いてモデルのパラメータの更新を行う)の操作を実装します!今回は訓練データセットが800サンプルあり、バッチサイズ128で設定しているので7ステップで1エポックとなります!

そして今回はデータをTrain用とValidation用に分割して、Train用のデータのみでモデルの訓練を行い、残りのValidation用データでモデルの性能の検証を行っていきます!

epochs = 30
batch_size = 128

model = MyClassificationModel()
steps = x_train.shape[0] // batch_size

for epoch in range(epochs):
    epoch_loss = 0.
    x_, t_ = shuffle(x_train, t_train)
    for step in range(steps):
        start = step * batch_size # ミニバッチの先頭インデックス
        end = start + batch_size # ミニバッチの末尾インデックス
        train_step(x_[start: end], t_[start: end])

    print(f"epoch: {epoch+1}, loss: {train_loss.result():.3}, acc: {train_accuracy.result():.3}")

    train_loss.reset_states()
    train_accuracy.reset_states()

おおよそ15エポックほどでAccuracyは0.99を超えていてかなり良い精度のモデルが訓練できているようです!

さて、モデルの訓練ができたので、このモデルの性能の検証をValidation用のデータを用いて行います!

検証用データセットによるモデルの性能の確認

検証用に訓練データセットから除外したx_validationを用いてモデルの性能の確認を行います!

preds = model(x_validation)

bn_acc = tf.keras.metrics.BinaryAccuracy(threshold=0.5)
bn_acc.update_state(t_validation, preds)
validation_acc = bn_acc.result().numpy()
validation_loss = loss(t_validation, preds)
print(f"val_loss: {validation_loss:.3}\nval_acc: {validation_acc:.3}"
)
# val_loss: 0.0975
# val_acc: 0.995

やることはシンプルで、訓練したモデルを使ってx_validationの予測を行います!そして自分の計算したい評価関数を用いて性能の評価を行います!

BinaryAccuracyとLossを計算すると上記のような結果になりました!検証用のデータでも訓練データとほぼ同じ程度の性能を持っていることが確認できました!

 

まとめ

今回は簡単なニューラルネットワークをSubclassing APIで実装し、そのモデルをGradientTapeを用いて訓練してみました!

前回の線形回帰と比較してモデルはニューラルネットワークを使用して、データセットの訓練用と検証用への分割、バッチ単位での訓練を行うためのtrain_step関数の実装を行いました!

前回よりも実践的なコードになったかと思います!モデル自体はあまりにもシンプルなためSubclassing APIの利点が活かせていないので、次回はもう少し複雑な処理を含むモデルを使ってみます!

ここまで読んでいただきましてありがとうございました!

参考

https://www.tensorflow.org/tutorials/quickstart/advanced?hl=ja

https://www.tensorflow.org/guide/keras/customizing_what_happens_in_fit?hl=ja

https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html

NO IMAGE
最新情報をチェックしよう!