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

推理は点数では進めない。「主張」の条件を原文で確かめる

Deduction doesn't advance by score: checking a "claim" against the exact wording

 D10-4-T20-claim-contract・技術・日本語入力でプレイする仕組み

入力を解いて返す主張(claim・造語)recognizing textual entailmentnatural language inferenceclaude自然言語処理

「時計が遅れていた」と「時計が遅れていたとは言えない」は、文の埋め込みではほとんど同じ所に置かれます。
推理アドベンチャー『霧走町 流氷に眠る』では、この二つで話が逆に進まなければいけません。
この回は、推理の段を進める前に、入力が本当にその論点を述べたかを原文で確かめる「主張」の仕組みを書きます。

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

区切り: 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 回まで繰り返します。

  1. 主張が成立した、実行できて、まだ立っていない旗を立てる段。違う進行の論点が 2 つ以上なら自動で選ばず、入力の引用を並べて選んでもらう。同じ旗を立てる論点どうしは 1 つにまとめる
  2. 受け取り。ただし照合の 1 位がここで実行できて話を進め、採用線から 0.08 以上の余裕があるなら、そちらに譲る
  3. 疑問として挙げた主張(照合が推理の方を向いているときだけ)
  4. 照合の 1 位。その段の主張が unsupported なら、その候補を外して 1 へ戻る。ambiguous なら確かめる
  5. それ以外は従来の聞き返し・誘導・受け流し

一つの意図の中で、論点ごとに段を分けることもできます。
状態で選んだ段の主張が成り立たず、同じ状態で入れる別の進行の段の主張が成り立つなら、そちらの段を選びます。
述べた論点の台詞だけを返し、述べていない論点を主人公が代わりに語らない、という約束です。

成立した主張は、推理記録(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

第一章の終わりには、自分の言葉で見立てを述べる場面があります。言い切るか、問いの形にするかで、相手の受け取り方が変わります。ブラウザで試せます。プロフィールのリンクから。

#claude #自然言語処理