ディープラーニング(CNN)で仮面ライダー俳優を見分けるAIを作る(前編)の続きになります。
前回までに、TensorFlowで学習するためのデータが集まったので、TensorFlowでモデルを構築していきたいと思います!
目次
Tensor Flowを始める前に
ディープラーニングが少しもわからない、という方は今後やっていることが少し難しいかもしれません。おすすめのサイトや書籍をご紹介しておきます。
どれも私が独学する際に役立ったものです。
【書籍編】
【サイト編】
Convolutional Neural Networkとは何なのか
Tensor Flow2を使って顔を識別するAIをつくる
Tensor Flowの流れ
Tensor Flowでの流れをざっくり説明すると以下のようになります。
- ディープラーニングのモデル構築
- モデルをコンパイル
- 訓練する
- 正答率を評価する
- 予測する(ここで顔を識別します)
0.パッケージのインポート、画像の前処理
必要なパッケージをインポートします
1 2 3 4 5 6 7 8 |
# 必要なパッケージをインポート import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense,Conv2D,Flatten,Dropout,MaxPooling2D from tensorflow.keras.preprocessing.image import ImageDataGenerator import os import numpy as np import matplotlib.pyplot as plt |
モデルを構築する前に、画像の前処理というものを行わないといけません。
まだまだ詳しくないので、チュートリアルと同じ方法で画像の読み込み等を行っていきます。
オリジナルのデータセットを読み込むための準備などを参考にすれば、もっとスマートにできるのかもしれませんが... まあいずれ笑
tf.keras.preprocessing.image.ImageDataGenerator
というものを使って、画像をロードしていきます。
ジェネレータはメモリが限られているときなどに有効な手法のようです。
画像の前処理の流れは以下のようになります。
- ディスクから画像を読み取る
- これらの画像のコンテンツをデコードし、RGB値にしたがって適切なグリッド形式に変換
- それらを浮動小数点テンソルに変換
- テンソルを0〜255の値から0〜1の値にリスケーリング(ニューラルネットワークは小さな入力値を扱う方が適しているため)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# 訓練・テストデータのディレクトリ train_dir = './split_data/train/' validation_dir = './split_data/validation/' # バッチサイズ、エポック数、画像リサイズ用ピクセル値 batch_size = 32 epochs = 10 IMG_HEIGHT = 150 IMG_WIDTH = 150 # modelへのデータ送信前に浮動小数点テンソルにフォーマット # ImageDataGeneratorクラスで全て実装可能 train_image_generator = ImageDataGenerator(rescale=1./255) # 学習データのジェネレータ validation_image_generator = ImageDataGenerator(rescale=1./255)# 検証データのジェネレータ # 画像をロードし、リスケーリングを適用 # 3クラスなのでclass_mode='categorical'とする train_data_gen = train_image_generator.flow_from_directory(batch_size=batch_size, directory=train_dir, shuffle=True, target_size=(IMG_HEIGHT,IMG_WIDTH), class_mode='categorical') val_data_gen = validation_image_generator.flow_from_directory(batch_size=batch_size, directory=validation_dir, shuffle=True, target_size=(IMG_HEIGHT,IMG_WIDTH), class_mode='categorical') |
実際にいくつかの画像を表示してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# サンプル画像の表示 # next:画像とラベルが戻り値として返ってくる sample_training_images,sample_training_labels = next(val_data_gen) plt.figure(figsize=(10,10)) for i in range(5): plt.subplot(5,5,i+1) plt.xticks([]) plt.yticks([]) plt.grid(False) plt.imshow(sample_training_images[i]) plt.xlabel(sample_training_labels[i]) plt.show() |

ラベルを見てみると、ディレクトリの順番通りに振り分けられていることが確認できました。

データの数が足りないかもな、とこの辺で思う。とりあえず進めます。
1.ディープラーニングのモデル構築
学習用のモデルを構築します。ここはかなりシンプルです。レイヤーはチュートリアル通りです。
1 2 3 4 5 6 7 8 9 10 11 12 |
# モデルの構築 model = Sequential([ Conv2D(32, (3, 3), activation='relu', input_shape=(IMG_HEIGHT, IMG_WIDTH ,3)), MaxPooling2D((2, 2)), Conv2D(64, (3,3), activation='relu'), MaxPooling2D((2, 2)), Conv2D(64, (3,3), activation='relu'), MaxPooling2D((2, 2)), Flatten(), Dense(512, activation='relu'), Dense(3, activation='softmax') ]) |
2.モデルをコンパイル
モデルをコンパイルします。最適化の手法や損失関数をどうするか設定します。
1 2 3 4 |
# モデルをコンパイルする model.compile(optimizer='adam',# 最適化 loss='categorical_crossentropy',# 損失関数 metrics=['accuracy'])# 訓練とテストのステップを監視するのに使用 |
モデルの表示をします。
1 2 |
# モデルの概要を表示 model.summary() |

3.訓練する、4.正答率を評価する
モデルの学習をして、その結果を可視化してみます!
エポック数は5です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# モデルの学習 history = model.fit_generator( train_data_gen, steps_per_epoch=352 // batch_size, epochs=epochs, validation_data=val_data_gen, validation_steps=89 // batch_size, verbose=1 ) #学習結果の可視化 acc = history.history['accuracy'] val_acc = history.history['val_accuracy'] loss = history.history['loss'] val_loss = history.history['val_loss'] epochs_range = range(epochs) plt.figure(figsize=(8, 8)) plt.subplot(1, 2, 1) plt.plot(epochs_range, acc, label='Training Accuracy') plt.plot(epochs_range, val_acc, label='Validation Accuracy') plt.legend(loc='lower right') plt.title('Training and Validation Accuracy') plt.subplot(1, 2, 2) plt.plot(epochs_range, loss, label='Training Loss') plt.plot(epochs_range, val_loss, label='Validation Loss') plt.legend(loc='upper right') plt.title('Training and Validation Loss') plt.show() |

微妙です。データ数が少ないからでしょうか。全体の正答率も低いですね。
エポック数を10にしてみます。

明らかにテストデータの正確性が6エポックぐらいで頭打ちしてますのでいわゆる過学習というやつでしょう…
とりあえず予測することが目的なので精度については妥協します...
学習データ、モデルの保存方法
学習させたデータを一度保存しておきます。保存方法は簡単です。
saved_modelというディレクトリを作成してください。
1 |
mkdir saved_model |
model.saveを呼ぶことで、モデルのアーキテクチャや重み、訓練の設定を単一のファイル/フォルダに保存できます。
1 2 |
# モデルの保存 model.save('./saved_model') |
assets,saved_model.pb,variablesが保存されます。

データをロードして、アーキテクチャを確認すると、保存できていることがわかります。
1 2 3 4 5 6 7 |
# 保存したモデルの読み込み new_model = tf.keras.models.load_model('saved_model') # モデルの概要を表示 #new_model.summary() # テストデータの損失関数、正答率を評価 loss, acc = new_model.evaluate_generator(val_data_gen, verbose=2) print('Restored model, accuracy: {:5.2f}%'.format(100*acc)) |

テスト用モデルを評価すると、先ほどと同じ値になっていることも確認できます。
85%だと実際少し低いですね(笑)

一度、ここまでのソースコードをまとめておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# -*- coding:utf-8 -*- # 必要なパッケージをインポート import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense,Conv2D,Flatten,Dropout,MaxPooling2D from tensorflow.keras.preprocessing.image import ImageDataGenerator import os import numpy as np import matplotlib.pyplot as plt # 訓練・テストデータのディレクトリ train_dir = './split_data/train/' validation_dir = './split_data/validation/' # バッチサイズ、エポック数、画像リサイズ用ピクセル値 batch_size = 32 epochs = 10 IMG_HEIGHT = 150 IMG_WIDTH = 150 # modelへのデータ送信前に浮動小数点テンソルにフォーマット # ImageDataGeneratorクラスで全て実装可能 train_image_generator = ImageDataGenerator(rescale=1./255) # 学習データのジェネレータ validation_image_generator = ImageDataGenerator(rescale=1./255)# 検証データのジェネレータ # 画像をロードし、リスケーリングを適用 # 3クラスなのでclass_mode='categorical'とする train_data_gen = train_image_generator.flow_from_directory(batch_size=batch_size, directory=train_dir, shuffle=True, target_size=(IMG_HEIGHT,IMG_WIDTH), class_mode='categorical') val_data_gen = validation_image_generator.flow_from_directory(batch_size=batch_size, directory=validation_dir, shuffle=True, target_size=(IMG_HEIGHT,IMG_WIDTH), class_mode='categorical') # サンプル画像の表示 # 画像とラベルが戻り値として返ってくる sample_training_images,sample_training_labels = next(val_data_gen) plt.figure(figsize=(10,10)) for i in range(25): plt.subplot(5,5,i+1) plt.xticks([]) plt.yticks([]) plt.grid(False) plt.imshow(sample_training_images[i]) plt.xlabel(sample_training_labels[i]) plt.show() # データの量を確認 print(len(train_data_gen)) print(len(val_data_gen)) # モデルの構築 model = Sequential([ Conv2D(32, (3, 3), activation='relu', input_shape=(IMG_HEIGHT, IMG_WIDTH ,3)), MaxPooling2D((2, 2)), Conv2D(64, (3,3), activation='relu'), MaxPooling2D((2, 2)), Conv2D(64, (3,3), activation='relu'), MaxPooling2D((2, 2)), Flatten(), Dense(512, activation='relu'), Dense(3, activation='softmax') ]) # モデルをコンパイルする model.compile(optimizer='adam',# 最適化 loss='categorical_crossentropy',# 損失関数 metrics=['accuracy'])# 訓練とテストのステップを監視するのに使用 # モデルの概要を表示 model.summary() # モデルの学習 history = model.fit_generator( train_data_gen, steps_per_epoch=352 // batch_size, epochs=epochs, validation_data=val_data_gen, validation_steps=89 // batch_size, verbose=1 ) #学習結果の可視化 acc = history.history['accuracy'] val_acc = history.history['val_accuracy'] loss = history.history['loss'] val_loss = history.history['val_loss'] epochs_range = range(epochs) plt.figure(figsize=(8, 8)) plt.subplot(1, 2, 1) plt.plot(epochs_range, acc, label='Training Accuracy') plt.plot(epochs_range, val_acc, label='Validation Accuracy') plt.legend(loc='lower right') plt.title('Training and Validation Accuracy') plt.subplot(1, 2, 2) plt.plot(epochs_range, loss, label='Training Loss') plt.plot(epochs_range, val_loss, label='Validation Loss') plt.legend(loc='upper right') plt.title('Training and Validation Loss') plt.show() # モデルの保存 model.save('./saved_model') # 保存したモデルの読み込み new_model = tf.keras.models.load_model('saved_model') # モデルの概要を表示 #new_model.summary() # テストデータの損失関数、正答率を評価 loss, acc = new_model.evaluate_generator(val_data_gen, verbose=2) print('Restored model, accuracy: {:5.2f}%'.format(100*acc)) |
5.予測する
では、俳優を識別するためのプログラムを作っていきます。
ほんとはweb上に作りたかったですけど今回は諦めてターミナルで実行するだけのものとしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# -*- coding:utf-8 -*- # 必要なパッケージをインポート import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense,Conv2D,Flatten,Dropout,MaxPooling2D from tensorflow.keras.preprocessing.image import ImageDataGenerator from tensorflow.keras.preprocessing import image import os import numpy as np import matplotlib.pyplot as plt import cv2 # 保存したモデルの読み込み model = tf.keras.models.load_model('saved_model') #カスケード分類器のパスと読み込み cascade_path = 'haarcascade_frontalface_default.xml' faceCascade = cv2.CascadeClassifier(cascade_path) # 識別する画像ファイル名を入力 例:zio.jpg print("画像のファイル名を入力") img_path = './check_rider/'+str(input())# ディレクトリは各自設定 # 画像を読み込む img = cv2.imread(img_path, cv2.IMREAD_COLOR) # モノクロ画像に変換 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) face = faceCascade.detectMultiScale(gray, 1.1, 3) if len(face) > 0: for rect in face: # 顔を赤線で囲む cv2.rectangle(img, tuple(rect[0:2]), tuple(rect[0:2]+rect[2:4]), (0, 0, 255), thickness=2) # 顔部分を赤線で囲った画像の保存先 face_detect_img_path = './images/' + '.jpg' # 顔部分を赤線で囲った画像の保存 cv2.imwrite(face_detect_img_path, img) x = rect[0] y = rect[1] w = rect[2] h = rect[3] # 検出した顔を切り抜いた画像を保存 cv2.imwrite('./images/'+'a.jpg', img[y:y+h, x:x+w]) # TensorFlowへ渡す切り抜いた顔画像 target_image_path = './images/'+'a.jpg' else: # 顔が見つからなければ処理終了 print("顔が見つかりません") #切り抜いた画像を読み込んでリサイズする img_path = target_image_path img = image.load_img(img_path,target_size=(150, 150, 3)) x = image.img_to_array(img) x = np.expand_dims(x, axis=0) #予測 predictions = model.predict(x) #クラスのラベルの値で場合分け 例:ビルド[1,0,0,] if predictions[0,0] == 1: print("仮面ライダービルドの桐生戦兎です") elif predictions[0,1] == 1: print("仮面ライダーゼロワンの飛電或斗です") else: print("仮面ライダージオウの常盤ソウゴです") |
実際にやってみましょう!
適当にネットから拾ってきて、名前を付けておきます。

実際の結果は以下の通りです。
1 2 3 4 5 6 7 8 9 10 |
1.jpg 仮面ライダージオウの常盤ソウゴです 2.jpg 仮面ライダージオウの常盤ソウゴです 3.jpg 顔が見つかりません 4.jpg 仮面ライダーゼロワンの飛電或斗です 5.jpg 仮面ライダービルドの桐生戦兎です |
割としっかり認識してくれました。3枚目の写真は顔が小さくて、OpenCv側が取得できなかったようです。
クラス分類なので違う人は一番似ている人になってしまうんですね。
悩んだ点
実際、ここまでたどり着くのに1週間ほどかかりました。
オリジナルデータを読み込ませる際、普通はnumpyの配列にして渡すそうなのですが違うやり方でやろうと思い、ジェネレータを採用しました。ジェネレータを使用してやっている方はあまりおらず、使い方に苦戦しました。
Kerasで大規模な画像分類 - vgg16 転移学習 という記事にめっちゃ助けられました。感謝です。
そして、取り組んでいる最中に
- テストデータ
- 検証用
- 訓練データ
の違いがよくわからないなと思いました。
色々調べた結果、様々な方法・過程があるようですが、訓練→検証→テストと進むのが一般的。
そもそも、テストデータの用意を忘れてしまったことにギリギリになって気づきました。
なので今回はスルーしましたが、実装する場合は前回のmakeデータを1度実行した後、validationに対してもう一度実行することでテストデータの作成を行えます。
モデルも大事ですが、データの準備がいかに大事かということを実感しました。
今後もTensorflowの学習を続けていこうと思います
最後までありがとうございました!
参考サイト
CNNを使って衛星データに雲が映っているか否か画像分類してみた
Kerasの時系列予測でgeneratorを使って大容量データを扱う 後編
TensorFlow + Kerasでフレンズ識別する - その2: 簡単なCNNを使った学習編