物体検知の精度性能を評価する指標には、mAP (mean Average Precision)がよく用いられます。手法同士の精度の優劣を比較するためには、学習用のデータと評価用のデータを揃えた上でmAPを測定しないと意味がありません。さらにややこやしいことに、mAPの定義はいくつかのバージョンがあるので、その点も留意が必要です。
一般によく用いられているのは、Pascal VOCとCOCOというオープンデータセットで、それぞれのmAPについても定義が決まっています。ここでは当面、Pascal VOC 2007および2012の16,551枚のtrainval画像を用いて学習し、Pascal VOC 2007の4,952枚のtest画像を用いて評価する手法を使っていくことにします。論文でも多く使われている評価法のひとつで、クラス数は20。物体検知としては比較的簡単な対象物が集められています。
#Pythonで実装されているPascal VOC用のmAP測定プログラムを使用します。公式実装ではありませんが作者はそれに非常に近いはずだと書いてるので信用。^_^
アカデミックな世界ではある評価指標での最高値を達成したときに、achieve state-of-the-art result (SOTA)みたいに言います。物体検知のPascal VOC 2007テストのSOTAの変移を見るには(公式サイトではないですが) https://paperswithcode.com/sota/object-detection-on-pascal-voc-2007 が参考になります。もちろん検証なしにスコアを鵜呑みできるわけではないのですが、コードと紐付けられているものも多く助かります。個人的には、mAP=0.70をひとつの目安とし、0.80を超えると性能の良さを感じます。(0.70以下だと使えないという意味。速度やモデルのサイズなど精度以外の指標もあるため高ければいいというものでもないからです)
さて、前置きが長くなりましたが先程触れたM2Detの学習と評価をやってみましょう。M2Detの論文を読むとこれまでに提案されたさまざまな手法を合わせ技で持ってきてる印象を持ちました。githubに公開されているコードを改変し、Pascal VOCの学習と評価のコードが動くようにします。さて結果やいかに!
AP for aeroplane = 0.5837 AP for bicycle = 0.6655 AP for bird = 0.4137 AP for boat = 0.3316 AP for bottle = 0.2773 AP for bus = 0.6471 AP for car = 0.7267 AP for cat = 0.7056 AP for chair = 0.2730 AP for cow = 0.3793 AP for diningtable = 0.5536 AP for dog = 0.5646 AP for horse = 0.7400 AP for motorbike = 0.6282 AP for person = 0.5832 AP for pottedplant = 0.3012 AP for sheep = 0.3861 AP for sofa = 0.5730 AP for train = 0.6107 AP for tvmonitor = 0.5549 Mean AP = 0.5250
そういうわけで、ここからはstate-of-the-artの物体検知を手元で実現するために、あらためて物体検知にじっくりと向き合ことにします。^_^
よく知られていることですが、Deep Learningを用いた物体検知には大きく分けて二つのアプローチがあります。ひとつはtwo stage型と呼ばれるもの。two stage型はR-FCNの流れを汲む手法で、対象オブジェクトの領域候補を作るstageとCNNを用いた認識を行うstageのふたつを持つものです。もうひとつのone stage型はYoloやSSDの流れを汲み、すべての学習をone stageでやってしまうものです。
もともとtwo stage型は精度がよいけど遅く、one stage型は速いけれど精度に劣っていたのですが、最近はone stage型の精度向上が進んでおり、Faster RCNのような代表的なtwo stage型の精度を凌ぐと主張する手法も増えています。one stage型を世に広めた立役者はなんと言ってもJoseph Redmon氏によるYoloなのですが、以降の様々な手法に大きく影響を与えたという意味では、SSDの功績も大変に大きいものがあります。
そこでまずはSSDでのPascal VOCを用いた学習と評価をきっちりやり、そこからイロイロと手を入れてみることにします。以前ご紹介したMax deGroot氏のPyTorch版SSD実装をベースに、少し改造を加えます。
オリジナルの実装はiterationベースなのですが、これをepochベースに変更し、最初のepochはwarmupとしてlearning rateを0からスタート値にリニアに増加させることにします。これはYoloでburninとして実装されているものと同じような効果を狙っており学習初期の不安定な動作を抑制させることができます。
# iterationの数 = mini-batchを回した数。mini-batchのサイズはGPUメモリに収めるためにモデルの大きさに応じて変更する必要があり、そうするとiterationの数が学習量(枚数)の指標として統一的に考えにくくなります。一方epochはデータセットを一周学習した回数なので、同じデータセットを使う限りはbatch sizeが変わっても学習量(枚数)の指標としては統一的に用いることができます。
# yoloで実装されているburninはlearning rateのスタート値に指数関数的に近づく実装になっていますが、リニアに近づけても十分に効果がありました。
改造済みのコードはGitHubに置いてありますのでご参考に。basenetのpretrain weightのvgg16_reducedfc.pthはググって落とし、weights以下に置いてください。Pascal VOCのデータがもちろん必要ですがこれも落としてきて、できればSSDに置いてあげてください。(置いた場所を引数で指定します)
学習と評価を行ってみます。多めに250epoch回してみました。オプティマイザはMomentum SGD。learning rateはwarmup後に0.001からはじめて、150epoch目と200epoch目にx0.1しています。最後のepochの値が最良とは限りませんが、そこは割愛します。
# training $ CUDA_VISIBLE_DEVICES=0 python train.py --dataset=VOC --dataset_root=/mnt/ssd/VOCdevkit/ --weight_prefix='VOC300_' # evaluation $ CUDA_VISIBLE_DEVICES=0 python eval.py --voc_root=/mnt/ssd/VOCdevkit/ --trained_model weights/VOC300_249.pth ~snip~ AP for aeroplane = 0.8262 AP for bicycle = 0.8413 AP for bird = 0.7645 AP for boat = 0.6910 AP for bottle = 0.5185 AP for bus = 0.8503 AP for car = 0.8632 AP for cat = 0.8936 AP for chair = 0.6163 AP for cow = 0.8230 AP for diningtable = 0.7632 AP for dog = 0.8527 AP for horse = 0.8704 AP for motorbike = 0.8367 AP for person = 0.7887 AP for pottedplant = 0.5002 AP for sheep = 0.7611 AP for sofa = 0.8044 AP for train = 0.8540 AP for tvmonitor = 0.7671 Mean AP = 0.7743
モデルへの入力画素数を増加させると、情報が大きくなって精度の改善が見込めます。Pascal VOCのイメージデータは長辺が500あるので、入力サイズを512x512とするモデルを作ってみます。オリジナルの実装は入力サイズを300x300しかサポートしていませんが、いくつか変更を加えると512x512とすることができます。
アンカーボックスを設置する特徴マップの数を6から7に変更し、以下のように入力をダウンサンプリングしながら、以下のように(0)~(6)の部分に設置します。
512x512 -> 256x256 -> 128x128 -> 64x64(0) -> 32x32(1) -> 16x16(2) -> 8x8(3) -> 4x4(4) -> 2x2(5) -> 1x1(6)
train.pyとeval.pyの上の方を以下のように変更してください。# from ssd import build_ssd # cfg = voc from ssd512 import build_ssd cfg = voc512
学習と精度の測定を行ってみます。batch_sizeはお使いのGPUのメモリに収まるように調整ください。GTX1080TiとかRTX2080Tiとかだと、batch_size=16くらいが適当です。
# training $ python train.py --dataset=VOC --dataset_root=/mnt/ssd/VOCdevkit/ --loss_type='cross_entropy' --weight_prefix='VOC512_' --batch_size=16 # evaluation $ python eval.py --voc_root=/mnt/ssd/VOCdevkit/ --trained_model weights/VOC512_249.pth ~snip~ AP for aeroplane = 0.8577 AP for bicycle = 0.8705 AP for bird = 0.8107 AP for boat = 0.7505 AP for bottle = 0.6079 AP for bus = 0.8841 AP for car = 0.8842 AP for cat = 0.8807 AP for chair = 0.6537 AP for cow = 0.8654 AP for diningtable = 0.7685 AP for dog = 0.8652 AP for horse = 0.8908 AP for motorbike = 0.8540 AP for person = 0.8291 AP for pottedplant = 0.5624 AP for sheep = 0.8177 AP for sofa = 0.7923 AP for train = 0.8662 AP for tvmonitor = 0.8008 Mean AP = 0.8056
論文で報告されているmAPは0.772(SSD300)および0.798(SSD512)なので、それよりもいい値が出てしまいました。それぞれ複数回の学習と評価を行って同じくらいの値がでることを確認しています。正直いい値が出過ぎてしまった感あり。ともかくここからは、SSD512をベースラインに改善を試みます。
SSDは特徴マップに設置したすべてのアンカーボックスに対して、クラス推定を行います。また、与えられた教師データとのIoUが0.5を超えたものをPositive Matchとして、教師データとアンカーボックスとの差分を学んでいきます。SSDに設置されるアンカーボックスの数は標準設定のSSD300で
(38x38x4)+(19x19x6)+(10x10x6)+(5x5x6)+(3x3x4)+(1x1x4) = 8732
標準設定のSSD512で
(64x64x4)+(32x32x6)+(16x16x6)+(8x8x6)+(4x4x6)+(2x2x4)+(1x1x4) = 24564
となります。Positive Matchしなかったアンカーボックスはbackgroundクラスとして学習されることになりますが、これがSSDの課題のひとつとして認識されている部分で、Positive Matchせずにbackgroundクラスにアサインされたアンカーボックスの数が、大変に多くなるのです。
ディープラーニングの学習はLossを少なくなるように行うのはご存知の通りですが、極端にクラスの数に差がある(class imbalance)と学習がうまくいかないことがあります。(Lossの割当が均一であれば常にbackgroundと推測することがLossを減らす近道となる)
オリジナルのSSDはこの問題に対処するために、ハードネガティブマイニングという手法を採用しています。これはbackgroundクラスの教師データの数を、ロスの大きいものから一定の数に制限するというものです。
一方でclass imbalanceに対する解決策としてFacebook AIが提案したのがFocal Lossです。クラスtの推論確率をptとして、Focal Lossは以下のように定義します。
FL(pt) = −αt(1 − pt)^γ * log(pt).
ハードネガティブマイニングがロスの小さなサンプルを除外するアプローチを取ったのに対し、Focal Lossはロスの小さなサンプルのロスをγ乗することで急激に小さくして、数が多く累計されてもclass imbalanceの弊害が起きにくくするものです。
論文では、αとγの値を適切に設定することでハードネガティブマイニングを行わずにより良いmAPを達成したと報告しています。早速実装して評価してみましょう。
# training $ python train.py --dataset=VOC --dataset_root=/mnt/ssd/VOCdevkit/ --loss_type='focal' --weight_prefix='VOC512_FL_' --batch_size=16 # evaluation $ python eval.py --voc_root=/mnt/ssd/VOCdevkit/ --trained_model weights/VOC512_FL_249.pth ~snip~ AP for aeroplane = 0.8512 AP for bicycle = 0.8692 AP for bird = 0.7823 AP for boat = 0.7356 AP for bottle = 0.5707 AP for bus = 0.8761 AP for car = 0.8817 AP for cat = 0.8794 AP for chair = 0.6343 AP for cow = 0.8555 AP for diningtable = 0.7579 AP for dog = 0.8432 AP for horse = 0.8778 AP for motorbike = 0.8399 AP for person = 0.8191 AP for pottedplant = 0.5238 AP for sheep = 0.8272 AP for sofa = 0.7836 AP for train = 0.8675 AP for tvmonitor = 0.7843 Mean AP = 0.7930
もちろん私の実装が間違ってるってこともありえますが。。。今後役に立つかもしれないし。
今回はここまでとします。
今回はここまでとします。