[DeepLearning3] AIモデルのカスタマイズを味わう

投稿日:

更新日:

カテゴリ:

ディープラーニング実践ガイド」の3章は移転学習についての補足説明です。

ディープラーニングがここまで流行している理由は以下の4つと挙げられてますが、一番の理由は4番目の訓練済みモデルの再利用、つまり移転学習だと言われています。

  1. ImageNetのような大規模で高品質なデータセットの出現
  2. 計算機環境の向上(高速で安価なGPUなど)
  3. モデルのアーキテクチャー、オプティマイザー、訓練手法などでのアルゴリズムの改良
  4. 数ヶ月かけて訓練したモデルを迅速に再利用可能であること

ここではいよいよデータセットを用意してモデルを改造することになりますね!AI開発している感が出てきてワクワクです!

データセットの準備

現時点では、本の資材で記載されている「downloaded dataset」の下記リンクは切れてます。

https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition/download/train.zip

探したところ、データは見つかりましたが、直リンク貼れなかったので、少し紹介します。

まず「Dogs vs. Cats Redux: Kernels Edition」にアクセスします。アカウントが必要ですので、未登録の場合は登録しましょう。

ログインできましたら、上のリンクを踏むと次の画面に行けるはずです。

上のように、右下の「Data Explorer」から「train.zip」をクリックするとダウンロードできます。

 

あとはデータセットを訓練用と検証用に分ける作業ですが、本ではLinux/Macのコマンドで紹介されています。その通りにやればいけます。

Windowsではどうするだろうか、docker環境であればjupyter labのターミナルでいけるかもしれませんが、もしお困りでしたらコメントください。少し試せるかもしれません。

ソース修正と実行

Practicode/chapter-3/1-keras-custom-classifier-with-transfer-learning.ipynb」を実行して行きますが、ソースと本の記載に少し食い違いがあり、2箇所ファイルパスの変更が必要です。

  1. 次のパス指定ありましたが、dataフォルダがあり、これをipynbファイルと同じ階層に配置することを考えると、本の記述が正しいと思います。
    TRAIN_DATA_DIR = 'train/'
    VALIDATION_DATA_DIR = 'val/'

    次のようにパスを直してあげれば大丈夫のはずです。

    TRAIN_DATA_DIR = 'data/train/'
    VALIDATION_DATA_DIR = 'data/val/'
  2. このパスの階層は戻りすぎてます。
    img_path = '../../../sample-images/dog.jpg'
    

    次のようにパスを直してあげれば大丈夫のはずです。

    img_path = '../../sample-images/dog.jpg'
    

そうしましたら、次のように実行結果を確認できるではないかと思います。

途中モデルの訓練は次のようになります。
「loss」は損失関数の値、「acc」は精度、「val_」付けられるものは検証の意味であるはずです。
最も注目すべき「val_acc」になるそうで、これは検証精度のはずです。

    Epoch 1/10
    8/8 [==============================] - 7s 867ms/step - loss: 0.6492 - acc: 0.7120 - val_loss: 0.1536 - val_acc: 0.9480
    Epoch 2/10
    8/8 [==============================] - 6s 791ms/step - loss: 0.2528 - acc: 0.8880 - val_loss: 0.1102 - val_acc: 0.9640
    Epoch 3/10
    8/8 [==============================] - 6s 790ms/step - loss: 0.1719 - acc: 0.9300 - val_loss: 0.0851 - val_acc: 0.9720
    Epoch 4/10
    8/8 [==============================] - 6s 818ms/step - loss: 0.1245 - acc: 0.9500 - val_loss: 0.0737 - val_acc: 0.9740
    Epoch 5/10
    8/8 [==============================] - 7s 856ms/step - loss: 0.0980 - acc: 0.9600 - val_loss: 0.0688 - val_acc: 0.9720
    Epoch 6/10
    8/8 [==============================] - 6s 822ms/step - loss: 0.0871 - acc: 0.9680 - val_loss: 0.0663 - val_acc: 0.9740
    Epoch 7/10
    8/8 [==============================] - 6s 800ms/step - loss: 0.0576 - acc: 0.9860 - val_loss: 0.0663 - val_acc: 0.9760
    Epoch 8/10
    8/8 [==============================] - 6s 805ms/step - loss: 0.0720 - acc: 0.9740 - val_loss: 0.0692 - val_acc: 0.9740
    Epoch 9/10
    8/8 [==============================] - 6s 849ms/step - loss: 0.0594 - acc: 0.9760 - val_loss: 0.0653 - val_acc: 0.9740
    Epoch 10/10
    8/8 [==============================] - 6s 817ms/step - loss: 0.0528 - acc: 0.9860 - val_loss: 0.0686 - val_acc: 0.9740

本では、この検証精度は回数に重なって上がっていきましたが、私の環境では、3回目でもう97%を超えており、そこからあまり変わってないです。
これは、今のモデルではこれぐらいのデータは3回ぐらい訓練すれば済む話だよという意味ですかね?!

結果は次のような感じで、96%の確率でこの画像は犬だと言ってますかね。

    1/1 [==============================] - 0s 161ms/step
    [[0.03877336 0.9612267 ]]
    {'cat': 0, 'dog': 1}

スクショには赤い部分もあるかと思いますが、H5の形式が古かったり、M1/M2だとAdamは遅いよだとかです。
とりあえず動作確認には支障がないようですので、とりあえずスルーです。

You are saving your model as an HDF5 file via `model.save()`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save(‘my_model.keras’)`.

the v2.11+ optimizer `tf.keras.optimizers.Adam` runs slowly on M1/M2 Macs

ソース解説

import tensorflow as tf
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Flatten, Dense, Dropout, GlobalAveragePooling2D
from tensorflow.keras.applications.mobilenet import MobileNet, preprocess_input
import math

TRAIN_DATA_DIR = 'data/train/'
VALIDATION_DATA_DIR = 'data/val/'
TRAIN_SAMPLES = 500
VALIDATION_SAMPLES = 500
NUM_CLASSES = 2
IMG_WIDTH, IMG_HEIGHT = 224, 224
BATCH_SIZE = 64

# 訓練用データを拡張
# 回転、ランダムシフト、ズームすることで画像データを増やします。
train_datagen = ImageDataGenerator(preprocessing_function=preprocess_input,
                                   rotation_range=20,
                                   width_shift_range=0.2,
                                   height_shift_range=0.2,
                                   zoom_range=0.2)
# 検証用データの準備、拡張はしない
val_datagen = ImageDataGenerator(preprocessing_function=preprocess_input)

# 訓練用データのジェネレーターを生成します。
# 順番はシャッフルですが、何回実行しても同じ結果得られるようにseedを指定されてます。
train_generator = train_datagen.flow_from_directory(TRAIN_DATA_DIR,
                                                    target_size=(IMG_WIDTH,
                                                                 IMG_HEIGHT),
                                                    batch_size=BATCH_SIZE,
                                                    shuffle=True,
                                                    seed=12345,
                                                    class_mode='categorical')
#検証用データのジェネレーター
validation_generator = val_datagen.flow_from_directory(
    VALIDATION_DATA_DIR,
    target_size=(IMG_WIDTH, IMG_HEIGHT),
    batch_size=BATCH_SIZE,
    shuffle=False,
    class_mode='categorical')

def model_maker():
    # 高速に実行できるようにMobileNetを利用
    # 他の任意のニューラルネットワークについても適応できるはず
    base_model = MobileNet(include_top=False,
                           input_shape=(IMG_WIDTH, IMG_HEIGHT, 3))

    # すべての層を変更不可にする
    for layer in base_model.layers[:]:
        layer.trainable = False
    input = Input(shape=(IMG_WIDTH, IMG_HEIGHT, 3))
    custom_model = base_model(input)

    # パラメーター数を減らす ※参考1
    custom_model = GlobalAveragePooling2D()(custom_model)

    # ノード数と活性化関数指定 ※参考2
    custom_model = Dense(64, activation='relu')(custom_model)

    # 過学習を防ぐため、ランダムで50%の出力を0にする ※参考3,4
    custom_model = Dropout(0.5)(custom_model)
    predictions = Dense(NUM_CLASSES, activation='softmax')(custom_model)

    # モデル生成 ※参考5
    return Model(inputs=input, outputs=predictions)

model = model_maker()

# 損失関数、オプティマイザー(学習率)、指標を指定
model.compile(loss='categorical_crossentropy',
              optimizer=tf.keras.optimizers.Adam(0.001),
              metrics=['acc'])

# モデルを訓練
model.fit_generator(
    train_generator,
    steps_per_epoch=math.ceil(float(TRAIN_SAMPLES) / BATCH_SIZE),
    epochs=10, # データセット全体に対する訓練回数を10回として指定
    validation_data=validation_generator,
    validation_steps=math.ceil(float(VALIDATION_SAMPLES) / BATCH_SIZE))

model.save('model.h5')


# 訓練したモデルをテスト
from tensorflow.keras.models import load_model
from tensorflow.keras.preprocessing import image
import numpy as np

model = load_model('model.h5')

img_path = '../../sample-images/dog.jpg'
img = image.load_img(img_path, target_size=(224, 224))
img_array = image.img_to_array(img)
expanded_img_array = np.expand_dims(img_array, axis=0)
preprocessed_img = expanded_img_array / 255.  # Preprocess the image
prediction = model.predict(preprocessed_img)
print(prediction)
# ラベルと番号を出力 ※参考6
print(validation_generator.class_indices)

延長

サンプル画像には猫の画像もあったかと思います。それを次のように試しました。

img_path = '../../sample-images/cat.jpg'
img = image.load_img(img_path, target_size=(224, 224))
img_array = image.img_to_array(img)
expanded_img_array = np.expand_dims(img_array, axis=0)
preprocessed_img = expanded_img_array / 255.  # Preprocess the image
prediction = model.predict(preprocessed_img)
print(prediction)
print(validation_generator.class_indices)

# ※参考6
label_dict = validation_generator.class_indices
#最大の要素のインデックスを取得
a = np.argmax(prediction, axis=1)[0]
#インデックスから、ラベルを取得
keys = [k for k, v in label_dict.items() if v == a]
print(keys)

実行結果は次のようになります。

1/1 [==============================] - 0s 39ms/step
[[9.9995565e-01 4.4392778e-05]]
['cat']

問題なく猫だと判定できたようですね!

ちなみに、上記のよく分からないクラスを調べてみると、なんだかクラスごとに論文が1つ紐づいてる感じがします。
これまでにはこんな事ありましたっけ?コンピューターサイエンスってすごいことになってますね!

参考

  1. Github – ゼロから作るDeep Learningとともに学ぶフレームワーク(畳み込みニューラルネットワーク編)
  2. Qiita – ニューラルネットを作る
  3. Keras – Dropout layer
  4. Medium – Dropoutによる過学習の抑制
  5. TensorFlow – モデル全体の保存と読み込み
  6. betashort-lab – Keras-画像のラベルと番号を確認する方法

投稿日

カテゴリー:

投稿者:

タグ:

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です