NMS(重なり抑制)の実務チューニングガイド【評価と本番を揃える意味】
「学習の中身はNMSに依存しない。でも“何を良しとするか”はNMSに依存する」。この当たり前を仕組みに落とし込む。なお、本内容は調査中の内容を含みます。
目次
1. NMSって何?なぜ必要?
要点 物体検出モデルは同じ物体に複数の予測箱を出します。NMS(Non-Maximum Suppression)は、重なり具合(IoU)やスコアを指標に代表1個だけ残す後処理です。
困るパターン
- 同じモノに2〜3箱 → 重複検出で見づらい・誤カウント
- 密集シーンで本当は複数あるのに強い1箱に潰される → 見逃し
つまり「重複を減らす」×「見逃しを減らす」のバランス調整がNMSの核心です。
2. 調整するツマミ(conf / nms_iou / 方式)
conf(スコア閾値)
↑ 高い=厳選(外れ減)/ ↓ 低い=拾う(見逃し減)
nms_iou(IoU閾値)
↑ 高い=重なっても複数残す(見逃し減・重複増)/ ↓ 低い=すぐ1箱にまとめる(重複減・見逃し増)
方式(標準 / Soft / DIoU…)
標準NMSは二者択一、Soft-NMSは「消す代わりに減点」、DIoU-NMSは中心距離も考慮。
初手の目安(YOLO系):conf=0.25
, nms_iou=0.45
。ここから上下に刻んで最適点を探す。
3. 重要な真実:学習は非依存、評価は依存
結論:学習の損失や勾配はNMSに依存しない(NMSは後処理)。しかし、「どの重みを良しとするか」=評価・選抜・停止判断はNMSに依存します。
なぜ“学習時にNMSを決める”意味がある?
- チェックポイント選抜:学習中valのmAPはNMS設定で変わる。本番と同じNMSで評価しないと「学習で最良=本番で最良」とは限らない。
- Early stopping / LRスケジュール:plateau判定はval指標依存。本番NMSと同条件だと判断がブレない。
- ハイパラ比較の公平性:NMSが違うとAP/ARがズレ、結論が変わる。評価の物差しとしてNMS固定は有用。
- 擬似ラベル・AL:“残す予測”はNMS+閾値に依存。学習ループに間接的に効くので、ここでは意味が大きい。
要するに 「探索は自由でOK、でも最終的には“本番NMS=val NMS”に固定して選抜」が筋が良い。
4. 実務ワークフロー:探索 → 固定 → 選抜 → 本番
- 探索:推論側でグリッド探索(例:conf ∈ {0.2, 0.25, 0.3}, IoU ∈ {0.45, 0.5, 0.55}, Soft有無)で「業務要件に合う運用プロファイル」を決める。
- 固定:決めたNMS(conf / IoU / 方式)を 評価設定としてExpに固定。
- 選抜:その設定でvalし、最良エポック(チェックポイント)を選ぶ。
- 本番:運用でも同じNMSでデプロイ。学習で見た数字=現場の数字、に近づく。
5. YOLOXでの設定ポイント(Exp・CLI・postprocess)
5.1 Exp(設定ファイル)に固定(再現性◎)
# yolox_s.py の Exp.__init__ に追記
self.test_conf = 0.25 # 評価/推論のスコア閾値(本番と同じにする)
self.nmsthre = 0.50 # NMSのIoU閾値(本番と同じにする)
学習中のval・tools/eval.py・demo.pyがこの値を参照。評価軸の固定が目的。
5.2 CLIで一時上書き(探索向き)
# 推論デモ
python tools/demo.py image -f yolox_s.py -c weights/best_ckpt.pth \
--path assets/test.jpg --conf 0.25 --nms 0.50
# 評価(mAP計測)
python tools/eval.py -f yolox_s.py -c weights/best_ckpt.pth \
--conf 0.25 --nms 0.50
探索で当たり値が見えたら、最終的にExpへ固定しよう(評価と本番を揃える)。
5.3 postprocessを合わせる(Soft/DIoU/Class-wise等)
本番がBatchedNMS/Soft-NMS/DIoU-NMS/Class-agnosticなら、YOLOX側でも同等のpostprocessに合わせて評価するとズレが減る。クラス別閾値はpostprocess直前にscore
でフィルタするだけでも効く。
6. 用途別レシピ(そのまま試せる)
6.1 重複が目立つ(同一物体に2〜3箱)
conf = 0.32〜0.35
(弱い予測を捨てる)nms_iou = 0.40〜0.45
(まとめやすくする)- Soft-NMSはオフ(シンプル優先)
AP↑ / ARは微減になりやすい。見逃し増えすぎなら nms_iou=0.45
へ戻す。
6.2 見逃しが多い(小物・密集)
conf = 0.20〜0.25
(拾いにいく)nms_iou = 0.55〜0.60
(重なっても複数残す)- Soft-NMS ON(linear/gaussian,
sigma=0.5〜0.8
)
AR↑の狙い。誤検出が増えたら conf
を+0.05。
6.3 細長い/平行物(線・ケーブル等)
- DIoU-NMSを試す(中心距離を加味)
nms_iou = 0.50〜0.60
conf = 0.25
から微調整
6.4 クラス別に“甘辛”を振る(class-wise)
- 誤検出が多いクラス:
conf +0.05〜0.10
、nms_iou −0.05
- 見逃しが多いクラス:
conf −0.05
、nms_iou +0.05
、Soft-NMS
# 疑似コード(スコア閾をクラス別に)
per_cls_conf = {"earphones":0.30, "stationery":0.30, "pebble":0.28}
filtered=[]
for b in boxes:
cls = id2name[b.cls]
thr = per_cls_conf.get(cls, 0.25)
if b.score >= thr:
filtered.append(b)
final = nms(filtered, iou=0.55, method="soft", sigma=0.6, class_agnostic=False)
6.5 サイズ依存NMS(小物は残しやすく)
- 小さい箱には
nms_iou +0.05
(潰れ防止) - 大きい箱は標準設定
7. 良し悪しの見分け方(AP/ARだけ見ない)
観察 | ありがちな原因 | 調整のヒント |
---|---|---|
重複検出が多い | conf低すぎ / nms_iou高すぎ | conf↑ / nms_iou↓ |
見逃しが多い | conf高すぎ / nms_iou低すぎ | conf↓ / nms_iou↑ / Soft-NMS |
小物が消える | NMSが強すぎ | nms_iou↑ / Soft-NMS / 入力解像度↑ |
似た物体を取り違える | 難負例不足 / スコア閾が甘い | 難負例追加 / conf↑ / class-wise |
見るべき数字(補助指標)
- FP/画像:誤検出の平均個数(低いほど良い)
- 重複率:1GTあたりの予測箱数(1.0〜1.2程度が目安)
- クラス別AP/AR:弱点クラスの伸びを確認
8. 一歩進める:Soft-NMS / DIoU-NMS / class-wise
8.1 Soft-NMS
完全削除ではなく重なりに応じて減点。密集・小物で有効。sigma≈0.5〜0.8
、方式はlinear
かgaussian
。
8.2 DIoU-NMS / GIoU-NMS
IoUに加えて中心距離や外接矩形を考慮。細長い物や近接多発に強い。nms_iou=0.50〜0.60
で試す価値あり。
8.3 class-wise / size-wise
クラスやサイズ帯で閾値を変えると、現実のデータ分布に合わせやすい。評価時も本番も同じ戦略を適用し、学習で見た挙動=現場の挙動に近づける。
9. 最終チェックリスト(評価と本番を揃える)
- 探索:推論でグリッド探索 → 運用プロファイル(conf / IoU / 方式)を決める
- 固定:Expに test_conf / nmsthre を設定(本番と同一)
- 選抜:その設定でvalし、最良チェックポイントを採用
- 本番:同じNMSでデプロイ(TensorRT/ONNX側のBatchedNMS等も揃える)
- AP/AR+FP/画像+重複率+クラス別を確認(APだけで判断しない)
- 小物・密集なら Soft-NMS / DIoU-NMS / size-wise を検討
- クラス特性が違うなら class-wise 閾値で“甘辛”を振る
まとめ(超短縮)
学習の内部はNMSに非依存。でも評価・選抜はNMSに依存。
だから「探索→固定→選抜→本番」で、本番NMS=val NMSに揃えて意思決定する。
同じ重みでもNMSで見える性能は動く――評価軸は固定して勝ちに行こう。