DarknetにおけるYOLOモデルのAnchor Box最適化:K-meansクラスタリングによる精度向上手法

Darknetフレームワークは、YOLOv4やScaled-YOLOv4などの高性能検出アーキテクチャを実装するための軽量かつ柔軟な基盤です。しかし、その検出性能は、単にネットワーク構造や学習率に依存するものではなく、Anchor Boxの設計品質に強く影響されます。本稿では、データセット固有のAnchorを科学的に導出するためのK-meansベースのクラスタリング手法を、実装中心で解説します。

Anchor Boxが検出精度に与える影響

Anchor Boxは、事前に定義された高さ・幅を持つ矩形テンプレートであり、モデルが各特徴マップのグリッドセルに対して予測する「候補ボックス」の基準となります。デフォルトのAnchor(例:YOLOv4公式設定)はCOCOデータセット向けに最適化されており、医療画像やドローン映像など、スケールやアスペクト比が異なる独自データセットでは、適合性が著しく低下します。このミスマッチは、IOUの低下、誤検出の増加、特に小物体検出の失敗を招きます。

クラスタリングツールの選択肢

Darknetは、Anchor生成を支援する2つの標準手段を提供します:

  • scripts/kmeans_anchors.py — Python実装(scikit-learn依存)
  • darknet detector calc_anchors — C言語で記述されたネイティブコマンド

両者ともユークリッド距離ではなく、IoU距離(1 − IoU)をクラスタリングの類似度指標として採用しており、検出タスクに特化した最適化が可能です。

PythonスクリプトによるカスタムAnchor生成手順

以下は、kmeans_anchors.pyを用いた実践的ワークフローです(ファイル名・パスは環境に応じて調整):

import numpy as np
from sklearn.cluster import KMeans
from pathlib import Path

def load_bboxes_from_txt(txt_path: str) -> np.ndarray:
    """訓練リストから各アノテーションの正規化済みbbox寸法を抽出"""
    bboxes = []
    with open(txt_path, 'r') as f:
        for line in f:
            img_path = line.strip()
            label_path = Path(img_path).with_suffix('.txt')
            if not label_path.exists():
                continue
            with open(label_path, 'r') as lf:
                for ann_line in lf:
                    parts = ann_line.strip().split()
                    if len(parts) < 5:
                        continue
                    _, cx_norm, cy_norm, w_norm, h_norm = map(float, parts)
                    # 入力解像度416×416を仮定 → 絶対ピクセル値へ変換
                    w_px, h_px = w_norm * 416, h_norm * 416
                    if w_px > 0 and h_px > 0:
                        bboxes.append([w_px, h_px])
    return np.array(bboxes)

def iou_distance(box_a, box_b):
    """IoU距離:1 - IoU(box_a, box_b)"""
    w1, h1 = box_a
    w2, h2 = box_b
    intersection = min(w1, w2) * min(h1, h2)
    union = w1 * h1 + w2 * h2 - intersection
    return 1.0 - (intersection / union) if union > 0 else 1.0

# クラスタリング実行
dataset_list = "data/train_list.txt"
raw_boxes = load_bboxes_from_txt(dataset_list)
if len(raw_boxes) == 0:
    raise ValueError("アノテーションが見つかりません")

# K-means初期化:k-means++ + IoU距離に基づくカスタムメトリクス(簡易近似)
kmeans = KMeans(n_clusters=9, init='k-means++', n_init=20, random_state=42)
labels = kmeans.fit_predict(raw_boxes)

# 各クラスタの代表Anchor(クラスタ重心)を算出し、整数化
centroids = kmeans.cluster_centers_
anchors = np.round(centroids).astype(int)
anchors = anchors[anchors[:, 0].argsort()]  # 幅でソート

print("生成されたAnchor(width, height):")
for i, (w, h) in enumerate(anchors):
    print(f"{i+1}. {w}×{h}")

# 平均IoU推定(簡易評価)
avg_iou = np.mean([
    max([1 - iou_distance(box, anchor) for anchor in anchors])
    for box in raw_boxes
])
print(f"\n推定平均IoU: {avg_iou:.4f}")

ネイティブコマンドによる高速生成

コンパイル済みDarknetバイナリがある場合、以下の1行コマンドで即時生成可能です:

./darknet detector calc_anchors data/custom.data -num_of_clusters 9 -width 608 -height 608 -show

オプション解説:

  • -num_of_clusters: Anchor数(YOLOv4.cfgでは通常9)
  • -width/-height: 推論時の入力サイズと一致させる
  • -show: 各クラスタの中心座標を可視化(オプション)

Anchor適用時の重要な注意点

  • 入力サイズとの整合性:Anchor値は絶対ピクセル単位であるため、yolov4-custom.cfg内のanchors =行には、608×608で生成したAnchorをそのまま記述。リサイズ不要。
  • クラスタ数の実験:5〜12の範囲で複数回実行し、avg_iouが最大となるnを選択。過剰なクラスタ数はmAP向上を妨げ、推論遅延を引き起こす。
  • アノテーション品質の確認:不正確なbbox(例:ゼロ面積、逆方向、外れ値)はクラスタリングを歪める。前処理段階でlabelImgCVATで検証することを推奨。

効果検証のための再学習

生成したAnchorを.cfgに反映後、必ず以下を実行してください:

  1. 学習済み重みの読み込み(例:yolov4.conv.137
  2. 最低でも30エポックのファインチューニング
  3. 検証セットでのmAP@0.5およびmAP@0.5:0.95の計測

実際の改善幅はデータセットにより異なりますが、小物体が多いシーンではmAP向上が5〜12%観測されるケースが多く、特にRecallの向上が顕著です。

タグ: darknet YOLOv4 anchor-box kmeans-clustering object-detection

7月22日 21:55 投稿