推理は点数では進めない。「主張」の条件を原文で確かめる
Deduction doesn't advance by score: checking a "claim" against the exact wording
D10-4-T20-claim-contract・技術・日本語入力でプレイする仕組み
「時計が遅れていた」と「時計が遅れていたとは言えない」は、文の埋め込みではほとんど同じ所に置かれます。
推理アドベンチャー『霧走町 流氷に眠る』では、この二つで話が逆に進まなければいけません。
この回は、推理の段を進める前に、入力が本当にその論点を述べたかを原文で確かめる「主張」の仕組みを書きます。
この回の位置と、前後の回#
区切り: T03〜T24 日本語入力を解いて返すまで(22 回のうち 18 回目)
先に読むとよい回: T13(否定の掛かり先)・T15(意図の分類)・T19(段と旗)
次に読む回: T21(出所つきの手帳)
造語と、近い名前#
「主張」(claim、データでは claims.json と段の claimId)は、うちの造語です。
近い既存の名前は recognizing textual entailment(RTE、テキスト含意認識)で、いまは natural language inference(NLI、自然言語推論)と呼ばれることが多い課題です。
課題の定義は Dagan, Glickman & Magnini, "The PASCAL Recognising Textual Entailment Challenge"(LNCS 3944, 2006)。
先に違いを書きます。
RTE は、前提の文 T と仮説の文 H を与え、T を読んだ人が H を推論できるかを機械に判定させます。
主張は、仮説を文では持ちません。作者が「この語とこの語が、同じ文に、この順で、否定されずに出ること」という条件を書き、入力がそれを満たすかを見るだけです。
推論はしていません。だから名前を借りず、造語のままにしています。
テキスト含意認識とは#
テキスト含意認識(recognizing textual entailment、RTE)とは、前提の文 T と仮説の文 H を与え、T を読んだ人が H を推論できるかを機械に判定させる課題です。
例で書きます。
T: 佐伯は十時まで番屋にいたと言ったが、女将は八時前に帰ったと言っている
H: 佐伯の証言と女将の証言は食い違っている
T から H が言えるなら「含意する」、逆が言えるなら「矛盾する」、どちらでもなければ「中立」です(RTE の初期は二値、後に三値が広まりました)。
質問応答・要約・情報抽出に共通する推論を、応用から切り離して測る課題として作られ、RTE-1 には 17 の提出がありました。
方法は、語の重なりを数える基準線から、論理式への変換、構文木の対応、ニューラルの分類器へと進みました。
よく知られた弱点は三つです。
- 語の重なりへの依存。T と H の語が多く重なると、否定があっても「含意」と答える分類器が多い
- データの癖。仮説の側の言い回しだけで正解が当たってしまう、という偏りが知られている
- 説明できない。なぜ含意と判定したかを、人が読める形で返さない
主張の形#
作品の主張は第一章の決着の手がかりそのものなので、ここでは単体検査の検査用データを写します。
{ "id": "c.time", "purpose": "deduction", "recordLabel": "時刻の矛盾", "knownWhen": ["f_clock", "!f_done"],
"alternatives": [ { "all": [ { "textAny": ["時計"] }, { "textAny": ["遅れ", "進んで"] } ] } ],
"examples": { "positive": ["時計が遅れていた"] } },
{ "id": "c.seq", "recordLabel": "順序",
"alternatives": [ { "all": [ { "sequence": [["鍵"], ["閉め"]] } ] } ] },
{ "id": "c.lie", "recordLabel": "嘘",
"alternatives": [ { "scope": "utterance", "all": [ { "entitiesAny": ["saeki"] }, { "textAny": ["嘘"] } ] } ] },
{ "id": "c.accident", "recordLabel": "事故",
"alternatives": [ { "all": [ { "textAny": ["事故"] } ], "excludeAny": ["ではなく"] } ] },
{ "id": "c.link", "recordLabel": "つなぎ",
"alternatives": [ { "scope": "linked", "linkEntities": ["saeki"], "referenceAny": ["彼"],
"all": [ { "entitiesAny": ["saeki"] }, { "textAny": ["嘘"] } ] } ] }
欄の意味です。
- alternatives … 述べ方の候補(OR)
- all … その述べ方で揃うべき項(AND)。項の中の語は OR
- 項の種類は三つだけ。textAny(語のどれか)、entitiesAny(固有名詞の辞書の id のどれか。別名も当たる)、sequence(語の群がこの順に出る)
- scope … 揃う範囲。sentence(既定。同じ文の中)、utterance(入力全体)、linked(隣の文が同じ人物か指示語でつながっていれば 2 文をまとめる)
- excludeAny … その文にこれがあれば、この述べ方では述べていない
- knownWhen … その主張を述べてよい状態(旗)。入力が述べた証明ではなく、推理記録に出してよいかの条件
- recordLabel … 推理記録の見出し
- examples … 通るべき文・通ってはいけない文・どちらとも言えない文。検査が読む
任意の正規表現も、作品のコードも実行しません。
読み込みは未知の欄をエラーにし、1 つの項に種類を 2 つ書く、群が 1 つしか無い sequence、空の語、なども拒否します。
評価#
AdvClaims.hpp の EvaluateClaim が、入力の解析結果(T11〜T13 の回)を受けて、どこまで述べたかを返します。
揃った後の判定を写します。
if (Hit == 0) { continue; }
if (Hit < (int)Alt.all.size())
{
M.result = ClaimResult::Ambiguous;
M.why = "alternative " + std::to_string(Ai) + ": " + std::to_string(Hit) + "/" + std::to_string(Alt.all.size()) + " terms";
}
else
{
M.result = ClaimResult::Supported;
…
for (const std::string& W : Alt.excludeAny)
{
for (const InputSpan& S : ClaimFindWord(A, Norm, W))
{
if (Region.contains(S)) { M.result = ClaimResult::Ambiguous; M.why = "excludeAny: " + W; }
}
}
if (M.result == ClaimResult::Supported)
{
for (const InputSpan& R : A.reversals)
{
if (!Region.contains(R) || R.beginByte < FirstBegin) { continue; }
bool bInside = false;
for (const InputSpan& S : M.spans) { if (S.overlaps(R)) { bInside = true; break; } }
if (!bInside) { M.result = ClaimResult::Ambiguous; M.why = "polarity reversed: " + A.slice(R); break; }
}
}
if (M.result == ClaimResult::Supported)
{
if (Clause) { M.bInterrogative = Clause->explicitQuestion && !Clause->explicitAssertion; }
else { M.bInterrogative = (A.speech == SpeechKind::Question); }
}
}
結果は四つです。
- supported … 全部の項が揃い、除外も否定も無い。質問の文なら、疑問として挙げた印(interrogative)が付く
- ambiguous … 項の一部だけ揃った、除外の語があった、一致より後ろに否定があった
- unsupported … 関係する語が一つも無い
- none … 主張を持たない段
述べ方が複数あれば、supported(言い切り)> supported(疑問)> ambiguous > unsupported の順で最も良いものを採ります。
語を探すときは、引用の中(他人の発言)を除きます。
単体検査の結果を並べます(今日 90 項目すべて合格)。
- 時計が遅れていた。→ supported
- 時計が進んでいた → supported(項の中の OR)
- 時計が遅れていたのか? → supported・疑問
- 時計を見た。→ ambiguous(2 項のうち 1 項)
- 時計を見た。遅れていた。→ ambiguous(既定の範囲は同じ文。2 文はつながない)
- 時計が遅れていたとは言えない。→ ambiguous(一致の後ろの否定)
- 「時計が遅れていた」と佐伯が言った。→ unsupported(引用の中は数えない)
- 「時計が遅れていた」→ supported(括弧だけなら自分の言葉)
- 鍵を閉めた → supported、閉めてから鍵を → unsupported(順序)
- 佐伯は港にいた。あれは嘘だ。→ supported(範囲が入力全体の述べ方)
- 事故ではなく殺しだ → ambiguous(除外の語)
- 佐伯は港にいた。彼は嘘をついている。→ supported(指示語でつないだ 2 文)
進行への組み込み#
主張は、照合(T15・T16)の後、段を実行する前に効きます。
推理の段に claimId があれば、照合の点数が採用線を超えていても、主張が supported にならない限り旗を立てません。
候補ごとの評価(EvaluateIntent)は、次の七つのどれかを返します。
- execute … 実行してよい(主張の要らない段、または主張が成立)
- claim-interrogative … 内容は揃ったが疑問として挙げた。「はい」で確かめれば実行できる
- claim-ambiguous … 関係する語はあるが論法が決まらない。進めずに確かめる
- claim-unsupported … その論点を述べていない。この候補を外して照合し直す
- reception … ここでは実行できないが、論点は受け取れる(受け取りの応答を返し、進めない)
- reception-error・no-stage … データの不整合と、解決できる段が無い
選ぶ順は AdvSession.hpp の selectByMatch で、照合を最大 4 回まで繰り返します。
- 主張が成立した、実行できて、まだ立っていない旗を立てる段。違う進行の論点が 2 つ以上なら自動で選ばず、入力の引用を並べて選んでもらう。同じ旗を立てる論点どうしは 1 つにまとめる
- 受け取り。ただし照合の 1 位がここで実行できて話を進め、採用線から 0.08 以上の余裕があるなら、そちらに譲る
- 疑問として挙げた主張(照合が推理の方を向いているときだけ)
- 照合の 1 位。その段の主張が unsupported なら、その候補を外して 1 へ戻る。ambiguous なら確かめる
- それ以外は従来の聞き返し・誘導・受け流し
一つの意図の中で、論点ごとに段を分けることもできます。
状態で選んだ段の主張が成り立たず、同じ状態で入れる別の進行の段の主張が成り立つなら、そちらの段を選びます。
述べた論点の台詞だけを返し、述べていない論点を主人公が代わりに語らない、という約束です。
成立した主張は、推理記録(c1・c2… の公開カード)に入力の引用とともに残ります。
見出しには recordLabel を使い、内部の id や旗は出しません。
今日の作品データでは、主張が 19(推理 18・報告 1)、述べ方が 62、主張つきの段が 26 でした(2026-10-29 時点)。
主張の検査は、例文 154・言い換え 255・実際の入力の証拠 75 を当てて、全部合格しました。
名前どおりでない所 — 主張(claim)#
造語なので、名前どおりかの判定はありません。
近い RTE との違いを、もう一度並べます。
- 仮説を文で持たず、作者が書いた語の条件で持つ
- 含意・矛盾・中立ではなく、成立・曖昧・不成立(と疑問の印)を返す
- 推論はしない。同義語は作者が語の一覧に書いたものだけ
- どの語がどこで一致し、何が否定したかを、原文の区間と理由の文字列で返す
弱点と、採らなかった案 — 主張(claim)#
語の条件なので、作者が思いつかない言い方は ambiguous か unsupported になります。
そのときは進めずに確かめに行くので、間違って進むことはありませんが、正しい推理が一度で通らないことはあります。
二文に分けて言う人(「時計を見た。遅れていた。」)は、範囲を入力全体にした述べ方を足さないと通りません。
否定が二つ重なった文は、意味がほぼ肯定でも ambiguous に倒れます(T13 の回)。
採らなかった案です。
- NLI のモデルで含意を判定する。否定に弱いことが知られていて、うちが一番困るのがそこです。判定の理由も返らず、同じ入力に同じ判定が返るかの検査も、モデルの版に縛られます
- 照合の点数の差で推理を決める。言い換えただけの文が僅差で別の決め手に当たり、プレイヤーが言っていない論法で章がクリアされたことが 8 月にありました。点数は話題までで、論法は原文で見る、と分けたのがこの仕組みです
- 否定を見つけたら逆の主張として成立させる。プレイヤーが言っていない結論を、主人公が言うことになります
「話題が分かった」と「その論法を述べた」と「話を進めてよい」を、三つの別の判定にする。
ソースの冒頭のコメントは、この三つの分け方から始まっています。
検索語#
recognizing textual entailment
natural language inference
PASCAL RTE challenge
textual entailment negation
第一章の終わりには、自分の言葉で見立てを述べる場面があります。言い切るか、問いの形にするかで、相手の受け取り方が変わります。ブラウザで試せます。プロフィールのリンクから。