入力トークン配置は、どれほど推論の深さを変えるか?
※本記事は実行前の計画書を兼ねています。バグ取りはこれから。結果欄はまだ空欄ですがご容赦を。
先取りして先に実験しちゃいたい人向けです。
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