#はじめに
本記事では,ファインチューニングを使ったCNNのコード(train)を掲載し,簡単な解説をします.
仮想環境の構築やライブラリのインストール方法に関しては割愛しますので,ご了承ください.
#本記事を書いた経緯
人工知能がTVや各種メディアに取り上げられることからも,AIがより身近なものになってきています.
大学の研究でAI(画像認識)をすることになる学生もますます増えていることかと思います.しかしながら,周りに頼れる人や先輩がおらず,苦労する大学生や大学院生が発生しているのではないかと考え,本記事を書きました.
そういった方々(もちろんそうでない方々も)の助けになれば幸いです.
#ファインチューニングについて
ファインチューニングとは,すでに学習され,重みが与えられている既存のモデルをベースにして新たなモデルを構築することで,学習に使用するデータ数が少なくても,適切な学習が行えるというものです.CNNの場合,通常,学習に使用するデータ数は数千,数万のデータが必要であるといわれています.一方,ファインチューニングをすることで学習に使用するデータ数を減らすことができ,数百個のデータで学習を行っている研究もあります.
#開発環境
- Windows10
- Python 3.7.7
- TensorFlow 1.14.0
- keras 2.3.1
- numpy 1.16.4
- matplotlib 3.1.3
- hdf5 1.8.20
Anacondaでroot環境をコピーして,tensorflow,kerasあたりを入れて作りました.
root環境だとnumpyのverが1.17とかなので,バージョンをダウンさせてます.
一応TensorFlowとKeras両方を入れています.
※仮想環境の構築は以下の記事が参考になるかと
https://qiita.com/ozaki_physics/items/13466d6d1954a0afeb3b
#CNNのコード
from PIL import Image
import os, glob
import numpy as np
import random, math
#画像が保存されているルートディレクトリのパス
#root_dir = "パス"
root_dir = "./image"
#分類する種別ごとにフォルダーを分ける。モデルの最終出力もここで設定したフォルダーの数に合わせる
categories = ["dog","rabbit","marmot"]
# 画像データ用配列
X = []
# ラベルデータ用配列
Y = []
#画像データごとにadd_sample()を呼び出し、X,Yの配列を返す関数
def make_sample(files):
global X, Y
X = []
Y = []
for cat, fname in files:
add_sample(cat, fname)
return np.array(X), np.array(Y)
#渡された画像データを読み込んでXに格納し、また、
#画像データに対応するcategoriesのidxをY格納する関数
def add_sample(cat, fname):
img = Image.open(fname)
img = img.convert("RGB")
img = img.resize((224, 224))
data = np.asarray(img)
X.append(data)
Y.append(cat)
#全データ格納用配列
allfiles = []
#カテゴリ配列の各値と、それに対応するidxを認識し、全データをallfilesにまとめる
for idx, cat in enumerate(categories):
image_dir = root_dir + "/" + cat
files = glob.glob(image_dir + "/*.jpg")
for f in files:
allfiles.append((idx, f))
#シャッフル後、学習データと検証データに分ける
random.shuffle(allfiles)
th = math.floor(len(allfiles) * 0.9)
train = allfiles[0:th]
test = allfiles[th:]
X_train, y_train = make_sample(train)
X_test, y_test = make_sample(test)
xy = (X_train, X_test, y_train, y_test)
np.save("image_data", xy)
#モデルの構築
from keras import layers, models
from keras.applications import VGG16
#転移元のモデルとしてVGG16を使用
conv_base = VGG16(weights='imagenet',
include_top=False,
input_shape=(224, 224, 3))
model = models.Sequential()
model.add(conv_base)
model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
#model.add(layers.Dense(3,activation="sigmoid")) #分類先の種類分設定,分類数に応じて数字変更
model.add(layers.Dense(3, activation='softmax'))
#モデル構成の確認
model.summary()
#モデルのコンパイル
from keras import optimizers
#model.compile(loss="binary_crossentropy",バイナリは二値分類用らしい
model.compile(loss="categorical_crossentropy",
optimizer=optimizers.RMSprop(lr=1e-4),
metrics=["acc"])
#データの準備
from keras.utils import np_utils
import numpy as np
categories = ["dog","rabbit","marmot"]
nb_classes = len(categories)
#X_train, X_test, y_train, y_test = np.load("保存した学習データ・テストデータのパス")
X_train, X_test, y_train, y_test = np.load("image_data.npy",allow_pickle=True)
#データの正規化
X_train = X_train.astype("float") / 255
X_test = X_test.astype("float") / 255
#kerasで扱えるようにcategoriesをベクトルに変換
y_train = np_utils.to_categorical(y_train, nb_classes)
y_test = np_utils.to_categorical(y_test, nb_classes)
#モデルの学習
model = model.fit(X_train,
y_train,
epochs=50,
batch_size=16,
validation_data=(X_test,y_test))
#学習結果を表示
import matplotlib.pyplot as plt
acc = model.history['acc']
val_acc = model.history['val_acc']
loss = model.history['loss']
val_loss = model.history['val_loss']
epochs = range(len(acc))
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
#plt.savefig('精度を示すグラフのファイル名')
plt.savefig('cnn_accuracy')
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
#plt.savefig('損失値を示すグラフのファイル名')
plt.savefig('cnn_loss')
#モデルの保存
json_string = model.model.to_json()
open('animal_model.json', 'w').write(json_string)
#重みの保存
hdf5_file = "animal_weight.hdf5"
model.model.save_weights(hdf5_file)
print('Finish!')
#コードの解説
ちょっと長くなりますが,上から順に簡単な説明をしていきたいと思います.
コードがわかっている方は読み飛ばしてしまって全く問題ないです!
from PIL import Image
import os, glob
import numpy as np
import random, math
#画像が保存されているルートディレクトリのパス
#root_dir = "パス"
root_dir = "./image"
#分類する種別ごとにフォルダーを分ける。モデルの最終出力もここで設定したフォルダーの数に合わせる
categories = ["dog","rabbit","marmot"]
importでは,本コードにて使用するライブラリーの指定をしています.できるだけimportはまとめた方が見やすいです(このコードではまとめてません).
root_dirは画像を読み込む場所を指定しています.categoriesは分類するものを指定してます.ここではdogとrabbitとmarmotの画像を分類するようにしてます.
# 画像データ用配列
X = []
# ラベルデータ用配列
Y = []
#画像データごとにadd_sample()を呼び出し、X,Yの配列を返す関数
def make_sample(files):
global X, Y
X = []
Y = []
for cat, fname in files:
add_sample(cat, fname)
return np.array(X), np.array(Y)
#渡された画像データを読み込んでXに格納し、また、
#画像データに対応するcategoriesのidxをY格納する関数
def add_sample(cat, fname):
img = Image.open(fname)
img = img.convert("RGB")
img = img.resize((224, 224))
data = np.asarray(img)
X.append(data)
Y.append(cat)
#全データ格納用配列
allfiles = []
#カテゴリ配列の各値と、それに対応するidxを認識し、全データをallfilesにまとめる
for idx, cat in enumerate(categories):
image_dir = root_dir + "/" + cat
files = glob.glob(image_dir + "/*.jpg")
for f in files:
allfiles.append((idx, f))
#シャッフル後、学習データと検証データに分ける
random.shuffle(allfiles)
th = math.floor(len(allfiles) * 0.9)
train = allfiles[0:th]
test = allfiles[th:]
X_train, y_train = make_sample(train)
X_test, y_test = make_sample(test)
xy = (X_train, X_test, y_train, y_test)
np.save("image_data", xy)
ここの処理については,簡単に言うと,画像データをXに格納し,教師値をYに格納,そのあと学習データと検証データに分けてます.
教師値の与え方は各フォルダーごとに与えています.dogは[1,0,0],rabbitは[0,1,0],marmotは[0,0,1]といった具合です.同じフォルダーに入れた画像には,同じ教師値が与えられます.フォルダーの場所等に関しては後述の”使い方”で説明します.
学習データと検証データの比は9:1です.コード内の数値"0.9"を変えることで,好きな比にできます.
その後,image_dataという名前で保存しています.このファイルは後で読み込みます.
#モデルの構築
from keras import layers, models
from keras.applications import VGG16
#転移元のモデルとしてVGG16を使用
conv_base = VGG16(weights='imagenet',
include_top=False,
input_shape=(224, 224, 3))
model = models.Sequential()
model.add(conv_base)
model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
#model.add(layers.Dense(3,activation="sigmoid")) #分類先の種類分設定,分類数に応じて数字変更
model.add(layers.Dense(3, activation='softmax'))
#モデル構成の確認
model.summary()
#モデルのコンパイル
from keras import optimizers
#model.compile(loss="binary_crossentropy",バイナリは二値分類用らしい
model.compile(loss="categorical_crossentropy",
optimizer=optimizers.RMSprop(lr=1e-4),
metrics=["acc"])
続いてモデルの構築+コンパイルです.
今回はImageNetで学習をしたVGG16を既存モデルとして使用してます.
活性化関数がsoftmaxの層は出力層です.出力する数は分類の個数と同じにする必要があります.
今回はdog, rabbit, marmotの3分類なので3です.
モデルのコンパイルでは損失関数などを指定します.今回は3分類なので損失関数はcategolical…です.
最適化関数は適当です.
#データの準備
from keras.utils import np_utils
import numpy as np
categories = ["dog","rabbit","marmot"]
nb_classes = len(categories)
#X_train, X_test, y_train, y_test = np.load("保存した学習データ・テストデータのパス")
X_train, X_test, y_train, y_test = np.load("image_data.npy",allow_pickle=True)
#データの正規化
X_train = X_train.astype("float") / 255
X_test = X_test.astype("float") / 255
#kerasで扱えるようにcategoriesをベクトルに変換
y_train = np_utils.to_categorical(y_train, nb_classes)
y_test = np_utils.to_categorical(y_test, nb_classes)
#モデルの学習
model = model.fit(X_train,
y_train,
epochs=50,
batch_size=16,
validation_data=(X_test,y_test))
データの準備では,さっき保存したimage_dataを読み込みます.
その後model.fitでモデルの学習を行います.エポック数やバッチサイズは変更可能です.
学習しているときは,その経過が表示されます(〇〇%まで学習中みたいな).
#学習結果を表示
import matplotlib.pyplot as plt
acc = model.history['acc']
val_acc = model.history['val_acc']
loss = model.history['loss']
val_loss = model.history['val_loss']
epochs = range(len(acc))
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
#plt.savefig('精度を示すグラフのファイル名')
plt.savefig('cnn_accuracy')
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
#plt.savefig('損失値を示すグラフのファイル名')
plt.savefig('cnn_loss')
#モデルの保存
json_string = model.model.to_json()
open('animal_model.json', 'w').write(json_string)
#重みの保存
hdf5_file = "animal_weight.hdf5"
model.model.save_weights(hdf5_file)
print('Finish!')
最後に正解率の変動がcnn_accuracyというファイル名,損失関数の値の変動がcnn_lossというファイル名で保存され,表示されます.
不要なら消してしまっても構いません.
学習が完了したモデルがanimal_model.json,重みがanimal_weight.hdf5というファイル名で保存されます.
こちらはpredictする際に読み込むことになるかと思います.
本記事に示したコードを入れたファイルと同じフォルダーに"image"という名前のフォルダーを置き,その中にdog, rabbit, marmotのフォルダーを入れてさらにその中に画像を入れるといった具合です.
学習を終えると以下の図のようになります.
コードのファイルがあるフォルダー内に,image_dataやれweightやれいろいろなファイルが保存されます.
ファイルがこういう配置になっているのは,ファイルの保存・読み込む場所のパスをほとんど指定してないからです(学習に使う画像のみファイルを指定してます).理由は私がパスの書き方について"./"ぐらいしかわかってないからです.
普通はこんなにごちゃごちゃにはならないです!
#おわりに
CNNでファインチューニングするコードについて,書かせていただきました.
少しでも参考になったのなら幸いです.
気が向いたらpredictのコードも書きたいと思います.
といっても,predictはモデルと重みと画像を読み込むだけですが…
#参考にしたもの
画像認識で「綾鷹を選ばせる」AIを作る
https://qiita.com/tomo_20180402/items/e8c55bdca648f4877188
データの読み込みなどコードのほとんどの部分において,この記事を参考にしました.
Keras documentation
https://keras.io/ja/
kerasの公式(?)サイトです.大変お世話になっております.