こんにちは。Aidemy研修生の松岡です。
プログラミング初心者で、現在勉強中です。
早速ですが、皆さんは車はお好きでしょうか?
男性の方は車好きが多い印象ですが、私はそこまで好きという訳ではなく、
正直乗れれば良いかなと思っているレベルであまり知識はないです。
たまに、街中を歩いていると、かっこいい車を目にすることがあります。
しかし、車の名前を全然知らないので、調べたくても調べることができません。
そこで、今回はCNNを使用して自動車の車種を判別するAIを作りたいと思います。
#CNN(Convolution Neural Network)
CNNとは、 画像や動画認識に広く使われているモデルで畳み込みニューラルネットワークといいます。CNNには畳み込み層やプーリング層などの生様な層から構成されています。
畳み込み層とプーリング層では入力のニューロンの一部の領域を絞り局所的に次の層へと対応付けをしていき、各層はフィルタと呼ばれる検出器をいくつも持っています。
最初の層でエッジを検出して、次の層でテクスチャを検出し、さらに次のそうではより抽象的な特徴を検出します。CNNはこういった特徴を抽出するための検出器であるフィルタのパラメータを自動で学習します。
(引用:https://deepage.net/deep_learning/2016/11/07/convolutional_neural_network.html)
「畳み込み層」は全結合層と同じように特徴の抽出を行う層ですが、全結合層とは違い2次元のままの画像データを処理し特徴の抽出が行えるため、線や角といった 2次元的な特徴 を抽出するのに優れており、以下の特徴があります。
1.畳み込み層は、元の画像にフィルタをかけて特徴マップを出力する(構成性)。
2.特徴マップのサイズは元の画像より少し小さくなる。
3.画像全体をフィルタがスライドするので、特徴がどこにあっても抽出できる。
4.フィルタは自動作成され、学習により変わってゆく(誤差逆伝搬)。
5.フィルタの数だけ特徴マップが出力される。
(引用:https://jp.mathworks.com/discovery/convolutional-neural-network.html)
「プーリング層」では、畳み込み層から得た情報を縮約し、最終的に画像の分類などを行います。
畳み込み層での計算結果を左上から順番に4ピクセルずつ抽出して、4つのピクセルの最大値を代表として選び1ピクセルとして画像にセットします。プーリングされた画像を見るときちんと元画像の特徴を持ちながら、4分の1に凝縮されていることが確認できます。このプーリングされた画像が、次の畳み込み層の入力画像になって、前の層とは別の新たなフィルタ群と比較されます。
(引用:https://products.sint.co.jp/aisia/blog/vol1-16)
#使用するデータ
データはKaggleにあるトヨタ車の画像を使用します。
https://www.kaggle.com/occultainsights/toyota-cars-over-20k-labeled-images
このデータは38種類の車の画像が保存されています。
まず実際にダウンロードして、画像を確認しました。
あーー車内の画像もありますね、、、
今回作りたいのは、外から見たときの車を判別するモデルなので、車内の画像は取り除かないといけないです。
しかし、1枚1枚手作業で行うと、かなりめんどくさいですね。ざっと2万枚程ありそうです。
なので、機械学習を使用して車内の画像を取り除くことにしました。
画像分別機にもCNNを使用します。
#実行環境
Google Colaboratory
Python 3.7.2
Keras 2.4.3
numpy 1.19.5
tensorflow 2.4.1
#車内判別器の作成
車内画像分別器を作るために車内の画像が必要です。そこでKaggleにある車内のデータセットを使います。
https://www.kaggle.com/igorkovr/car-interiors
こちらのデータ数は70とかなり少ないです。
そこで、アフィン変換などを行うことで、データの水増しを行ってからCNNを実装します。
CNNのネットワーク構造はこちらのサイトを参考に作成しました。
以下がCNNのプログラムになります。
#車内判別
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras.utils import np_utils
import numpy as np
import keras
import matplotlib.pyplot as plt
classes = ['車内','車']
num_classes = len(classes)
image_size = 64
#メインの関数を定義する
def main():
X_train,X_test,y_train,y_test = np.load("./car-hanbetu.npy", allow_pickle=True)
#画像ファイルの正規化
X_train = X_train.astype('float') / 255
X_test = X_test.astype('float') / 255
y_train = np_utils.to_categorical(y_train,num_classes)
y_test = np_utils.to_categorical(y_test,num_classes)
model = model_train(X_train,y_train,X_test,y_test)
model_eval(model,X_test,y_test)
def model_train(X_train,y_train,X_test,y_test):
model = Sequential()
model.add(Conv2D(32,(3,3), padding='same',input_shape=X_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(32,(3,3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.25))
model.add(Conv2D(64,(3,3),padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(64,(3,3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(2))
model.add(Activation('softmax'))
#最適化の手法
opt = keras.optimizers.RMSprop(lr=0.00005,decay=1e-6)
#モデルのコンパイル
model.compile(loss='categorical_crossentropy',
optimizer=opt,metrics=['accuracy'])
#historyに['val_loss', 'val_acc', 'loss', 'acc']を保存
history = model.fit(X_train, y_train, batch_size=32,epochs=40,validation_data=(X_test,y_test))
#モデルの保存
model.save("./car-hanbetu.h5")
#学習曲線の可視化
graph_general(history)
return model
def model_eval(model,X_test,y_test):
scores = model.evaluate(X_test,y_test,verbose=1)
print('Test Loss: ', scores[0])
print('Test Accuracy: ', scores[1])
def graph_general(history):
# Plot training & validation accuracy values
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('Model accuracy')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Train_acc', 'Test_acc'], loc='upper left')
plt.show()
# Plot training & validation loss values
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Model loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Train_loss', 'Test_loss'], loc='upper left')
plt.show()
if __name__ == '__main__':
main()
こちらが学習曲線になります。
Accuracy | Loss |
---|---|
Test Accuracyが0.990とかなり高い値であり、十分判別できるモデルであることが確認できます。 | |
では作成したモデルを使用して、車内画像かを判別し、車内画像であれば削除するプログラムを実装します。 |
#車内判別
from keras.models import load_model
import matplotlib.pyplot as plt
from PIL import Image
import os,glob
import numpy as np
from sklearn import model_selection
img_size = 64
model_param = "./car-hanbetu.h5"
classes = ['4runner','camry','corolla','highlander','prius','rav4','sienna','tacoma','yaris']
model = load_model(model_param)
#それぞれのファイルごとにループさせる
for index, class_ in enumerate(classes):
photos_dir = "./車/" + class_
#jpg形式の画像データを保存
files = glob.glob(photos_dir + '/*.jpg')
for i, file in enumerate(files):
img = Image.open(file)
img = img.convert('RGB')
#画像データを64 x 64に変換
img = img.resize((img_size, img_size))
# 画像データをnumpy配列に変換
img = np.asarray(img)
img = img / 255.0
pred = model.predict(np.array([img]))
print(round(pred[0][0] / 1.0, 3) *100 , "%", round(pred[0][1] / 1.0, 3) *100 , "%" )
ans = np.argmax(pred, axis=1)
#車外か車内かを判別
if ans == 0:
print(">>> 車")
elif ans == 1:
print(">>> 車内なので削除")
os.remove(file)
実際に以下の2枚の画像を入れてみましょう。
車の画像 | 車内の画像 |
---|---|
実行結果は以下になります。
99.7 % 0.3 %
>>> 車
6.5 % 93.5 %
>>> 車内なので削除
ちゃんと判別できていますね。
このコードを実行することで、元データから車内画像を排除することができました。
では、データを整えたので車種を判別するAIを実装していきます。
#車種を判別するCNNの実装
####ラベル2個
まずはラベルを2個にして行います。
使用するのは「4runner」と「prius」です。
データ数は「4runner」が718、「prius」が838となっています。
CNNのネットワーク構造については、上記で使用したものと変わりませんので、コードは省略します。
こちらが学習曲線になります。
Accuracy | Loss |
---|---|
あまり精度がよろしくないですね。 | |
やはり車の形を見極めるにはもう少しデータが必要なようです。 |
そこで、アフィン変換などを行ってデータの水増しします。
こちらが水増しを行ったデータになります。
データ数は「4runner」が13623、「prius」が15827になりました。
このデータを使用してCNNを行った結果が以下になります。
Accuracy | Loss |
---|---|
だいぶ改善されましたね! | |
Test Accuracyが0.978でした。 | |
ここまで精度が高ければ問題なさそうです。 |
しかし、これは2つの車種の分類です。
日本車だけでもたくさんの車種があるので、2つを分類するモデルなんてあまり実用的ではないですね。そこでこれからラベルをさらに増やします。
####ラベル9個
ラベルは先ほど使用した「4runner」と「prius」に、
「camry」「corolla」「highlander」「rav4」「sienna」「tacoma」「yaris」
を追加した合計9個です。
ネットワーク構造は最後の全結合層の出力を9に変更しただけです。
def model_train(X_train,y_train,X_test,y_test):
model = Sequential()
model.add(Conv2D(32,(3,3), padding='same',input_shape=X_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(32,(3,3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.25))
model.add(Conv2D(64,(3,3),padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(64,(3,3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(9))
model.add(Activation('softmax'))
これが実際に行った結果です。
Accuracy | Loss |
---|---|
|
Test Accuracyは0.869でした。やはり、2個で行った時よりかなり精度が落ちてしまいました。
理由としては、車にはバージョンやクラスがあり、同じラベルに所属していてもかなり形が変わっているものがあります。以下は今回使用した元データにある「corolla」の画像2枚です。
corolla | corolla |
---|---|
同じラベルでも形が全然違いますね。
これが精度を下げる原因と考えられます。
#アンサンブル学習
これまでCNNを作成してきて、この精度で終了するのは少し悔しいのでもう少し粘ってみます。使用する手法はEnd-to-Endなアンサンブル学習です。
End-to-Endなアンサンブル学習とは、分類機をいくつか作成し、分類機ごとに出力された結果を多数決する手法で、ソフトアンサンブルとハードアンサンブルの2種類があります。
(引用:https://qiita.com/koshian2/items/d569cd71b0e082111962)
ソフトアンサンブルは、ラベルの推定確率同士の平均をとり出力を出す方法です。例えば、分類器Aが犬=0.8・猫=0.2、分類器Bが犬=0.9・猫=0.1、分類器Cが犬=0.4・猫=0.6だったら,全体の出力は犬=0.7,猫=0.3 となり,全体の出力結果は「犬」になります。
ハードアンサンブルは、分類器単位でラベルを求め、その多数決を取る方法である。例えば、ソフトな例なら、分類器Aは犬、分類器Bも犬、分類器Cは猫なので、全体の出力は多数決により「犬」になります。
コードはこちらのサイトを参考に作成しました。
# 確率の平均を取るアンサンブル(ソフトアンサンブル)
def ensembling_soft(models, X):
preds_sum = None
for model in models:
if preds_sum is None:
preds_sum = model.predict(X)
else:
preds_sum += model.predict(X)
probs = preds_sum / len(models)
return np.argmax(probs, axis=-1)
# 多数決のアンサンブル(ハードアンサンブル)
def ensembling_hard(models, X):
pred_labels = np.zeros((X.shape[0], len(models)))
for i, model in enumerate(models):
pred_labels[:, i] = np.argmax(model.predict(X), axis=-1)
return np.ravel(mode(pred_labels, axis=1)[0])
####ソフトアンサンブル
以下がソフトアンサンブルの結果になります。
分類器の数 | 単体の精度 | アンサンブルの精度 |
---|---|---|
1 | 0.875 | 0.875 |
2 | 0.872 | 0.880 |
3 | 0.871 | 0.894 |
4 | 0.880 | 0.905 |
5 | 0.878 | 0.906 |
分類器が増えるごとに精度が上昇していることがわかります。 | ||
分類器5個ではTest Accuracyが0.906まで上がりました。 |
また、アンサンブル学習で重要になるのは相関係数です。相関係数が0.95以上あるようなモデル同士で多数決をとっても、ほとんど同じ結果しか返さないため意味がないです。
相関係数は以下のようになっています。
相関係数は0.81~0.85と低いので、かなり効率の良い多数決が行われていることが確認できます。
####ハードアンサンブル
以下がハードアンサンブの結果になります。
分類器の数 | 単体の精度 | アンサンブルの精度 |
---|---|---|
1 | 0.870 | 0.870 |
2 | 0.879 | 0.876 |
3 | 0.873 | 0.887 |
4 | 0.880 | 0.895 |
5 | 0.875 | 0.901 |
こちらでも分類器が増えるごとに精度が上昇しており、5個では0.901になりました。
相関係数は以下のようになっています。
相関係数は0.81~0.85と低いので、かなり効率の良い多数決が行われていることが確認できます。
どちらの結果も、Test Accuracyが0.9より高く、普通のCNNに比べて精度を向上させることができました。
#まとめ
今回は、CNNを使用して車種を見極めるAIを作成しました。
精度はラベル9個で0.869とあまり高くなかったですが、アンサンブル学習を使用して0.9まで上昇させることができました。
さらに精度を上げたい場合は、元データのラベルをさらに分けることが有効だと考えます。
車種ごとではなく、車種のバージョンごとにデータを作成すれば、もっと高い精度を出すことができると考えます。
#参考文献
https://www.kaggle.com/occultainsights/toyota-cars-over-20k-labeled-images
https://www.kaggle.com/igorkovr/car-interiors
https://premium.aidemy.net/magazine/entry/2019/04/12/114812
https://qiita.com/koshian2/items/d569cd71b0e082111962
https://jp.mathworks.com/discovery/convolutional-neural-network.html
https://deepage.net/deep_learning/2016/11/07/convolutional_neural_network.html
https://products.sint.co.jp/aisia/blog/vol1-16