ブログ
Sigfoss Blog

2019/12/09

カスタムデータセットで物体検知を行うために (完結編)

これまで何回かに分けて説明してきた物体検知モデルの潜在能力は、現時点での物体検知技術の最高水準(state of the art)にあるといって過言ではありません。ただモデルの潜在能力=最終的な学習済みモデルの検知性能というわけではありません。

今回は物体検知シリーズのひとまずの完結編として、最終的な学習済みモデルの性能をあげるために重要なポイントを説明するとともに、カスタムデータの例としてBDD(Berkeley DeepDrive 100k)に対応する学習と推論のコードを公開します。BDDはUC Berkeleyが、自動車で録画した大量のデータからアノテーション付きのデータを作成し、公開したものです。

学習における大切なポイントのひとつは学習データそのものにあります。さまざまな環境下から大量のデータを収集し、正しくアノテーションを行うことは大変なコストがかかります。Pascal VOCやCOCOのようなパブリックデータの整備が進んでいることはとてもありがたいことですが、実際に学習したいオブジェクトや環境と一致しないときには自分でデータを収集しアノテーションを行う必要があります。

学習データの用意には、広い意味では学習データを膨らませるData Augmentationのテクニックも含まれるでしょう。これまでのご紹介したコードにも標準的なData Augmentationは含んでいますが、実際のプロジェクトの一線ではさまざまなAugmentationテクニックが駆使されています。どこかの先生が指摘していましたが、Data AugmentationがDeep Learningの美しいEnd-to-End学習スキームのあまり美しくない部分であることは否めませんが、学習モデルの推論精度をあげることに大きく貢献します。

残されたポイントは、学習対象にあわせたパラメータの調整です。たとえばBDDの画像は1280x720のサイズを持ちます。以前Pascal VOC用に設計した物体検知モデルは512x512の入力設定でした。モデルへの入力サイズを300x300から512x512に拡大した時には性能が大きく向上しました。1280x720の画像を活かす設定にすることで性能向上が期待できそうです。

SSD型の物体検知モデルにとって最も重要な調整項目はアンカーボックスです。SSDは各ステージレイヤ上に配置したアンカーボックスと学習データにあるオブジェクトの重なり(IoU)を手がかりに学習を進めていきます。標準のアルゴリズムではIoUが0.5以上あるものだけの学習を行ないますから、適切なアンカーボックスを配置できないと思った学習が行われません。

このアンカーボックスの数や形を、学習対象に応じて決めていくことで高い精度を狙うことができるのです。公開コードでは縦横の違うアンカーボックスを扱うために、アンカーボックの処理を変更しています。(layers/functions/prior_box.py) またBDDの画像は1280x720ですが、便宜上モデルへの入力を1280x768とし、入力はこのサイズにリサイズして使っています。

#このサイズだと1/2化を繰り返しても割り切れるためです。割り切れなくなったところでステージレイヤの設置をやめていますが、もっと大きなアンカーボックスを設置したい場合はさらにステージレイヤを続けます。

使用するモデルは、以前と同じResNeXt-FSSDですが、ステージレイヤの数や引き出す層を変更しています。data/config.pyのアンカーボックスの設定部分を以下に示します。設定値は参考例ですので、実際は学習データに応じて試行錯誤しながらベストのものを選んでいきます。

#アンカーボックスのアスペクト比は例として縦長のものを多く置いてみました。歩行者は得意かもしれませんが、車高の低いスポーツカーは苦手かもしれません。

bdd1280x768 = {
    'num_classes': 11,
    'lr_steps': (100, 130, 150),
    'max_epoch': 150,
    # (w,h)
    'size': (1280,768),
    # (h,w)
    'feature_maps': [(48,80), (24,40), (12,20), (6,10), (3,5)],
    'steps': [16, 32, 64, 128, 256],
    'anchor_sizes': [32, 64, 128, 256, 512],
    'box': [5, 5, 5, 5, 5],
    'aspect_ratios':  [(0.5,1.0,1.5,2.0,2.5),
                      (0.5,1.0,1.5,2.0,2.5),
                      (0.5,1.0,1.5,2.0,2.5),
                      (0.5,1.0,1.5,2.0,2.5),
                      (0.5,1.0,1.5,2.0,2.5)],
    'variance': [0.1, 0.2],
    'clip': True,
    'name': 'BDD',
}

経験上、ひとつ重要なポイントをあげておきましょう。上記の設定例でもっとも浅いステージレイヤはH,W=(48,80)のグリッドを構成しています。元々の入力サイズがH,W=(768, 1280)だったことを考えると、ひとつのグリッドが16x16のサイズになります。このグリッドの上にアンカーボックスを設置していくのですが、ひとつのグリッド上でのアスペクトの異なるアンカーボックスの配置を図示するとこんな感じになります。



上記の設定では隣合うアンカーボックスが重なりあうように作っています。隣り合う三つのグリッド上のアンカーボックスを重ねて表示すると以下のようになります。



この重なり具合のバランスをうまく取り、候補となるアンカーボックスと学習データ中のオブジェクトとのIoUを大きくすることが重要なのです。(パラメータでいうとsteps, anchor_sizes, aspect_rations)

今回のコードはいつものGitHubに置いてあります。ResNeXtのpretrainedウェイト(resnext50_32x4d.pth)をweightsの下に置き、BDDのデータを/mnt/ssd以下に置くことを想定した、学習のためのコマンドは以下の通り。(batch_sizeはGPUのメモリサイズに応じて変えてください)

$ CUDA_VISIBLE_DEVICES=0 python train_bdd.py --weight_prefix=BDD1280_ --batch_size=8

3エポックほど学習して、推論してみたのが以下の結果です。

$ CUDA_VISIBLE_DEVICES=0 python pred_bdd.py --trained_model=weights/BDD1280_2.pth ce46abf5-60a664ae.jpg --confidence_thresh=0.2
[415.92377 329.04257 502.94916 397.7107 ] tensor(0.8938) car
[494.7047  335.94577 526.7527  369.91336] tensor(0.8869) car
[1027.0021   337.89703 1120.1086   393.16125] tensor(0.4312) car
[ 937.7295   335.13718 1009.41394  382.30765] tensor(0.3465) car
[1199.7373   344.98965 1279.0842   410.4869 ] tensor(0.3064) car
[325.1724  324.3158  354.42175 348.1307 ] tensor(0.2986) car
[ 12.810516 326.43558   65.90016  349.636   ] tensor(0.2841) car
[413.11337 333.74448 430.0153  347.4073 ] tensor(0.2636) car
[249.97311 323.83926 283.2886  346.79236] tensor(0.2592) car
[782.54095 337.47507 809.06555 360.97693] tensor(0.2582) car
[ 981.97253  337.19232 1044.2208   383.9483 ] tensor(0.2183) car
[806.02325 340.3947  844.5136  367.0659 ] tensor(0.2086) car
[ 38.670067 327.00745   71.28928  347.9672  ] tensor(0.2035) car
[364.0655  326.34375 382.8531  345.4213 ] tensor(0.2006) car
[493.36868 300.9129  504.27814 316.19363] tensor(0.3380) traffic light
[393.98975 310.80786 405.80594 324.72968] tensor(0.2558) traffic light
[815.8112 250.5596 841.8939 277.3361] tensor(0.3759) traffic sign
[1185.5919   144.57173 1278.6012   256.4125 ] tensor(0.2577) traffic sign



順調に学習ができているようです。

物体検知はなかなか奥深い分野ですが、クラスの数が限定され、かつ十分な学習データが用意できる時には、人間と近い精度を出せるようになってきた実感があります。今後も応用の幅を広げ、多くのソリューションやサービスの基盤となっていくことを期待しています。

森 英悟