Google Colab + MMDetectionで楽してObject Detection - 4. データセットでモデルを学習 -
はじめに
こんにちは!すだです!
このシリーズ記事では、MMDetection で行えるいろんな便利な機能を紹介していきます。
今回は、前回の「3. データセットでモデルを評価」の続きで、メジャーな公開データセットを使って Object Detection モデルを学習をしてみたい思います!
過去の記事をまだ読んでいない方は、先にお読みください!
今回作成した Colab Notebook はこちらです。
環境構築
導入編を参考に環境構築を進めてください。
!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 データセットはデータ数が膨大で学習に時間がかかるため)。
下記コマンドでデータをダウンロードします。
!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 を実装した有名なモデルですね。
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
という名前で下記ファイルを作成しました。
_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.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.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 については下記の記事を参照ください。
学習がうまくいったのかを確認するために、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
で指定したディレクトリに、good
とbad
のサブディレクトリが作成され、その中に 20 枚ずつの画像が出力されます。
この画像は、正解に対し一致度が高い予測ができた 20 例/できなかった 20 例のようです。
出力結果の一例を示します。
good な例
成功例 1 上:正解 下:予測
成功例 2
成功例 3 dog を検出できているが, 誤検出もある
bad な例
失敗例 1
失敗例 2
この画像を見ると、対象が大きい場合やはっきり映っている場合は正しく検出しやすく、対象が小さくい場合やわかりにくい場合に失敗しやすい傾向にあることが分かります。
誤検出があっても good になってしまうのはちょっと残念ですが、このように手軽に成功例、失敗例の傾向を把握できるのは良いですね。
まとめ
今回は、PASCAL VOC データセットを使い、モデルの学習をしてみました!
次回は、MMDetection の config システムに触れたいと思います!
Discussion