MENU

問い合わせ


    【YOLOで作る画像認識ツール 第6回】精度を数字で測る:正解データと、適合率・再現率

    前回の第5回までは、認識の結果を画像で見て、「レモンがorangeになっている」などと目で確認してきました。しかし、システムを発注するときには「精度は何%か」を同じ物差しで測れる必要があります。

    今回は、正解データを用意して、AIの結果と突き合わせ、適合率と再現率という2つの数字で精度を測ります。「精度○%」という言葉の中身を、実際の数字で理解する回です。

    目次

    この回で作るもの

    • 正解データ(何が・どこに写っているか)をJSONで持つ仕組み
    • AIの結果と正解を突き合わせて、正解・誤検出・見逃しを数える処理 app/evaluate.py
    • 種類ごとの適合率・再現率を表示するコマンド

    用語:TP・FP・FNと、適合率・再現率

    AIの結果を、正解データと1つずつ照らし合わせると、次の3つに分けられます。

    呼び方言い換え意味
    TP正解正解の枠と重なっていて、種類も合っている検出
    FP誤検出正解にない(または種類が違う)のに見つけたもの
    FN見逃し正解にあるのに、見つけられなかったもの

    この3つの数から、2つの割合を計算します。

    • 適合率 = TP ÷(TP + FP):見つけたもののうち、正しかった割合。誤検出が多いと下がる
    • 再現率 = TP ÷(TP + FN):正解のうち、見つけられた割合。見逃しが多いと下がる

    「精度」という1つの言葉は、実際にはこの2つを指しているか、混ぜて使われていることが多くあります。どちらを重視するかは、第1回の「見逃しと誤検出のどちらが困るか」と同じ問いです。

    「正しく見つけた」の判定:枠の重なり(IoU)

    AIの枠と正解の枠が「同じ物体を指している」かどうかは、枠の重なり具合(IoU。共通部分の面積 ÷ 合わせた面積)で判断します。この連載では、0.5以上(半分以上重なる)を一致としました。

    実装

    枠の重なりを計算する

    # app/evaluate.py(抜粋)
    def iou(a: Box, b: Box) -> float:
        """2つの枠の重なり具合(0〜1)。共通部分の面積 ÷ 合わせた面積。"""
        left, top = max(a[0], b[0]), max(a[1], b[1])
        right, bottom = min(a[2], b[2]), min(a[3], b[3])
        inter = max(0, right - left) * max(0, bottom - top)
        union = (a[2] - a[0]) * (a[3] - a[1]) + (b[2] - b[0]) * (b[3] - b[1]) - inter
        return inter / union if union else 0.0

    突き合わせて数える

    # app/evaluate.py(抜粋)
    def evaluate(truths, detections, iou_threshold=0.5) -> dict[str, Score]:
        """種類ごとの TP / FP / FN を数える。信頼度の高い検出から順に、まだ使われていない正解と1対1で対応づける。"""
        scores: dict[str, Score] = defaultdict(Score)
        used: set[int] = set()
        for det in sorted(detections, key=lambda d: -d.confidence):
            best, best_iou = None, iou_threshold
            for i, t in enumerate(truths):
                if i in used or t.label != det.label:
                    continue
                value = iou(det.box, t.box)
                if value >= best_iou:
                    best, best_iou = i, value
            if best is None:
                scores[det.label].fp += 1
            else:
                used.add(best)
                scores[det.label].tp += 1
        for i, t in enumerate(truths):
            if i not in used:
                scores[t.label].fn += 1
        return dict(scores)

    ポイントは、1つの正解に対応できる検出は1つだけという点です。同じ物体に2つの枠を付けても、正解になるのは1つで、もう1つは誤検出として数えます。

    正解データ

    正解データは、JSONで、種類と枠を書きます。

    {
      "image": "fruits.jpg",
      "objects": [
        {"label": "orange", "box": [0, 0, 158, 105]},
        {"label": "orange", "box": [69, 44, 348, 470]},
        {"label": "banana", "box": [0, 95, 146, 294]},
        {"label": "lime", "box": [322, 120, 512, 305]},
        {"label": "lemon", "box": [318, 247, 512, 480]},
        {"label": "kiwi", "box": [0, 277, 130, 480]}
      ]
    }

    このサンプルの正解データは、説明用にAI(この連載の執筆者)が写真を見て枠を決めたものです。 実際の業務では、現場の担当者が正解を作り、複数人で確認します(後述)。

    動かして確かめる

    python -m app.evaluate samples/ground_truth/fruits.json --conf 0.25
    fruits.jpg: 正解 6 個 / 検出 4 件(しきい値 0.25、重なり 0.5)
    種類            TP  FP  FN  適合率  再現率
    banana           0   0   1    -       0%
    kiwi             0   0   1    -       0%
    lemon            0   0   1    -       0%
    lime             0   0   1    -       0%
    orange           1   3   1    25%    50%
    全体              1   3   5    25%    17%

    これまで見てきた問題が、数字で表れました。

    • orange:見つけた4件のうち、本物のオレンジは1件だけ(適合率25%)。写真に2個あるオレンジのうち、見つけられたのは1個(再現率50%)
    • banana・kiwi・lemon・lime:学習済みモデルが知らない、または見つけられなかったため、再現率0%
    • 全体:適合率25%・再現率17%

    しきい値を0.1に下げると、結果はこう変わります。

    fruits.jpg: 正解 6 個 / 検出 8 件(しきい値 0.1、重なり 0.5)
    種類            TP  FP  FN  適合率  再現率
    banana           0   0   1    -       0%
    dining table     0   1   0     0%    -
    kiwi             0   0   1    -       0%
    lemon            0   0   1    -       0%
    lime             0   0   1    -       0%
    orange           2   5   0    29%   100%
    全体              2   6   4    25%    33%

    オレンジの再現率は100%になりました(見逃しなし)が、誤検出は5件に増え、適合率は29%のままです。しきい値を下げると再現率は上がるが、適合率は上がらないという、第1回で見た関係が数字で確かめられました。

    数字をそのまま信じてはいけない

    この結果を見て、「この学習済みモデルは精度25%」と結論づけるのは誤りです。理由は次のとおりです。

    • たった1枚の写真で測っている:写真1枚では、偶然の影響が大きすぎます。実際の評価には、対象の現場で撮った数十〜数百枚が必要です
    • 正解データの作り方で数字が変わる:バナナを1つの枠にするか、2つに分けるかで、結果が変わります
    • AIが苦手な写真を、あえて選んでいる:この写真は、モデルが知らない果物ばかりを集めた、説明用の写真です

    大切なのは、数字そのものより、「どの写真で、誰が作った正解で、どう測ったか」を説明できることです。

    テスト

    pytest -q      # 25件成功
    ruff check .   # 問題なし

    枠の重なりの計算、正解・誤検出・見逃しの数え方(1つの正解に対応できるのは1つだけという規則を含む)、適合率・再現率の計算を、テストで確認しています。

    つまずきやすい点

    • 種類の名前がそろわない:正解データの orange とモデルの orange の綴りが違うと、全部が誤検出・見逃しになります。名前の一覧を最初に決めます
    • 重なりのしきい値:0.5が標準的に使われますが、小さな物体を数える場合は厳しすぎることがあります。目的に合わせて決めます
    • 数えない種類がある:正解にもAIにも無い種類は、表に出ません(- は「割り算できない」の意味)

    発注者向けメモ:「テスト用の正解付き写真」は発注者が用意する

    精度の目標を決めて、それを満たしたかを確かめるには、正解付きのテスト用写真が必要です。この写真は、業務の中身を知っている発注者側が用意するのが基本です。開発会社は、現場の「正解」を知らないためです。

    発注者がやること チェックリスト

    • ☐ 現場で実際に撮る写真を、状況を変えて(明るさ・角度・混み具合)数十〜数百枚集めた
    • ☐ 集めた写真に、何が・いくつ写っているかの正解を付けた(複数人で確認するとよい)
    • ☐ 見逃し(再現率)と誤検出(適合率)のどちらを優先するか、目標の数字と一緒に決めた
    • ☐ テスト用の写真は、開発会社が学習に使う写真とは別に取り分けた
    • ☐ 納品時と、稼働後の定期的な確認で、同じテスト用の写真を使うことを決めた

    開発会社への質問例

    • 「精度の目標は、適合率と再現率のどちらで、どの写真を使って測りますか」
    • 「テスト用の写真は、何枚必要ですか。学習に使う写真と分けて管理されますか」
    • 「精度が目標に届かなかった場合、追加の学習や調整は、どの範囲まで契約に含まれますか」
    • 「稼働後も、同じ方法で精度を測り直せますか。その作業は保守に含まれますか」

    まとめと次回予告

    第6回では、次のことを行いました。

    • 正解データとAIの結果を突き合わせ、正解・誤検出・見逃しを数えた
    • 適合率と再現率を計算し、しきい値を変えたときの関係を数字で確認した
    • 写真1枚の数字は結論にならないこと、正解の作り方が大切なことを確認した

    次回の第7回では、学習済みモデルが知らない対象(レモンやキウイなど)を、自分たちの写真で追加学習して覚えさせる手順を扱います。

    この連載の記事一覧

    この記事は連載「YOLOで作る画像認識ツール」の1回です。連載のほかの回は次のとおりです(連載の一覧ページ)。

    システム制作・運用・保守のお問い合わせはこちら


      よかったらシェアしてね!
      • URLをコピーしました!
      • URLをコピーしました!

      この記事を書いた人

      株式会社THIRD HERO代表取締役 朝野貴朗
      Webシステム開発を中心に、toC向けサービスサイトの運営、ツール開発などを行ってまいりました。

      目次