【Python】ウルトラマン画像判別

アイデミーで学習中です。
今後、AIを利用する機会が増えるため、予め仕組みを理解するために学習をしています。

成果物としてウルトラマンシリーズの判別のモデルを作成します。

今回は、”ウルトラマントリガー(ノーマル・現行)”,”ウルトラセブン(めがね)”,”ウルトラマンタロウ(つの)”の判別します。

環境はGoogleコラボラトリーです。

【判別実行までの流れ】
1.画像の収集
2.画像のリサイズ
3.画像の統合、ラベル付け、ランダムに並び替え
4.トレーニングデータ、テストデータに分割
5.学習モデルを構築
6.学習を実施
7.問題解決
8.学習制度の向上

【実行内容】
まずは画像をクロール。3種類の画像を各500枚と設定しデータ収集。それぞれ「名前+画像/」フォルダへ格納します。(コードはトリガーのみ)

 #クローラーをインストール 
pip install icrawler  #ウルトラマントリガー画像を500枚収集 
from icrawler.builtin import BingImageCrawler

root_dirs = ['ウルトラマントリガー画像/']
keywords = ['ウルトラマントリガー']

for root_dir, keyword in zip(root_dirs, keywords):
   crawler = BingImageCrawler(storage={"root_dir": root_dir})
   crawler.crawl(keyword=keyword, max_num=500)

実際に収集できたデータ数を確認します。

print(len(y_train))

それぞれ300-400枚前後のデータを収集できていました。

続いて、画像判別のため必要なライブラリをインポート。
パスを指定し、フォルダ内のファイルリスト、それぞれの画像を格納するための空のリストを作成します。

 #ライブラリをインポート import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.layers import Dense, Dropout, Flatten, Input
from tensorflow.keras.applications.vgg16 import VGG16
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras import optimizers

#パスを設定し画像フォルダを指定
path1 = os.listdir('/content/ウルトラマントリガー画像/') 
path2 = os.listdir('/content/ウルトラセブン画像/') 
path3 = os.listdir('/content/ウルトラマンタロウ画像/') 

#格納する箱を設定?
img_trigger = []
img_seven = []
img_taro=[]

つぎに、画像サイズを64に統一。
全ての画像データを結合し、ラベルは.ウルトラマントリガー:0,ウルトラセブン:1,ウルトラマンタロウ:2とラベルを設定します。
統合したデータをnumpy配列としランダムに入れ替えます。
そして、トレーニングデータとテストデータに分ける。割合は8:2とします。

#それぞれの画像をサイズ変更
for i in range(len(path1)):
   img = cv2.imread('/content/ウルトラマントリガー画像/'+ path1[i])
   img = cv2.resize(img, (64,64))
   img_trigger.append(img)

for i in range(len(path2)):
   img = cv2.imread('/content/ウルトラセブン画像/'+ path2[i])
   img = cv2.resize(img, (64,64))
   img_seven.append(img)

for i in range(len(path3)):
   img = cv2.imread('/content/ウルトラマンタロウ画像/'+ path3[i])
   img = cv2.resize(img, (64,64))
   img_taro.append(img)

#3種類の収集画像をひとつに統合、ラベルを設定
X = np.array(img_trigger + img_seven + img_taro)
y =  np.array([0]*len(img_trigger) + [1]*len(img_seven) +[2]*len(img_taro))

 #ランダムに並び替え rand_index = np.random.permutation(np.arange(len(X)))
X = X[rand_index]
y = y[rand_index]

# データをトレーニングデータ、テストデータに分割
X_train = X[:int(len(X)*0.8)]
y_train = y[:int(len(y)*0.8)]
X_test = X[int(len(X)*0.8):]
y_test = y[int(len(y)*0.8):]

結果を確認します。

print(y)
[2 0 2 2 0 0 1 1 0 1 0 1 1 1 2 0 0 1 1 1 0 1 2 0 0 0 2 2 2 2 1 0 0 1 2 1 1
0 2 2 0 1 2 1 1 1 0 1 2 0 0 0 0 2 2 2 0 2 1 0 0 2 2 2 1 0 0 0 0 2 0 1 0 0
2 2 2 1 1 0 2 1 0 2 2 2 0 1 0 0 0 0 1 1 0 2 2 2 1 1 1 2 1 2 2 2 1 2 2 1 1
1 0 0 2 2 1 1 1 2 2 2 2 0 0 2 2 0 1 1 1 0 2 1 1 0 1 2 1 0 1 1 2 1 1 0 2 0
2 0 0 2 2 2 1 0 0 1 2 2 0 2 2 1 2 2 2 2 1 1 2 1 2 1 1 2 0 1 1 0 0 1 0 0 1
0 0 0 1 0 1 2 0 1 0 0 2 0 0 2 2 2 0 2 1 1 1 2 0 2 2 0 0 0 0 0 0 0 2 1 2 2
1 0 1 0 0 2 2 2 0 2 1 0 0 0 0 1 1 0 0 0 1 2 2 2 2 0 2 2 2 2 2 1 2 1 2 1 1
0 0 1 2 0 0 1 1 1 0 1 1 2 2 0 2 0 0 0 1 1 0 1 1 0 2 2 1 0 2 1 0 1 1 1 2 0
0 0 0 0 1 1 1 2 2 0 0 1 2 0 2 2 2 1 2 2 2 0 0 0 0 1 0 1 1 0 1 2 1 0 1 1 2
1 1 1 2 0 2 1 0 2 0 2 0 2 0 1 1 2 1 1 2 2 0 0 1 2 1 2 2 2 2 0 0 1 1 0 1 0
1 1 1 0 2 2 0 0 1 2 1 2 1 0 1 0 1 0 0 1 2 2 0 0 1 2 2 0 2 2 1 2 1 0 0 0 0
1 2 1 1 1 1 1 1 1 1 2 1 1 2 1 1 0 2 2 0 0 1 1 1 1 1 2 2 2 2 2 2 0 2 1 1 2
1 2 1 2 1 1 2 2 2 2 2 0 0 0 0 0 0 1 1 2 0 0 1 0 1 1 2 1 2 2 0 1 2 0 2 0 0
1 1 0 2 0 0 1 2 1 2 2 2 1 1 0 2 1 0 2 1 2 0 1 2 2 0 0 1 2 2 0 2 2 1 1 1 2
1 2 2 0 1 2 0 2 1 0 0 1 0 0 2 2 2 1 2 0 2 1 1 1 2 0 1 0 2 0 1 1 2 0 0 0 1
0 2 1 0 0 1 0 2 2 2 0 0 2 1 0 2 2 0 2 0 0 0 0 1 2 2 0 1 2 1 1 2 1 2 2 0 2
1 1 0 2 2 1 1 2 1 2 0 2 2 2 2 0 2 2 1 0 2 2 1 2 2 0 2 1 1 1 0 2 1 0 1 1 2
1 0 1 0 2 1 1 0 2 1 0 1 2 0 2 1 1 2 0 0 0 2 0 1 1 2 2 1 1 2 0 0 2 0 0 1 1
1 1 0 1 0 2 1 1 0 1 2 2 0 0 2 0 2 1 2 0 0 2 2 2 2 1 0 1 0 2 0 1 2 0 2 2 0
1 1 2 0 0 1 1 2 2 0 1 1 2 0 2 2 1 1 2 1 0 0 1 1 1 0 0 2 0 2 2 2 2 0 1 1 0
1 1 0 1 2 0 0 2 0 2 1 1 2 1 0 0 0 0 0 2 1 0 0 1 1 2 1 1 1 1 0 2 1 2 1 0 2
2 0 0 1 0 1 2 2 2 1 0 0 1 2 2 2 1 2 1 2 2 1 2 1 0 1 1 0 1 2 1 1 2 2 0 1 2
2 1 2]

統合できており、ランダムに散っていることが確認できました。

ここからは、モデルを構築し学習します。
モデルはVGG16を利用した転移学習モデルとします。nodeを設定。VGG16の層を固定。コンパイルしモデルを学習します。

# ワンホットベクトル化
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

 #VGG16を利用 input_tensor = Input(shape=(64, 64, 3))
vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)

# vggのoutputを受け取り、3クラス分類する層を定義
top_model = Sequential()
top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
top_model.add(Dense(256, activation='relu'))
top_model.add(Dropout(0.5))
top_model.add(Dense(3, activation='softmax'))


# vggと、top_modelを連結
model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output))

#層を固定
for layer in model.layers[:15]:
  layer.trainable = False

# コンパイル
model.compile(loss='categorical_crossentropy',
            optimizer=optimizers.SGD(lr=1e-2, momentum=0.9),
            metrics=['accuracy'])

# 学習
model.fit(X_train, y_train, batch_size=10, epochs=10, validation_data=(X_test, y_test))

テストデータの結果を表示します。

 #判別結果を表示 
for i in range(len(X_test)):
   x = X_test[i]
   plt.imshow(x)
   plt.show()
   pred = np.argmax(model.predict(x.reshape(1,64,64,3)))
   print(y_test[i])
   print(pred)

オール2。全員ウルトラマンタロウと判別結果。うまく学習ができていません。

学習結果を確認します。

/usr/local/lib/python3.7/dist-packages/keras/optimizer_v2/optimizer_v2.py:356: UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.
 "The `lr` argument is deprecated, use `learning_rate` instead.")
Epoch 1/10
131/131 [==============================] - 74s 563ms/step - loss: nan - accuracy: 0.3145 - val_loss: nan - val_accuracy: 0.3058
Epoch 2/10
131/131 [==============================] - 73s 558ms/step - loss: nan - accuracy: 0.3198 - val_loss: nan - val_accuracy: 0.3058
Epoch 3/10
131/131 [==============================] - 72s 553ms/step - loss: nan - accuracy: 0.3198 - val_loss: nan - val_accuracy: 0.3058

val accuracyが1/3の確率で変わっておらず、ほぼ当てずっぽうの形です。val lossはnanです。

/usr/local/lib/python3.7/dist-packages/keras/optimizer_v2/optimizer_v2.py:356: UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.
 "The `lr` argument is deprecated, use `learning_rate` instead.")
Epoch 1/10
131/131 [==============================] - 74s 563ms/step - loss: nan - accuracy: 0.3145 - val_loss: nan - val_accuracy: 0.3058
Epoch 2/10
131/131 [==============================] - 73s 558ms/step - loss: nan - accuracy: 0.3198 - val_loss: nan - val_accuracy: 0.3058
Epoch 3/10
131/131 [==============================] - 72s 553ms/step - loss: nan - accuracy: 0.3198 - val_loss: nan - val_accuracy: 0.3058

画像を整理し、判別対象が一体で映るものなど、明確にわかるものに絞り込みました。結果、200枚ずつのデータ数くらいになりました。
学習結果は変わりません。

学習モデル nodeの数値を極端に高め、ほぼ丸暗記をする状態を作成し検証しましたが、学習結果は変わりません。
正解ラベルの表示も正しいため、モデルに問題あると考えます。

対策として、
・差異が小さすぎて数値が動いていない可能性があるため、lr数を大きい数値に変更。
・学習モデルの層追加

等を試行錯誤しましたが学習結果はかわりません。

最終的に原因を解明でき、VGG16の層固定数を修正。
VGGのサマリーを確認したところ、固定されていないデータ処理数が膨大であり、今回判別するデータ数が少ないため、全体からすると誤差程度の学習にしななっておらず、結果学習していないことと同じ状態でした。1/3ずつの判別結果となっていたと思われます。

Model: "vgg16"
_________________________________________________________________
Layer (type)                Output Shape              Param #   
=================================================================
input_7 (InputLayer)        [(None, 64, 64, 3)]       0         
                                                                
block1_conv1 (Conv2D)       (None, 64, 64, 64)        1792      
                                                                
block1_conv2 (Conv2D)       (None, 64, 64, 64)        36928     
                                                                
block1_pool (MaxPooling2D)  (None, 32, 32, 64)        0         
                                                                
block2_conv1 (Conv2D)       (None, 32, 32, 128)       73856     
                                                                
block2_conv2 (Conv2D)       (None, 32, 32, 128)       147584    
                                                                
block2_pool (MaxPooling2D)  (None, 16, 16, 128)       0         
                                                                
block3_conv1 (Conv2D)       (None, 16, 16, 256)       295168    
                                                                
block3_conv2 (Conv2D)       (None, 16, 16, 256)       590080    
                                                                
block3_conv3 (Conv2D)       (None, 16, 16, 256)       590080    
                                                                
block3_pool (MaxPooling2D)  (None, 8, 8, 256)         0         
                                                                
block4_conv1 (Conv2D)       (None, 8, 8, 512)         1180160   
                                                                
block4_conv2 (Conv2D)       (None, 8, 8, 512)         2359808   
                                                                
block4_conv3 (Conv2D)       (None, 8, 8, 512)         2359808   
                                                                
block4_pool (MaxPooling2D)  (None, 4, 4, 512)         0         
                                                                
block5_conv1 (Conv2D)       (None, 4, 4, 512)         2359808   
                                                                
block5_conv2 (Conv2D)       (None, 4, 4, 512)         2359808   
                                                                
block5_conv3 (Conv2D)       (None, 4, 4, 512)         2359808   
                                                                
block5_pool (MaxPooling2D)  (None, 2, 2, 512)         0         
                                                                
=================================================================
Total params: 14,714,688
Trainable params: 0
Non-trainable params: 14,714,688
_________________________________________________________________

以上を改善したところ、学習が進行するようになりました。

Epoch 1/15
24/24 [==============================] - 2s 44ms/step - loss: 28.8639 - accuracy: 0.3937 - val_loss: 5.5015 - val_accuracy: 0.4167
Epoch 2/15
24/24 [==============================] - 1s 32ms/step - loss: 14.4080 - accuracy: 0.4409 - val_loss: 2.7824 - val_accuracy: 0.5833
Epoch 3/15
24/24 [==============================] - 1s 31ms/step - loss: 9.8391 - accuracy: 0.4829 - val_loss: 2.5119 - val_accuracy: 0.5521
Epoch 4/15
24/24 [==============================] - 1s 32ms/step - loss: 6.7868 - accuracy: 0.5748 - val_loss: 1.3164 - val_accuracy: 0.5833
Epoch 5/15
24/24 [==============================] - 1s 32ms/step - loss: 5.9076 - accuracy: 0.5223 - val_loss: 1.0925 - val_accuracy: 0.6354
Epoch 6/15
24/24 [==============================] - 1s 30ms/step - loss: 3.6008 - accuracy: 0.5486 - val_loss: 0.9734 - val_accuracy: 0.6042
Epoch 7/15
24/24 [==============================] - 1s 31ms/step - loss: 3.1652 - accuracy: 0.5827 - val_loss: 0.9635 - val_accuracy: 0.6146
Epoch 8/15
24/24 [==============================] - 1s 32ms/step - loss: 2.7116 - accuracy: 0.5591 - val_loss: 0.8538 - val_accuracy: 0.6042
Epoch 9/15
24/24 [==============================] - 1s 31ms/step - loss: 2.0038 - accuracy: 0.5853 - val_loss: 0.8856 - val_accuracy: 0.6771
Epoch 10/15
24/24 [==============================] - 1s 30ms/step - loss: 1.5037 - accuracy: 0.6535 - val_loss: 0.8323 - val_accuracy: 0.6562
Epoch 11/15
24/24 [==============================] - 1s 31ms/step - loss: 1.3173 - accuracy: 0.6194 - val_loss: 0.8320 - val_accuracy: 0.5625
Epoch 12/15
24/24 [==============================] - 1s 30ms/step - loss: 1.0244 - accuracy: 0.6352 - val_loss: 0.8201 - val_accuracy: 0.6146
Epoch 13/15
24/24 [==============================] - 1s 32ms/step - loss: 1.0914 - accuracy: 0.6299 - val_loss: 0.8247 - val_accuracy: 0.6042
Epoch 14/15
24/24 [==============================] - 1s 30ms/step - loss: 1.0419 - accuracy: 0.6194 - val_loss: 0.7807 - val_accuracy: 0.6875
Epoch 15/15
24/24 [==============================] - 1s 32ms/step - loss: 0.9872 - accuracy: 0.6745 - val_loss: 0.8321 - val_accuracy: 0.6354

ここからは、学習の制度向上をしていきます。
コンパイルの optimizerをadamに変更します。

# コンパイル
model.compile(loss='categorical_crossentropy',
            optimizer="adam",
            metrics=['accuracy'])

画像サイズを128に変更し画像を鮮明に、
データ内容が特撮の画像からデフォルメされた画像まで様々なためブレを吸収するためbatch_sizeを上げ32としました。

Epoch 1/15
12/12 [==============================] - 9s 433ms/step - loss: 38.1073 - accuracy: 0.3832 - val_loss: 20.8340 - val_accuracy: 0.4271
Epoch 2/15
12/12 [==============================] - 1s 112ms/step - loss: 32.0633 - accuracy: 0.4383 - val_loss: 5.9295 - val_accuracy: 0.5312
Epoch 3/15
12/12 [==============================] - 1s 112ms/step - loss: 21.2973 - accuracy: 0.5302 - val_loss: 2.2120 - val_accuracy: 0.5938
Epoch 4/15
12/12 [==============================] - 1s 111ms/step - loss: 15.2078 - accuracy: 0.5066 - val_loss: 2.7018 - val_accuracy: 0.5312
Epoch 5/15
12/12 [==============================] - 1s 111ms/step - loss: 8.7747 - accuracy: 0.6115 - val_loss: 1.3675 - val_accuracy: 0.6354
Epoch 6/15
12/12 [==============================] - 1s 116ms/step - loss: 8.4077 - accuracy: 0.6247 - val_loss: 1.7090 - val_accuracy: 0.6458
Epoch 7/15
12/12 [==============================] - 1s 110ms/step - loss: 4.8089 - accuracy: 0.6850 - val_loss: 1.1515 - val_accuracy: 0.7188
Epoch 8/15
12/12 [==============================] - 1s 112ms/step - loss: 5.2084 - accuracy: 0.7034 - val_loss: 1.2756 - val_accuracy: 0.7500
Epoch 9/15
12/12 [==============================] - 1s 118ms/step - loss: 4.1010 - accuracy: 0.6903 - val_loss: 1.3573 - val_accuracy: 0.7292
Epoch 10/15
12/12 [==============================] - 1s 117ms/step - loss: 3.5441 - accuracy: 0.7297 - val_loss: 1.3654 - val_accuracy: 0.8438
Epoch 11/15
12/12 [==============================] - 1s 110ms/step - loss: 2.9915 - accuracy: 0.7533 - val_loss: 1.7941 - val_accuracy: 0.8021
Epoch 12/15
12/12 [==============================] - 1s 111ms/step - loss: 2.7498 - accuracy: 0.7848 - val_loss: 1.7455 - val_accuracy: 0.7604
Epoch 13/15
12/12 [==============================] - 1s 110ms/step - loss: 2.6049 - accuracy: 0.7717 - val_loss: 2.1416 - val_accuracy: 0.8125
Epoch 14/15
12/12 [==============================] - 1s 110ms/step - loss: 2.7114 - accuracy: 0.7979 - val_loss: 1.5553 - val_accuracy: 0.8125
Epoch 15/15
12/12 [==============================] - 1s 111ms/step - loss: 1.7786 - accuracy: 0.8346 - val_loss: 1.5437 - val_accuracy: 0.8438

val accuracyが向上しました。一方val lossが上昇。正答率向上がうれしかったのでこちらを採用。

過学習を起こしていないか確認。

loss=history.history['loss']
val_loss=history.history['val_loss']
epochs=len(loss)

plt.plot(range(epochs), loss, marker = '.', label = 'loss')
plt.plot(range(epochs), val_loss, marker = '.', label = 'val_loss')
plt.legend(loc = 'best')
plt.grid()
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()

画像1

valがval lossに近いが追い越してはいないため、過学習は起こしていない。
(val lossが上昇していることは気になります。)
今回はこちらで完了とします。今後、”ウルトラマン”と”帰ってきたウルトラマン”のように判別難易度を上げていきたいです。


余談ですが、途中でGPUに変更しましたたところ速度が10倍になりました。しかし、学習結果が毎回変わるというデメリットが出現。再現性を考えると一長一短です。
そのため、ある程度はGPUで実行し、詰めは再現性ある環境で実施することが良いと考えます。


おまけ

顔だけ検出することで精度を上げようと考えました、そもそもウルトラマンをOpenCV人の顔のデータセットを用いての判別はできませんでした。



さいごに

プログラミングとはを少しかじった段階だと考えています。
分類、回帰、強化学習等どんなことができるのかなんとなくわかりました。
ディープラーニングは構造はわかりましたが、回答が出るまでの過程がミステリアスなイメージです。

今後、AI活用事例を見ていき、どのような構造か検討するなどで知見を広げていこうと思います。

自らの仕事で活用していくにはデータを整えるのが大変そうです。

チューターの皆さんとのカウンセリングが新鮮で楽しかったです。楽しんでいるだけで、おんぶにだっこ的なところがあったため、ちゃんと独り立ちしたいと思います。

ありがとうございました。


この記事が気に入ったらサポートをしてみませんか?