【Deeplearningでポケモン図鑑計画_その3】ポケモン×Fine Tuning(1)
はじめに
こんにちは、がんがんです。今回はついに転移学習を行っていきます。
一気に151種類行うのは大変だったので、まずはフシギダネからライチュウまでの26種類で実験を行いました。今回はそのまとめです。
前回記事はこちらです。
gangannikki.hatenadiary.jp
転移学習とは
大規模な画像データセットを用いて学習したモデルを利用して新しいモデルを構築していく手法です。
私はkerasを使用しており、kerasには多くの事前学習済みモデルが用意されています。
詳しい種類については、以下の日本語用ドキュメントを参照ください。
Applications - Keras Documentation
まずは、転移学習とFine Tuningについてどう違うのか気になったので調べてみました。
すると、こちらの方がまとめられていました。
こちらから引用させていただくと、転移学習とFine Tuningはこのような違いがあるようです。
- 転移学習:既存の学習済モデル(出力層以外の部分)を、重みデータは変更せずに特徴量抽出機として利用する。
- ファインチューニング:既存の学習済モデル(出力層以外の部分)を、重みデータを一部再学習して特徴量抽出機として利用する。
ということみたいです。
今回の場合はimagenetの重みをそのまま使用するため、厳密には転移学習にあたるようです。
参考記事
以下に参考記事をまとめておきます。上記にあるkerasのドキュメントは非常に参考になりますので、そちらも参考にしてみて下さい。
今回は主にkeras関連の記事を載せていますが、Chainer、PyTorchでも多くの記事があるので自身に合うものを調べてみてください。
実験
実験1
まずは現在作っているモデルにおいて実験を行いました。すると、驚くほどに精度が低くなりました。
検証結果を保存し忘れてしまいましたが、正解のクラスはe-6くらいになり、それ以外はe-10くらいなので一応分類はできている(?)ようです。
それにしても精度が驚くべき低さです。
モデルが間違っているのか、それとも学習データの質が悪いのかを調べることにしました。
まずはモデルについて検証しました。検証についてはこちらの記事にまとめています。
検証の結果、モデルがきちんと完成していなかったという結論に至りました。無事に検証が上手くいってよかったです。
実験2
実験2では、ベンチマークを用いた検証により修正したプログラムを用いて再度実験を行います。実験の結果は以下に示します。
val_loss
とval_acc
は50epochを過ぎたあたりからほぼ変化してません。実際の予測結果はこちらです。
フシギダネ、トランセルはしっかりと結果が出ています。ゼニガメも低いですがまあ精度が出てるのではと思います。
ちょっと予測精度が気になったので26種類すべてのポケモンで試してみました。以下は失敗してた結果です。
すごいぐらいカメックスが認識されています。26種類中15種類しかきちんと認識されていませんでした…
名前 | 確率 |
---|---|
フシギダネ | 99% |
ゼニガメ | 69% |
カメール | 99% |
カメックス | 98% |
キャタピー | 99% |
トランセル | 98% |
バタフリー | 99% |
ビードル | 98% |
ピジョン | 87% |
ピジョット | 59% |
コラッタ | 80% |
ラッタ | 88% |
オニドリル | 96% |
アーボ | 99% |
アーボック | 99% |
コード
使用言語はPython 3.5です。深層学習用のフレームワークはKeras 2.1.5(backendはTensorFlow)で使用しています。OSはUbuntu 16.04 LTSです。
Fine_Tuning_vgg16.py
#------------------------------------------------------------ # # Fine_Tuning_vgg16.py # 転移学習を行うプログラム(VGG16) # #------------------------------------------------------------ import math import numpy as np import matplotlib.pyplot as plt from pathlib import Path from keras.models import Sequential from keras.layers import Dense, Dropout, Flatten from keras.optimizers import SGD from keras.applications.vgg16 import VGG16, preprocess_input from keras.callbacks import CSVLogger, EarlyStopping, TensorBoard, ModelCheckpoint from keras.preprocessing.image import ImageDataGenerator #from keras.backend import # SGDのパラメータ lr = 1e-4 momentum = 0.9 decay = 1e-9 nesterov = False """ モデル(VGG16の転移学習) """ class VGG16_Transfer(object): def __init__(self, args, classes): self.train_dir = args.train_dir self.validation_dir = args.validation_dir self.batch_size = args.batch_size self.epochs = args.epochs self.input_shape = ( args.img_size, args.img_size, 3 ) self.classes = classes # VGG16の読み込み vgg16 = VGG16(include_top=False, weights='imagenet', input_shape=self.input_shape) #vgg16.summary() # モデル定義 self.model = self.build_transfer_model(vgg16) # モデル構築 self.model.compile( optimizer=SGD(lr=lr, momentum=momentum, nesterov=nesterov), loss='categorical_crossentropy', metrics=["accuracy"] ) # モデルの表示 print("Model Summary------------------") self.model.summary() print(self.classes) """ モデル構築 """ def build_transfer_model( self, vgg16 ): model = Sequential(vgg16.layers) # 再学習しないように指定 for layer in model.layers[:15]: layer.trainable = False # FC層を構築 model.add(Flatten()) model.add(Dense(256, activation="relu")) model.add(Dropout(0.5)) model.add(Dense(len(self.classes), activation="softmax")) return model """ モデル学習 """ def training( self ): # 学習データ train_datagen = ImageDataGenerator( rescale=1/255., rotation_range=90, # 画像をランダムに回転 shear_range=0.1, # せん断する zoom_range=0.1, # ランダムにズーム horizontal_flip=True, # 水平方向にランダム反転 vertical_flip=True, # 垂直方向にランダム反転 preprocessing_function=self.preprocess_input ) train_generator = train_datagen.flow_from_directory( self.train_dir, target_size=self.input_shape[:2], classes=self.classes # batch_size=self.batch_size ) # 検証データ test_datagen = ImageDataGenerator( rescale=1/255., # rotation_range=90, # 画像をランダムに回転 # shear_range=0.1, # せん断する # zoom_range=0.1, # ランダムにズーム horizontal_flip=True, # 水平咆哮にランダム反転 vertical_flip=True, # 垂直方向にランダム反転 preprocessing_function=self.preprocess_input ) validation_generator = test_datagen.flow_from_directory( self.validation_dir, target_size=self.input_shape[:2], classes=self.classes # batch_size=self.batch_size ) # steps_per_epoch, validation_stepsの算出 steps_per_epoch = math.ceil( train_generator.samples / self.batch_size ) validation_steps = math.ceil( validation_generator.samples / self.batch_size ) # callbacksの設定 csv_logger = CSVLogger("./training.log") # early_stop = EarlyStopping( monitor="val_loss", mode="auto" ) Path("./logs").mkdir(parents=True, exist_ok=True) tensor_board = TensorBoard( "./logs", histogram_freq=0, write_graph=True, write_images=True ) Path("./model").mkdir(parents=True, exist_ok=True) check_point = ModelCheckpoint( filepath='./model/model.{epoch:02d}-{val_loss:.4f}.hdf5', monitor="val_loss", save_best_only=True, # save_weights_only=True, mode="auto" ) cb = [ csv_logger, tensor_board, check_point ] # 学習 hist = self.model.fit_generator( train_generator, steps_per_epoch=steps_per_epoch, epochs=self.epochs, validation_data=validation_generator, validation_steps=validation_steps, callbacks=cb ) return hist # keras.applications.imagenet_utilsのxは4Dテンソルなので # 3Dテンソル版を作成 def preprocess_input(self,x): """Preprocesses a tensor encoding a batch of images. # Arguments x: input Numpy tensor, 3D. # Returns Preprocessed tensor. """ # 'RGB'->'BGR' x = x[:, :, ::-1] # Zero-center by mean pixel x[:, :, 0] -= 103.939 x[:, :, 1] -= 116.779 x[:, :, 2] -= 123.68 return x """ 損失,精度のグラフ描画 """ def plot_history( self, hist ): #print(history.history.keys()) # 損失の経過をプロット plt.figure() loss = hist.history['loss'] val_loss = hist.history['val_loss'] plt.plot( range(self.epochs), loss, marker='.', label='loss' ) plt.plot( range(self.epochs), val_loss, marker='.', label='val_loss' ) plt.legend( loc='best', fontsize=10 ) plt.xlabel('epoch') plt.ylabel('loss') plt.title('model loss') plt.legend( ['loss','val_loss'], loc='upper right') plt.savefig( "./model_loss.png" ) # plt.show() # 精度の経過をプロット plt.figure() acc = hist.history['acc'] val_acc = hist.history['val_acc'] plt.plot( range(self.epochs), acc, marker='.', label='acc' ) plt.plot( range(self.epochs), val_acc, marker='.', label='val_acc' ) plt.legend( loc='best', fontsize=10 ) plt.xlabel('epoch') plt.ylabel('acc') plt.title('model accuracy') plt.legend( ['acc','val_acc'], loc='lower right') plt.savefig( "./model_accuracy.png" ) # plt.show()
main.py
#------------------------------------------------------------ # # main.py # 転移学習を行うプログラム # #------------------------------------------------------------ import argparse import pandas as pd from pathlib import Path from Fine_Tuning_vgg16 import VGG16_Transfer """ メイン処理 """ if __name__ == '__main__': # parameter parser = argparse.ArgumentParser(description="Pokemon of Keras") parser.add_argument("--train_dir", default= "./image/train/") parser.add_argument("--validation_dir", default="./image/validation/") parser.add_argument("--batch_size", "-b", type=int, default=32, help="Number of images in each mini-batch") parser.add_argument("--epochs", "-e", type=int, default=100, help="Number of sweeps over the dataset to train") parser.add_argument("--img_size", "-s", type=int, default=224, help="Number of images size") args = parser.parse_args() # csvファイルを読み込み,図鑑を取り出す csv_path = Path(".") / "zukan" / "RG.csv" df = pd.read_csv(csv_path, engine="python", encoding='utf-8', header=0) # クラスを設定 classes = list(df["name"].iloc[:26]) # for i in range(len(classes)): # print(classes[i]) print(classes) # モデル md = VGG16_Transfer(args,classes) # 学習 print("Training Start------------------") hist = md.training() # グラフの表示 md.plot_history( hist )
Predict.py
#------------------------------------------------------------ # # テスト # #------------------------------------------------------------ import sys import numpy as np import pandas as pd from PIL import Image from pathlib import Path from keras.models import load_model from keras.preprocessing.image import load_img, img_to_array, array_to_img from keras.applications.vgg16 import preprocess_input, decode_predictions """ テスト """ if __name__ == '__main__': # コマンドラインより入力(後にparamに変える) if len(sys.argv) <= 1: quit() # 画像がないと終了 """ 画像の処理 """ img_path = Path("./zukan_image/") / sys.argv[1] # img_path = Path(".") / sys.argv[1] / sys.argv[2] # 画像をarrayとして読み込む img = img_to_array( load_img(img_path, target_size=( 224, 224)) ) # print(img) img_ori = array_to_img( img ) img_ori.save( "./image/test/test_before.png" ) # PILで保存 # 3次元テンソルを4次元テンソルに変換 x = np.expand_dims( img, axis=0 ) x = x / 255. """ # モデル, 重みの読み込み """ # model_pathの一番最後が最良モデル model_path = sorted(list(Path("./model/").glob("*.hdf5"))) num = len(model_path) - 1 model = load_model( model_path[num] ) model.load_weights( model_path[num] ) print("-------------------------------------------------") print("load image:{}".format(img_path)) print("model_path:{}".format(model_path[num])) print("-------------------------------------------------") """ # 予測 """ print("Predicting Start------------------") pred = model.predict(x)[0] # csvファイルを読み込み,図鑑を取り出す csv_path = Path(".") / "zukan" / "RG.csv" df = pd.read_csv(csv_path, engine="python", encoding='utf-8', header=0) # クラスを設定 classes = list(df["name"].iloc[:26]) # 予測確率が高いトップ5を出力 top = 5 top_indices = pred.argsort()[-top:][::-1] result = [(classes[i], pred[i]) for i in top_indices] for x in result: print(x)
まとめ
今回は26種類のポケモン分類を行う転移学習モデルを作成していきました。15/26種類ではありますが、きちんと分類できたようでよかったです。
今回改善できなかったエラーは学習率を変更するなどして改善していきます。pngとjpgが混じっているのが問題かな?
徐々に種類数を増やしていき、最終的には151種類の分類可能モデルを構築していきます。
また、GitHubについても登録したので徐々にプログラムを上げていきます。なかなか難しいですが慣れていきます。
いろいろ検索していたときに以下を見つけました。Androidにて図鑑の実装を試みている人の記事です。
同じように考える人がいるのはうれしいですね。
qiita.com