🤖

SSD-Mobilenet メモ

2025/01/13に公開

なぜ今 SSD-Mobilenet なのか

学校で NVIDIA Jetson Orin Nano を使っていて、物体検出をしたかったため。
Jetson で物体検出を行う際に、MobileNet は軽量でよく使われるモデルとして知られている。

https://github.com/dusty-nv/jetson-inference

Nvidia が onnx にエクスポートするツールを提供しているので、すぐに Jetson で動かせる。

https://github.com/dusty-nv/pytorch-ssd

また、私が知見を元に変更を加えたフォーク版もある。

https://github.com/fa0311/pytorch-ssd-forked

この記事の目的

私が Jetson で物体検出を行った際に、発生した問題や精度向上についてのメモを残す。
Windows または Linux(Ubuntu) で動かしたい人の参考になれば幸い。

環境構築

pytorch-ssd-forked を利用する場合は、忙しい人向け環境構築を参照してください。

git clone https://github.com/dusty-nv/pytorch-ssd
cd pytorch-ssd

ドキュメントが古い上にバージョンについて詳しい記載がないため、何をインストールすれば良いのかわからない。
可能な限り最新の環境で動作させることを目指す。
Python 3.6 + PyTorch 0.4 とか使ってられないので

幸いにも、ほぼ最新の環境(Python 3.12 + PyTorch 2.5.1 + CUDA 12.4) でそのまま動作した。
最新は Python3.13 であるが、PyTorch が対応していないため、Python3.12 で動かす。

pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124

pytorch-ssdrequirements.txt は利用せずに手動でインストールする。

この時に依存としてインストールされる、numpy は新しすぎて動かないが、後で直す。
protobuf も新しいものは動かないので、4.25 に固定する。

pip install boto3 pandas urllib3 tensorboard opencv-python protobuf==4.25

run_ssd_example.py を修正して最新の numpy でも動くようになる。
1 行変えるだけで直ったのがうれしい。

-    box = boxes[i, :]
+    box = list(map(int, boxes[i, :]))

MobileNet V2 で GPU を使ってくれない問題を修正するには vision/ssd/mobilenet_v2_ssd_lite.py を修正する。
なぜこれだけ、device が CPU になっているのか謎。

-    def create_mobilenetv2_ssd_lite_predictor(net, candidate_size=200, nms_method=None, sigma=0.5, device=torch.device('cpu')):
+    def create_mobilenetv2_ssd_lite_predictor(net, candidate_size=200, nms_method=None, sigma=0.5, device=None):

https://github.com/fa0311/pytorch-ssd-forked/commit/cf6439044c1355ccab415f5b4cbe4ed38d277242#diff-7b4423f461d4aeb6a012dfc0953b5860c71b8757155f29ffd2eac3cca6f94389L59

そして、README に書いてある Google Drive のリンクからモデルをダウンロードする。
このモデルのライセンスは不明、ソースコードと同じという解釈で良いのかな。

https://drive.google.com/drive/folders/1pKn-RifvJGWiOx0ZCRLtCXM5GT5lAluu

忙しい人向け環境構築

私の作成した fork 版を使用する。上記の問題を修正しているので、忙しい方はこちらを使うと良い。

git clone https://github.com/fa0311/pytorch-ssd-forked
cd pytorch-ssd-forked
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124
pip install -r requirements.txt # うまくいかない場合は pip install -r requirements-lock.txt

wget https://github.com/fa0311/pytorch-ssd-archive-model/releases/download/v0.0.1/mobilenet-v1-ssd-mp-0_675.pth -O models/mobilenet-v1-ssd-mp-0_675.pth
wget https://github.com/fa0311/pytorch-ssd-archive-model/releases/download/v0.0.1/mb2-ssd-lite-mp-0_686.pth -O models/mb2-ssd-lite-mp-0_686.pth

忙しい人向け環境構築 (Docker)

Entrypointtensorboard を起動するように設定している。
https://github.com/fa0311/ichigo-compose/blob/main/ichigo2025/mobilenet/Dockerfile

Jetson で MobileNet V3 を動かす

Nvidia が Fork した後に、MobileNet V3 が追加されたので、Nvidia の Fork には MobileNet V3 が含まれていない。
マージしてあげれば動くが、コンフリクトしまくって大変だった。
あんまりテストしてないので、動かないものもあるかもしれない。

https://github.com/fa0311/pytorch-ssd-forked/commit/124bd531b7a7299867588f05cc8861553227977c

学習とパラメータ

train_ssd.py で学習する際のパラメータを説明する。
MobileNet V2VOC を学習する場合の例を示す。

python train_ssd.py `
    --pretrained-ssd=models/mb2-ssd-lite-mp-0_686.pth `
    --net=mb2-ssd-lite `
    --dataset-type=voc `
    --data=data/voc `
    --model-dir=models/output `
    --validation-epochs=1 `
    --num-workers=8 `
    --batch-size=32 `
    --num-epochs=30 `
    --t-max=30 `
    --lr=0.001 `
    --extra-layers-lr=0.0001 `
    --base-net-lr=0.001 `
    --weight-decay=0.0001 `
    --validation-mean-ap

また、Windows では pickle 化がうまくいかないため num-workers0 にしないと動かない。
並列処理ができないため、かなりの時間を要する。

デフォルトのスケジューラー (--scheduler=cosine) を利用する場合、--t-max はデフォルトが100で、--num-epochs と値が違う場合は --t-max を指定したほうが良い。

--base-net-lr はベースネットワークの学習率で、--extra-layers-lr は追加のレイヤーの学習率である。
どちらも指定していると、--lr は無視されるはずだが、SGD でこの値が使われていそうなので念の為指定する。

この例では行っていないが、--freeze-base-net を指定することで、ベースネットワークの学習を行わず、追加のレイヤーのみ学習することができる。
--freeze-base-net を指定する場合、--base-net-lr は無視される。

--validation-mean-ap を指定することで、Validation の mAP を計算することができる。

学習率のパラメータなどの具体的な値は適当です。実際に動かしてみて調整してください。

tensorboard --logdir models/output/tensorboard で学習の進捗を確認する。

pytorch-ssd-forked を利用する場合

pytorch-ssd-forked で学習する際のパラメータを説明する。
MobileNet V2VOC を学習する場合の例を示す。

python3 train_ssd.py `
    --pretrained-ssd=models/mb2-ssd-lite-mp-0_686.pth `
    --net=mb2-ssd-lite `
    --dataset-type=voc `
    --data=data/voc `
    --model-dir=models/output `
    --validation-epochs=1 `
    --num-workers=8 `
    --batch-size=32 `
    --num-epochs=56 `
    --t-0=8 `
    --lr=0.001 `
    --extra-layers-lr=0.0001 `
    --base-net-lr=0.001 `
    --weight-decay=0.0001 `
    --validation-mean-ap `
    --scheduler=cosine-warmrestart `
    --no-augment

--no-augment を指定することでデータ拡張を削除することができる。
データ拡張を削除したい場合は、このオプションを指定する。

--scheduler=cosine-warmrestart を指定することで、スケジューラーを CosineAnnealingWarmRestarts に変更する。
これにより局所最適解に陥りにくくなる。
--t-0 で最初にリセットされるエポック数を指定することができる。

https://pytorch.org/docs/stable/generated/torch.optim.lr_scheduler.CosineAnnealingWarmRestarts.html

実際に利用した結果は以下の通りで、8, 24 エポック目で学習率がリセットされていることがわかる。
(最初の周期で8エポック、次の周期で16エポック、次の周期で32エポック、次の周期で64エポック、という感じでリセットされる)
1738126480728

ハードコーディングされているパラメータを変更する

MultiboxLoss のパラメータがハードコーディングされているので、変更する。
neg_pos_ratio はデフォルトで 3 になっているので、調節する。
他にも iou_threshold center_variance size_variance という引数が与えられているが、使われていないように見える。(要検証)

データ拡張を削除する

TrainAugmentation がデータ拡張を行っているが、色を変更する必要がない場合や、欠けている物体を認識する必要がない場合は削除したほうが良い。
この辺は、認識させたい物体やデータセットによって異なるので、適宜削除する。

以下はいちごの写真だが、いちごは必ず赤色であり、色を大きく変更する必要がない。彩度や明るさを変更するだけで十分である。
人間が見て、いちごとは認識出来ないものが生成されている。

1736717035070

私は独自のデータ拡張プログラムを作成してデータ拡張しているので削除した。

        self.augment = Compose([
            ConvertFromInts(),
-            PhotometricDistort(),
-            Expand(self.mean),
-            RandomSampleCrop(),
-            RandomMirror(),
            ToPercentCoords(),
            Resize(self.size),
            SubtractMeans(self.mean),
            lambda img, boxes=None, labels=None: (img / std, boxes, labels),
            ToTensor(),
        ])

SubtractMeans によって色が見やすくなっており、lambda img, boxes=None, labels=None: (img / std, boxes, labels) によって色が薄くなっている。
色が重要な場合は、これらを削除したほうが良さそうだが、せっかくの転移学習が台無しになるかもしれない。この辺は検証が必要。

また、train_loader の中身を確認するには以下のプログラムを train_loader が読み込まれた後に追加すれば良い。
train_loader には TrainAugmentation が適用されたデータが格納されている。

pytorch-ssd-forked を利用する場合は、--no-augment を指定することでデータ拡張を削除することが出来る。

for i, data in enumerate(train_loader):
    images, boxes, labels = data
    for ii, img in enumerate(images):
        rgb = cv2.cvtColor((img.permute(1,2,0).numpy() * 255).clip(0,255).astype(np.uint8), cv2.COLOR_BGR2RGB)
        cv2.imwrite(f"images/{i}-{ii}.jpg",rgb  )

データ拡張を削除するべき状況

  • すでに既存のデータセットで十分なデータ拡張が行われている場合
  • Validation Loss が Train Loss よりも低い

私の場合、後者の問題で悩み、データ拡張を削除したところ解決した。

過学習させる

学習を辞める目安やパラメータを調整する際の目安として過学習させたいが、MobileNet は軽量なため、過学習させるのが難しい。
エポック数を増やしたり、学習率を下げたりしても、過学習しない。
また、デフォルトでは cosine スケジューラなので、100 エポックで学習率が 0 になり、それから学習率が上がる。

TrainAugmentation によるデータ拡張を削除し、SGD の --weight-decay を小さくすることで、過学習させることができる。

調整して過学習させたものと比較したものが以下の画像のグラフ。20 エポックを超えたあたりから Classification Loss/valLoss/val が上がっているのがわかる。
1736724077853

モデルの評価

run_ssd_example.py は、単一の画像しか受け付けず、少し不便なのでカメラからの入力を受け付けるスクリプトを作成した。

https://github.com/fa0311/pytorch-ssd-forked/blob/fix/run_ssd_camera.py

1736727305879

GIF にする際にフレームレートを下げたため実際よりも遅く見えるが、実際は 30 FPS で動いている。
ちなみに、TensorRT に変換して Jetson で動かすと、80 FPS くらいで動作する。

GitHubで編集を提案

Discussion