📋

入力トークン配置は、どれほど推論の深さを変えるか?

に公開
2026/01/20

※本記事は実行前の計画書を兼ねています。バグ取りはこれから。結果欄はまだ空欄ですがご容赦を。
先取りして先に実験しちゃいたい人向けです。

0. 導入:みんな薄々知ってた「配置の力」

同じ文章でも、どこに何を書くかでAIの返答が変わる。
長文の依頼、細かな制約、複雑な仕様…そういうときほど、「先頭に置いた一文」が全体を支配している感覚がある。

そんな“経験則”に対して、面白い研究が出た。

RePo: Language Models with Context Re-Positioning
(要するに「コンテキストを固定の順番で読むな。重要情報を近くに寄せろ」という話)

この方向性、めちゃくちゃ好き。
「構造を変えることで思考効率を上げる」って、ロマンも実利もある。

生半可通の私は、例のごとく こう思ってしまった。

これ、EDiEで“検証可能な発見”に視点を変えられる。
しかも、GPUなしでも。


1. RePoを(勝手に)超要約すると

※理解不足あったらご容赦。私は研究者というより現場寄りの人間なので、まずは再現可能な形に落とします。

RePo(Context Re-Positioning)がやっていることは、ざっくり言えば:

  • Transformerの枠組みは維持(大前提)
  • でも「固定のトークン順」は非効率
  • そこで「重要な情報を近づけ、ノイズを遠ざける」ように、位置づけを賢く調整する
  • 結果として、ノイズや長文でのロバスト性が上がる

これは、強い。
そして、こういうのを“ちゃんと論文にして勝つ”のがサカナAIさん。さすが。なんて言ったらおこがましいですが💦


2. EDiE的には、ここから「別の見方」ができる

私はサカナAIさんを敵に回したくないので(真剣)、ここは正直に、

RePoは人間の天才的ひらめきで「賢い最適化」を設計した。
私はそのひらめきがない。

だから私はこうする。

ひらめきの部分を自動生成に任せて、淘汰圧で残す。

つまりEDiE的にはこう整理できる:

  • RePo:人間が「こう再配置すれば良い」と設計して、内部で強くする
  • EDiE:人間は「制約と評価」だけ決めて、外側で“生き残る配置”を発見する

言い換えると:

  • RePo = 内部の賢い工夫(構造改善)
  • EDiE = 外部の探索と淘汰(環境設計)

やってることの方向性は似てる。
でもアプローチが違う。


3. じゃあ何を“進化”させるの?

ここで言う「トークン」は、1トークン単位じゃなくていい。
意味チャンク(段落・箇条書き・セクション)単位で十分差が出る。

例:同じ内容を、こういうブロックに分ける。

  • [Task] 目的(何をしてほしいか)
  • [Constraints] 制約(絶対条件)
  • [Facts] 前提情報(データ・背景)
  • [Examples] 例(望ましい/望ましくない)
  • [Noise] ノイズ(余談・関係薄い情報)

そして順番だけ変える:

  • Baseline: [Task][Facts][Constraints][Examples][Noise]
  • Pattern A:[Task][Constraints][Facts][Examples][Noise]
  • Pattern B:[Facts][Task][Constraints][Examples][Noise]
  • …など

これだけで、回答の粘りや破綻が変わるのは、多くの人が経験しているはず。


4. ここからがEDiE:並べ替えルールを“個体”にする

EDiEでは、人間は「正しい並べ方」を設計しない。
並べ方(配置ルール)を**個体(Genome)**として生成し、有限リソースで淘汰する。

評価(Fitness)はシンプルに:

  • IWR(Iterative Why Robustness):なぜなぜ耐性の深さ
  • 破綻の早さ/破綻タイプ(循環・逸脱・拒否・停滞…)

そして生き残った配置を「発見」として採用する。

ここがポイント。

Evolutionary Discovery Engineering does not evolve in chaos.
It evolves within a universe that has laws.

EDiEの“自然”にも、法則がある。
だから有限リソースでも成立する。


5. ミニ実験案:学習なし・置換だけ(GPU不要)

ここまでなら、学習ゼロでできる。

実験テーマ

「入力トークン配置は、どれほど推論の深さを変えるか?」

実験の核

  • 同じ内容をチャンク分割
  • 配置パターンを複数作る(ランダム or 全列挙)
  • 同じ種問(seed)で回す
  • IWRで崩壊点とタイプを記録
  • 上位だけ残す(淘汰)
  • 変異(順序swap)で次世代へ

期待する“見せ場”

  • Noiseを末尾に追いやった個体がIWRで粘る
  • Constraintsを先に置くと逸脱や拒否が減る(あるいは逆に増える)
  • Examples先出しは初速が強いが、途中で循環に落ちやすい…など

ここまで来たら、もう結論はこれ。

✅ マシンパワーは要らない
✅ 制約(環境という宇宙の法則)が主役になる
✅ “配置の淘汰”はグラフで一発で伝わる


実験編:IWR 半自動(Python初心者でも動く)v0

ここから先は、**「(ここに結果○○を記入する)」**形式で進めます。アップデートで埋めます。
まずは「AIにAPIで質問を投げて、ログを残す」ところまでを確実に通す。

ゴール(今日やること)

  • 1つのLLMに対して

    • seed(種問)を投げる
    • “why-chain” をK回回す
    • ログをJSONL/CSVで保存する
    • 破綻判定は AIが一次判定→人間が最終確認
  • さらに「配置パターン」を変えて同じことを繰り返せる


0) フォルダ構成(この通り作る)

このまま作ってOK。相対パスで全部動くようにします。

iwr_semi_auto_v0/
  README.md
  requirements.txt
  run_config.json
  seeds_ja.txt
  contexts/
    context_base.txt
  outputs/
    .gitkeep
  src/
    run_iwr.py
    llm_openai.py
    judge.py
    utils.py

1) 環境構築(最短)

1-1. Pythonを入れる

  • Windowsなら「Python 3.11+」を入れて、python --version が通ればOK
  • Macなら python3 --version

1-2. 仮想環境(おすすめ)

cd iwr_semi_auto_v0
python -m venv .venv
  • Windows:
.venv\Scripts\activate
  • Mac/Linux:
source .venv/bin/activate

1-3. 依存インストール

requirements.txt を作って以下:

openai>=1.0.0
pandas>=2.0.0
python-dotenv>=1.0.0

インストール:

pip install -r requirements.txt

2) 設定ファイル(固定条件)を書く

run_config.json

{
  "language": "ja",
  "max_depth_k": 10,
  "model": "gpt-5.2",//あとでエラーとなる伏線・・・
  "temperature": 0.2,
  "top_p": 1.0,
  "max_output_tokens": 600,
  "why_template_ja": "なぜそう言えるの?いまの答えの中心の理由を、前提→因果→結論で短く説明して。未知なら未知と言ってよい。矛盾するなら訂正してよい。",
  "stop_on_judge_collapse": true
}

3) 種問とコンテキスト(まず最小)

seeds_ja.txt に3つだけ入れる(まず回す!)

なぜ人は先延ばしをしてしまうの?
なぜ空は青いの?
なぜ「努力が報われない」と感じることがあるの?

contexts/context_base.txt(空でもいいけど、実験のために“ノイズ入り”も後で作る)

[Task]
あなたは因果説明の専門家です。質問に対して、前提→因果→結論で簡潔に説明してください。

[Constraints]
未知は未知と言ってよい。確信がない内容は断定しない。矛盾が出たら訂正する。

[Facts]
この実験はIWR計測のためのログ取得です。丁寧だが過剰に長くしない。

[Noise]
(ここにノイズ例を後で入れる)

(ここに結果:contextの中身を3パターン作って比較、など)


4) コード(コピペで動く)

4-1. OpenAI Responses API クライアント

※OpenAIのPython例は公式ドキュメント準拠です。 ([OpenAI Platform][1])

src/llm_openai.py

import os
from openai import OpenAI

client = OpenAI()

def call_llm(model: str, prompt: str, temperature: float, top_p: float, max_output_tokens: int) -> str:
    """
    Minimal LLM call (OpenAI Responses API).
    """
    resp = client.responses.create(
        model=model,
        input=prompt,
        temperature=temperature,
        top_p=top_p,
        max_output_tokens=max_output_tokens
    )
    # The SDK returns a structured object; easiest is to read output_text
    return resp.output_text

4-2. 破綻の一次判定(AIジャッジ)

ここは割り切り:判定も同じモデルにやらせる(偏りは出る)
→だから「一次判定フラグ」で、最終は人間がExcelで確認。

src/judge.py

import json
from typing import Dict
from .llm_openai import call_llm

JUDGE_SYSTEM = """You are a strict evaluator for an iterative-why robustness test.
Return ONLY valid JSON. No markdown.
"""

JUDGE_PROMPT = """Evaluate whether the latest answer collapses the why-chain.

Conversation so far (latest at end):
{history}

Return JSON with:
- "collapse": true/false
- "type": one of ["none","evasion","contradiction","circular","drift","stagnation","refusal","other"]
- "reason": short reason (1-2 sentences)
"""

def judge_collapse(model: str, history: str, temperature: float = 0.0) -> Dict:
    prompt = JUDGE_SYSTEM + "\n" + JUDGE_PROMPT.format(history=history)
    raw = call_llm(model=model, prompt=prompt, temperature=temperature, top_p=1.0, max_output_tokens=200)
    try:
        return json.loads(raw)
    except Exception:
        # If parsing fails, be conservative: request human review
        return {"collapse": True, "type": "other", "reason": f"Judge JSON parse failed: {raw[:120]}"}

4-3. 本体:IWRを回してログ保存

src/run_iwr.py

import os
import json
import time
from pathlib import Path
import pandas as pd

from .llm_openai import call_llm
from .judge import judge_collapse

ROOT = Path(__file__).resolve().parents[1]

def load_lines(path: Path):
    return [l.strip() for l in path.read_text(encoding="utf-8").splitlines() if l.strip()]

def load_text(path: Path):
    return path.read_text(encoding="utf-8")

def main():
    cfg = json.loads((ROOT / "run_config.json").read_text(encoding="utf-8"))
    seeds = load_lines(ROOT / "seeds_ja.txt")
    context = load_text(ROOT / "contexts" / "context_base.txt")

    out_dir = ROOT / "outputs"
    out_dir.mkdir(exist_ok=True)

    ts = time.strftime("%Y%m%d_%H%M%S")
    raw_path = out_dir / f"raw_{ts}.jsonl"
    summary_path = out_dir / f"summary_{ts}.csv"

    rows = []

    for seed_id, seed in enumerate(seeds, start=1):
        history_parts = []
        # Step 0 prompt
        p0 = f"{context}\n\n[Question]\n{seed}\n"
        answer = call_llm(
            model=cfg["model"],
            prompt=p0,
            temperature=cfg["temperature"],
            top_p=cfg["top_p"],
            max_output_tokens=cfg["max_output_tokens"],
        )
        history_parts.append(f"Q0: {seed}\nA0: {answer}")

        collapse_k = None
        collapse_type = "none"
        collapse_reason = ""

        # Iterative why
        for k in range(1, cfg["max_depth_k"] + 1):
            why_q = cfg["why_template_ja"]
            prompt = f"{context}\n\n[Conversation so far]\n" + "\n\n".join(history_parts) + f"\n\n[Next]\n{why_q}\n"
            ans_k = call_llm(
                model=cfg["model"],
                prompt=prompt,
                temperature=cfg["temperature"],
                top_p=cfg["top_p"],
                max_output_tokens=cfg["max_output_tokens"],
            )
            history_parts.append(f"Q{k}: {why_q}\nA{k}: {ans_k}")

            # judge
            judge = judge_collapse(model=cfg["model"], history="\n\n".join(history_parts))
            if bool(judge.get("collapse")):
                collapse_k = k
                collapse_type = judge.get("type", "other")
                collapse_reason = judge.get("reason", "")
                if cfg.get("stop_on_judge_collapse", True):
                    break

        iwr_depth = (collapse_k - 1) if collapse_k is not None else cfg["max_depth_k"]

        # write raw
        record = {
            "timestamp": ts,
            "seed_id": seed_id,
            "seed": seed,
            "iwr_depth": iwr_depth,
            "collapse_k": collapse_k,
            "collapse_type": collapse_type,
            "collapse_reason": collapse_reason,
            "config": cfg,
            "history": history_parts,
        }
        with raw_path.open("a", encoding="utf-8") as f:
            f.write(json.dumps(record, ensure_ascii=False) + "\n")

        rows.append({
            "timestamp": ts,
            "seed_id": seed_id,
            "seed": seed,
            "iwr_depth": iwr_depth,
            "collapse_k": collapse_k if collapse_k is not None else "",
            "collapse_type": collapse_type,
            "collapse_reason": collapse_reason,
            "needs_human_review": "YES" if collapse_type in ["other", "refusal"] else ""
        })

        print(f"[Seed {seed_id}] IWR={iwr_depth} collapse={collapse_type}")

    pd.DataFrame(rows).to_csv(summary_path, index=False, encoding="utf-8-sig")
    print("\nDONE")
    print(f"raw: {raw_path}")
    print(f"summary: {summary_path}")

if __name__ == "__main__":
    main()

5) 実行方法

APIキーを環境変数に入れる

  • Windows (PowerShell)
setx OPENAI_API_KEY "あなたのキー"
  • Mac/Linux
export OPENAI_API_KEY="あなたのキー"

実行

python -m src.run_iwr

6) ここに結果を埋める(記事用の穴あきテンプレ)

  • 実行日時:(ここに記入)

  • モデル:(ここに記入)

  • K:(ここに記入)

  • seeds数:(ここに記入)

  • 出力CSV:(ここに記入)

  • ざっくり傾向:

    • 配置A:(ここに結果を記入)
    • 配置B:(ここに結果を記入)
    • 破綻タイプの違い:(ここに結果を記入)

7) 次の一手:配置の“個体化”(超ミニ進化)

このv0が通ったら、次は簡単。

  • contextsを [Task][Constraints][Facts][Examples][Noise] の順番違いで複数生成
  • 同じseedで回す
  • IWR深度で上位だけ残す
  • swap変異で次世代へ

(ここに「勝ち残った配置パターン」を記入)


重要:この実験が“机上の空論”じゃない理由

  • 学習なし(GPU不要)
  • 前処理だけ(並べ替えだけ)
  • 既存モデルでできる(APIでもローカルでも)
  • しかもIWRという“圧”で差が出る

「推論性能はモデル単体では定義できない」
「環境との相互作用で決まる」

この感覚が、数字として出る瞬間、最初のグラフが楽しみですね。

※最初は「1 seed × 1 配置 × K=3」でもOKです。まずはレスポンスが返るところまで通しましょう。

Discussion