機械学習

【TensorFlow2入門】ディープラーニング(CNN)で仮面ライダー俳優を見分けるAIを作る(後編)

スポンサーリンク

ディープラーニング(CNN)で仮面ライダー俳優を見分けるAIを作る(前編)の続きになります。

前回までに、TensorFlowで学習するためのデータが集まったので、TensorFlowでモデルを構築していきたいと思います!

Tensor Flowを始める前に

ディープラーニングが少しもわからない、という方は今後やっていることが少し難しいかもしれません。おすすめのサイトや書籍をご紹介しておきます。

どれも私が独学する際に役立ったものです。

【書籍編】

【サイト編】

TensorFlowチュートリアル(日本語版)

Convolutional Neural Networkとは何なのか

Tensor Flow2を使って顔を識別するAIをつくる

Tensor Flowの流れ

Tensor Flowでの流れをざっくり説明すると以下のようになります。

  1. ディープラーニングのモデル構築
  2. モデルをコンパイル
  3. 訓練する
  4. 正答率を評価する
  5. 予測する(ここで顔を識別します)

0.パッケージのインポート、画像の前処理

必要なパッケージをインポートします

# 必要なパッケージをインポート
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 というものを使って、画像をロードしていきます。

ジェネレータはメモリが限られているときなどに有効な手法のようです。

画像の前処理の流れは以下のようになります。

  1. ディスクから画像を読み取る
  2. これらの画像のコンテンツをデコードし、RGB値にしたがって適切なグリッド形式に変換
  3. それらを浮動小数点テンソルに変換
  4. テンソルを0〜255の値から0〜1の値にリスケーリング(ニューラルネットワークは小さな入力値を扱う方が適しているため)

# 訓練・テストデータのディレクトリ
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')

実際にいくつかの画像を表示してみます。

# サンプル画像の表示
# 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.ディープラーニングのモデル構築

学習用のモデルを構築します。ここはかなりシンプルです。レイヤーはチュートリアル通りです。

# モデルの構築
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.モデルをコンパイル

モデルをコンパイルします。最適化の手法や損失関数をどうするか設定します。

# モデルをコンパイルする
model.compile(optimizer='adam',# 最適化
              loss='categorical_crossentropy',# 損失関数
              metrics=['accuracy'])# 訓練とテストのステップを監視するのに使用

モデルの表示をします。

# モデルの概要を表示
model.summary()

3.訓練する、4.正答率を評価する

モデルの学習をして、その結果を可視化してみます!

エポック数は5です。

# モデルの学習
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というディレクトリを作成してください。

mkdir saved_model

model.saveを呼ぶことで、モデルのアーキテクチャや重み、訓練の設定を単一のファイル/フォルダに保存できます。

# モデルの保存
model.save('./saved_model') 

assets,saved_model.pb,variablesが保存されます。

データをロードして、アーキテクチャを確認すると、保存できていることがわかります。

# 保存したモデルの読み込み
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%だと実際少し低いですね(笑)

一度、ここまでのソースコードをまとめておきます。

# -*- 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上に作りたかったですけど今回は諦めてターミナルで実行するだけのものとしました。

# -*- 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.jpg
仮面ライダージオウの常盤ソウゴです
2.jpg
仮面ライダージオウの常盤ソウゴです
3.jpg
顔が見つかりません
4.jpg
仮面ライダーゼロワンの飛電或斗です
5.jpg
仮面ライダービルドの桐生戦兎です

割としっかり認識してくれました。3枚目の写真は顔が小さくて、OpenCv側が取得できなかったようです。

クラス分類なので違う人は一番似ている人になってしまうんですね。

悩んだ点

実際、ここまでたどり着くのに1週間ほどかかりました。

オリジナルデータを読み込ませる際、普通はnumpyの配列にして渡すそうなのですが違うやり方でやろうと思い、ジェネレータを採用しました。ジェネレータを使用してやっている方はあまりおらず、使い方に苦戦しました。

Kerasで大規模な画像分類 - vgg16 転移学習 という記事にめっちゃ助けられました。感謝です。

そして、取り組んでいる最中に

  • テストデータ
  • 検証用
  • 訓練データ

の違いがよくわからないなと思いました。

色々調べた結果、様々な方法・過程があるようですが、訓練→検証→テストと進むのが一般的。

そもそも、テストデータの用意を忘れてしまったことにギリギリになって気づきました。

なので今回はスルーしましたが、実装する場合は前回のmakeデータを1度実行した後、validationに対してもう一度実行することでテストデータの作成を行えます。

モデルも大事ですが、データの準備がいかに大事かということを実感しました。

今後もTensorflowの学習を続けていこうと思います

最後までありがとうございました!

参考サイト

CNNを使って衛星データに雲が映っているか否か画像分類してみた

Kerasの時系列予測でgeneratorを使って大容量データを扱う 後編

Kerasで大規模な画像分類 - vgg16 転移学習

TensorFlow2でのモデルファイルの保存と読み込み

TensorFlow + Kerasでフレンズ識別する - その2: 簡単なCNNを使った学習編

教師あり学習における訓練、検証、テストデータについて(図解なし)

TensorFlowのSavedModelの便利さを紹介する

スポンサーリンク

-機械学習