以前から書いているDeep Learningによるアイドル顔識別の話の続き。
コツコツと顔画像収集とラベル付けを続けて、そこそこにデータが集まってきたので ここらでちゃんと性能評価をしてみよう、と。
データセットの作成
今回は、現時点で重複なく180件以上の顔画像が集まっている40人のアイドルを分類対象とした。
これらのアイドルに分類のラベルindexを振り(推してる順とかじゃなくてランダムにね)、それぞれから無作為に抽出した180件の顔画像をそれぞれラベルとセットでレコードを作り、シャッフルして30件ずつ6つのデータセットに分けて保存。
data-00.tfrecords data-01.tfrecords data-02.tfrecords data-03.tfrecords data-04.tfrecords data-05.tfrecords
レコードは、以前の記事に書いた TFRecord 形式。
それぞれのファイルには 40人 × 30件 = 1,200 のデータが含まれることになる。画像サイズは112 x 112
。
中身を並べてみるとこんな感じ。
まぁ40人、教室1クラスぶんの人数くらいだし、見慣れてる人が見れば簡単に見分けられますね。
アイドル顔画像データセット(取り下げました)
Cross Validation での評価
学習データに対していくら正確に分類できても、過学習していて学習データ以外のものに対しては正確な分類ができない可能性もある。また学習データに偏りがあっては汎化性能は測れない。 そういったことが起きていないか確認し、精度を検証するための手法として"Cross Validation (交差検証)"というものがあるそうで。 ここでは K-fold Cross Validation と呼ぶようなやり方を試してみる。
上記のデータセットは合計7,200件の「顔画像・ラベル」のセットがあるが、各1,200件の6つのファイルに分けているので、これらを学習用:評価用で5:1に分割し
- Case #0:
data-01,02,03,04,05
で学習し、data-00
で性能評価 - Case #1:
data-00,02,03,04,05
で学習し、data-01
で性能評価 - Case #2:
data-00,01,03,04,05
で学習し、data-02
で性能評価 - Case #3:
data-00,01,02,04,05
で学習し、data-03
で性能評価 - Case #4:
data-00,01,02,03,05
で学習し、data-04
で性能評価 - Case #5:
data-00,01,02,03,04
で学習し、data-05
で性能評価
の6つの学習・評価パターンを作って それぞれ場合の結果を調べてみる。 どの場合も学習用データ6,000件/評価用データ1,200件(40人それぞれ学習用に150件、評価用に30件ずつ含まれることになる)となり、評価用データは学習データには無いものたちなので汎化性能が測れるし、もしデータセットに偏りがあればどこかのケースで異常な結果になったりするはず。
評価結果
評価対象の分類モデルは、以前の記事で作った、4層の畳み込み&プーリング層と3層の全結合層からなるモデル。
入力は 96 x 96 x 3
のカラー画像で、以前と同様にランダムで切り出したり色味を変えたりして128サイズのmini batchを作り、cross entropyにweight decayを加えたものをAdamOptimizer
でminimizeするよう学習する。
これをそれぞれのCaseで8,000 stepまで進めた。
各stepでの、評価データに対する正答率は以下の通りとなった。
step | Case #0 | Case #1 | Case #2 | Case #3 | Case #4 | Case #5 | AVERAGE |
---|---|---|---|---|---|---|---|
0 | 3.667 | 5.083 | 2.250 | 2.500 | 2.500 | 4.750 | 3.458 |
1000 | 87.417 | 84.583 | 84.667 | 80.000 | 83.750 | 84.167 | 84.097 |
2000 | 91.833 | 90.583 | 90.417 | 89.000 | 91.500 | 90.917 | 90.708 |
3000 | 93.000 | 90.750 | 92.667 | 89.833 | 93.083 | 90.833 | 91.694 |
4000 | 93.083 | 94.000 | 93.417 | 92.583 | 92.583 | 94.500 | 93.361 |
5000 | 95.167 | 94.417 | 94.583 | 91.250 | 94.417 | 95.333 | 94.195 |
6000 | 96.083 | 93.667 | 94.500 | 93.583 | 94.667 | 94.500 | 94.500 |
7000 | 95.667 | 94.250 | 94.667 | 92.750 | 94.500 | 94.833 | 94.445 |
8000 | 95.333 | 94.750 | 94.667 | 93.583 | 95.583 | 96.333 | 95.042 |
グラフにするとこんな感じ。
100%付近を大きめにすると
まぁ95%よりちょっと下くらいに落ち着いているかな、と…。まだもう少し続ければ上昇しそうな傾向ではあるけど、こんなところで(CPUマシンだとめっちゃ時間かかるしキツい…)。
考察
どのケースの場合も同様の正答率に落ち着いているし、特にデータセットに依存するものではなく汎化能力として94%程度の正答率が得られる、と言える、はず。 Weight Decayのおかげなのか、少なくとも8,000 step程度では特に過学習となって評価用データに対する正答率が落ちたりはしないようだ。
この「約94%」という正答率を十分高いと捉えるか、まだ低いと捉えるか。 もうちょっと入力データを弄ったり(random distortの範囲を広げる、とか)モデルの構造を改良したり(畳み込みカーネルサイズを変える、とか 畳み込み層をもう1段深くする、とか)で改善する余地はあるかもしれない。 どういうデータに対して上手く分類できて、どういうものに対し失敗しているのか、などの傾向を調べてみると何か分かるかもしれない。
けど、とりあえず「1分類あたり150件の学習データ」で94%くらいの汎化性能があるならまずは十分かな、と。 出来ることならもっと多くのデータと分類数で試したかったけど現時点で用意できたのがこれだけだったので、もっとデータ数が増えればまた結果も少し変わってくるだろうし。 その上でもっと正答率を高めたい、となったときにまた改善方法を検討したい。
実験
現状のモデルがこれくらいの精度だということが分かったので、ここで入力やモデルを色々と変えて正答率がどう変化するか、などを試してみる。
上述のCross Validationでデータセットの偏りは無い、と判断したので(資源の都合もあり)ここから先はCase #0
のみで試した。
1. パラメータを減らす
以前の記事で作った分類モデルは、その後ちょっとパラメータ数を変えて
96 * 96 * 3
→48 * 48 * 32
の畳み込み&プーリング層48 * 48 * 32
→24 * 24 * 48
の畳み込み&プーリング層24 * 24 * 48
→12 * 12 * 72
の畳み込み&プーリング層12 * 12 * 72
→6 * 6 * 108
の畳み込み&プーリング層3888 (= 6 * 6 * 108) * 1024
の全結合層1024 * 256
の全結合隠れ層256 * <number of classes>
の全結合出力層
としていた。が、本当にこれだけのパラメータが必要なのか?もっと減らしても大丈夫なのでは?という疑問があったので 試してみることにした。
1.1 全結合層のユニット数
今使っている分類モデルはTensorFlow Tutorial の CIFAR-10 Modelを参考にして作ったのだけど、そこでは"sparsity"という値をsummaryで出力している。
tf.scalar_summary(tensor_name + '/sparsity', tf.nn.zero_fraction(x))
これはtf.nn.zero_fraction
つまり出力Tensorのうち値が0
となっているものの割合を示している。
全結合層においては出力0
のユニットは次の層への入力に影響を及ぼさない(次の層との接続の重みの値が幾らであっても0 * w
は0
になるだけ)ので、存在しなくても結果は変わらない、はず。
このsparsityは当然 学習の過程で上下していくのだけど、(5.)最初の全結合層と次の(6.)隠れ層での値の遷移をみてみると
となっていて、特に最初の全結合層では徐々に上昇して最終的に0.96
くらいにまで上がる。
これはつまり学習が進むにつれてこの層で出力が必要なユニット数が減っていっていて ある程度まで進んだ段階では多くのユニットが0
しか出力しなくなっている、ということ。
(5.)の全結合層では1024
のユニットを用意していたけど、結局次の層の入力で使われるのは1024 * (1 - 0.96)
≒41
個程度、ということ。
こんなにたくさん用意する必要ないのでは…。てことで、(一応余裕を持たせて)96
まで減らしてみる。
同様に(6.)の隠れ層も、256
ユニット用意していたけれど そのうち0.75
くらいは出力が0
になる、ってことなのでこれも半分くらいまで減らしても問題なさそう、ということで128
に減らす。
1.2 畳み込み&プーリング層のchannel数
前半の畳み込み&プーリング層の方も同様に無駄な部分あれば減らしたいところだけど、構造が全結合とは違うので単純にsparsityだけで判断するのは良くないかな、と。
こちらは2次元の出力ではなく12 * 12 * 72
→ 6 * 6 * 108
のように3次元から3次元への変換となるので、各層でそれぞれのchannelごとに出力を見てみることにした。
学習済みモデルにおける、ある入力画像に対しての各層での各channelの出力を適当に可視化してみた結果。
1層目
2層目
3層目
4層目
ちょっと分かりづらいけど、2, 3層目では幾つかの四角が真っ黒になっていて完全に出力が0
になっている。
小さくて全然分からないけど4層目は特に、半分くらいのものが黒くなっていて108
も要らなそうだったので前段の層と同じchannel数72
に減らしてみた。
ということで
96 * 96 * 3
→48 * 48 * 32
の畳み込み&プーリング層48 * 48 * 32
→24 * 24 * 48
の畳み込み&プーリング層24 * 24 * 48
→12 * 12 * 72
の畳み込み&プーリング層12 * 12 * 72
→の畳み込み&プーリング層6 * 6 * 108
6 * 6 * 72
に削減
の全結合層3888 (= 6 * 6 * 108) * 1024
2592 (= 6 * 6 * 72) * 96
に削減
の全結合隠れ層1024 * 256
96 * 128
に削減
の全結合出力層256 * <number of classes>
128 * <number of classes>
に削減
という変更をしてみる。
結果
正答率の変化を 前述のCross Varidationの全ケース平均と比較すると
となり、ほぼ同様の変化を示して最終的に94%程度まで上がった。sparsityの変化は
となり、当然ながら分母が減っているので より小さな値で落ち着くようになった。
効果としては、別に精度が上がるわけではないけれど より少ないパラメータ数で同程度の分類性能を出せるようになる、ということ。
CPUマシンで試していた限りでは計算速度にはほとんど差異は感じられなかった(計算グラフには変化が無いから?)が、パラメータを保存・復元するcheckpointファイルのサイズは元々が67MB
ほどだったものが5.5MB
と、1/10以下まで軽くなった。これはちょっと嬉しいかもしれない。
2. 色情報を落とす
入力にはRGBカラー画像を使っていたけど、これがもしグレースケールの画像だったらどうなるだろうか。
自分で目視するぶんには、グレースケールでも輪郭や髪型、表情などの情報は十分に得られるし、顔としての識別はできる。 機械学習だとそのあたりはどうなるだろうか?
データセットから入力mini batchを作る際に、tf.image.decode_jpeg
でJPEGバイナリから画像を復元するが、その際にchannels
オプションを1
に指定することでグレースケール画像として読み込むことができる。
あとはdistort系の処理でhue
, saturation
を変化させるものはグレースケールには適用できないので省くことになるが、これだけの変更で入力を[batch_size, height, width, 1]
のTensorに変更できる。
features = tf.parse_single_example(value, features={ 'label': tf.FixedLenFeature([], tf.int64), 'image_raw': tf.FixedLenFeature([], tf.string), }) - image = tf.image.decode_jpeg(features['image_raw'], channels=3) + image = tf.image.decode_jpeg(features['image_raw'], channels=1) image = tf.cast(image, tf.float32) - image.set_shape([Recognizer.IMAGE_SIZE, Recognizer.IMAGE_SIZE, 3]) + image.set_shape([Recognizer.IMAGE_SIZE, Recognizer.IMAGE_SIZE, 1]) # distort - image = tf.random_crop(image, [Recognizer.INPUT_SIZE, Recognizer.INPUT_SIZE, 3]) + image = tf.random_crop(image, [Recognizer.INPUT_SIZE, Recognizer.INPUT_SIZE, 1]) image = tf.image.random_flip_left_right(image) image = tf.image.random_brightness(image, max_delta=0.4) image = tf.image.random_contrast(image, lower=0.6, upper=1.4) - image = tf.image.random_hue(image, max_delta=0.04) - image = tf.image.random_saturation(image, lower=0.6, upper=1.4) + # image = tf.image.random_hue(image, max_delta=0.04) + # image = tf.image.random_saturation(image, lower=0.6, upper=1.4)
分類モデル側は最初の畳み込み層のweightが変わるだけ。
with tf.variable_scope('conv1') as scope: - kernel = tf.get_variable('weights', shape=[3, 3, 1, 32], initializer=tf.truncated_normal_initializer(stddev=0.08)) + kernel = tf.get_variable('weights', shape=[3, 3, 3, 32], initializer=tf.truncated_normal_initializer(stddev=0.08)) conv = tf.nn.conv2d(images, kernel, [1, 1, 1, 1], padding='SAME') biases = tf.get_variable('biases', shape=[32], initializer=tf.constant_initializer(0.0)) bias = tf.nn.bias_add(conv, biases)
これで学習させてみる。
結果
正答率の変化を 前述のCross Varidationの全ケース平均と比較すると
と。 カラー画像の場合に対しそれほど大きく劣ることなく正答率が出せているようだ。もう少し追試すると有意差がハッキリ出るかもしれないけど…。
まぁ既にカラー画像のデータが用意できているのならわざわざ色情報を落とす必要は無いだろうな、とは思うけれども。 例えば学習データを集める段階からデータサイズを小さく済ませるよう設計したい、とかの場合に これくらいの顔識別タスクに対しては必ずしもカラー画像で用意しなくても それなりの精度は出る、と言えるかもしれない。
最初の畳み込み層へのchannel数が減っているのとdistortの処理が少し減っている影響か、計算時間はカラー画像の場合よりは少し短縮されていたようだった。これはこれで利点かもしれない。
3. 画像サイズを縮小する
ここまではすべて96 x 96
サイズに切り出したものを使ってきたけれど、もっと縮小されると どれくらい精度に影響が出るだろうか?
80 x 80
64 x 64
48 x 48
32 x 32
感覚的には、64 x 64
サイズくらいなら96 x 96
とそんなに変わらない感じで見分けられそうで、48 x 48
だとちょっと小さくて分かりづらいなー、32 x 32
はなかなか厳しそうだ…という感じ。
画像のリサイズはtf.image.resize_images
でmini batch丸ごと変更できるので、それぞれの場合に1行加えるだけで試すことができる。
モデル定義の方はまったく変更する必要がない。
結果
各入力サイズでの正答率の変化を 前述のCross Varidation全ケース平均と比較すると
となった。
最も小さい32 x 32
でも一応85%弱くらいまでは上がる。48 x 48
以上だと90%超えるくらいまでは上がるようだ。
当然ながら縮小しない96 x 96
のものが最も精度が高いが、64 x 64
くらいに縮小しても致命的なほど精度が下がるわけではない、と言える ような気がする。
サイズ縮小は計算時間には結構大きく影響していて、32 x 32
なんかだとCPUマシンでもサクサクと計算が進んだので、どうしても計算量を減らして早く結果を得たい!というときにはこういう縮小もアリかもしれない。
まとめ
- アイドル顔識別に使えるデータセットを作成した
- 自作した分類モデルを評価して、94%程度の正答率を得た
- パラメータを減らしても同程度の精度のものが出来ることを確認した
- 他にも色々と試して遊んでみた
顔画像データが蓄積されて より大きなデータセットが作れたら再び評価してみたいし、他に何か試したいことを思い付いたら挑戦してみようと思います。
Repository : https://github.com/sugyan/tf-face-recognizer/tree/experiment