Edited at

Google ColabでYOLOの学習を行う

前回の続きです。予告通りGoogle ColabでYOLOの学習を行いたいと思います。かなり苦労しました・・・。


実行環境


  • Google Colaboratory

  • YOLOv3


コード


準備

前回同様まずはDarknetのダウンロード

!git clone https://github.com/pjreddie/darknet

前回と違うのはGPUを利用したいので、Makefileを書き換える必要があります。Google Colabのノートブックの設定変更(GPU利用できるように)も忘れずに。

%cd darknet

!sed -i 's/GPU=0/GPU=1/g' Makefile
!make

次にデータセットを取得します。自分でも作れるようですが大変なので、今回は世の中の資産を活用します。PASCAL VOCというものを使うことにしました。なぜPASCAL VOCかと言うと公式Training YOLO on VOCとしてここから先の手順が全部載ってて、なぞるだけで実装できるからです(COCOの手順も載っています)。ということで、データセットをダウンロードします。

!mkdir VOCDataset

%cd VOCDataset
!wget https://pjreddie.com/media/files/VOCtrainval_11-May-2012.tar
!wget https://pjreddie.com/media/files/VOCtrainval_06-Nov-2007.tar
!wget https://pjreddie.com/media/files/VOCtest_06-Nov-2007.tar
!tar xf VOCtrainval_11-May-2012.tar
!tar xf VOCtrainval_06-Nov-2007.tar
!tar xf VOCtest_06-Nov-2007.tar

次にDarknet用のLabelファイルを作る必要がありますが、自分で作る必要はありません。加工ツール(voc_label.py)が提供されています。ただ、実行権限がなかったため、先に権限の変更が必要でした。

!chmod 755 ../scripts/voc_label.py

!python ../scripts/voc_label.py

この処理が終わると以下のようなファイルが作成されます。

!pwd

!ls

/content/darknet/VOCDataset
2007_test.txt 2012_val.txt VOCtest_06-Nov-2007.tar
2007_train.txt train.all.txt VOCtrainval_06-Nov-2007.tar
2007_val.txt train.txt VOCtrainval_11-May-2012.tar
2012_train.txt VOCdevkit

次に設定ファイルのtrainとvalidのパスを変更します。元のパスを先ほど作成したtrain.txtと2007_test.txtに変更します(↓のコードはエスケープが入っているので見づらくなっていますがご容赦下さい)。

%cd ../

!sed -i 's/\/home\/pjreddie\/data\/voc\/train.txt/VOCDataset\/train.txt/g' cfg/voc.data
!sed -i 's/\/home\/pjreddie\/data\/voc\/2007_test.txt/VOCDataset\/2007_test.txt/g' cfg/voc.data

最後に事前学習済の重みを取得します。これで準備は完了です。

!wget https://pjreddie.com/media/files/darknet53.conv.74


訓練

ということで、いざ訓練!先ほど変更した設定ファイルや重みを引数に与えています。yolov3-voc.cfgはモデルやハイパーパラメーターが定義されたファイルですが、今回は何も変更していません。中身を見てみるとResNetっぽい(ショートカットがある)モデルですが、Darknetと呼ぶそうです。重みを更新するかどうかは書かれていないように見えましたが、全てのレイヤーの重みを更新しているんでしょうか・・・。であれば、事前学習済の重みは必要だったのか?よくわかりません。ご存知の方コメント頂けると嬉しいです。

!./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg darknet53.conv.74

ここからはGoogle Colabに任せ待つしかありません。

・・・1時間後・・・

出力エリアがクラッシュ(?)してる!



なんかやたらログ出てるなぁと思ってたらバッチサイズ1で学習してました。これはまずそうなので、先ほどのyolov3-voc.cfgを変更します。さらにmax_batchesも大きすぎるようなので変更します。置換の順番は注意が必要です。詳細割愛しますが、うまく置換できずはまっていました。

!sed -i 's/ batch=1/# batch=1/g' cfg/yolov3-voc.cfg

!sed -i 's/# subdivisions=16/ subdivisions=32/g' cfg/yolov3-voc.cfg
!sed -i 's/ subdivisions=1/# subdivisions=1/g' cfg/yolov3-voc.cfg
!sed -i 's/# batch=64/ batch=128/g' cfg/yolov3-voc.cfg
!sed -i 's/max_batches = 50200/max_batches = 10000/g' cfg/yolov3-voc.cfg

そして、再度学習コマンドを実行!

・・・(12時間後)・・・

全く終わりません。このままではせっかく学習した重みが消えてなくなるため、別ノートブックを使いbackupディレクトリ以下の学習済みの重みを定期的(今回は1時間毎)にGoogle Driveにコピーします。

import sys

import os
import glob
import time
import datetime
from time import sleep

def save_file(file_name):
!cp $file_name /content/drive/'My Drive'/temp/

def epoch_to_datetime(epoch):
return datetime.datetime(*time.localtime(epoch)[:6])

JST = datetime.timezone(datetime.timedelta(hours=+9), 'JST')

while True:
now = datetime.datetime.now()
now_jst = datetime.datetime.now(JST)
print(now_jst)
file_list = glob.glob('backup/*')
for file in file_list:
sys.stdout.write(file)
created_time = epoch_to_datetime(os.path.getctime(file))
if created_time >= now - datetime.timedelta(hours=1):
try:
save_file(file)
print(' was uploaded!')
except IOError:
print(' had IOError!')
else:
print(' was ignored.')

print('waiting...')
sleep(60 * 60 * 1)

そして12時間後にはdarknet53.conv.74の代わりに上記でコピーしておいた学習した重みを使い学習再開ということを繰り返すこと約2週間(仕事が忙しくて学習しない日もあったり)ようやく10000エポック終わりました!こちらに以下のように書いてありましたが、最終的にlossは0.7くらいになっていたと思います。


When you see that average loss 0.xxxxxx avg no longer decreases at many iterations then you should stop training. The final avgerage loss can be from 0.05 (for a small model and easy dataset) to 3.0 (for a big model and a difficult dataset).



テスト

ようやく学習も終わり、効果のほどを確認しようと思い、コマンド実行してみます(正確には前回同様以下のコマンドの後にpredictions.jpgを表示しています)。

!./darknet detect cfg/yolov3-voc.cfg backup/yolov3-voc_final.weights VOCDataset/VOCdevkit/VOC2007/JPEGImages/000028.jpg

すると・・・

なるほど、この茶色い生物っぽいものはtruckと呼ぶのか・・・(しかも精度100%と自信満々の回答)。他の画像のテストをしてみても軒並み分類結果が異なります。しかし物体検出自体はできていました。これはさすがに何かおかしいと思い色々と調べていると、どうもdata/coco.namesを参照しラベル表示しているようでした。同じようなことがこちらにも書いてあり、./darknet detector testを実行してみたか?というようなコメントがありました。よく見てみると自分の実行したコマンドは./darknet detectでちょっと違います。ということで・・・

!./darknet detector test cfg/voc.data cfg/yolov3-voc.cfg backup/yolov3-voc_final.weights VOCDataset/VOCdevkit/VOC2007/JPEGImages/000028.jpg

を実行!すると・・・

ついに正しく表示されました!長かった・・・。その他の画像も概ねうまく物体検出できているようでした。






まとめ


  • batch、subdivisions、max_batchesの値を忘れずに変更しましょう

  • 学習は12時間では終わらないため、重みをGoogle Driveにコピーし、次はそれを元に学習を再開しましょう

  • テストは./darknet detector testを使いましょう


最後に

どなたかのお役に立てれば幸いです。コードはGitHubに公開したいなぁとは思っています。公開しました


参考にさせて頂いたページ