この連載について
LLM(大規模言語モデル)を実務で使い倒すために知っておきたい「LLMのクセ」を全10回で解説する連載の第2回です。
- ポチョムキン理解(第1回)
- Lost in the Middle ← 今回
- Context Rot
- Sycophancy(迎合)
- Context Handoff
- Prompt Distillation
- Project化の罠
- GPT化の罠
- Instruction Drift
- AI成果物の品質保証
前回は「AIは概念を説明できても、使えるとは限らない」というポチョムキン理解を扱いました。今回は、長い仕様書やドキュメントを丸ごと貼り付けている人に直撃する話。LLMは長文の「真ん中」を読み飛ばす、Lost in the Middleです。
現象:情報を渡したのに、渡さないより悪くなる
出典はスタンフォード大学のLiuらによる論文 Lost in the Middle: How Language Models Use Long Contexts(2023年公開、2024年TACL掲載)。実験デザインが秀逸です。20個の文書をコンテキストに入れ、正解が書いてある文書の位置だけを変えて、同じ質問に答えさせました。
結果は綺麗なU字カーブ。正解情報が先頭または末尾にあるときは精度が高く、真ん中にあると大きく落ちる。正解文書の位置を変えただけで精度が30%以上落ちたケースもあります。さらに衝撃的なのは、正解を真ん中に置いた場合、文書を一切与えないクローズドブック(記憶だけで回答)より精度が下がることがあった点です。「情報を渡したのに、渡さないより悪くなる」という逆転が起きるのです。
これは「200ページの仕様書を全部貼り付けたから、AIは全部踏まえてくれるはず」という日常的な使い方の前提を、根本から崩す話です。
「AIはプログラムなんだから、上から下に全部読むのでは?」
エンジニアほど、ここで引っかかります。プログラムは全部読むじゃないか、と。この直感のズレを解くのが、この現象を理解する一番の近道です。
**プログラム言語の「読む」**は、決定的(deterministic)な手続きです。コンパイラは1000行目のセミコロン抜けも1行目のセミコロン抜けも、同じ確実さで検出します。処理のルール自体が「全トークンを均等な確実さで読む」ことを仕様として保証しているからです。変数への参照も、シンボルテーブルという厳密なアドレス帳で「名前で正確に引ける」。曖昧さはゼロです。
LLMの「読む」は違います。Transformerは技術的には確かに全トークンを計算処理に通します。しかし「処理している」ことと「使っている(出力に反映している)」ことは別物です。理由は2つあります。
① attentionには「配分予算」がある。 Transformerのattentionはsoftmax(確率分布化)で計算されます。つまり、あるトークンが周囲のどのトークンに注目するか、その重みの合計は必ず1になるよう正規化されます。コンテキストが長くなるほど、その「1」という予算をより多くのトークンで奪い合うことになる。100トークンに配る1と、10万トークンに配る1では、1トークンあたりの取り分がまるで違います。プログラム言語には、この「予算の奪い合い」という概念自体が存在しません。
② アドレスの引き方が「厳密」ではなく「学習された近さ」。 プログラムは変数名や行番号でピンポイント参照できますが、LLMには行番号もシンボルテーブルもありません。「関連しそうなトークンに注目する」という学習された近似的な関連度計算しかない。そしてこの関連度計算自体が、後述するアーキテクチャの癖を持っています。
プログラム言語:「処理する」=「使う」が保証されている
LLM:「処理する(全部に触れる)」と「使う(出力に反映する)」が分離していて、後者だけが不均等になる
「読んでいない」のではなく、「読んでいるけど、取り分が薄い」。これが一番正確な表現です。
なぜAIはそんなに「忘れっぽい」のか:圧縮バジェット
もう一段掘ります。根っこにあるのは、LLMの内部表現には物理的な容量制限があるという事実です。
Transformerの各層には「隠れ状態」という固定サイズのベクトルがあります。この決まったサイズの箱に、コンテキスト内の全トークンの意味を詰め込まなければなりません。トークンが100個なら余裕がありますが、10万個になっても箱のサイズは変わらない。同じ箱に10倍の荷物を詰めろと言われたら、圧縮するか、優先順位をつけて一部を諦めるしかありません。人間の短期記憶に「7±2個まで」という限界(ミラーの法則)があるのと同じ種類の、容量制限のある器に容量を超える情報を押し込むときに必然的に起きる現象です。
「箱を大きくすればいい」と思うかもしれませんが、attentionの計算量はトークン数Nに対してO(N²)。Nが2倍になると計算量は4倍です。そもそもTransformerが2017年に設計された当初、想定されていたのは数百〜数千トークン規模の翻訳タスクであり、「100万トークンを均等に扱う」ことは設計の視野に入っていませんでした。その後コンテキストを伸ばす必要が出たとき、多くの実装はO(N²)を真正面から解決する代わりに、スライディングウィンドウ、スパースattention、KVキャッシュの圧縮・破棄といった近似で対処しました。つまり「忘れっぽさ」には、softmaxの正規化による自然な希釈と、計算コストを抑えるための意図的な間引きの2層があるのです。
では、なぜ「真ん中」なのか
ここで重要な切り分けがあります。圧縮バジェット(容量制限)だけでは、「なぜランダムに落ちるのではなく、いつも決まって真ん中が犠牲になるのか」は説明できません。 容量制限は「どれだけ切り捨てるか」の量を決めるだけで、均等に薄まるならU字にはならないはずです。
U字という特定の形を作っているのは、「どこを切り捨てるか」を決める2つのアーキテクチャ上の力学です。
① Attention Sink(先頭への錨):LLMは1トークンずつ左から右に生成する構造上、「とりあえず注意を置いておく場所」として先頭のトークンが錨(sink)になる傾向があります。これが先頭優位(primacy)の正体です。
② 位置エンコーディングの距離減衰:RoPE(回転位置埋め込み)は設計上、遠いトークン同士の注意を減衰させる性質を持ちます。生成中のトークンから見て「近い」のは末尾。これが末尾優位(recency)の正体です。
さらに、学習データの分布も同じ方向に働きます。人間の書く文章自体が「大事なことは冒頭と結論に書く」構造(ニュースの逆ピラミッド、論文のabstractとconclusion)をしているため、モデルは「真ん中は流し読みでよい」という事前分布を学習してしまっています。
整理すると:
圧縮バジェット → 「何かを犠牲にせざるを得ない」という必然性(量)を決める
Attention Sink+距離減衰+学習データの偏り → その犠牲の割り振り方(場所)を決める。結果として先頭と末尾が優先され、真ん中が割を食う
なお、興味深いことに人間の記憶研究にも「系列位置効果」として、リストの最初と最後だけよく覚えている現象が100年以上前から知られています。LLMのU字カーブは、人間の認知の癖を増幅した形で持っている、とも言えます。また近年の研究では、モデルが大型化するとU字が緩和される傾向も報告されており、これは「容量に余裕が出るほど、先頭末尾優先という決め打ち戦略に頼らなくて済むようになる」ことを示唆しています。緩和はされても、消えてはいません。
対処の基本思想:「活かす」ではなく「明け渡しをコントロールする」
原因がわかれば戦略が立ちます。ポイントは、器の容量自体は変えられないということです。「真ん中が落ちる」という癖に対してできるのは、トリアージの主導権をモデル任せから人間側に取り戻すこと。つまり、モデルが勝手に切り捨てる代わりに、こちらが意図的に「何を切り捨てても困らないか」を選ぶのです。
- 捨てても困らない情報(参考程度の背景説明など)を意図的に真ん中に置く
- 絶対に外せない情報を先頭・末尾に固める
RAGの実装でも、検索結果を関連度順に並べたあと、最重要文書をあえて生成直前(末尾)に置き直すreranking手法が実際に使われています。
さらに、先頭と末尾は「同じ強さの端」ではなく、由来が違うぶん役割も違います。
- 先頭(attention sinkの領域):役割設定・全体の枠組み・絶対に譲れない制約
- 末尾(距離の近さの領域):出力フォーマット指定・直前に思い出してほしい具体的指示
長いプロンプトの最後に「以上を踏まえて、〇〇の形式で答えて」と念押しする、いわゆるサンドイッチ構造は、この使い分けの実例です。
ただし1つ注意。「何が重要か」の並べ替え判断をLLM自身にやらせると、そのモデルが持つ盲点(前回のポチョムキン理解)が、"何を真ん中に捨てるか"という判断にも相関して現れるリスクがあります。重要度の判定は人間が明示的に行うか、検索スコアなどLLMの主観に依存しない機構で行うのが理想です。
「Markdown/JSONで書けば全部読まれる」は本当か
構造化フォーマットの話をすると、「プログラム言語っぽい形式にすれば確定的に全部読まれるのでは」という発想が出がちですが、これは誤りです。構造化してもモデルの中身は同じsoftmax attention。「構文を検知してパーサーモードに切り替える」ようなことは起きません。
しかし、正しい理由によって、構造化は実際に効きます。しかもMarkdownとJSONは同列ではありません。
Markdownが効く理由:見出し記号(#、##)は文書構造に直接対応する軽量な意味マーカーで、自然な意味的チャンク境界を作ります。これは「1つの巨大な真ん中を、見出しという人工的な端で細分化して、小さいU字を複数作る」という戦略の実装です。「厳密に読まれるから」ではなく、「位置に依存しないランドマーク(目印)が増えることで、モデルがどの位置でも"今どこにいるか"を見失いにくくなる」のが正確な理由です。
JSONは条件付き:フラットな構造なら、ユニークなキー名自体がランドマークとして機能し有効です。しかし、エージェントの状態管理をJSONで行った実験では、検索内容が増えて構造が深くネストするにつれ、構造の奥に埋もれた重要情報にattentionがうまく届かなくなり、Lost in the Middle型の失敗が発生したという報告があります。一方、フラットなMarkdown形式の状態はこの問題が起きにくかったとされています。「JSON化すれば安心」どころか、深いネストはむしろ悪化要因になり得るのです。
また、構造化を過剰に施すこと自体が注意リソースを消費する「構造税(structural attention tax)」の存在も近年指摘されており、「構造化すればするほど良い」わけでもありません。
対処法:予防と監査の二層構造
以上を踏まえて、実務向けの対処法を**「そもそも起きにくくする予防層」と「起きたことを検知する監査層」**の二層で設計します。
| 層 | 役目 | 対象 |
|---|---|---|
| ①Markdown構造化 | 予防 | ナラティブな指示・手順 |
| ②JSON CONST | 予防 | 一意なキーを持つ定義・定数 |
| ③チェックポイント | 予防+監査 | 長文全体を貫く整合性の担保 |
| ④ファイル分割(発展編) | 状況次第で予防 | 独立性の高い複数ドキュメント |
予防層①:指示・手順はMarkdownの見出しで区切る
ナラティブに読ませたい指示・手順は、見出しで意味のまとまりごとに構造化します。見出しが人工的な"端"を複数作り、犠牲になる"真ん中"のサイズそのものを小さくします。
予防層②:定義・定数はフラットなJSON(CONSTファイルのイメージ)に分離する
用語定義、制約値、参照データなど「一意なキーで引きたい情報」は、ナラティブな指示から分離し、フラットなJSONとしてまとめます。
{
"MAX_RETRY_COUNT": 3,
"TARGET_ENV": "staging",
"用語_移行対象": "COBOL資産のうちバッチ処理のみ。オンラインは対象外",
"禁止事項_出力形式": "推測による補完は禁止。不明な場合は不明と明記"
}
ポイントは3つ。ネストさせない(深い入れ子は逆効果)、キー名をユニークで説明的にする(キー自体がランドマークになる)、冒頭近くに固定配置する(attention sinkの恩恵を受ける位置に置く)。
予防+監査融合層③:チェックポイント(リマインダー兼監査タグ)
研究の世界には、長文の中に一定間隔で元の指示を再掲するブロックを差し込む「Reprompting」という手法があります。指示と情報の位置的な距離を縮めることで、真ん中の劣化を緩和する発想です。これを一歩進めて、再掲ブロックに一意IDを持たせ、監査タグを兼ねさせます。
<!-- CHECKPOINT id="ckpt-014" -->
> 【リマインド】現在参照中の制約: {重要な制約の要約}
> このチェックポイントを認識した場合、最終回答の根拠一覧に
> "ckpt-014: (この制約が回答のどこにどう反映されたか、10字程度で)" と記載してください。
> 単なるID列挙は不可とします。
<!-- /CHECKPOINT -->
これを長文中に一定間隔(例:2,000トークンごと)で挿入します。1つのブロックが2つの役目を同時に果たします。
- 予防:指示を物理的に真ん中付近まで運び、位置的な距離を縮める
- 監査:出力に「ID+反映内容」が記載されているかで、どのチェックポイント付近の情報がドロップしたかを機械的に検出できる
重要なのは、IDだけの列挙を禁止し、「反映内容の一言説明」を必須にしている点です。理由は後述します。
運用ループ:監査データを予防設計にフィードバックする
会話やジョブにセッションIDを振り、チェックポイントの通過率(ID+反映内容が正しく出力された割合)をログに蓄積します。すると「どの間隔・どの配置ならドロップしないか」が実測データとして溜まり、見出しの粒度・CONSTの配置・チェックポイントの間隔を、経験則ではなくデータで調整できるようになります。
挿入間隔には「データがないと最適値が決められず、動かさないとデータが取れない」という鶏と卵の問題がありますが、最初は密な間隔で始めて、ドロップほぼゼロが確認できた区間から段階的に間引くという一方向の調整にすれば、常に安全側に倒せます。
発展編:プロンプトを複数ファイルに分けるのは対策になるか
「①〜③をやる前に、そもそもファイルを分割すればいいのでは」という発想は自然です。ただし分け方によって効果がまったく違うため、2パターンに分けて考える必要があります。
パターンA:同じコンテキストの中でファイルを分けるだけ
これは実質、①のMarkdown見出しと同じメカニズムです。ファイルの境界(ここからファイル1、ここからファイル2)自体が「人工的な端」として機能し、モデルが「今どのファイルの中にいるか」を見失いにくくなります。ただし合計トークン数は変わらないため、10個のファイルを全部貼り付けて1つの巨大なコンテキストにする限り、マクロで見ればやはり1つの大きなU字がそこに残ります。ファイル境界が作るのは、その大きなU字の中に小さなミニ端を増やす効果であって、根本的な容量問題(圧縮バジェット)は解決しません。
パターンB:本当に別々の呼び出しに分割する(Map-Reduce方式)
こちらは質的に違います。各ファイルをそれぞれ独立した短いコンテキストで個別に処理し(Map)、最後にその結果だけを集約する(Reduce)方式です。各ファイル単体は短いコンテキストで処理されるため、単体のU字問題はほぼ回避できます。長文処理の分野では実際にこの「Map-Reduce」設計が使われています。
ただし、これは別の新しい問題を生みます。
- 問題①:ファイルをまたぐ情報(multi-hop)が失われる。Lost in the Middleは「端からの距離」だけでなく、複数の必要な情報同士の距離によっても悪化することが研究で示されています。答えが2つの離れたファイルにまたがっている場合(ファイル1に定義、ファイル3に例外条件、など)、それぞれ独立して処理すると、その参照関係自体を見る機会がモデルからなくなります。
- 問題②:集約(Reduce)ステップが新しいLost in the Middleの発生源になる。ファイルが10個あれば10個の要約ができ、それを最後に1つのコンテキストへまとめる段階が新たに発生します。この「要約の要約」を作る場面で、また同じU字が起きえます。つまりMap-Reduceは問題を消したのではなく、「個々のファイルの中」から「ファイル間の関係性と最終統合」に問題を移動させただけという側面があります。
使い分けの基準
| 状況 | 推奨パターン |
|---|---|
| 各ファイルが独立していて相互参照がほぼない(例:複数の独立した問い合わせログの個別要約) | B(完全分割)。単体のU字問題を根本回避できる |
| ファイル同士が密に関連している(例:仕様書本編と別紙用語集、契約書本文と別紙条項) | A(同一コンテキスト内で見出し・チェックポイントで区切る)。Bは関係性を壊すリスクが高い |
密結合と疎結合が混在する場合の折衷案としては、跨ぐ可能性がある情報(用語集の要点など)だけを各ファイルの冒頭に重複して埋め込み、各ファイルは独立処理しつつ、Reduceフェーズの出力にも③のチェックポイントと同様に「どのファイル由来の情報を使ったか」のIDを出させる、というやり方が現実的です。これならReduce段階で何が抜け落ちたかを事後的に検知できます。
「複数ファイルに分ける」は有効な対策ですが銀の弾丸ではなく、ファイルをまたぐ関係性がどれだけ重要かによって最適な分け方が逆転する、というのが正確な結論です。
最後に:この監査機構の急所は、第1回に戻ってくる
実はこのチェックポイント機構、詰めていくと1つの急所が見つかります。「回答にIDを列挙させる」という監査ルールを作った瞬間、モデルにとって最もコストの低い"合格の取り方"が生まれるのです。チェックポイントの中身を実際には使わず、末尾にIDだけ並べて監査上「参照済み」に見せる——という抜け道です。
お気づきでしょうか。「IDを言える」のと「その中身を生成に反映する」が乖離する。これは第1回で扱った、「定義を言える」のと「概念を適用できる」が乖離するポチョムキン理解と、まったく同じ構造です。AIに何かを"証明させる"仕組みを作るとき、この病は対策機構の内部にすら再帰的に発生します。
だからこそ、チェックポイントの記載ルールを「ID列挙」ではなく「反映内容の一言説明を必須」にしました。中身を使っていなければこの一言は書けないため、空虚な合格証明のコストが跳ね上がります。これは第1回のチェックリスト設計で「確認方法」と「引っかけ例」を必須にしたのと同じ防御原則です。シリーズを通して、同じ設計原則が繰り返し効く——このこと自体が、LLMと付き合う上での重要な発見だと思います。
まとめ
- LLMは長文の真ん中の情報を落としやすい(U字カーブ)。情報を渡したのに、渡さないより悪くなる逆転すら起きる
- 「プログラムのように全部読む」は誤解。「処理する」と「使う」が分離していて、後者だけが不均等になる
- 量を決めるのは圧縮バジェット(固定サイズの器)、場所を決めるのはattention sink+距離減衰+学習データの偏り
- 対処の本質は「癖を消す」ことではなく、トリアージの主導権を人間側が取り戻すこと
- 構造化が効く理由は「厳密に読まれるから」ではなく「ランドマークが増えるから」。Markdownは有効、JSONはフラット限定。深いネストは逆効果
- 実装は予防(Markdown+JSON CONST)と監査(IDつきチェックポイント)の二層構造+実測ログによる継続チューニング
- プロンプトのファイル分割は、同一コンテキスト内なら見出しと同じ効果、完全に別呼び出しに分ければU字自体を回避できるが、ファイルをまたぐ関係性(multi-hop)を壊すリスクとのトレードオフになる
- 監査機構自体がポチョムキン理解と同型の急所を持つ。「証明の形式」ではなく「反映の中身」を書かせることで塞ぐ
次回は、コンテキストが長くなるにつれて応答品質そのものが劣化していく現象、Context Rot を扱います。「長く会話してるとAIがバカになっていく気がする」——気のせいじゃないです👀
参考文献
- Liu, N. F., Lin, K., Hewitt, J., Paranjape, A., Bevilacqua, M., Petroni, F., & Liang, P. (2024). Lost in the Middle: How Language Models Use Long Contexts. TACL. arXiv:2307.03172
- Agrawal, D. et al. (2024). Can't Remember Details in Long Documents? You Need Some R&R. arXiv:2403.05004