依存関係はシステム全体ではなく、conda 環境のみでインストールされます。
前書き
エッジデバイスでの高速物体検出のためにYOLOv4モデルを学習させようと考え、YOLOv3 DarknetリポジトリからフォークされたYOLOv4公式リポジトリを見つけました 。
DarknetはC言語とCUDAで書かれたニューラルネットワークフレームワークです。GPU用のセットアップは明示的に説明されていませんが、OpenCVとCUDA-Toolkitの公式サイトにリンクされ、システム全体にインストールするためのセットアップガイドが提供されています。私はPC1台で複数ニューラルネットワークのフレームワーク(Pytorch、Tensorflow 1と2など)を使用しているため、依存関係のあるライブラリをシステム全体にインストールすると、バージョンの競合を引き起こす可能性が高くなります。それにシステム全体にインストールされたライブラリーのアンインストール方法が統一されておらず、正しいアンインストール方法の調査時間も必要です。Anacondaを使用するとセットアップ環境を分離できます。conda環境でのGPU学習のためのDarknetのセットアップに関するガイドを見つけることができなかったため、このガイドを書くことにしました。
conda について
conda は 公式 Web ページ にあるように、多くのプログラミング言語用のパッケージ、依存関係、環境管理ツールです。python の場合、 pip に比べて、 conda は python ライブラリとその依存パッケージだけでなく、CUDA-Toolkit や cuDNN ライブラリのような他の言語で書かれた依存パッケージも環境にインストールすることが可能です。パッケージは環境にインストールされるため、1つのパッケージの複数のバージョンを環境毎に分けておき、切り替えて使えるという利点があります。conda のパッケージの削除は、アンインストールスクリプトなどが必要なく、以下のコマンドで特定の環境を削除するだけで十分です。
conda env remove -n <環境名>
セットアップ
Ubuntu 20.04 LTSにMinicondaをインストールしたPCでセットアップをテストしました。セットアップは非常に簡単で、インターネット環境にもよりますが、10分程度で完了します。また、CUDA-ToolkitとcuDNNライブラリには最低2GBのメモリが必要です。Darknetのニューラルネットワークフレームワークを構築するための要件にはOpenCVが含まれており、これもcondaを使用してインストールします。
conda環境の作成
YOLOv4_conda
という conda 環境を作成し、ターミナルで以下のコマンドを実行して python 3.7 と OpenCV 3.x をインストールします。
conda create -n YOLOv4_conda python=3.7 opencv=3
CUDA-Toolkit ドキュメントの説明に従って、以下のターミナルコマンドでnvidia
チャンネルからCUDA-ToolkitとcuDNNライブラリをインストールします。
conda install cuda cudnn -c nvidia -n YOLOv4_conda
cuda
パッケージは、GPU で動作する Darknet CUDA コードをビルドするための CUDA-Toolkit と NVCC (NVIDIA CUDA Compiler) をインストールするパッケージです。
nvidia
チャンネルにはcuda-toolkit
パッケージもあり、一般にTensorflowやPytorchなどのpythonベースの機械学習フレームワークのGPU学習に使用されますが、このパッケージにはcuda-toolkitだけが含まれ、NVCCは含まれません。
最後に、新しく作成した環境に切り替えます。
conda activate YOLOv4_conda
Darknet YOLOv4 プロジェクトをクローンします。
ターミナルでクローンしてリポジトリに切り替えます。
git clone https://github.com/AlexeyAB/darknet.git
cd darknet
今後の環境構築のために、ターミナルでクローンしたバージョンを確認します。
git --no-pager log --oneline -1
以下のようなコミットを返します。
8a0bf84 (HEAD -> master, origin/master, origin/HEAD) various fixes (#8398)
Darknetの設定
テキストエディタに Makefile
を読み込んで、以下の3箇所を修正します。
- 1~4行目:GPU ビルド、OpenCVとcuDNN を有効にする。
GPU=1
CUDNN=1
CUDNN_HALF=1
OPENCV=1
- 27~58行目:GPUの計算能力をアンコメントする(例:RTX 3090 GPU で学習する場合、以下をアンコメントしてください)。
`#` GeForce RTX 3070, 3080, 3090
ARCH= -gencode arch=compute_86,code=[sm_86,compute_86]
- 80行目: conda環境にインストールされているヘッダーファイルのパスを追加する (CONDA_PREFIX 変数に格納されます)
COMMON= -Iinclude/ -I3rdparty/stb/include -I$(CONDA_PREFIX)/include
Darknet のビルド
Darknet をビルドするのに必要なパッケージを pkgconfig
で見つけられるように、ターミナルで検索パス(またCONDA_PREFIX 変数を使います)に環境変数を一時的に追加します (現在のターミナルセッションでのみ)。
export PKG_CONFIG_PATH=$CONDA_PREFIX/lib/pkgconfig:$PKG_CONFIG_PATH
ビルドコマンドを実行する。
make
ビルド中にエラーが発生した場合、以下のような表示でビルド処理が停止します。
make: *** [Makefile:176: darknet] Error 1
ビルドエラーを修正するための解決策については、トラブルシューティングを参照してください。
データセットの準備
Darknet のドキュメントでは、データセットは build/darknet/x64/data
フォルダに作成することになっていますが、実際には darknet
リポジトリフォルダ内の任意の場所に配置することができます。ドキュメントには、./darknet
実行ファイルとデータセットを格納するフォルダの場所についての説明に矛盾があります。私の環境の場合、./darknet
実行ファイルはリポジトリのルートフォルダにあり、それに関しては、リポジトリのルートフォルダ内のdata
フォルダにデータセットを作成します。テスト用に、COCOデータセットから収集した画像で、小さなカスタムデータセット street_views_yolo
を作成しました。
アノテーションについて
データセットフォルダ内に、データセットで使われるすべてのクラス名を含むファイル classes.txt
を作成します。今回のテストデータセットでは、3つのクラスを使っているので、classes.txt
ファイルは次のような内容になっています。
car
bus
person
画像のアノテーションは、画像のファイル名と同じで、末尾が.txt
のファイルに保存されます。アノテーションは、画像が保存されているフォルダに保存してください。アノテーションのフォーマットは、1行に1バウンディングボックスで、以下のような構造になっています。
<object-class> <x_center> <y_center> <width> <height>
<object-class>
は, classes.txt
ファイル内の0から (car
) 始まるクラス番号です.<x_center>
と <y_center>
は、0 から 1 の間で正規化されたバウンディングボックスの中心点を含みます(それぞれ画像の幅と高さで割る)。<width>
と <height>
は、バウンディングボックスの幅と高さを表し、0から1の間で正規化されます。
例として、2つのバウンディングボックス(car
とbus
)を持つ画像 street1.jpg
に対するアノテーションは、以下の内容を持つファイル street1.txt
に格納されます。
0 0.5072916666666667 0.54453125 0.43125 0.3484375
1 0.5135416666666667 0.2875 0.40625 0.26875
アノテーションは Labelimg のようなアノテーションツールで行うことができ、すでに Darknet のアノテーションフォーマットが提供されています。インストールは、以下のコマンドを使用して conda
で簡単に行うことができます。
conda install labelimg -c conda-forge
データセットの分割
学習用画像と検証用画像は train
と val
フォルダに格納されます。すべての学習画像を一覧するファイル train.txt
と検証用を一覧するファイル val.txt
が必要です。一覧(JPEG画像の場合)は以下のコマンドで作成することができます。
ls train/*.jpg > train.txt
ls val/*.jpg > val.txt
データセットに関するすべての情報をまとめるためのインデックスファイル index.txt
を以下の内容で作成します。
classes = 3
train = train.txt
valid = val.txt
names = classes.txt
backup = backup
classes
は検出するクラスの数、 train
と valid
は学習画像と検証画像の一覧ファイルの場所、 names
はクラス名を含むファイルの場所と backup
はトレーニング中にモデルの重みが保存されるフォルダを指します。データセットのフォルダ構造は以下のようになっているはずです。
street_views_yolo/
├── classes.txt
├── index.txt
├── train/
│ ├── street1.jpg
│ ├── street1.txt
│ ├── ...
│ ├── street50.jpg
│ └── street50.txt
├── train.txt
├── val/
│ ├── street2.jpg
│ ├── street2.txt
│ ├── ...
│ ├── street10.jpg
│ └── street10.txt
├── val.txt
└── yolov4-custom.cfg
学習
モデルをゼロから学習させるのではなく、既に学習させたモデルの微調整をします。これは転移学習と呼ばれ、学習時間を大幅に短縮することができる。
上記のデータセットでYOLOv4モデルを学習させます。この学習マニュアルによると、検出するカスタムクラスの数に合わせて、学習済みモデルのいくつかのレイヤーを変更する必要があります。カスタムデータセットでYOLOv4を学習するための設定ファイル yolov4-custom.cfg
が cfg
フォルダの中に既に用意されているので、それを修正して使用します。
ネットワークの修正
まず、yolov4-custom.cfg
ファイルを以下のコマンドでデータセットフォルダにコピーします。
cp cfg/yolov4-custom.cfg data/street_views_yolo/.
次に、コピーした yolov4-custom.cfg
の行を学習マニュアルに説明されているように調整します。
基本的には、設定ファイルにある3つのYOLO-layerをyolo
というキーワードで検索するだけです。そして、3つの [yolo]
レイヤーのそれぞれで、 classes=80
を classes=3
の3つのクラスの上記データセットを合わせて変更します。そしてそれぞれの [yolo]
レイヤーの上にある [convolutional]
レイヤーでは、 filters=255
を filters=24
((クラス数 + 5) * 3) に変更します。最後に、必要であれば、 [net]
層の画像入力 width
と height
を、 width=608
と height=608
を 32 で割り切れるサイズ(例えば、 width=416
と height=416
)に変更することも可能です。
学習済みの重みファイルのダウンロード
YOLOv4の場合、学習マニュアルでリンクされた学習済みの重みファイル yolov4.conv.137
をダウンロードし、親フォルダ data
に保存しておけば、他のデータセットで学習する際に学習済みの重みファイルを再利用できます。ファイルとフォルダの構成は以下のようになります。
darknet/
├── build/
├── cfg/
│ ├── ...
│ └── yolov4-custom.cfg
├── darknet
├── ...
└── data/
├── ...
├── street_views_yolo/
│ ├── classes.txt
│ ├── index.txt
│ ├── train/
│ ├── train.txt
│ ├── val/
│ ├── val.txt
│ └── yolov4-custom.cfg
└── yolov4.conv.137
学習の開始
darknet
が conda 環境にインストールされた CUDA と cuDNN ライブラリを見つけるために、一時的に(現在のターミナルセッションでのみ)ターミナル内のライブラリパスに環境変数を追加します(ここでも CONDA_PREFIX 変数をを使います)。
export LD_LIBRARY_PATH=$CONDA_PREFIX/lib:$LD_LIBRARY_PATH
データセット内の画像へのパスは darknet
実行ファイルからの相対パスに合わせる必要があります。現在、データセット内の画像へのパスは street_views_yolo
のデータセットフォルダからの相対パスになっていますが、darknet
実行ファイルはリポジトリのルートフォルダに配置されています。
実行ファイルがデータセットのインデックスファイルに記載したデータセットの画像を見つけるために、データセットフォルダ内で実行します。まず、dataset フォルダに移動します。
cd data/street_views_yolo
そして、datasetのフォルダに相対パスで以下のコマンドで学習を開始します。
../../darknet detector train \
index.txt \
yolov4-custom.cfg \
../yolov4.conv.137 \
-dont_show -map -mjpeg_port 8090
学習実行中にエラーが発生した場合は、トラブルシューティングを参照して対処してください。
-mjpeg
のパラメータにより、ブラウザで URL http://ip-address:8090
にアクセスすると、以下のようなグラフが表示され、学習の進捗確認ができます。
青線は損失、赤線は mean Average Precision (mAP) を表す。
学習の成果
学習終了後、結果はデータセット内の backup
フォルダに保存されます。
street_views_yolo/
└── backup/
├── yolov4-custom_10000.weights
├── yolov4-custom_best.weights
├── yolov4-custom_final.weights
└── yolov4-custom_last.weights
yolov4-custom_best.weights
は、平均精度が最も高い重みファイルになります。
クリーンアップ
CUDA、cuDNN、OpenCVなどのインストールされているパッケージやライブラリを全てアンインストールするには、以下のコマンドで作成したconda環境を削除するだけです。
conda env remove -n YOLOv4_conda
そして、ローカルにクローンした darknet
リポジトリフォルダを削除します。
これで完了です。
トラブルシューティング
Darknetの構築について
問題: Darknetのビルドが以下のようなエラーで停止する。
No package 'opencv' found
/usr/bin/ld: cannot find -lcudart
/usr/bin/ld: cannot find -lcublas
/usr/bin/ld: cannot find -lcurand
collect2: error: ld returned 1 exit status
make: *** [Makefile:176: darknet] Error 1
解決方法: 以下のコマンドで、PKG_CONFIG_PATH
にconda環境を追加してください。この設定は、現在のターミナルセッションを閉じると消えます。新しいセッションでは再度コマンド実行することを忘れないでください。
export PKG_CONFIG_PATH=$CONDA_PREFIX/lib/pkgconfig:$PKG_CONFIG_PATH
Darknetの学習について
問題: 学習が以下のようなエラーで停止する。
error while loading shared libraries: libopencv_dnn.so.<version>: cannot open shared object file: No such file or directory
解決方法: 以下のコマンドで、conda 環境を LD_LIBRARY_PATH
に追加します。この設定は、現在のターミナルセッションを閉じると消えます。新しいセッションでは再度コマンド実行することを忘れないでください。
export LD_LIBRARY_PATH=$CONDA_PREFIX/lib:$LD_LIBRARY_PATH
問題: 学習が以下のようなエラーで停止する。
Error in load_data_detection() - OpenCV Cannot load image <path_to_image>
解決方法:
train.txt
またはval.txt
の画像のパスが正しく設定されていません。画像を保存するフォルダ構造が両方のファイルの画像パスと一致しているかを確認してください。パスはデータセットフォルダからの相対パスになります。