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・技術・日本語入力でプレイする仕組み
台本を流すと、入力した日本語 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
自然な文で聞き込んでも、話題に触れている限り、町の人は答えます。『霧走町 流氷に眠る』の第一章をブラウザで入力して試せます。プロフィールのリンクから。