本記事は「TensorFlow チュートリアル Part2:2クラスデータセットの分類を体験」の続きです。今回は3クラスデータの分類に挑戦します。
本記事の構成は下記の通りです。
Contents
3クラスデータセットの生成とデータの確認
今回は3クラスデータセットの分類を行ってみます。使用するデータは、sklearnなどのフレームワークの機能を用いたりせずに作成します。各データは原点からの距離、角度の二つの数字を変化させることで、プロットすると螺旋状になるように作成します。
上の出力を見てわかる通り、3つのクラスが存在します。今回もモデルに学習させることで、試験用データをこのように上手く分類することが目標です。以降本記事では、データセットの中のデータを「データ」、データセットの中で各データが属するクラスを「ラベル」と呼びます。
では、生成したデータについてより詳しく見てみましょう。データX、ラベルYの形状及びYの中身を出力してみます。
1クラスあたり500個のデータ×3で1500個というデータ数です。クラスはクラス0、クラス1、クラス2の三種類存在しますが、今回はラベルをワンホットエンコーディングしているため、Yの形状は(1500,3)となっていることに注意してください。
以上より、このデータセットは以下のような構造となっています。
ディープラーニング:モデルの学習による3クラス分類
使用するデータについては把握できたのではないかと思いますので、早速学習に移りたいと思います。一般的に、ディープラーニングの流れは以下のようになっています。
- 訓練用データセット、試験用データセットの用意
- モデルを用意
- 訓練用データセットでモデルに学習させる
- 試験用データセットで推論
訓練用データセット、試験用データセットの用意
まずは訓練用データセットと試験用データセットの用意ということで、生成したデータセットを訓練用データセット : 試験用データセット = 8 : 2 の割合で分割します。また、データをシャッフルしてから分割する必要がある点に注意しましょう。便利なことに、sklearnのtrain_test_splitクラスは、データをシャッフルしながら分割してくれます。
from sklearn.model_selection import train_test_split
train_X, test_X, train_Y, test_Y = train_test_split(X, Y, test_size=0.2) #データを8:2に分割
分割した結果、データセットの構造は下の図のようになります。
これで訓練用、試験用のデータセットを用意できました。
モデルを用意
続いて、モデルを用意します。今回のモデルもSequential APIで定義します。モデルの出力層のサイズと活性化関数、及び損失関数に注意してください。今回は3クラス分類なので出力層のサイズは3で、活性化関数にはソフトマックス関数を用い、損失関数にクロスエントロピー誤差を用います。多クラス分類では活性化関数にソフトマックス関数を、損失関数にはクロスエントロピー誤差を用いることが多いということを思い出してください。
層数は2クラス分類のときと同じく2層に設定しました。隠れ層におけるニューロンの数は4つに設定しています。
訓練用データセットでモデルに学習させる
では、訓練用データセットで学習させてみましょう。今回はミニバッチのサイズ10、エポック数は100で学習させます。
損失の減少があまり芳しくないですね。これは、データ数の多さに対しモデルが単純すぎたためです。あまり期待できませんが、このまま推論させてみましょう。
前回2クラス分類でワンホットエンコーディングによるラベル付けを行ったときと同様、あるデータを入力したときの3つの出力の内、最も値が大きいクラスにデータが分類されるようにします。
pred_labels = [] #結果格納用
pred = model.predict(test_X)#試験データを入力して出力を得る
for p in pred:
if np.argmax(p) == 0 :
pred_labels.append([1.,0.,0.])
elif np.argmax(p) == 1:
pred_labels.append([0.,1.,0.])
else:
pred_labels.append([0.,0.,1.])
pred_labels = np.array(pred_labels) #numpy arrayに変換
試験用データセットで推論
では、test_Xに対して推論を行わせ、その結果を描画してみましょう。
ある程度上手く分類できているようですが、やはり多くのデータが誤分類されてしまっているのがわかります。このように、データに対してモデルが単純すぎると、学習させてもバイアスと呼ばれる汎化誤差が高いままです。これはモデルのパラメータを調整したり、より複雑なモデルにすることで改善できます。
モデルの改善
分類の精度がいまいちだったので、モデルを少しだけ変更します。モデルには調整できるパラメータがたくさんあります。モデルの層数、隠れ層のニューロン数、用いる活性関数、用いる損失関数、オプティマイザ、学習率などです。今回はそれほど大きな変更をする必要はなさそうなので簡単に、隠れ層におけるニューロンの数を増やしてみましょう。
上のコードは、隠れ層のサイズ(ニューロン数)を4から8に変更したものです。それ以外については変更前と全く同じです。
学習させてみます。訓練データは同じものを用い、訓練ループにおける処理も同じです。
損失は変更前に比べてかなり下がっていますね。
推論させ、結果を描画してみましょう。
変更前と見比べてみてください。ニューロン数を増やさなかったときに比べてかなり上手く分類できているようです。
なお、この流れをSubclassing APIで実装すると以下のようになります。興味がある人は参考にしてみてください。
import tensorflow as tf
from tensorflow.keras.losses import CategoricalCrossentropy
from tensorflow.keras.optimizers import SGD
class Model(tf.keras.Model):
def __init__(self,input_dim, hidden_dim, output_dim):
super(Net,self).__init__()
self.f1 = tf.keras.layers.Dense(
units = hidden_dim,
input_dim = input_dim,
activation = 'relu'
)
self.f2 = tf.keras.layers.Dense(
units = output_dim,
activation = 'softmax'
)
def call(self, x, training = None):
x = self.f1(x)
y = self.f2(x)
return y
#モデルをNetクラスのインスタンスとして生成
model = Model(2, 8, 3)
#損失関数
loss_fn = CategoricalCrossentropy()
#オプティマイザ
optimizer = SGD(learning_rate = 0.5)
#勾配計算、パラメータ更新を行う関数
def train(x,t):
with tf.GradientTape() as tape:
pred = model(x)
loss = loss_fn(t,pred)
grad = tape.gradient(
loss,
model.trainable_variables
)
optimizer.apply_gradients(zip(grad, model.trainable_variables))
return loss
#学習させる
for epoch in range(100):
epoch_loss = 0.
loss = train(train_X,train_Y)
epoch_loss += loss.numpy()
if epoch % 100 == 0:
print("epoch: %d loss: %f" % (epoch+1 ,float(loss)))
#推論
pred = model(test_X)
まとめ
今回は3クラスデータセットの分類に挑戦しました。2クラス分類のときと比べて新しいことはほとんどやっていないので、よい復習になったのではないかと思います。このように、3クラス以上の多クラスを分類するときもTensorFlowの機能を用いれば簡単に実装することができます。また、モデルは様々な改善を施すことで精度を上げることができます。自分でも改造に挑戦してみてはどうでしょうか。
参考文献
- チーム・カルポ 『物体・画像認識と時系列データ処理入門』. 2021
- TensorFlow API v2.8.0 https://www.tensorflow.org/api_docs/python/tf?hl=ja