【Python】TensorFlowで画像分類モデルを作る方法

Python
スポンサーリンク

今回はTensorFlowで画像分類モデルを作る方法を解説します。

モデル作成の基礎的なことは以下の記事に書いています。

データセット作成

CIFAR10

今回は画像データであるCIFAR10を使います。

import numpy as np
import tensorflow as tf

data = tf.keras.datasets.cifar10.load_data()

(X_train, y_train), (X_valid, y_valid) = data

y_train = tf.one_hot(y_train.ravel(), depth = 10)
y_valid = tf.one_hot(y_valid.ravel(), depth = 10)

print(X_train.shape, y_train.shape, X_valid.shape, y_valid.shape)

tensorflowのdatasetsにあるcifar10をダウンロードしました。

(学習データ, 検証データ)の順でタプル型になっています。

さらにその中で(画像, ラベル)の順になっています。

学習用は5万、検証用は1万のデータです。

ラベルは10クラスで、ニューラルネットで多クラス分類をするためにOneHot変換する必要があります。

import matplotlib.pyplot as plt
plt.imshow(X_train[0])
plt.title(y_train[0].numpy().astype("int8").tolist())
plt.show()

1枚目の画像を表示しました。

32 x 32なので粗いですがカエルですね。

ラベルは6番で、OneHot変換しているので左から0, 1, 2, … , 9の6番目に1が入っています。

こんな感じで画像からラベルに該当する番号に1が入っているデータとなっています。

Dataset

train_dataset = tf.data.Dataset.from_tensor_slices((X_train, y_train))
valid_dataset = tf.data.Dataset.from_tensor_slices((X_valid, y_valid))

from_tensor_slicesにデータを渡すとデータセットが作れます。

これをモデルに渡すことになります。

batch_size = 32

train_dataset = train_dataset.batch(batch_size)
valid_dataset = valid_dataset.batch(batch_size * 2)

train_dataset = train_dataset.shuffle(1024 * 50)

batch_sizeは画像を小出しにするサイズです。つまり32枚ごとに画像をモデルに入れます。

学習用データはシャッフルしておきました。

モデル作成

import tensorflow.keras.layers as L
import tensorflow.keras.models as M

def get_model(image_size, num_classes):
    inputs = L.Input(shape = image_size)
    x = tf.keras.applications.efficientnet.EfficientNetB0(include_top = False)(inputs)
    x = L.Flatten()(x)
    outputs = L.Dense(units = num_classes, activation = "softmax")(x)

    model = M.Model(inputs = inputs, outputs = outputs)
    model.compile(
        optimizer = tf.keras.optimizers.Adam(learning_rate = 0.001),
        loss = "categorical_crossentropy"
    )
    return model

tf.keras.backend.clear_session()
model = get_model(X_train.shape[-3:], y_train.shape[-1])
model.summary()
Inputs:データを受け取る層。画像サイズを指定しておく。
EfficientNetB0:学習済みで優秀な画像モデル。
Flatten:effnetの出力を1次元に圧縮する。
Dense:最終10クラスの出力にする。多クラス分類はsoftmaxにする。

以上の層を定義して、画像サイズ⇒クラス数になるように一連の流れを記述しましょう。

根幹となる構造はEfficietnNetで、これはすでに完成済みかつ学習済み優秀なモデルです。

最終の出力層だけDenseにして、出力サイズをラベル数の10にすればOKです。

そしてcompileで最適化手法と損失関数を指定します。

多クラス分類なのでcategorical_crossentropyを使いました。

実際にモデルを呼び出すときは、画像サイズとクラス数を渡しましょう。

こんな感じで32 x 32 x 3の画像を入力して、クラス数の10になっていればOKです。

学習と検証

学習実行

checkpoint = tf.keras.callbacks.ModelCheckpoint(
    "best_weight.h5",
    monitor = "val_loss",
    mode = "min",
    save_best_only = True,
    save_weights_only = True
)

history = model.fit(
    train_dataset,
    validation_data = valid_dataset,
    epochs = 10,
    callbacks = [checkpoint]
)

fitで学習をします。epochsはループ回数です。

ModelCheckpointはモデルを保存する機能です。

“val_loss”が最も小さいときのステータスを保存します。

CPUで学習すると1エポックで5分くらいかかります。

なのでGPUを使うといいですが、持っていない人にはGoogleColabをおすすめします。

import matplotlib.pyplot as plt
plt.plot(history.history["loss"], label = "train")
plt.plot(history.history["val_loss"], label = "valid")
plt.legend()
plt.show()

学習推移はこんな感じです。

検証データ(valid)のlossが下がって上がる傾向(過学習)にありますね。

なので”val_loss”が最小になるときのステータスを保存するようにしました。

予測

from sklearn.metrics import accuracy_score, precision_score, recall_score

model.load_weights("best_weight.h5")
pred = model.predict(X_valid, verbose = 1).argmax(axis = 1)
truth = y_valid.numpy().argmax(axis = 1)

accuracy = accuracy_score(truth, pred)
precision = precision_score(truth, pred, average = "macro")
recall = recall_score(truth, pred, average = "macro")
print("ACC:", accuracy.round(3), "PRE:", precision.round(3), "REC:", recall.round(3))

predictで予測できます。

検証データで最もスコアの良かったステータスを読み込みましょう。

10列の予測結果になっており、各行において最も大きい値の列番号が予測するラベルです。

なのでargmax(axis = 1)で最大値の列番号をとるようにしました。

accuracy:正解率
precision:適合率
recall:再現率

以上の評価指標で、それぞれ0~1の値をとります。

だいたい83%くらいのはずです。

改善例

いくつか改善を試してみましょう。

乱数固定

まず乱数で結果が変わるので、改善が効いているか確認するために乱数を固定します。

seed = 0
np.random.seed(seed)
tf.random.set_seed(seed)

たぶんシャッフルとか固定できていないのでぴったり同じ値にはなりませんが、だいたい固定できます。

82%くらいがベースラインです。

augmentation

画像モデルでよくある改善例の1つにaugmentationがあります。

同じ画像でも水平に反転させたり色を変えたりして、データの水増しをする方法です。

def augmentation(image, label):
    image = tf.image.random_flip_left_right(image)
    return image, label

plt.figure(figsize = (30, 5))
for i in range(5):
    plt.subplot(1, 5, i + 1)
    plt.imshow(augmentation(X_train[0], _)[0])
plt.show()

random_flip_left_rightで水平方向にランダムに反転できます。

上図だと右を向いているカエルと左を向いているカエルがいますね。

例えばデータセットに右を向いているカエルしかない場合、左向きのカエルを分類する能力が弱いモデルになってしまいます。

どっちを向いていてもカエルなので、それを教えるためにaugmentationする感じです。

train_dataset = train_dataset.map(augmentation)

mapで学習データにだけ適用しましょう。検証データには不要です。

こんな感じです。良くなっていますね。

他にも画像を回転させたり、一部を欠損させたり、色を変えたりと色々なaugmentationがあります。

scheduler

learning_rateを調整するスケジューラーを設定します。

checkpoint = tf.keras.callbacks.ModelCheckpoint(
    "best_weight.h5",
    monitor = "val_loss",
    direction = "min",
    save_best_only = True,
    save_weights_only = True
)

epochs = 10

scheduler = tf.keras.optimizers.schedules.CosineDecay(
    initial_learning_rate = 0.001, decay_steps = epochs,
)

history = model.fit(
    train_dataset,
    validation_data = valid_dataset,
    epochs = epochs,
    callbacks = [
        checkpoint,
        tf.keras.callbacks.LearningRateScheduler(scheduler)
    ]
)

CosineDecayはコサイン関数に従ってlearning_rateを減少させます。

なので1epochで最大の0.001、10epochで最小の0となります。

徐々にlearning_rateを下げることで極小値にたどり着きやすくする手法です。

こんな感じです。85%まで上がりましたね。

まとめ

今回はTensorFlowで画像分類モデルを作成しました。

モデル自体はEfficientNetをダウンロードするだけなので簡単です。

コメント

タイトルとURLをコピーしました