実践型AIプログラミング特講 karasによる動画認識について その2 #35

実践型AIプログラミング特講 karasによる動画認識について その2  #35

今回は前回に引き続いて、pythonのライブラリであるkerasを用いたニューラルネットワーク実装して、映像認識をしてみましょう。

前回の内容から少々レベルが上がっているので最後まで食って下がるくらいの心持ちで望んでみて下さい。これは私の至らない点でもありますが、もしご不明な点があれば、各々検索エンジンで調べながらコーディングの意図を理解していただけると幸いです。また、検索する行為自体恥ずべきことではなく、それもシステムエンジニアとして必要な素養なので是非そういう力もこれを機に磨いてみて下さい。

検証セットの作成

検証セットを作成するためには、トレーニングセットと検証セットの両方で、各クラスの分布が似ていることを確認する必要があります。そのためにはstratifyパラメータを使います。

# ターゲットの分離

y = train[‘class’].

# トレーニングセットとバリデーションセットの作成

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42, test_size=0.2, stratify = y)

ここで、stratify = y (各フレームのクラスまたはタグ)は、トレーニングセットと検証セットの両方で、クラスの類似した分布を保持する役割です。覚えておいてほしいのは、ビデオを分類できるカテゴリーは101あるということです。そのため、ターゲットに101個の異なる列を作成する必要があります。そのために、get_dummies()関数を使います。

# 訓練セットと検証セットのターゲット変数のダミーの作成

y_train = pd.get_dummies(y_train)

y_test = pd.get_dummies(y_test)

次のステップ は 映像分析モデルのアーキテクチャを定義します。

動画分類モデルのアーキテクチャの定義

私たちはそれほど大きなデータセットを持っていないので、ゼロからモデルを作るのはうまくいかないかもしれません。そこで、事前に学習したモデルを使用し、その学習結果を利用して問題を解決しましょう。今回のデータセットでは、VGG-16の学習済みモデルを使用します。それでは、事前に学習されたモデルのベースモデルを作成しましょう。

# 学習済みVGG16モデルのベースモデルの作成

base_model = VGG16(weights=’imagenet’, include_top=False)

このモデルは、1,000個のクラスを持つデータセットで学習されました。このモデルを必要に応じて微調整します。include_top = Falseにすると、このモデルの最後のレイヤーが削除され、必要に応じて調整できるようになります。

次に、トレーニング画像と検証画像のために、事前にトレーニングされたモデルから特徴を抽出します。

# トレーニングフレーム用の特徴量の抽出

X_train = base_model.predict(X_train)

X_train.shape

出力を見てみましょう。

(59075, 7, 7, 512)

トレーニングセットには59,075枚の画像が含まれており、これらの画像をVGG16アーキテクチャに通しているため、形状は(7, 7, 512)に変更されています。同様に、検証フレーム用の特徴量を抽出します。

# 検証用フレームの特徴量の抽出

X_test = base_model.predict(X_test)

X_test.Shape

出力してみると

(14769, 7, 7, 512)

検証セットには14,769枚の画像があり、これらの画像の形状も(7, 7, 512)に変更されています。これから完全連結ネットワークを使って、モデルを微調整していきます。この完全連結ネットワークは、一次元の入力を受け取ります。そこで、画像の形状を一次元に変更します。

# トレーニングフレームと検証フレームを一次元に再形成する

X_train = X_train.reshape(59075, 7*7*512)

X_test = X_test.reshape(14769, 7*7*512)

ピクセル値を正規化することは、常に推奨されています。つまり、ピクセル値を0と1の間に保つことです。

# 画素値の正規化

max = X_train.max()

X_train = X_train/max

X_test = X_test/max

次に、モデルのアーキテクチャを作成します。そのためには、入力形状を定義しなければなりません。そこで、画像の形状を確認してみましょう。

# 画像の形状

X_train.shape

OUTPUT (59075, 25088)

入力形状は25,088になります。それでは、アーキテクチャを作成してみましょう。

#モデルのアーキテクチャを定義する

model = Sequential()

model.add(Dense(1024, activation=’relu’, input_shape=(25088,)))

model.add(Dropout(0.5))

model.add(Dense(512, activation=’relu’))

model.add(Dropout(0.5))

model.add(Dense(256, activation=’relu’))

model.add(Dropout(0.5))

model.add(Dense(128, activation=’relu’))

model.add(Dropout(0.5))

model.add(Dense(101, activation=’softmax’))

完全に接続された密な層が複数あります。モデルがオーバーフィットしないように、ドロップアウト層も追加しています。最終層のニューロンの数はクラスの数と同じなので、ここでのニューロンの数は101となります。

 動画分類モデルの学習

次に、トレーニングフレームを使用してモデルをトレーニングし、検証フレームを使用してモデルを検証します。モデルを何度も再学習する必要がないように、モデルの重みを保存しておきます。

そこで、モデルの重みを保存するための関数を定義してみましょう。

# ベストモデルの重みを保存する関数の定義

from keras.callbacks import ModelCheckpoint

mcp_save = ModelCheckpoint(‘weight.hdf5′, save_best_only=True, monitor=’val_loss’, mode=’min’)

検証損失に基づいて、最適なモデルを決定します。なお、重みはweights.hdf5として保存されますが、必要に応じてファイル名を変更できます。モデルを学習する前に、モデルをコンパイルしなければなりません。

# モデルのコンパイル

model.compile(loss=’categorical_crossentropy’,optimizer=’Adam’,metrics=[‘accuracy’])

損失関数としてcategorical_crossentropyを、オプティマイザーとしてAdamを使用しています。それでは、モデルをトレーニングしてみましょう。

# モデルの学習

model.fit(X_train, y_train, epochs=200, validation_data=(X_test, y_test), callbacks=[mcp_save], batch_size=128)

200回のエポックでモデルを学習しました。これで、新しい動画を予測するための重みができました。さて、次のセクションでは、このモデルが動画分類のタスクでどれだけの性能を発揮するかを見てみましょう。

動画分類モデルを評価する

モデルを評価するために、新しいJupyterノートブックを開いてみましょう。評価の部分は複数のステップに分けることで、より明確に理解することができます。

1.モデルのアーキテクチャを定義し、重みをロードする

2.テストデータの作成

3.テスト動画の予測を行う

4.最後に、モデルを評価する

 モデルアーキテクチャの定義と重みの読み込み

最初のステップである、必要なライブラリのインポートには慣れていると思います。

from keras.models import Sequential

from keras.models import Sequential from keras.layer import Dense, Dropout, Flatten

from keras.layers import Conv2D, MaxPooling2D

keras.preprocessing import image

import numpy as np

import pandas as pd

from tqdm import tqdm

from keras.applications.vgg16 import VGG16

import cv2

import Math

import os

from glob import glob

from scipy import stats as s

次に、モデルのアーキテクチャを定義しますが、これはモデルのトレーニング時と同様のものです。

base_model = VGG16(weights=’imagenet’, include_top=False)

これが事前に学習されたモデルで、次にそれを微調整します。

#モデルアーキテクチャの定義

model = Sequential()

model.add(Dense(1024, activation=’relu’, input_shape=(25088,)))

model.add(Dropout(0.5))

model.add(Dense(512, activation=’relu’))

model.add(Dropout(0.5))

model.add(Dense(256, activation=’relu’))

model.add(Dropout(0.5))

model.add(Dense(128, activation=’relu’))

model.add(Dropout(0.5))

model.add(Dense(101, activation=’softmax’))

アーキテクチャの定義が完了したので、weights.hdf5に保存した学習済みの重みをロードします。

# 学習済みの重みの読み込み

モデル.load_weights(“weights.hdf5”)

モデルのコンパイルも行います。

# モデルをコンパイルします。

model.compile(loss=’categorical_crossentropy’,optimizer=’Adam’,metrics=[‘accuracy’])

損失関数、オプティマイザー、メトリクスがモデルの学習時に使用したものと同じであることを確認してください。

 テストデータの作成

UCF101データセットの公式ドキュメントにあるように、train/testのスプリットファイルをダウンロードしておく必要があります。ダウンロードしていない場合は、こちらからダウンロードしてください。ダウンロードしたフォルダの中に、「testlist01.txt」というファイルがあり、その中にテスト動画のリストが入っています。これを利用してテストデータを作成します。

# テストリストの取得

f = open(“testlist01.txt”, “r”)

temp = f.read()

videos = temp.split(‘n’)

# データフレームの作成

test = pd.DataFrame()

test[‘video_name’] = videos

test = test[:-1]

test_videos = test[‘video_name’].

test.head()

これで、すべての動画のリストがデータフレームに格納されました。予測されたカテゴリと実際のカテゴリを対応させるために、train_new.csvファイルを使用します。

# タグの作成

train = pd.read_csv(‘UCF/train_new.csv’)

y = train[‘class’]

y = pd.get_dummies(y)

それでは、テストセットのビデオに対する予測を行います。

テスト動画の予測値を生成する

コードを見る前に、このステップで行うことをまとめておきます。以下の手順を見れば、予測の部分が理解できると思います。
まず、2つの空のリストを作成します。1つは予測を格納するため、もう1つは実際のタグを格納するためです。
次に,テストセットから各ビデオを取り出し,そのビデオのフレームを抽出して,あるフォルダに格納します(フレームを格納するために,カレントディレクトリにtempという名前のフォルダを作成します)。このフォルダから他のすべてのファイルを削除していきます。
次に、temp フォルダからすべてのフレームを読み込み、事前に学習したモデルを使用してフレームの特徴を抽出し、タグを予測し、特定のビデオにタグを割り当ててリストに追加するモードを実行します。2つ目のリストに、各ビデオの実際のタグを追加します。これらの手順をコード化して、予測値を生成してみましょう。

# 予測タグと実際のタグを格納する2つのリストの作成

predict = []

actual = []

各テストビデオからフレームを抽出する # for ループ

for i in tqdm(range(test_videos.shape[0])):

count = 0

videoFile = test_videos[i].

cap = cv2.VideoCapture(‘UCF/’+videoFile.split(‘ ‘)[0].split(‘/’)[1])   # 与えられたパスからビデオをキャプチャする

frameRate = cap.get(5) #フレームレート

x=1

# tempフォルダから他のファイルをすべて削除

files = glob(‘temp/*’)

for f in files:

os.remove(f)

while(cap.isOpened()):

frameId = cap.get(1) #現在のフレーム番号

ret, frame = cap.read()

if (ret != True):

break

if (frameId % math.floor(frameRate) == 0):

# このビデオのフレームをtempフォルダに格納する

filename =’temp/’ + “_frame%d.jpg” % count;count+=1

cv2.imwrite(filename, frame)

cap.release()

# tempフォルダから全てのフレームを読み込む

images = glob(“temp/*.jpg”)

prediction_images = [].

for i in range(len(images)):

img = image.load_img(images[i], target_size=(224,224,3))

img = image.img_to_array(img)

img = img/255

prediction_images.append(img)

# テスト動画の全フレームをnumpy配列に変換

prediction_images = np.array(prediction_images)

# 事前に学習したモデルを使って特徴量を抽出

prediction_images = base_model.predict(prediction_images)

# 特徴量を1次元配列に変換

prediction_images = prediction_images.reshape(prediction_images.shape[0], 7*7*512)

# 各配列に対するタグの予測

prediction = model.predict_classes(prediction_images)

# 予測リストのモードを追加してタグをビデオに割り当てる

predict.append(y.columns.values[s.mode(prediction)[0][0]])

# ビデオの実際のタグを追加する

actual.append(videoFile.split(‘/’)[1].split(‘_’)[1])

テストセットには約3,800本のビデオが含まれているので、このステップには時間がかかります。予測結果が出たら、モデルの性能を計算します。

モデルを評価する

モデルを評価確認しましょう。実際のタグと、モデルが予測したタグがあります。これらを利用して、精度のスコアを求めます。UCF101の公式ドキュメントページでは、現在の精度は43.90%です。私たちのモデルはこれに勝てるでしょうか?確認してみましょう。

# 予測されたタグの精度のチェック

from sklearn.metrics import accuracy_score

accuracy_score(predict, actual)*100

output 44.80570975416337

いいですねー。私たちのモデルの精度は44.8%で、公式ドキュメントに記載されている値(43.9%)に匹敵します。

なぜ50%以下の精度で満足できるのか、不思議に思われるかもしれません。この低い精度の理由は、主にデータの不足によるものです。私たちは約13,000本のビデオを保有していますが、それらも非常に短い時間のものです。

いかがでしたでしょうか。いかがでしたでしょうか、少々小難しい話になってきましたね。ただ、これを全て理解する必要はありません。少しずつ、各コードが何の意味をなしているのか感覚的に理解できることが大切なので誰もコーディングしたものをその場ですんなり理解できる人は、たとえ現場のSEでも難しいでしょう。なので着実に少しづつ進歩していることを信じ学習を継続していただけたら幸いです。

 

プログラミングカテゴリの最新記事