kaedeasset記事 · Notes霧走町 流氷に眠る

1 手ごとに ANSWERED・DRIFT・MISS・LEAK。正解表なしで付ける判定を、混同行列で言い直す

ANSWERED, DRIFT, MISS, LEAK per move: restating an oracle-free verdict as a confusion matrix

 D21-3-T41-verdict-four-values・技術・日本語入力でプレイする仕組み

確かめ方confusion matrixprecision and recallcoverage (selective classification)false accept / false rejecttest oracleclaude自然言語処理

台本を流すと、入力した日本語 1 手ずつに 4 つの札のどれかが付きます。
ANSWERED(話に答えた)、DRIFT(別の話をした)、MISS(拾えなかった)、LEAK(話題に触れていないのに事件が進んだ)。
正解の表を持たずに、機械だけで付けています。
混同行列の言葉で言い直すと、どこが同じで、どこが言えないのかがはっきりします。

この回の位置と、前後の回#

区切り: T34〜T49 確かめ方(16 回のうち 8 回目)
先に読むとよい回: T16(答えない判断)・T37(台本の中に期待を書く)・T38(今の出力を写して固定する)
次に読む回: T42(holdout を見た回数で数える)

名前 — confusion matrix#

正式名
confusion matrix(混同行列)と、そこから出る precision(適合率)・recall(再現率)
関連
false accept / false reject(受け入れの誤り・拒否の誤り。照合や認証の分野の呼び方)、coverage(棄却つきの分類で、答えた割合)
初出
confusion matrix・precision・recall の語を最初に使った文献は、検索で確かめられませんでした。

coverage と誤りの交換(risk–coverage trade-off)を正面から扱った論文として、Ran El-Yaniv と Yair Wiener の "On the Foundations of Noise-free Selective Classification"(JMLR 11, 2010)を挙げておきます(閾値と coverage の話は T16 で書きました)。

混同行列とは#

分類器の出力を、正解と並べて数えた表です。
2 クラスなら 2×2 の 4 つの升になります。

  • TP(true positive)… 正解が陽性で、陽性と答えた
  • FP(false positive)… 正解が陰性なのに、陽性と答えた
  • FN(false negative)… 正解が陽性なのに、陰性と答えた
  • TN(true negative)… 正解が陰性で、陰性と答えた

ここから出す数がいくつかあります。

precision = TP / (TP + FP) 答えた陽性のうち、本当に陽性だった割合
recall = TP / (TP + FN) 本当の陽性のうち、陽性と答えた割合
accuracy = (TP + TN) / 全部

照合や認証の分野では、FP を false accept(受け入れてはいけない人を通した)、FN を false reject(通すべき人を拒んだ)と呼びます。
多クラスなら k×k の表になり、対角線の外が「別のクラスと取り違えた」数です。

棄却(reject option)を持つ分類器では、もう 1 列増えます。
「答えなかった」列です。
答えた割合を coverage、答えた中での誤りの割合を selective risk と呼びます。

よく知られた注意が二つあります。

  • 表を作るには、全部の入力に正解が要る。正解の付け方が間違っていれば、表も間違う。
  • 陰性がとても多い問題では、accuracy が高く出て何も言わない。precision と recall を分けて見る理由です。

正解がある側: golden#

うちの照合には、正解を付けた組があります(T38)。
adv-sim golden は 1 問ずつ照合を呼び、正解の意図と比べます。
今日の、進行に要る入力の組(probe-critical・244 問、全部が「どれかの意図に当たるべき」問い)を、混同行列の形に並べ直しました。

  • 正しい意図に採用 … 217
  • 別の意図に採用 … 9(多クラスの取り違え)
  • 聞き返し … 10(うち、聞き返した相手が正しい意図だったもの 8)
  • 受け流し・誘導 … 8

coverage は (217 + 9) / 244 で 92.6%。
答えた 226 問の中での誤りは 9 問、4.0% です。

陰性の側は、凍結した組(golden/dev)の none の 34 問で見ます。
none は「事件を進めてはいけない」問いです。

  • 進行する段に採用(false accept)… 0(NEG_FP)
  • 雑談の応答に採用 … 18(進まないので誤りに数えない)
  • 聞き返し … 8
  • 受け流し・誘導 … 8

false accept の 0 は、毎周の合格ラインに入っています(maxNegFp 0)。
ここまでは、名前どおりの混同行列です。

正解がない側: play --verdict#

golden は 1 問 1 回の照合です。
続けて入力したときの手の列は測れません。
本筋の台本は通るのに、一歩外れると世界が黙る。
そういう状態は、1 問ずつの正解率には出ませんでした。

そこで、台本を流す play に、1 手ずつ札を付ける判定を足しました。
正解の表は持っていません。
代わりに、意図ごとに登録してある話題の語(entities の別名)を使います。
main.cpp の JudgeSay から写します。

const char* Kind = "MISS";
if (T.kind == kaede::adv::AdvTurn::EKind::Accepted)
{
const kaede::adv::Intent* It = Session.intents().findById(T.intentId);
const bool bTopic = It && InputMentionsTopic(Session, *It, Input);
if (It && (It->bSmalltalk || It->bAccuse)) { Kind = "ANSWERED"; ++gTally.answered; }
else if (!T.flagsGained.empty() && !bTopic) { Kind = "LEAK"; ++gTally.leak; }
else if (!bTopic) { Kind = "DRIFT"; ++gTally.drift; }
else { Kind = "ANSWERED"; ++gTally.answered; }
gTally.missRun = 0;
}
else
{
++gTally.miss;
++gTally.missRun;
if (gTally.missRun > gTally.maxMissRun) { gTally.maxMissRun = gTally.missRun; }
}

表にするとこうです。

  • 採用 × 入力に話題の語がある → ANSWERED
  • 採用 × 話題の語がない × フラグが立たない → DRIFT
  • 採用 × 話題の語がない × フラグが立った → LEAK
  • 採用しなかった(聞き返し・誘導・受け流し)→ MISS
  • 雑談と疑いの意図 → 話題の語を見ずに ANSWERED

雑談は話題の語を持たず、事件を進めない決まり(lint)なので外しています。
疑い(「あんたが殺したんだろう」)は、相手そのものが話題で、固有名詞を挙げずに言うのが自然です。
これも外しています。

話題の語を探すときは、照合と同じ正規化を通します。

// 照合と同じ正規化を通す。 生バイトの find だと、カタカナ・全角・敬体の畳み
// (FoldPolite)を通った照合が「話題に触れていない」と判定され、通った手が LEAK に化ける。
const std::vector<char32_t> Hay = NormalizeText(Input);

2026-08-19 に、ここが生の文字列の検索だったせいで、正しく通った手が LEAK に数えられたことがあります。

合格ラインは、台本ごとに目録が渡します。
--min-answered(ANSWERED の割合の下限)、--max-leak(LEAK の上限)、--max-miss-run(MISS が続く手数の上限)、--require-clear(章を終えること)、--min-fallback(聞き返しなどの受け皿を何手以上通ること)です。
通し台本の多くは --max-leak 0 です。

今日の値(10/29) — confusion matrix#

46 本を 1 本ずつ --verdict を付けて流した合計です。

says 1,581 ANSWERED 1,348 DRIFT 90 MISS 113 LEAK 30

この合計そのものには、あまり意味がありません。
わざと外す台本が入っているからです。
救済の一文が出ることを見る台本(rescue-check)は、9 手全部が MISS です。
同じ問いの前後で段が変わることを見る台本(chardiff)は、20 手全部が ANSWERED です。

意味があるのは、言い換えの辞書に無い自然な文だけで最後まで通す台本(selfplay・68 手)です。
ANSWERED 56、LEAK 12、MISS 0、章は最後まで終わりました。
ループの中では、この台本は別の道具(Scripts/adv-agent-selfplay.ps1)が 1 手 1 プロセスで回し、合格は「取りこぼし 0 でクリア」です。
LEAK は合格に入っていません。
今日の 68 手は、同じ台本を play でまとめて流した値です。

名前どおりでない所 — confusion matrix#

混同行列と呼べるのは golden の側だけです。
play --verdict の 4 値は、混同行列の升とは一対一になりません。

一つ目、正解の代わりに「話題の語があるか」を使っています。
これは正解の近似(proxy oracle)です。
selfplay の LEAK の 1 つを例にします。
源三に、事件の晩に外で何か見えなかったかを尋ねる自然な文です。
照合は源三のその話の段に 0.681 で採用し、進行のフラグが立ちました。
人が読めば、正しい意図への正しい着地です。
ただ、入力にはその意図に登録した話題の語が入っていませんでした。
だから LEAK です。
LEAK は「false accept の疑い」であって、false accept そのものではありません。

二つ目、MISS には正しい棄却と誤った棄却が混ざります。
名前の対応表は「MISS=false reject / abstention」と書いていますが、正解が無いので、拒んでよかった手かは分かりません。
混同行列なら TN と FN を分けますが、4 値ではどちらも MISS です。
雑談でも、事件と関係のない入力に受け流しが返るのは正しい動きです。

三つ目、聞き返しも MISS に数えます。
港で佐伯に「どこいた」と入力すると、佐伯が聞き返します。これは MISS です。
続けて「はい」と答えると採用されますが、その手の入力は「はい」だけなので、話題の語がありません。
今日の例では、フラグが立たないので DRIFT になりました。
フラグが立つ段なら LEAK になります。
聞き返しは、うちの照合のいちばん大事な受け皿です(T17)。
1 手ずつ、その手の入力だけを見る判定では、2 手で 1 つの往復を正しく数えられません。

四つ目、DRIFT と LEAK の境目は「フラグが立ったか」で、正しさとは別の軸です。
進行が動いたかどうかで重さを分けています。
同じ取り違えでも、事件の状態を変えたなら LEAK、変えなければ DRIFT です。
被害の大きさで升を分けている、と言った方が近い。

弱点と、採らなかった案 — confusion matrix#

  • 話題の語の登録が薄い意図ほど、DRIFT と LEAK が出やすくなります。札は照合の良し悪しと、辞書の厚さの両方を測っています。
  • 台本が正解の手だけをなぞると、MISS 0・LEAK 0 で緑になります。受け皿を一度も通っていないことに気づけないので、--min-fallback を足しました(2026-08-16 に、通し台本が受け皿を 1 度も通っていなかったのが見つかったため)。
  • 4 値は 1 手ずつ独立です。聞き返しから「はい」で届く往復は、MISS と DRIFT(または LEAK)の 2 つに割れます。

採らなかった案です。

  • 台本の全部の手に正解の意図を書く。書けば本物の混同行列になります。それは T37 の !expect そのもので、大事な手には書いています。全部の手に書くと、言い回しを 1 つ変えるたびに期待を全部見直すことになるので、残りは 4 値の近似に任せています。
  • LEAK の判定を「入力と意図の点数が低い」にする。点数は照合の中の量で、照合そのものを照合の点数で採点することになります。外の物差し(登録した話題の語)で見る方を選びました。
  • 聞き返しを MISS から外す。外すと、聞き返しばかりの台本が緑になります。受け皿を通った数は FALLBACK として別に数えています。

検索語#

confusion matrix precision recall
false accept rate false reject rate
selective classification coverage
test oracle problem

自然な文で聞き込んでも、話題に触れている限り、町の人は答えます。『霧走町 流氷に眠る』の第一章をブラウザで入力して試せます。プロフィールのリンクから。

#claude #自然言語処理