Pytorch-YOLOv3で独自データセットの学習レビュー

こんにちは、なんちゃってエンジニアのもにょです。

今回はGitHub上で公開されている下記のYOLOv3のリポジトリのコードを使って、
独自データセットの学習を行ったので、レビュー結果と手順を紹介します。
PyTorch-YOLOv3 (eriklindernorenさん)
https://github.com/eriklindernoren/PyTorch-YOLOv3

動機

  • YOLOv3が実装されているコードを使って物体検出したい
  • PytorchイケてそうだからPytorchのがいい
  • GitHubにコードあるけどいっぱいあってどれ使えるのかわからん
  • とりあえずいけそうなの使ってみるか

レビュー結果

先にレビュー結果はこんな感じでした。
  • Window10環境を想定していないのでbashのシェルで実行してる部分で手間かかる
  • pillow 7.0.0を入れるとエラー出る
  • COCOで学習しようとしたら「1エポック8日かかる」と表示されたので即中止
  • 推論しようとしたらoutput/samplesがないのでエラーが出る
  • 独自のデータセットを整えるのめんどくさい
  • 学習しようとしたらutils/logger.pyでWarningが出る(無視)
    The name tf.summary.FileWriter is deprecated. Please use tf.compat.v1.summary.FileWriter instead.
  • 学習しようとしたらメモリのエラーが出るRuntimeError: CUDA out of memory)
  • 学習しようとしたらEvaluating Modelフェーズでフリーズする
  • 学習中に下記の大量のWarningが出る(無視)
    Warning: indexing with dtype torch.uint8 is now deprecated, please use a dtype torch.bool instead.
  • なんとか学習して推論できたけど空しくなった
    (物体検出できて何が嬉しいんやろ・・・)

筆者環境

  • OS:Window 10 Home
  • GPU:NVIDIA GeForce GTX 1080
  • GPUメモリ:8GB
  • CUDA:10.0
  • Python環境:Anacondaインストール済み
  • Git for Windowsインストール済み

手順

Anacondaで仮想環境を作成

Anaconda のプロンプトを開いて作業開始
conda create -n pytorch-yolov3 python==3.6.8
conda activate  pytorch-yolov3
※私のAnaconda環境では、仮想環境を作成する標準ディレクトリを変更しています。
 標準ディレクトリを変更するには、環境変数に「CONDA_ENVS_PATH」を追加して、任意のディレクトリ(xxxx/envs)を指定します。「envs」で終わらないとよくないことが起きる
※pythonはたぶん3.6.8・・・バージョンが明記されていないのでこれで確認した

リポジトリをクローン

※作業したいディレクトリに移動後、下記行ってください。
git clone https://github.com/eriklindernoren/PyTorch-YOLOv3
cd PyTorch-YOLOv3/

requirements.txtのリストをconda使ってインストール

conda install numpy matplotlib pillow==6.2.1 tqdm
conda install pytorch==1.0.0 torchvision==0.2.1 cuda100 -c pytorch
conda install tensorflow==1.15 
pip install terminaltables
※pillow 7.0.0を入れるとpytorchのtransformが動かんので前のバージョンを入れました
※pythorchは1.4.0を入れると大量のWarning(uint8)が出るので1.0.0にしました。
※tensorflowは正直どのバージョンかわかりません。とりあえず1.15にしてます。

重みダウンロード

https://pjreddie.com/media/files/yolov3.weights
https://pjreddie.com/media/files/yolov3-tiny.weights
https://pjreddie.com/media/files/darknet53.conv.74
※この記事で使うのは一番下だけです。

ダウンロード後、PyTorch-YOLOv3/weights/ フォルダに移動

独自の学習データを作成

学習データを集める

・icrawlerをインストールする
pip install icrawler

icrawler.pyを作成する(ファイル名任意)
from icrawler.builtin import BingImageCrawler
bing_crawler = BingImageCrawler(storage={'root_dir': 'images'})
bing_crawler .crawl(keyword='犬', max_num=100)
※上記は、「犬」のキーワードで100枚画像ダウンロードします。
100枚行かないときもあります。

・画像をダウンロード
python icrawler.py

imagesフォルダに画像ダウンロードされていることを確認する。

アノテーションデータを作る

・アノテーションツールのVoTT(vott-2.1.0-win32.exe)をダウンロードする

・下記のブログがわかりやすいので参考にアノテーションデータを作成する
【物体検出】アノテーションツールVoTTの使い方

※「Pascal VOC」形式で出力すること

アノテーションデータをVOC形式からyolo形式へ変換する

変換ツールを作っている人がいたので、下記の記事を参考に変換する

アノテーションファイル変換(xmlからYOLOへ)

・下記のリポジトリを任意のディレクトリでクローン
git clone https://github.com/Ryo-Kawanami/xml2yolo.git
cd xml2yolo

・必要なライブラリをインストール
pip install lxml
conda install -c conda-forge opencv

・画像データ配置ディレクトリを作る
mkdir traffic_label\20200222
mkdir traffic_img\20200222
mkdir traffic_format4yolo

・画像データとアノテーションデータをフォルダに配置
traffic_img/20200222:画像データ
traffic_label/20200222:XMLのアノテーションデータ

・クラス名を記述
traffic_classes.txt」の中にクラス名を記述する(dogならdogのみ)

・変換ツール(xml2yolo.py)のコードを修正

- XMLの座標データが浮動小数になっているので、floatでキャストしてから整数に丸めるように修正
変更前
xmin = int(bndbox.find('xmin').text)
ymin = int(bndbox.find('ymin').text)
xmax = int(bndbox.find('xmax').text)
ymax = int(bndbox.find('ymax').text)
変更後
xmin = round(float(bndbox.find('xmin').text))
ymin = round(float(bndbox.find('ymin').text))
xmax = round(float(bndbox.find('xmax').text))
ymax = round(float(bndbox.find('ymax').text))

- 出力パスを修正
変更前
parentpath = './' #"Directory path with parent dir before xml_dir or img_dir"
addxmlpath = parentpath + 'traffic_label/201702071403' #"Directory path with XML files"
addimgpath = parentpath + 'traffic_img/201702071403' #"Directory path with IMG files"
outputpath = parentpath + 'traffic_format4yolo' #"output folder for yolo format"
classes_txt = './traffic_classes.txt' #"File containing classes"
ext = '.png' #"Image file extension [.jpg or .png]"
変更後
parentpath = './' #"Directory path with parent dir before xml_dir or img_dir"
addxmlpath = parentpath + 'traffic_label/20200222' #"Directory path with XML files"
addimgpath = parentpath + 'traffic_img/20200222' #"Directory path with IMG files"
outputpath = parentpath + 'traffic_format4yolo' #"output folder for yolo format"
classes_txt = './traffic_classes.txt' #"File containing classes"
ext = '.jpg' #"Image file extension [.jpg or .png]"

・変換処理を実行
python xml2yolo.py
traffic_format4yolo」フォルダにyolo形式のアノテーションデータが出力されていることを確認する。

独自のデータセットを配置

カスタムモデル定義を作成

Git Bashで下記のコマンドを実行
※Git BashはGit for Windowsをインストールしていたら入ってるはず
cd config/                                
bash create_custom_model.sh <num-classes> 

クラス名ファイルを作成

下記のクラス名のみのファイルを作成する
data/custom/classes.names

画像データを配置

画像データを下記に配置する
data/custom/images/

アノテーションデータを配置

先ほど作成したyolo形式のアノテーションデータを下記に配置する
data/custom/labels/

訓練用と検証用のデータを定義

訓練用と検証用の画像データパスを記述した下記のファイルを作成する
data/custom/train.txt
data/custom/valid.txt

記述例:
data/custom/images/000001.jpg
data/custom/images/000002.jpg
data/custom/images/000003.jpg

コードの内容を修正する

評価部分でフリーズするので暫定対処

train.pyの下記部分(150行目)を修正する
変更前
if epoch % opt.evaluation_interval == 0:
変更後
if epoch % opt.evaluation_interval == 1:

学習を実行する

・GPUのアウトオブメモリーが出たのでバッチサイズを4に設定
・筆者の画像データは320x320で作成したので320
・下記のコマンドで学習開始
python train.py --batch_size 4 --img_size 320 --model_def config/yolov3-custom.cfg --data_config config/custom.data --pretrained_weights weights/darknet53.conv.74

結果を確認する

checkpointsフォルダに学習済みの重みが出力されていることを確認する

推論をする

推論用の画像データを配置する

筆者の場合、data/originalフォルダを作成し、そこに推論用の画像データを配置した

検出結果の画像の出力先フォルダを作成する

筆者の場合、output/originalフォルダを作成した。
多分推論用の画像フォルダと同じ名前じゃないといけないんだと思う。
出力に失敗したらエラーが出るのでそれ見て、出力先のフォルダ名つけてください。

推論を実行する

python detect.py  --image_folder data/original/ --model_def config/yolov3-custom.cfg --weights_path checkpoints/yolov3_ckpt_99.pth --class_path data/custom/classes.names --img_size 320
output/originalフォルダを見て、画像が出力されていることを確認する


皆さんはうまくいきましたでしょうか。
うまくいかなったところがあったらがんばってください・・・
結構つまずきまくったので、やっぱり人のコードを使うときは検証が必要ですね。
読んでくださった方ありがとうございました。
健闘を祈る!

その他

train.pytest.pyのevaluate関数を読んでるところで、batch_size=8と直書きしているので、
batch_size=opt.batch_sizeに直した方がいいんじゃね?って思いました。

以上です。
閲覧ありがとうございました。

コメント