日向坂46の顔識別をしてみた KerasでResnet50によるfine tuning
開発環境
CPU: Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz
RAM: 24GB
GPU: NVIDIA GeForce GTX 1060 6GB
TensorFlow 1.13.1
Keras 2.2.4
はじめに
以前TensorFlowで作成した識別器では精度もまあまあぐらいだったためもうちょっと良くしたいなと思っていたのでやってみました.
以前の記事
Deep Learningでうまくいってる事例で思い浮かんだのが jirou_deep だったので調べてみたところ,どうやらfine tuning(ファインチューニング)というものを使用していることを知りました.
Large Scale Jirou Classification - ディープラーニングによるラーメン二郎全店舗識別
fine tuning(ファインチューニング)
既存のモデルの一部を再利用し,新しいモデルを構築する手法です.VGG16,VGG19,ResNet50などすでに訓練済みの代表的なモデルを利用するのがよくあるパターンみたいです.注意点として学習に使用されていた画像と実際に自分が学習させて識別したい画像がかけ離れているとうまくいかないことがあります.(知らずに1回やってしまいました)
モデル選定
fine tuningをやってみるまでは決まり,どのモデルを使うか探してみたところKerasで利用できる keras-vggface というモデルがあったのでこれを使ってKerasで実装することにしました.
caffe(オープンソースのディープラーニングライブラリ)からKeras用にモデルをコンバートしたものみたいです.顔認識用に学習されたVGG16,Resnet50,Senet50のモデルを利用することができます.このうちResnet50のモデルを利用してfine tuningを行うことにしました.
fine tuningでは元のモデルの重みを自分が用いるデータセットに合わせて再学習させることができます.Resnet50では以下のようなモデル構造になっています.
CNNの入力層に近い層では画像のおおまかな特徴を学習して深くなるほど特徴的な部分を学習するらしいのでfine tuningでは最後の方の層だけは重みを学習してそこまでの層は元のモデルの重みを使うのがよくあるパターンみたいです.
例に習い最後の色のブロック部分([3x3 conv, 512, /2]~[3x3 conv, 512])の重みだけ学習するようにしました.
全結合層ではFlatten()の代わりにGlobalAveragePooling2D()を利用しています.Flatten()と同じように出力を平滑化するのですがパラメータ数を減らすことができるので過学習を防ぎやすくなるそうです.
以前と同様でTFRecordから画像を読み込んでいます.モデルは学習用とテスト用を2つ作成し1エポックごとにlossとaccuracyを計算します.TensorBoardに学習時の画像を出力してみたところ,KerasでTFRecordから読み込む場合だと1ステップごとの学習画像が同じ処理になっていたみたいなので学習画像の水増し処理はランダムクロップとランダムフリップだけにしておきました.
学習,テストに用いた画像は前回と同じものを利用しました.(学習:約14000枚,テスト:約3600枚)
参考にしたブログなど
学習曲線 (train test)
accuracy
loss
testの最も良い地点での accuracy:98.1%,loss:0.1527 でした.
以前作成したモデルよりもaccuracyが向上して良さそうな感じです.
検証
何種類かの画像でテストしてみました.
間違っている場合には手動で✕つけてます.
・109
https://collabo-cafe.com/events/collabo/hinatazaka46-shibuya109-2019/
正答率:13/18 未検出:2
やたら佐々木久美に識別されてるのはなぜか分からない...明るめの画像がそういう識別をされているのかな?
・HINABINGO!
https://abematimes.com/posts/6043370
正答率:18/20 未検出:0
・ひらがなけやき1期生
https://ticket-trade.emtg.jp/artists/keyakizaka46/budokan2018/top
正答率:11/11 未検出:0
・ブログ画像 (5/2)
https://www.hinatazaka46.com/s/official/diary/detail/28896?ima=0000&cd=member
正答率:5/5 未検出:0
・Mステ
https://twitter.com/hinatazaka46/status/1119189265039052800
正答率:16/19 未検出:0
これは前回と同じ画像での検証ですが正答率13/19だったのが16/19になってます.
・うたコン
https://twitter.com/hinatazaka46/status/1115562448176599040
正答率:16/19 未検出:0
Mステの画像と同様金村美玖と富田鈴花が間違ってる...
検証用のソースコード
まとめ
・KerasでResnet50によるfine tuningをして顔認識をしてみた
・以前TensorFlowで1から学習したモデルよりも精度を高くすることができた
TensorFlowで日向坂46の顔識別をしてみた
始めたのが3月頭で就職活動本格スタートと同時期だったのですがこの記事を書いている時点で内々定が1つもありません.やや焦っています.
背景
ブログから画像をダウンロードするプログラムを書いたはいいもののメンバーごとに分けてダウンロードしても他のメンバーが結構介入してくるので(特に井口のブログとかめちゃくちゃ他のメンバーが出てくる),どうにかこれを自動的にメンバーごとに分類できる手法はないかと考え始めたのがきっかけです.
ブログに画像たくさんあるしディープラーニングで識別したらいいのでは?という安直な考えからスタートしました.しかし,授業でほんの少しだけ聞いたことがあるくらいでディープラーニングに関する知識量はほぼゼロに近くかなり苦労しました...
いくつか項目ごとに分けて説明していきたいと思います.
*実行環境*
OS: Windows 10 64bit
CPU: Core™ i7-7700HQ
RAM: 16GB
GPU: GTX 1050 (2GB)
目次
TensorFlowで日向坂46の顔識別をしてみた -ブログ画像ダウンロード編- - タイトル未定
TensorFlowで日向坂46の顔識別をしてみた -顔画像検出編- - タイトル未定
TensorFlowで日向坂46の顔識別をしてみた -TFRecord作成編- - タイトル未定
TensorFlowで日向坂46の顔識別をしてみた -学習編- - タイトル未定
TensorFlowで日向坂46の顔識別をしてみた -検証編- - タイトル未定
TensorFlowで日向坂46の顔識別をしてみた -検証編-
ブログ画像分類
1つ前で学習した約91%の精度のモデルを使ってブログ画像の分類をやってみました.
今回は
・顔を1人検出 → 識別したメンバーフォルダへ元画像をコピー
・顔を2人検出 → 検出した2人の顔を識別し,それぞれのメンバーフォルダへ元画像をコピー
とし,2人まで顔検出された画像について分類しました.
結果としては正答率的に7割前後かなぁ~...といったところです.
ほんの一部の結果だけ載せます.(合ってる画像には左上に後付で丸つけてます)
・宮田愛萌 1人顔検出
元の画像が他メンバーブログ↓からなので当初の目標を達成できてはいるんですがまあまあ間違いが多いです.
・丹生明里 2人検出
未知画像からの識別
もう1つ検証として未知画像の写真からの識別をやってみました.
先日のMステの時の集合写真を使いました.
このあと20時より、日向坂46がテレビ朝日系「ミュージックステーション」に初の生出演Ⓜ️
— 日向坂46 (@hinatazaka46) April 19, 2019
デビューシングル「キュン」をパフォーマンスいたします💕
ぽかぽかキュン☀️#日向坂46#キュン#Mステ pic.twitter.com/k5UEukarrl
・ 顔検出
・識別結果
13/19 = 68%くらいです.テストデータが人ごとに枚数の偏りがあるので個々の識別を平均して90%近く行かないのは仕方ないのですがまだまだ精度足りないかなと思いました.
改善点など?
・モデルの再考案
・Zero Shot Learningでメンバー以外の顔であるかどうかを識別したい
・deprecatedやwarning部分を書き直したい
tf.contribが将来のバージョンで削除されるので代替の方法を探す
tf.layers の部分をkeras.layers に対応した書き方に
・学習時にテストの結果もSummaryに表示させるようにしたい
・識別結果をOpenCVで画像表示してるのアホっぽいのでもっとイイ感じに表示したい
TensorFlowで日向坂46の顔識別をしてみた -学習編-
ようやく本題です.
モデル構築
モデルの構築に関しては全く知識が無かったので同様のことをやっていた先駆者の方々のものを参考にすることにしました.
結果的にこんな感じのモデルになりました.(Convnet Drawerで書いてます)
コード本体
長くなりすぎて 全部は説明できないので違う点についてのみ説明していきたいと思います.
1. Batch Normalization の追加
最初同じようにモデルを構築して学習を始めた際に,ステップが進んでいるのにaccuracyが10%前後から進まないことがありました.
似たような事例を探した結果Batch Normalizationを追加すると学習が進んだという記事を見つけたので追加した結果学習が進むようになりました.
2. 入力画像サイズ
96x96,112x112も試してみたのですが128x128の方がテストデータに対する結果がよかったので128x128にしてます.さらに画像サイズを大きくもしてみたのですが大差なかったので128x128で落ち着いています.大きくすれば良いというわけでもなさそう?
学習
構築したモデルを用いて学習を行っていきます.入力のところの説明は(面倒なので...)割愛しますが学習を回している部分では以下の処理を行っています.
・学習の実行
_, lossResult, accuracyResult = session.run([trainOp, model.loss, model.accuracy], feed_dict={model.keepProb: 0.75, model.isTraining: True})
・10stepごとに学習精度と損失関数の結果を出力
if step % 10 == 0:
numExamplesPerStep = FLAGS.batchSize examplesPerSec = numExamplesPerStep / duration secPerBatch = float(duration) formatStr = ('%s: step %d, loss = %.5f (%.1f examples/sec; %.3f sec/batch)') print(formatStr % (datetime.now(), step, lossResult, examplesPerSec, secPerBatch)) print('accuracy result', accuracyResult)
・25stepごとにsummaryを書き込み
if step % 25 == 0 or step == 1: summaryStr = session.run(summaryOp, feed_dict={model.keepProb: 1.0, model.isTraining: True}) summaryWriter.add_summary(summaryStr, step)
・1000stepごとに損失関数の結果が最小だったstepの学習結果を保存
if step % 1000 == 0 or step == FLAGS.maxSteps or lossResult < threshold: # 1000回ごとの最良を保存 if bestSession is not None: checkpointPath = os.path.join(FLAGS.trainDir, 'model.ckpt') saver.save(bestSession, checkpointPath, global_step = bestStep) bestResult.append(bestLossResult) bestSteps.append(bestStep) bestLossResult = 0.01 bestStep = None bestSession = None else: checkpointPath = os.path.join(FLAGS.trainDir, 'model.ckpt') saver.save(session, checkpointPath, global_step = step)
学習結果のモデルはlog/data_YYYY-MM-DD_hhmm/以下に「model.ckpt-(step).data-00000-of-00001, index, meta」の形で保存されます.
テスト
学習結果のモデルを読み込んでテストデータで検証を行います.
注意したいのは
・model.ckpt-XXXXX.data-00000-of-00001
・model.ckpt-XXXXX.index
・model.ckpt-XXXXX.meta
の3つのファイルがありますがRestoreするときに指定するファイル名は
model.ckpt-XXXXX
であるということです.
初期段階のtrain:約5400枚で70000stepほど回して学習し,test:約540枚で検証した結果81%の精度になりました.
この8割くらいの精度で顔画像をある程度識別してもう1度学習データを集め直し,train:約14000枚で学習,test:約3600枚で検証した結果91%まで上がりました.
次の記事
TensorFlowで日向坂46の顔識別をしてみた -TFRecord作成編-
ここでは学習用データの準備を行います.
CSVファイルからデータ情報を読み込む方法もあり,1度試したのですがTFRecordで実装した場合の方が楽で使用するGPUメモリの量も少ないです.(これは実感ですが)
手順1. 顔画像の振り分けを行う
切り取った顔画像が「誰」なのかという振り分けを行います.人力でやらないと行けない分ここがおそらく一番苦行でした...
こんな感じで一人あたり200~400枚くらいの顔画像を振り分けました.人によってバラつきがあるのはブログ更新頻度によって全く画像枚数が異なるので顔画像データに多い少ないの差があるためです.
手順2. 学習データとテストデータに分ける
3つ目のセルで全部の画像をtrainフォルダに移動させ,4つ目のセルで2割の画像をランダムにテストデータに振り分けてtestフォルダに移動させています.初期段階では画像数が少なかったので9割を学習データ,1割をテストデータにしていました.(testRateで変更できます)
trainフォルダへの移動
learningPath = "images/learning/" learningMemberPath = glob.glob(learningPath + "*") for index, memberPath in enumerate(learningMemberPath): learningMemberPath[index] = memberPath.replace("\\", '/') # それぞれのメンバーの学習用画像を全部取得 fileList = glob.glob(memberPath + "/" + "*") for imageFile in fileList: os.makedirs(memberPath + "/train/", exist_ok=True) shutil.move(imageFile, memberPath + "/train/" + imageFile.split("\\")[-1])
testデータの振り分け
# move test folder for memberPath in learningMemberPath: fileList = glob.glob(memberPath + "/train/*") testRate = 0.2 testNum = math.floor(len(fileList) * testRate) testFileList = random.sample(fileList, testNum) for imageFile in testFileList: os.makedirs(memberPath + "/test/", exist_ok=True) shutil.move(imageFile, memberPath + "/test/" + imageFile.split("\\")[-1])
例:matsudaフォルダ
train 241枚
test 60枚
手順3. TFRecordファイルを作成する
関数CreateTensorflowReadFile()では引数に「画像リスト」と「出力するTFRecordファイルの名前」を持っています.それぞれの画像を作成するモデルの入力画像のサイズ(ここではまだ挙げていませんが128x128x3を入力としています)にリサイズしバイト列として書き込んでいきます.他の情報として,「縦サイズ」「横サイズ」「ラベル(0 ~ 20)」「画像のパス」も記録するようにしています.
TFRecordを作成するにあたり以下のサイトを参考にしました.
output_tfrecords.ipnybの一番最後のセルは正常にTFRecordファイルが作成できているかを確認する用です.実行するとTFRecordファイル内に記録した画像を checkTFrecords フォルダに出力します.
これでようやく準備が整いましたので次は学習にうつっていきます.
次の記事
TensorFlowで日向坂46の顔識別をしてみた -顔画像検出編-
前提として
画像から人物識別を行うにあたって画像から顔を検出する必要があります.意外にこれが曲者でモノによって精度とかだいぶ変わってくるので顔検出の選定は重要だと思います.
何種類か見て回ったり試したりしました.
・Haar-like特徴量
ブログなどをいろいろ見て回った結果誤検出が多いなど結構微妙っぽいので断念.
・dlibを利用した顔検出
顔のポイント68ヶ所を取得できるモデルを利用して検出する方法を試しました.(下のブログを参考にさせていただきました.)
Python(OpenCV) 顔が上を向くように写真から切り出す方法 – console dot log
精度は悪くなかったのですが画像を回転させないと検出できない場面が多く非常に時間がかかってしまい泣く泣く別の方法を探すことにしました.
・Tensorflow Face Detector
TensorFlow物体検出APIを用いてWIDERFACE データセットを学習したモバイルネットSSDベースの顔検出モデルらしいです.最終的にこの手法を利用することにしました.
https://github.com/yeephycho/tensorflow-face-detection
inference_video_face.py を参考にしてコードを書いています.
モデルなどはそのまま利用しています.もとのTensorflow Face Detectorでは人物の顔を長方形のBounding Boxで切り取っています.しかし,TensorFlowで学習する際に利用する画像は縦横のサイズを合わせる必要があるため長辺に合わせて短辺の長さを伸ばしてBounding Boxを正方形にしています.
本来はなくてもいいんですが,検出された顔の数でフォルダを分けるようにしています.後に学習用データを収集する時に少しでも楽にしようという考えでこのようにしています.(1人だけ写っている→おそらく自撮りの確率が高いので本人の顔画像が収集しやすい,2人写っている→ツーショットの画像なら本人の顔画像が収集しやすい)
分類した結果を載せておきます.
これがもとのフォルダ
1人検出
2人検出
3人以上検出
検出人数ゼロ
たまに顔ではないものを誤検出してしまったり,顔が検出できていないものもありますが概ね想定どおりに動いてくれて結構いい感じに検出できてるんではないでしょうか.
次は検出した顔画像をベースに学習用データを作っていきます.
次の記事
TensorFlowで日向坂46の顔識別をしてみた -ブログ画像ダウンロード編-
今回は顔識別をするきっかけとなってしまったブログ画像をダウンロードするコードについてです.
やっていることとしては
・メンバーごとのブログトップページに飛ぶ
・img src= から画像のURLを取得してリストに保存
・ブログ内以外の画像であるロゴとかバナー(ページ一番下のマギレコのやつとか)をリストから削除(removeUrls)
・次のページへ...
というのを最後のページまで繰り返したところで一気に保存します.
終わるとこんな感じで保存されます.
次の記事