📌

Google Colab + MMDetectionで楽してObject Detection - 4. データセットでモデルを学習 -

2023/02/28に公開約11,000字

はじめに

こんにちは!すだです!
このシリーズ記事では、MMDetection で行えるいろんな便利な機能を紹介していきます。
今回は、前回の「3. データセットでモデルを評価」の続きで、メジャーな公開データセットを使って Object Detection モデルを学習をしてみたい思います!
過去の記事をまだ読んでいない方は、先にお読みください!

https://zenn.dev/jizai_labo/articles/mmdet_outline

今回作成した Colab Notebook はこちらです。
https://colab.research.google.com/drive/1tb9A2-godxPck9PgIR7gMbP0z4RHhLfM?usp=sharing

環境構築

導入編を参考に環境構築を進めてください。

!pip install -U openmim
!mim install mmcv-full
!git clone https://github.com/open-mmlab/mmdetection.git
%cd mmdetection
!pip install -v -e .
!mkdir trained_models

データセットのダウンロード

今回は、PASCAL VOC データセットを使います。(前回使った COCO データセットはデータ数が膨大で学習に時間がかかるため)。
http://host.robots.ox.ac.uk/pascal/VOC/

下記コマンドでデータをダウンロードします。

!mkdir ./data
temp_file = './data/temp.tar'
for data_url in ['http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtrainval_06-Nov-2007.tar',
                 'http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtest_06-Nov-2007.tar',
                 'http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar']:
    !curl -o $temp_file $data_url
    !tar -xvf $temp_file -C ./data
    !rm $temp_file

モデルの学習

今回は、RetinaNet というモデルを学習します。はじめて Focal Loss を実装した有名なモデルですね。
https://arxiv.org/abs/1708.02002

config ファイルの準備

学習は MMDetection で用意されている学習用スクリプトを使って、以下のようなコマンドで実行できます。

!python tools/train.py ${configファイル}

このとき指定した config ファイルには、使用するモデルやデータセット、学習の設定などを書きます。
今回は、RetinaNet を PASCAL VOC で学習する設定が書かれた config ファイルがconfigs/pascal_voc/retinanet_r50_fpn_1x_voc0712.pyに用意されているので、そちらを利用します。
ただ、デフォルトの設定のままだと、学習がうまく進まなかったり、時間がかかったりしたので、少し設定を変更して学習してみようと思います。
configs/pascal_voc/retinanet_r50_fpn_1x_voc0712_bs16_lr0.001.pyという名前で下記ファイルを作成しました。

configs/pascal_voc/retinanet_r50_fpn_1x_voc0712_bs16_lr0.001.py
_base_ = './retinanet_r50_fpn_1x_voc0712.py'

data = dict(
    samples_per_gpu=16
)
optimizer = dict(lr=0.001)

config の書き方は別の投稿で改めて解説しようと思いますが、軽く説明します。

_base_ = './retinanet_r50_fpn_1x_voc0712.py'

MMDetection の config では、設定の継承を行うことができます。_base_にで指定したファイルが継承元のファイルになります。
そして、この文以下で指定した内容で設定を上書きできます。
今回の継承元に指定したのは、先ほど触れた MMDetection で用意されている config ファイルですね。

data = dict(
    samples_per_gpu=16
)
optimizer = dict(lr=0.001)

あとは設定変更箇所です。
まず、学習時のバッチサイズを指定しているsamples_per_gpuがデフォルトでは 2 で、学習のスピードが遅いため、16 に変更しています。
また、Optimizer の学習率を指定しているlrをデフォルトの 0.01 から、0.001 に変えています。

今回、Google Colab 上で上記ファイルを作成するにあたり、下記コードを使用しました。

config_file = 'configs/pascal_voc/retinanet_r50_fpn_1x_voc0712_bs16_e12_lr0.001.py'
config = """_base_ = './retinanet_r50_fpn_1x_voc0712.py'

data = dict(
    samples_per_gpu=16
)

optimizer = dict(lr=0.001)

"""
with open(config_file, 'w') as f:
    f.write(config)

学習の実行

それでは、作成した config ファイルを使って学習をしていきますが、学習に時間がかかって Google Colab が途中で停止してしまうので、学習が途中で止まっても途中から再開できるようにしたいと思います。

まず、Google Drive に途中経過を保存するフォルダを作成します。

from google.colab import drive
drive.mount('/content/drive/')

続いて学習用スクリプトを使って学習を行いますが、以下のようなオプションをつけて実行します。

!python tools/train.py configs/pascal_voc/retinanet_r50_fpn_1x_voc0712_bs16_e12_lr0.001.py \
        --work-dir /content/drive/MyDrive/mmdet-results/retinanet_voc \
        --auto-resume

追加したオプションは

  • --work-dir ${ディレクトリパス}: 中間結果ファイルやログファイルを出力するディレクトリ
  • --auto-resume: --work-dirで指定したディレクトリから最新の学習結果を読み込んで、そこから再開する

というものです。
--work-dirで指定したディレクトリは、存在しない場合は新規作成されるので、特に事前に作成しておく必要はありません。

上記コマンドを実行すると、たくさんの文字列が表示された後、

...

2023-02-26 12:25:12,940 - mmdet - INFO - workflow: [('train', 1)], max: 4 epochs
2023-02-26 12:25:12,940 - mmdet - INFO - Checkpoints will be saved to /content/drive/MyDrive/mmdet-results/retinanet_voc by HardDiskBackend.
2023-02-26 12:27:05,241 - mmdet - INFO - Epoch [1][50/3104]	lr: 1.000e-03, eta: 7:40:23, time: 2.234, data_time: 0.104, memory: 12229, loss_cls: 1.1504, loss_bbox: 0.6646, loss: 1.8151
2023-02-26 12:28:51,895 - mmdet - INFO - Epoch [1][100/3104]	lr: 1.000e-03, eta: 7:28:08, time: 2.133, data_time: 0.060, memory: 12229, loss_cls: 1.1495, loss_bbox: 0.6560, loss: 1.8055
2023-02-26 12:30:42,293 - mmdet - INFO - Epoch [1][150/3104]	lr: 1.000e-03, eta: 7:27:54, time: 2.207, data_time: 0.062, memory: 12229, loss_cls: 1.1508, loss_bbox: 0.6493, loss: 1.8001

のように、じわじわと行が増えていくかと思います。
eta の項目にあるように、Google Colab のノーマル GPU を使うと 7 時間以上かかるため、セッションが時間切れになり、学習が止まってしまうと思うので、気づいたときに再起動してください。
(それかおとなしく Google Colab Pro にしましょう笑)

学習がスタートすると、--work-dirで指定したディレクトリには、下記のようなファイルが出力されます。

  • YYYYMMDD_hhmmss.log : Google Colaba に出力されている文字情報とほぼ同一の情報が記載されたログファイル。ファイル名は学習を開始した日時。
  • YYYYMMDD_hhmmss.log.json : Loss の値や途中の性能評価の結果などが記載されたログファイル。ファイル名は学習を開始した日時。
  • epoch_{n}.pth : n エポック完了時の重みファイル。
  • latest.pth : 最新エポックの重みファイル

今回は学習エポック数が 4 なので、epoch_4.pthまで出力されたら学習完了です。

学習曲線の可視化

学習中の Loss の変化は下記スクリプトを実行することで確認できます。

log_path = '/content/drive/MyDrive/mmdet-results/retinanet_voc/20230226_122505.log.json'  # 適宜変更してください
out_path = '/content/drive/MyDrive/mmdet-results/retinanet_voc/loss.png'
!python tools/analysis_tools/analyze_logs.py plot_curve $log_path --keys loss_cls loss_bbox --legend loss_cls loss_bbox --out $out_path

out_pathで指定したファイルに以下のようなグラフが出力されます。

loss
loss.png

学習時にバリデーション Loss を計算していないので、過学習しているかはわかりませんが、学習が進むごとに Loss が下がっている様子が見てとれます。

また、同様に各エポック終了時に算出した性能指標の mAP(mean Average Pecision)も可視化できます。

log_path = '/content/drive/MyDrive/mmdet-results/retinanet_voc/20230226_122505.log.json' # 適宜変更してください
out_path = '/content/drive/MyDrive/mmdet-results/retinanet_voc/mAP.png'
!python tools/analysis_tools/analyze_logs.py plot_curve $log_path --keys mAP --legend mAP --out $out_path

out_pathで指定したファイルに以下のようなグラフが出力されます。

mAP
mAP.png

モデルの定量評価

評価用のスクリプトを使用して、学習したモデルを PASCAL VOC の評価用データで定量的に評価しましょう。

config_path = 'configs/pascal_voc/retinanet_r50_fpn_1x_voc0712_bs16_lr0.001.py'
weights_path = '/content/drive/MyDrive/mmdet-results/retinanet_voc/latest.pth'
result_path = '/content/drive/MyDrive/mmdet-results/retinanet_voc/result.pkl'
!python tools/test.py $config_path $weights_path --eval mAP --out $result_path

実行してしばらく待つと以下のように、各クラスの AP(Average Precision)やそれらを平均した mAP(mean Average Precision)が出力されます。


+-------------+------+-------+--------+-------+
| class       | gts  | dets  | recall | ap    |
+-------------+------+-------+--------+-------+
| aeroplane   | 285  | 7229  | 0.937  | 0.747 |
| bicycle     | 337  | 9626  | 0.979  | 0.810 |
| bird        | 459  | 7496  | 0.965  | 0.820 |
| boat        | 263  | 15973 | 0.947  | 0.610 |
| bottle      | 469  | 18150 | 0.915  | 0.613 |
| bus         | 213  | 6804  | 0.977  | 0.780 |
| car         | 1201 | 22495 | 0.983  | 0.852 |
| cat         | 358  | 6249  | 0.992  | 0.871 |
| chair       | 756  | 33626 | 0.962  | 0.588 |
| cow         | 244  | 6113  | 0.992  | 0.770 |
| diningtable | 206  | 14401 | 0.932  | 0.680 |
| dog         | 489  | 8381  | 0.996  | 0.842 |
| horse       | 348  | 7058  | 0.980  | 0.830 |
| motorbike   | 325  | 7852  | 0.982  | 0.789 |
| person      | 4528 | 68277 | 0.973  | 0.806 |
| pottedplant | 480  | 19807 | 0.913  | 0.496 |
| sheep       | 242  | 5274  | 0.963  | 0.761 |
| sofa        | 239  | 10532 | 0.971  | 0.719 |
| train       | 282  | 7172  | 0.972  | 0.787 |
| tvmonitor   | 308  | 8687  | 0.945  | 0.771 |
+-------------+------+-------+--------+-------+
| mAP         |      |       |        | 0.747 |
+-------------+------+-------+--------+-------+

AP や mAP については下記の記事を参照ください。
https://www.sigfoss.com/developer_blog/detail?actual_object_id=267
https://medium.com/@jonathan_hui/map-mean-average-precision-for-object-detection-45c121a31173

学習がうまくいったのかを確認するために、MMDetection の公式で配布している学習済みモデルと比較してみましょう。

まずは、公式の学習済みモデルの重みファイルをダウンロードします。

!curl -o trained_models/retinanet_mmdet.pth https://download.openmmlab.com/mmdetection/v2.0/pascal_voc/retinanet_r50_fpn_1x_voc0712/retinanet_r50_fpn_1x_voc0712_20200617-47cbdd0e.pth

上記を実行したら、trained_modelsディレクトリに出力されたretinanet_mmdet.pthを使って、下記コマンドを実行します。

config_path = 'configs/pascal_voc/retinanet_r50_fpn_1x_voc0712.py'
weights_path = 'trained_models/retinanet_mmdet.pth'
!python tools/test.py $config_path $weights_path --eval mAP

しばらく待つと、以下のような結果が表示されます。


+-------------+------+-------+--------+-------+
| class       | gts  | dets  | recall | ap    |
+-------------+------+-------+--------+-------+
| aeroplane   | 285  | 3586  | 0.951  | 0.828 |
| bicycle     | 337  | 7012  | 0.982  | 0.843 |
| bird        | 459  | 4151  | 0.950  | 0.819 |
| boat        | 263  | 7870  | 0.962  | 0.692 |
| bottle      | 469  | 13002 | 0.932  | 0.676 |
| bus         | 213  | 4198  | 0.986  | 0.846 |
| car         | 1201 | 16215 | 0.985  | 0.877 |
| cat         | 358  | 4001  | 0.986  | 0.882 |
| chair       | 756  | 24978 | 0.956  | 0.642 |
| cow         | 244  | 5806  | 0.992  | 0.780 |
| diningtable | 206  | 15086 | 0.956  | 0.642 |
| dog         | 489  | 6264  | 0.992  | 0.860 |
| horse       | 348  | 6675  | 0.974  | 0.834 |
| motorbike   | 325  | 6092  | 0.972  | 0.812 |
| person      | 4528 | 45674 | 0.976  | 0.835 |
| pottedplant | 480  | 14173 | 0.921  | 0.526 |
| sheep       | 242  | 4328  | 0.975  | 0.758 |
| sofa        | 239  | 9053  | 0.967  | 0.712 |
| train       | 282  | 4109  | 0.957  | 0.833 |
| tvmonitor   | 308  | 6267  | 0.948  | 0.779 |
+-------------+------+-------+--------+-------+
| mAP         |      |       |        | 0.774 |
+-------------+------+-------+--------+-------+

今回は、学習したモデルのほうが公式の学習済みモデルより若干性能指標が低くなりましたが、モデルパラメータの初期値や学習率の設定の違い等もあるので、学習自体はうまくいったと言ってもいいでしょう。

モデルの定性評価

最後に、学習したモデルで評価用画像を処理した結果を可視化して、性能を定性的に見てみましょう。
先ほどの評価用のスクリプトで指定した、

result_path = '/content/drive/MyDrive/mmdet-results/retinanet_voc/result.pkl'

を使って、以下のコードを実行します。

config_path = 'configs/pascal_voc/retinanet_r50_fpn_1x_voc0712_bs16_lr0.001.py'
result_path = '/content/drive/MyDrive/mmdet-results/retinanet_voc/result.pkl'
show_dir = '/content/drive/MyDrive/mmdet-results/retinanet_voc/analyze_results'

!python tools/analysis_tools/analyze_results.py $config_path $result_path $show_dir --show-score-thr 0.3

実行してしばらく待つと、show_dirで指定したディレクトリに、goodbadのサブディレクトリが作成され、その中に 20 枚ずつの画像が出力されます。
この画像は、正解に対し一致度が高い予測ができた 20 例/できなかった 20 例のようです。
出力結果の一例を示します。

good な例

good1
成功例 1 上:正解 下:予測

good2
成功例 2

good3
成功例 3 dog を検出できているが, 誤検出もある

bad な例

bad1
失敗例 1

bad2
失敗例 2

この画像を見ると、対象が大きい場合やはっきり映っている場合は正しく検出しやすく、対象が小さくい場合やわかりにくい場合に失敗しやすい傾向にあることが分かります。
誤検出があっても good になってしまうのはちょっと残念ですが、このように手軽に成功例、失敗例の傾向を把握できるのは良いですね。

まとめ

今回は、PASCAL VOC データセットを使い、モデルの学習をしてみました!
次回は、MMDetection の config システムに触れたいと思います!

Discussion

ログインするとコメントできます