はじめに
今回はmetric learningと呼ばれる技術を応用して、同じアニメキャラが写っている顔画像ほど類似度が大きくなるような特徴量空間を学習したいと思います。
この記事の内容は以前Qiitaで投稿した記事と同じプロジェクトをより発展させた(と思っている)ものです。
結論だけ知りたい人へ
人間のエラー率1.2%に対し、深層学習を使った今回の取り組みではエラー率11.3%と人間にはまだまだ及ばない結果になってしまいました。今後引き続きいろいろ取り組みを行って続編ではもう少し精度を上げられればと思います。
デモ
今回の取り組みで一番性能が良かったモデルを試せるデモを用意しました:
画像を2枚アップロードして、そこから抽出されたキャラクターの顔の間の類似度スコアを計算して表示するようなシンプルなデモになっております。
技術的な話
metric learning
Metric learningとは、似ている入力データ同士の距離が小さくなる(類似度が大きくなる)ような特徴量空間への変換を学習する技術です。
たとえばデータ と、それぞれ似ている・似ていないデータ があったとき、何らかの距離指標 において となるような変換 を学習することが目的です。
既にわかりやすい解説記事が多くありますので、詳細な説明は例えば以下の記事に譲りたいと思います。
今回はこのmetric learningの技術を応用するに当たり、既に様々なmetric learningの損失関数がPyTorchで実装されているpytorch-metric-learningライブラリを利用させていただきました。
ハイパーパラメータ探索
これまでmetric learningの損失関数は様々なものが提案されており、性能が改善されていっている事が報告されていました。しかし以下の記事で紹介されている論文で示されているように、より公平な比較を行ったところ既存の損失関数はこれまで報告されていたほどには性能を改善していないのではないかという意見も出されています。
そこで今回の取り組みでは単に最新の損失関数を使うのではなく、Contrastive損失などの古典的な損失関数も含めて評価の対象とすることとしました。
前述した論文でも示されている通り、ハイパーパラメータのチューニングは手法間の公平な比較には重要です。 そこで、今回は勉強も兼ねてハイパーパラメータチューニングのライブラリであるoptunaを使ってみました。
実験
利用データ
モデルの学習には、個人的に収集した11万枚のアニメキャラ顔画像データを利用しました。
顔部分の切り出しには、以前Qiitaの方で紹介したEfficientDetを自作データセットで学習したモデルを利用しています。詳細は以下の記事やレポジトリをご参照ください。
これらの顔画像にはタグに基づくキャラクターのラベルが付けられています。 キャラクターの総数は3502で、1キャラクターあたりの平均顔画像数は31です。 各キャラクターは少なくとも3枚以上の顔画像を持ちますが、低頻出キャラクターの数が多く頻度が3,4のキャラクターがそれぞれ720, 508もいます。
一方テストデータには、2枚の顔画像しか持たない1399人のキャラクターの顔画像を利用します。
このテストデータにおいて同じキャラクターの顔画像ペア全てを正例とし、ほぼ同じ数の異なるキャラクターの顔画像ペアを負例としてランダムにサンプルしました。
テスト時には、上記画像ペアが同じキャラクターかどうかの分類を正しく行えるかどうかを評価します。
実験コード
以降で説明するハイパーパラメータチューニングに関する実験コードは以下のGithubレポジトリで公開しています。ドキュメントが適当なので使い方など分かりづらいかもしれませんが、興味がある方はご覧ください。
各クラスの画像がフォルダごとにまとめられている形式のデータセットであれば、設定ファイルをいじるだけでそのまま利用できるはずです。
結果
人間
まず人間である私がテストデータに対してアノテーションを行った結果、FPR(False Positive Rate)=1.22%, FNR(False Negative Rate)=22.0%という結果になりました。
FNRがやや大きく感じますが、同じキャラクターであっても絵柄が大きく異なったりすると同じキャラクターであると判断するのは案外難しいです。 またラベルノイズや顔検出の失敗も含まれるため、こんなものだろうという感覚です。
今回はこの人間の性能を目指し、同じFNRでどれだけFPRを人間と同じくらい小さくできるかを調べていきたいと思います。
ベースライン
深層学習モデルの性能を評価する前に、基準となるシンプルな方法での性能を調べてみたいと思います。
1つ目のベースラインはランダムに答えを出力するものです。
2つ目のベースラインは、LAB色空間における色ヒストグラムのコサイン類似度を用いてキャラクターが似ているかどうかを判断するものです。画像ごとのヒストグラムの計算方法の実装は以下のとおりです。nbin
については性能が最も良かったnbin=5
に設定しました。
import cv2 import numpy as np def color_histogram(path, nbin): img = cv2.imread(path) img = cv2.cvtColor(img, cv2.COLOR_BGR2LAB) hist = cv2.calcHist(img, [0,1,2], None, [nbin]*3, [0,256, 0,256,0,256]) vec = hist.flatten() vec = vec / np.sum(vec) return vec
これらのベースラインの結果は以下のとおりになりました。
色ヒストグラムを使ってもランダムより多少良い程度で、人間の識別能力には全く及ばないことがわかります。
手法 | FPR |
---|---|
ランダム | 77.9% |
色ヒストグラム | 68.5% |
人間 | 1.22% |
損失関数の比較
続いて様々に提案されているmetric learningの損失を用いて学習した深層学習モデルの性能を比較してみます。
深層学習モデルにはResNet-18でクラス分類を行う全結合層を、500次元の特徴量ベクトルに変換する全結合層に置き換えたモデルを利用します。また、この全結合層の後にDropoutレイヤーを加え、特徴量ベクトルはL2ノルムが1となるように正規化されます。
この深層学習モデルを学習するにあたって、今回は以下の4種の損失関数を比較しました。
- Contrastive
- Triplet
- ArcFace
- ProxyNCA
それぞれの損失関数の性能をできるだけ正確に比較するため、以下のページに公開されている既存研究のハイパーパラメータ探索領域も参考にしながらハイパーパラメータ探索を行いました。
損失関数のハイパーパラメータ探索範囲はそれぞれ以下のとおりとしました。
- Contrastive
- pos_margin: [0,2]
- neg_margin: [0,2]
- Triplet
- margin: [0,2]
- ArcFace
- margin: [0,90]
- scale: [0.01,100]
- ProxyNCA
- scale: [0,100]
またembeddingに対しL1,L2正則化を行うかどうかもチューニングし、行う場合は係数をから までの範囲で探索しました。 モデル(および一部の損失関数)の最適化にはRAdamを利用し、学習率はからの間で、weight decayの係数はからの間で探索しました。
探索に利用できる計算資源には限りがあるため、optunaによる最大試行回数は各損失関数あたり60回とし、エポック数も最大30として2エポック連続で性能(MAP@R)が改善しなかった場合は学習を打ち切るearly stoppingも行います。
またハイパーパラメータ探索中は、データのばらつきのよる評価のゆらぎを抑えるため、2つの異なるtrain-devセットの分割における性能を平均しました。 なおテスト時にはより多くのデータを使ってモデルが収束するまで学習するため、early stoppingの条件を緩くするなど探索時とはやや条件を変えて再学習しています。
それぞれの損失関数で最良の結果は以下のとおりとなりました。 先程までのベースラインと比べて、大きくFPRが改善していることがわかります。
一方で、人間の識別能力にはまだまだ及ばない結果となってしまいました。
損失関数 | 探索時のMAP@R | 再学習時のMAP@R | 再学習時のFPR | 再学習時のEER |
---|---|---|---|---|
Contrastive | 0.260 | 0.308 | 12.2% | 15.0% |
Triplet | 0.248 | 0.272 | 13.0% | 16.0% |
ArcFace | 0.213 | 0.241 | 14.8% | 17.7% |
ProxyNCA | 0.236 | 0.251 | 16.6% | 18.9% |
(参考)人間 | N/A | N/A | 1.22% | N/A |
また損失関数に注目すると、興味深いことに最も古典的なContrastive損失が最も良い性能を示しました。 60回の試行回数で十分だったのかという疑問もあるため断定はできませんが、少なくとも今回の探索方針・データセット・タスクでは他の損失関数よりもContrastive損失が有用であったということのようです。 metric learningの損失に関しては、とりあえず新しい損失を使っておけば良いわけではないということが言えるのではないかと思います。
なお以後の実験も含め、設定ごとの探索結果の詳細はGoogle スプレッドシートにまとめてあります。興味のある方はご覧ください。
少し脇道にそれますが、得られたハイパーパラメータの探索結果を上記既存研究で得られていたものと比較してみます。
今回Contrastive損失に対しては
- pos_margin: 0.681
- neg_margin: 1.04
というハイパーパラメータが最良の結果を示しました。一方既存研究ではCUB200データセットに対するContrastive損失のハイパーパラメータ探索結果として以下のような結果が得られています。
データセットやその他探索の条件などが異なるため一概に比較はできませんが、それぞれのデータセットで性能が最適なハイパーパラメータの領域は当たらずとも遠からず、といった印象を受けます。
新しいデータセットでmetric learningのハイパーパラメータを探索する際は、既存研究での探索の結果良さそうなハイパーパラメータを初期情報として与えることも有用なのかもしれません。
次元数・データ拡張(Data augmentation)の効果
これまでの実験では最終的なembeddingの次元を500に固定し、精度向上のためのデータ拡張は行っていませんでした。この節ではこれらの設定を変えることでより性能が向上できないかを調べます。
次元数
まず次元数を増やしたときの効果について調べます。
損失関数は前の節の結果を踏まえてContrastive損失を用いることとします。また次元数を増やすことによって最適なdropoutや距離に対するマージン閾値が変わってしまう可能性があるため、前の節と同様のハイパーパラメータ探索を行いました。
結果は以下の通りです。次元数を500から1000に増やしても、性能を大きく改善させることはできませんでした。そこで以降の実験では次元数は500のままとすることにします。
次元数 | 探索時のMAP@R | 再学習時のMAP@R | 再学習時のFPR | 再学習時のEER |
---|---|---|---|---|
500 | 0.260 | 0.308 | 12.2% | 15.0% |
1000 | 0.264 | 0.296 | 11.9% | 16.1% |
データ拡張(Data augmentation)
続いて、入力画像をランダムに編集してデータを水増しするデータ拡張を用いた場合の効果について調べていきたいと思います。
古典的なデータ拡張
まずは数年前から用いられているような、古典的なデータ拡張の手法を試してみたいと思います。利用するデータ拡張の種類は、以下の論文で有効性が示されていたFlip, Rotation, Translation (Shift), Erasingの4種類とします。
RotationとTranslation, Scaleについては、データ拡張の強度を以下のハイパーパラメータで調整します。
- Rotation
- 回転角: [0, 180]
- Translation
- 画像サイズに対する平行移動の大きさ: [0,0.3]
- Scale
- 拡大・縮小率: [1,1.3]
これらのハイパーパラメータおよび、Flip, Erasingを適用するかどうかを再びoptunaによりチューニングします。損失関数はContrastive損失を用い、次元数は500とします。そして探索空間を削減するため、損失関数の比較の際にチューニングしておいたハイパーパラメータは最適値で固定しました。
RandAugment
また、より発展的な手法として、データ拡張の手法として近年提案されたRandAugmentも試してみたいと思います。 この手法は、少ないハイパーパラメータの探索空間で対象の問題に良いデータ拡張を実現することを目指しています。 探索すべきハイパーパラメータは、適用するデータ拡張の手法数 と、データ拡張の強度 の2つのみです。
論文や解説記事、利用した実装を以下に紹介しておきますので、興味のある方はご覧ください。
論文:
https://arxiv.org/pdf/1909.13719.pdf
実装:
解説記事:
このケースでは探索空間が小さいため、以下のハイパーパラメータ設定に対してグリッドサーチを行いました。
- : [1,2]
- の場合はデータ拡張なしに相当するため探索しませんでした。
- : [2,6,10,14,18,22,26,30]
結果
結果は以下のとおりです。データ拡張を行うことによって性能をやや向上できました。
またRandAugmentがFlip, Rotation, Translation, Erasingの4種を組み合わせてチューニングした結果よりも良い性能を達成していることも確認できます。今回RandAugmentはグリッドサーチで18パターンの探索のみでこの性能を得ることができているため、非常に使い勝手も良い手法だと感じました。
データ拡張 | 探索時のMAP@R | 再学習時のMAP@R | 再学習時のFPR | 再学習時のEER |
---|---|---|---|---|
なし | 0.260 | 0.308 | 12.2% | 15.0% |
古典的なデータ拡張 | 0.284 | 0.315 | 11.6% | 15.2% |
RandAugment | 0.279 | 0.341 | 11.3% | 14.3% |
まとめと今後の課題
以上、しっかりハイパーパラメータチューニングを行った上でいろいろな損失関数やデータ拡張の方法を試してみましたが、まだまだエラー率は11%と、人間の識別能力(エラー率1.2%)には大きく水をあけられてしまっています。
今後はResNet-18よりもより大きなモデルの利用や、教師なし学習による事前学習の利用、そして訓練データ数を増やすなどの方策によってどの程度性能が改善できるかを調べて行けたらと思います。(やる気が続けばですが…)