☃️

COCOEvalのコードを読んで学んだこと

2025/02/24に公開

はじめに

私は普段仕事で機械学習関連の開発を担当しています。
今まで何度か物体検出モデルの作成を担当することがあり、その度に評価コードをどうやって実装すればいいか迷いながら結局は都度自前で実装することでお茶を濁していました。というのもpycocotoolsで取得できる評価指標(AP, AR)はよりももう少し直感的に把握しやすいものを使いたいなと感じており、COCOEvalの実装を読み込むことと自分で実装することを天秤にかけて後者を選んでいました。(COCOEvalの実装はnumpyの配列をつかって実装されており、読み始めた途端にnumpy配列の軸迷子になっていました。。)
このままでは良くないと思い、最近少し時間をとってpycocotoolsの評価用のモジュールを読んでみたのでまとめてみようと思います。
この記事では物体検出の評価のみに絞って紹介します。

COCOEvalの実装形式

COCOEvalの実装を簡単に図にすると以下のようなものになります。

それぞれのクラスの主要なプロパティ/メソッドを整理すると以下のようになります。

COCOEval

名称 詳細
params Paramsクラスのインスタンス
evalImgs 画像1枚ずつの評価結果
eval データセット全体における評価結果
stats 最終的な評価結果を保持するプロパティ
evaluate() 画像1枚分の評価を実行するメソッド
accumulate() 画像1枚ずつの評価結果をまとめてself.evalプロパティに値をセットするメソッド
summarize() 評価結果を表示するメソッド

Params

名称 詳細
imgIds 評価に使用する画像のIDを指定するプロパティ
catIds 評価に使用するカテゴリのIDを指定するプロパティ
iouThrs IoUの閾値を指定するプロパティ
recThrs Recallの閾値を指定するプロパティ. Precisionを評価する際に使用する
maxDets 検出数の最大値を指定するプロパティ. Recallを評価する際に使用する
areaRng 矩形の面積を基準にグループごとの評価をする際に使用する面積の範囲
areaRngLbl areaRngの各グループに割り当てるラベル
useCats 評価の際にカテゴリごとに評価をするかを指定するフラグ

取得できる評価指標

pycocotoolsの評価用のサンプルノートブックを参照すると以下のような指標を計測することができます。
Average PrecisionとAverage RecallのそれぞれについてIoUの閾値, 矩形の面積, 推論結果の最大数を変えながら評価した結果を取得することができます。

Average Precision  (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.505
Average Precision  (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 0.697
Average Precision  (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 0.573
Average Precision  (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.586
Average Precision  (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.519
Average Precision  (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.501
Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=  1 ] = 0.387
Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets= 10 ] = 0.594
Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.595
Average Recall     (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.640
Average Recall     (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.566
Average Recall     (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.564

その他の評価結果

サンプルノートブックを見た限りだと確認できる評価指標はAR、PRの2つでした。これらの評価指標は少し複雑な概念になっており、もう少し直感的な取り回しのしやすい評価指標を取得したいなと感じていました。以下にそれらの取得方法についてまとめます。

画像単位での評価結果の取得

COCOEvalクラスのevaluate()メソッドをコールした後に、evalImgsプロパティに画像1枚ごとの評価結果が保持されています。
evalImgsはdefaultdictとして実装されており、各要素は以下のような情報が保存されています。

{
    'image_id': imgId,  # 評価対象の画像ID
    'category_id': catId,  # 評価対象のカテゴリID
    'aRng': aRng,  # 矩形の面積のクラス(small, medium, largeなど)
    'maxDet': maxDet,  # 最大検出数
    'dtIds': [d['id'] for d in dt],  # 検出結果のIDリスト
    'gtIds': [g['id'] for g in gt],  # 正解ラベルのIDリスト
    'dtMatches': dtm,  # 正解した検出結果のリスト(検出結果ベース)
    'gtMatches': gtm,  # 正解した検出結果のリスト(正解ラベルベース)
    'dtScores': [d['score'] for d in dt], # 検出結果の確信度のリスト
    'gtIgnore': gtIg,  # 評価対象かどうかのフラグ
    'dtIgnore': dtIg,  # 評価対象かどうかのフラグ
}

1つの辞書が1枚の画像の1つのカテゴリにおける評価結果に対応しています。(カテゴリ別に評価するオプション指定した場合、1枚の画像に対して複数の辞書ができるイメージ)
この辞書の情報を参照することで画像ごとの評価結果を確認することができます。

TP

evalImgsプロパティの各要素のgtMatches(dtMatchesでも可能)のキーを参考することで計算することができます。gtMatchesの値には、IoUの閾値別に正解した場合正解ラベルのID, 不正解の場合は0が割り当てられたnumpyの配列が格納されています。
以下はデフォルトのIoUの閾値の時の評価例です。
IoU閾値 : [0.5 , 0.55, 0.6 , 0.65, 0.7 , 0.75, 0.8 , 0.85, 0.9 , 0.95]

               # 正解ラベルのIDが8の矩形の検出に成功している
               # |
[              # v
     [6.0, 7.0, 8.0, 9.0, 10.0, 11.0], # <- IoUの閾値が0.5の時の評価結果
     [6.0, 7.0, 8.0, 9.0, 10.0, 11.0], # <- IoUの閾値が0.55の時の評価結果
     [6.0, 7.0, 8.0, 9.0, 10.0, 11.0], # <- IoUの閾値が0.6の時の評価結果
     [6.0, 7.0, 8.0, 9.0, 10.0, 11.0], # <- IoUの閾値が...
     [6.0, 7.0, 8.0, 9.0, 10.0, 11.0],
     [6.0, 0.0, 8.0, 9.0, 10.0, 11.0],
     [6.0, 0.0, 8.0, 9.0, 10.0, 11.0],
     [6.0, 0.0, 8.0, 9.0, 10.0, 0.0 ],
     [0.0, 0.0, 8.0, 0.0, 0.0 , 0.0 ],
     [0.0, 0.0, 8.0, 0.0, 0.0 , 0.0 ]
]

この行列の各行において、0でない要素をカウントすることでIoUの閾値別のTPの値を計算することができます。

FP

TPの場合はdtMatchesの値を参照することで取得することができます。

                   # 推論結果のIDが1733076の矩形の検出に成功している
                   # |
[                  # v
     [1747768.0, 1733076.0, 253933.0, 195946.0, 1225755.0, 1751664.0], # <- IoUの閾値が0.5の時の評価結果
     [1747768.0, 1733076.0, 253933.0, 195946.0, 1225755.0, 1751664.0], # <- IoUの閾値が0.55の時の評価結果
     [1747768.0, 1733076.0, 253933.0, 195946.0, 1225755.0, 1751664.0], # <- IoUの閾値が0.6の時の評価結果
     [1747768.0, 1733076.0, 253933.0, 195946.0, 1225755.0, 1751664.0], # <- IoUの閾値が...
     [1747768.0, 1733076.0, 253933.0, 195946.0, 1225755.0, 1751664.0],
     [1747768.0, 1733076.0, 0.0     , 195946.0, 1225755.0, 1751664.0],
     [1747768.0, 1733076.0, 0.0     , 195946.0, 1225755.0, 1751664.0],
     [1747768.0, 1733076.0, 0.0     , 195946.0, 1225755.0, 0.0      ],
     [0.0      , 0.0      , 0.0     , 0.0     , 1225755.0, 0.0      ],
     [0.0      , 0.0      , 0.0     , 0.0     , 1225755.0, 0.0      ]
]

この行列の各行における0の要素をカウントすることでIoUの閾値別のFPの値を計算することができます。

FN

FNの場合はTPの場合と逆に、各行における0の要素をカウントすることでIoUの閾値別の値を計算することができます。

Precision, Recall, F1score

これらの指標はTP、FP、FNを取得できれば簡単に計算することができます。

Discussion