DESIGN / CODE / INFRA / AI

解説

F1が高いのに少ない分類を外す?AI評価のmicroとmacroを分けて読もう

3種類の架空の分類結果からmicro-F1とmacro-F1を検算。分類ごとの件数と失敗を残し、集計方法による見え方の違いを確認する。

F1が高いのに少ない分類を外す?AI評価のmicroとmacroを分けて読もう
保存リストを見る →
この記事の道しるべ 5項目

AIの分類結果を比べたら、「F1は0.80」と書かれていた。別の表では同じ結果なのに約0.31。数字だけを見ると矛盾していそうだけれど、まとめ方が違うのかもしれないよ。

今回は適合率・再現率の基本を知っている人向けに、複数の分類を一つのF1へまとめるmicroとmacroを比べる。10件の架空データを使い、実際のAIモデルの性能を測った値ではないよ。

まず、分類ごとの失敗を残す

一つの入力にはA・B・Cのいずれか一つだけを付ける。正解ラベルは人があらかじめ用意したものとし、今回はその正しさ自体の評価は扱わない。

正解 件数 予測したラベル
A 8 8件ともA
B 1 A
C 1 B

Aは8件とも当たっているが、少ないB・Cはそれぞれ外している。全体では8/10が正解だね。この表を消して集計値だけにすると、どの分類を落としたかが見えにくくなる。

F1の材料は、拾えた件数と二種類の誤り

各ラベルについて、そのラベルを「対象」、それ以外を「対象外」と考える。

  • TP:対象を対象と予測できた件数。
  • FP:対象外を対象と予測した件数。
  • FN:対象を対象外と予測して見落とした件数。

F1は適合率と再現率の調和平均で、件数からは 2×TP÷(2×TP+FP+FN) と計算できる。今回AはTP=8・FP=1・FN=0なので16/17、BとCのF1は0になるよ。

ラベル TP FP FN F1
A 8 1 0 約0.9412
B 0 1 1 0
C 0 0 1 0

microは件数を合算し、macroは各分類を同じ重みで平均

micro-F1は、全ラベルのTP・FP・FNを合算してからF1を計算する。今回はTP=8・FP=2・FN=2で16/20=0.80だよ。

macro-F1は、先にラベルごとのF1を求め、それらを同じ重みで平均する。(16/17+0+0)÷3で約0.3137。件数が少ないB・Cも、それぞれAと同じ一分類として数えているんだ。

microが「間違い」でmacroが「正解」という話ではない。入力全体での出来を見たいか、少ない分類の出来も同じ重みで見たいかで、必要な情報が変わる。どちらを採用しても、分類別の件数と結果は残しておこう。

Pythonで、同じ入力から両方を出す

次は標準ライブラリだけでの検算。scikit-learnのモデルを学習・実行するコードではないよ。

from fractions import Fraction

truth = ["A"] * 8 + ["B", "C"]
pred = ["A"] * 9 + ["B"]
labels = ["A", "B", "C"]

def f1(tp, fp, fn):
    denominator = 2 * tp + fp + fn
    return Fraction(2 * tp, denominator) if denominator else Fraction(0)

counts = []
for label in labels:
    tp = sum(t == label and p == label for t, p in zip(truth, pred))
    fp = sum(t != label and p == label for t, p in zip(truth, pred))
    fn = sum(t == label and p != label for t, p in zip(truth, pred))
    counts.append((tp, fp, fn))

micro = f1(*(sum(values) for values in zip(*counts)))
macro = sum((f1(*values) for values in counts), Fraction(0)) / len(labels)
print(f"micro={float(micro):.4f}")
print(f"macro={float(macro):.4f}")

出力はmicro=0.8000、macro=0.3137。truthとpredは同じ長さで対応する順序も一致しているという前提だよ。実データでは長さ・IDの対応・ラベルの範囲を先に検査しよう。

このコードでは分母0のF1を0としている。存在しないラベルも集計に含めるのか、分母0をどう扱うのかは、使用する評価ツールの設定とそろえる必要がある。scikit-learnの公式資料でもaverageの種類、labels、zero_divisionを区別しているよ。

AIへ集計を頼む前に固定すること

「評価対象はA・B・Cのすべて。一件につき一ラベル。micro-F1とmacro-F1を両方求め、ラベル別TP・FP・FN・件数も残す。未知のラベルや件数の不一致は集計せず報告する」と指定すると、照合する材料が明確になるね。

今回のような全クラスを含む単一ラベル分類ではmicro-F1は正解率と一致する。複数ラベルを同時に付ける場合や、一部のラベルだけを評価する場合へ、その関係をそのまま持ち込まないようにしよう。

編集部では2026年11月1日にscikit-learnの公式仕様を確認し、上の架空例と全件正解・全件不正解・分母0のケースをPython 3.12.14で検算した。基本の適合率と再現率、別の問いである人数を重みにする平均も合わせて読めるよ。

参考・出典

公開と変更の記録
  • :初回公開

日時は日本時間です。記載のある変更は、確認できた編集記録に基づきます。更新日の違いだけから変更内容を推測して載せることはありません。

更新・訂正の方針 →

みんなのコメント 0

感想や、試してみたことを聞かせてね。コメント・返信は承認後に公開されます。

最初のコメントをどうぞ。