fine tuningで顔識別(福士蒼汰vs中川大志)をやってみた

はじめに

前回、fine tuningで味を占めたので、ちょっと同じようなパターンで変わり種の識別をいくつかやっていきたいと思います.

shirakonotempura.hatenablog.com

今回やること:『福士蒼汰』と『中川大志』の2クラス分類

早速ですが、今回は顔認識に挑戦したいと思います.対象は俳優福士蒼汰氏と中川大志氏.よく見たらもちろん違う顔なのですが、パッと見、見間違いかねないこの2人の顔識別を前回と同じくfine tuningを用いてやってみたいと思います.

福士蒼汰と中川大志の見分け方 - NAVER まとめ

顔データの収集にあたっては、以下記事を参考にして顔部分の切り出しを行いました.ありがとうございます.

qiita.com

私は、毎回google.colab.files.upload()を使って、ファイルのアップロードをしているのですが、Google Driveをマウント(使えるように認識させるくらいの認識でOKです)しておくとGoogle Drive上のファイルを参照できるようになります.最初にいくつか設定が必要ですが、この方法の方が手間と時間を短縮できるようです.

用意した画像

集めた画像を吟味して、ちゃんと本人の顔だけを選択しました.結局手元に残ったのは以下の枚数.これを水増しして学習用データとしたいと思います.(もちろんテストデータは最後の確認用なので水増ししません)

福士さん(fukushi):訓練用133枚、検証用20枚、テスト用30枚
中川さん(nakagawa):訓練用107枚、検証用20枚、テスト用30枚

こんな感じのデータをたくさん作成します.(念のため、モザイクかけておきます.モザイク越しでもイケメンぶりが伝わってきますね・・) f:id:shirakonotempura:20190116032657j:plain

上記のような画像が準備できましたら、以降はほぼ前回と同じことの繰り返しです.ただし、今回は2クラス分類である点が前回(4クラス分類)と異なりますので、該当する部分は若干変更する必要があります.

fine tuningによる画像識別

  • フォルダ構成について
    • 扱う問題は2クラス問題とする[fukushi, nakagawa]
    • images_dirの下にtrain, valid, testディレクトリがあり、それぞれにはfukushi, nakagawaのディレクトリを作成し、写真を保存.
    • 同じくimages直下に作成するdisplayディレクトリにはtestディレクトリと同じデータをfukushi, nakagawaに分けずに保存
    • 学習に適さないデータは削除しておく


* 検討の方針: * VGG16の学習済み結果をベースに、一部重みを変更するFine Tuningを行う. * 次に、fit_generator()を利用して、水増ししながら学習を行う


不必要なデータの削除は手作業となります.ちゃんとチェックしておかないと,たまに誰?みたいなおばあさんが保存されたりします.これは顔のトリミングはあくまで顔検出で行っているので,最初にダウンロードした写真が例えば舞台挨拶の写真だったり、テレビのキャプチャだったりした場合、下図のように写っている顔全てを切り出してしまいます.あっ、千眼さん・・.

f:id:shirakonotempura:20190116033752j:plain
フォーゼは顔として認識せず

VGG16モデルの構築およびコンパイル

主な手順としては、
* VGG16モデルと学習済みの重みをロード
* 今回の分類用の全結合層を構築
* VGG16とFC層を接続
* 学習させない層を定義
* モデルのコンパイル

今回は2クラス分類なので、model.compile()で定義するロス関数はloss = binary_crossentropyとして2値識別用の評価関数を定義しています(多クラスの場合はcategorical_crossentropy).
また、最終層の活性化関数は2値分類で使用するシグモイド関数とし、Dense(1, activation = "sigmoid")としています.(最初の引数は1です.カテゴリー数の2とは違いますので注意).

# 必要なライブラリのインポート
from keras.models import Model
from keras.layers import Dense, GlobalAveragePooling2D,Input
from keras.applications.vgg16 import VGG16
from keras.preprocessing.image import ImageDataGenerator
from keras.optimizers import SGD
from keras.callbacks import CSVLogger

# 今回は2カテゴリに分類する
n_categories=2
batch_size=32
img_size = 200

# 画像データを保存しているディレクトリの定義
train_dir="images/train"
validation_dir="images/valid"
test_dir="images/test"

# display_dirの中身はテストデータと同じもの.ただし、クラスごとのフォルダ分けはしていない
display_dir='images/display'

file_name='face_finetuning_vgg16'

# VGG16の既存の全結合層は、1000クラス分類用なので使えない→Falseで削除
vgg16_model=VGG16(weights='imagenet',include_top=False,
                 input_tensor=Input(shape=(img_size,img_size,3)))

# 全結合層(FC層)を構築
x=vgg16_model.output
x=GlobalAveragePooling2D()(x)
x=Dense(1024,activation='relu')(x)
prediction=Dense(1, activation='sigmoid')(x)

# vgg16と全結合層をつなぐ
model=Model(inputs=vgg16_model.input, outputs=prediction)

# 最後のCONV層の直前までの層を更新しない(freeze)
for layer in vgg16_model.layers[:15]:
    layer.trainable=False

# fine-tuningにおいては、optimizerはSGDがいいらしい.
model.compile(optimizer=SGD(lr=0.0001,momentum=0.9),
              loss='binary_crossentropy', # 2クラスの場合はbinary_crossentropy
              metrics=['accuracy'])

model.summary()

#save model
json_string=model.to_json()
open(file_name+'.json','w').write(json_string)

必要パラメータの定義

epochs はエポックの数 num_trainingおよびnum_validation:generatorで繰り返し作成する画像の枚数.

epochs = 50
num_training = 1600 
num_validation = 400
num_of_test = 60
label = ["fukushi","nakagawa"] 

fit_genratorを使って学習

ImageDataGeneratorで学習データの水増しを行います.train_generatorおよびvalidation_generatorで定義しているclass_modeをclass_mode = "binary"としています.(多クラスの場合は"categorical"

train_datagen=ImageDataGenerator(
    rescale=1.0/255,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True)

validation_datagen=ImageDataGenerator(
    rescale=1.0/255
)

train_generator=train_datagen.flow_from_directory(
    train_dir,
    target_size=(img_size,img_size),
    color_mode = "rgb",
    batch_size=batch_size,
    class_mode='binary', # categoricalからbinaryに変更
    shuffle=True
)

validation_generator=validation_datagen.flow_from_directory(
    validation_dir,
    target_size=(img_size,img_size),
    color_mode = "rgb",
    batch_size=batch_size,
    class_mode='binary',
    shuffle=True
)

hist=model.fit_generator(train_generator,
                         steps_per_epoch = num_training//batch_size,
                         epochs=epochs,
                         verbose=1,
                         validation_data=validation_generator,
                         validation_steps = num_validation//batch_size,
                         callbacks=[CSVLogger(file_name+'.csv')])

#save weights
model.save(file_name+'.h5')

テストデータによる結果

学習が終われば、テストディレクトリに入っているデータ(合計60枚)を使って精度の確認を行います.

# テストデータによる評価
from keras.models import model_from_json
import matplotlib.pyplot as plt
import numpy as np
import os,random
from keras.preprocessing.image import img_to_array, load_img


#load model and weights
json_string=open(file_name+'.json').read()
model=model_from_json(json_string)
model.load_weights(file_name+'.h5')

model.compile(optimizer=SGD(lr=0.0001,momentum=0.9),
              loss='binary_crossentropy',
              metrics=['accuracy'])

#data generate
test_datagen=ImageDataGenerator(rescale=1.0/255)

test_generator=test_datagen.flow_from_directory(
    test_dir,
    target_size=(img_size,img_size),
    batch_size= num_of_test,
    class_mode='binary',
    shuffle=True
)

#evaluate model
score=model.evaluate_generator(test_generator, steps = 1) 
print('\n test loss:',score[0])
print('\n test_acc:',score[1])

# 以下は図で表示するためのスクリプト
#predict model and display images
nb_of_disp = 16
nb_of_row = np.sqrt(nb_of_disp)
nb_of_col = np.sqrt(nb_of_disp)
files=os.listdir(display_dir)
img=random.sample(files,nb_of_disp)

plt.figure(figsize=(10,10))
for i in range(nb_of_disp):
    temp_img=load_img(os.path.join(display_dir,img[i]),target_size=(img_size,img_size))
    plt.subplot(nb_of_row, nb_of_col,i+1)
    plt.imshow(temp_img)
    #Images normalization
    temp_img_array=img_to_array(temp_img)
    temp_img_array=temp_img_array.astype('float32')/255.0
    temp_img_array=temp_img_array.reshape((1,img_size,img_size,3))
    #predict image
    img_pred=model.predict(temp_img_array)
    #img_pred が0.5以下ならfukushi, 0.5以上ならnakagawa
    if img_pred <= 0.5:
      plt.title(label[0])
    else:
      plt.title(label[1])
    #eliminate xticks,yticks
    plt.xticks([]),plt.yticks([])


plt.show()


気になる精度ですが、テストデータに対して識別精度87%となりました.2値識別なので決して高い制度ではないですが,得られたデータが少ないことを考えるとぼちぼちといったところでしょうか.おそらく30代・40代男性の程度の識別率には達したはずです.そもそも2人を知らないなんて人は新入社員と会話できるくらいには覚えておきましょう.

Found 60 images belonging to 2 classes.

 test loss: 0.34022757411003113

 test_acc: 0.8666666746139526

f:id:shirakonotempura:20190116034828p:plain
テストデータに対する予測結果の一部(学術研究ということで著作権、ご理解ください!)

まとめ

fine tuningを使って、福士蒼汰氏と中川大志氏の顔認識を行いました.
2クラス分類としては精度87%といまいちでしたが、個人的には満足です.おそらく私より精度いいですし.
参考とさせていただいたこちらの記事でやられている、ぼかし処理や閾値処理などをすればもう少し上がるかもしれませんが、とりあえずここまで.

Colaboratoryのノートはコチラにおいておきます.