当初ブラジル人が正解しやすく、日本人と中国人は見分けがつきにくいだろう、と予測していましたが、学習結果でテストデータを予測(predict)してみた結果、全く予想外の結果になりました。 日本人が67% 中国人が57%に対し、ブラジル人がたった20%しか正解しませんでした。 学習データに対する精度(accuracy)は99%を超えていて、学習自体は上手く行っているようですが、未知のデータには通用しないようです。 詳しい人に聞いてみたところ、おそらくこれは 学習データ数の差 が確率に出ているのだろうと言うことでした。 つまり、全くまともにAIとしては成立していない感じです。
ちなみに、Kerasでは予測精度を確認するのに predict_generator
というメソッドが使われますが、このメソッドはテストデータすべての予測成功率を求めてしまいます。
個別にそれぞれのカテゴリ(国)ごとの精度を出すには、テストディレクトリの中にそれぞれのカテゴリのディレクトリ作り、調べたいカテゴリだけテスト用画像を持つようするとできます。
次は、この精度を高めるための施策をしていきます。 おそらく、画像そのままを学習させたので余計な部分が非常に多かったのだろうと推測しました。 顔の特徴を見分けるだけなら顔の部分だけでいいはずなので、 OpenCVを使って顔だけを切り出しファイルとして保存します。 以前に見たようにOpenCVは検出精度が高くなく、特に斜めの顔には弱いので、 画像をすこしずつ左右に傾けながら最低1つ以上の顔が検出されるようにします。 もちろん学習データとテストデータの両方にその加工をします。
顔だけ抽出した画像で学習し、顔だけ抽出したテストデータを予測してみた結果、 日本人62% 中国人48% ブラジル人34% という結果が出ました。 ブラジル人の精度は上がりましたが中国人は大きく下がっています。 全体としてはあまり精度が上がっていないようです。 少し気になったのは、学習データに対する精度が88%までしか上がっていないことでした。
次に、学習データを与えるメソッド(fit_generator
)に渡すvalidation_data
を、元のテスト画像と顔だけ抽出したテスト画像の2パターンで実行して比較してみました。
このデータは学習結果を評価するためのものなので、学習には影響がないはずです。
ところが、実際に実行してみると、学習データに対しての精度には1%前後の誤差が表れました。
さらに、顔だけ抽出したテスト画像に対して予測をしてみると、大きな違いがありました。
国 \ validation_data | 顔抽出テスト | 元テスト |
---|---|---|
ブラジル人 | 0.34 | 0.27 |
中国人 | 0.48 | 0.35 |
日本人 | 0.61 | 0.77 |
20%以上も誤差1があることが分かります。 この現象が気になり、何度か学習を回してみると、同じ学習データでも毎回結果が違うことに気づきました。
ここで、困った時のMNISTに戻って実験しました。 MNISTで何度も学習を繰り返してみても同じように結果が違うことが確認できました。 これは初期値などのランダム値による誤差らしいのですが、 TDDやBDD2の世界で生きているWebプログラマには、再現性こそがなにより重要で、この誤差を許容できる気がしません。 Kerasの公式サイトのFAQに、開発中にKerasを用いて再現可能な結果を得るには?というドキュメントがありましたが、 この通りに乱数シードを固定してみてもやはり結果は再現性がありませんでした。 聞いてみるとどうやら、GPUを使うとどうしてもランダムを固定できないらしいのです。 これには絶望しました。 コードを変化させてそれが改善につながったかどうか、機械学習エンジニアは一体どうやって確信しているんでしょうか。
とはいえ20%の誤差は異常だと思うのですが、本当にvalidation_data
は学習に使われていないのでしょうか・・・?
ふと顔だけ抽出した画像ディレクトリを覗いてみると、全然顔じゃないもの がたくさん混じっていることに気づきました。 これを本当に顔だけのデータ(クレンジング)にすると、もしかして精度が大幅に上がるのではと考えました。 OpenCVマジ無能だわーと思いながら、正しい顔データを仕分けしていきます。 しかし、数千枚もあるデータ、しばらくすると当然仕分け作業が辛くなってきます。
そのとき、これこそ機械学習するべき案件ではと気づきました。 さっそく、各国の顔抽出データから理想的な切り取られ方をした画像を集め、同じだけ間違った画像を集めます。 間違った画像は、完全に顔ではないものや、顔だけど半分に切られているものなど、いろいろなパターンのものを集めました。 この画像を同じように学習させ、できあがった 顔判定器 によってすべての顔抽出データを分類します。 すると、理想通りの顔データがどんどん抽出されていくではありませんか! これが初めて機械学習に成功した瞬間です。
判定がだいぶ厳しいのか、クレンジングした画像はすべて理想的な画像でしたが、枚数はそれぞれ1/5程度まで減ってしまいました。 学習データは日本人と中国人が1400枚 ブラジル人が500枚程度です。 ちょっと少ないかと思いつつ、とりあえずクレンジング後の画像で学習させてみます。
すると、学習データに対する精度は99%を超え、テストデータに対する精度もブラジル人59%、中国人33%、日本人86%と非常に良い数字になりました。 ブラジル人は大幅に上がり、日本人に至ってはかなり高い精度になっています。 顔を抽出しだしてから中国人の精度が下がり続けているのはよくわかりません。 とはいえ、当初の予測通りブラジル人は判定しやすく、日本人と中国人は判定しづらい3傾向のデータが取れたような気がします。 しかし、この学習が上手くいったと言えるのかどうか全く確信が持てませんでした。
最後に、得られた学習結果で無加工のテスト画像を予測してみます。 その結果、ブラジル人36%、中国人25%、日本人50%という 今までで最も悪い結果 になりました。 このことから、テストデータは学習データと同じような 事前処理 をしないといけないことがはっきり分かりました。 つまり実際にアプリケーションにするときも、入力されたデータを同じように事前処理しないといけないということです。 そうなると、 クレンジングの段階で顔候補が排除されてしまったような画像 に対して、いったいどのように事前処理をすればいいんだろう、という点に疑問が生じました。 同じ事前処理をしてしまうと、半数以上の画像は有効な顔を1つも検出できないのではないでしょうか。
この観点を踏まえると、今回作ったデータからは、 「写真の中から顔らしきもの検出し、顔判定に通し、パスしたものだけ(半分以上パスしない)を日中ブラジルのどれに一番近いか判定」 するアプリケーション程度しか作れないだろうと考えられます。
今回分かった一番大事なことは、誤差 あるいは ランダム の存在です。 これはWebエンジニアにとって、機械学習を学ぶ上で最も厄介な敵になると感じました。
普段Web開発をする際は、誤差を意識することは必要ない、あるいは誤差は完全にコントロールできるものであると認識することが多いでしょう。 例えばモックやスタブを使うとか、誤差があってもメソッドの出力に単純な影響しかないので誤差の許容が簡単にできるとか。 しかし機械学習となると、小さな誤差が複雑な計算のなかで積み重なった結果、大きな違いを生じることがあるようです。 しかも学習結果の評価方法は、メソッドの出力のような単純なものではないのも厄介です。
多くのサンプルを見てみると、ロス率や精度をグラフにプロットし、やれこれ以上精度が上がらないだの、やれこれ以上は過学習してるだの、 グラフを目で見て判断しているようで、あまりエンジニアリングを感じません。 この辺りが非常に辛いと切に感じました。 というか再現性なくてどうやってコード書くのよ・・・