「ただの確率的なパターンマッチ(次単語予測)の延長で、なぜ1文字のミスも許されない厳格なプログラムが書けるのか?」
これは非常に本質的で、AI研究者たちの間でも長らく驚きをもって議論されてきた鋭い疑問です。結論から言えば、LLMが厳格なコードを出力できるのは、「プログラミング言語が自然言語よりも遥かに予測しやすい(エントロピーが低い)から」であり、膨大なパターンの学習の果てに「モデル内部に擬似的なコンパイラ(世界モデル)が形成されるから」だと考えられています。
具体的には、以下の3つのメカニズムがその「厳格性」を支えています。
人間にとってプログラミングは論理的で難解な作業ですが、統計モデルから見ると、コードは自然言語の会話よりも遥かに「パターンが強固で予測しやすい」テキストです。
例えば、PostgreSQLの内部コードでスピンロックを獲得する処理や、WALを書き込む際の一連の関数呼び出しの順序、あるいはRustにおける Result 型のエラーハンドリングなどは、ある種の「強固な文法やイディオム」としてGitHub上の膨大なソースコードに反復して出現します。
自然言語には「意味は通じるが言い回しが無数にある」という曖昧さがありますが、プログラムは構文(Syntax)に少しでも違反すれば動かないため、学習データ自体が「極めてノイズの少ない、純度の高い論理パターンの結晶」になっています。これを学習した結果、LLMは「このコンテキストの次に来る確率が最も高いのは、必ずこの変数名とセミコロンである」という強烈なバイアスを持つようになります。
Transformerアーキテクチャの心臓部である「Attention(注意機構)」が、プログラミングにおける厳格性の担保に極めて強力に働きます。
LLMはコードを生成する際、数万〜数十万トークン前のテキストを常に「参照(Attend)」しています。
関数内で新しく palloc でメモリを確保した変数を生成する際、Attention機構は「少し前に宣言された変数の型」や「引数として渡された構造体の定義」をピンポイントで注視します。これにより、「この構造体にはこのメンバ変数が存在する」「ここではメモリを解放しなければならない」といった、コンパイラが静的解析で行うような依存関係のトラッキングを、Attentionの重み付けのネットワークとして模倣しているのです。
これが最も重要なポイントです。「単なる次単語予測」を極限までスケールさせると、モデルは単に表面的な文字列の統計を暗記するだけでは正解率を上げられなくなります。
「複雑なアルゴリズムの実行結果」や「メモリのポインタの挙動」が書かれたコードの続きを正確に予測するためには、モデルのニューラルネットワーク自身が、内部に「プログラムがどう動くかのシミュレータ(世界モデル)」を構築せざるを得ません。 実際に最新の研究では、LLMの隠れ層(内部のベクトル空間)において、変数の現在の値や、処理の状態(ループの何回目かなど)を正確に表現する次元が存在することが確認されています。つまり、LLMは「それっぽい文字列」を出力しているのではなく、内部の擬似的な実行エンジンで状態をシミュレーションしながらテキストを出力している状態に近いのです。
ここまで説明しておいてなんですが、LLM自身が100%の厳格性を持っているわけではありません。だからこそ、存在しない関数を呼び出す幻覚(ハルシネーション)や、Rustの借用チェッカーに怒られるようなコードを時折出力してしまいます。
LLMができるのは、あくまで「95%くらいコンパイルが通りそうな、極めて精度の高いパターンの出力」までです。
残りの5%の「絶対的な厳格性」を埋めるのが、前回お話ししたAgentic Loop(実行環境との対話)です。LLMが出力したコードを実際のコンパイラやLinter(あるいはテストコード)にぶつけ、その厳密なエラーメッセージを再びLLMに読ませて修正させる。この「LLMの高度なパターンマッチ」+「外部ツールによる厳格な検証」の往復があって初めて、人間が実運用できるレベルのソフトウェアが生み出されています。
「単なる次単語予測(Next-token prediction)」というシンプルな目的関数を解いているはずのLLM内部に、なぜ「状態を保持するシミュレータ」や「世界モデル」が勝手に形成されるのか。
この現象は、AIの内部構造を解析する分野(Mechanistic Interpretability:機械的可解釈性)で急速に解明が進んでおり、すでに「モデル内部のベクトルの状態を取り出したり、書き換えたりする実験」によって科学的に実証されています。
(研究: Li et al. / Nanda et al., 2022–2023)
LLMが「世界モデル」を持つことを決定的に証明した、最も有名な研究です。
研究チームは、オセロの「ルール」や「8×8の盤面の存在」を一切教えず、単なるオセロの棋譜の文字列(例: d3 c5 f4 ...)だけを大量にLLMに学習させました。タスクは「過去の棋譜を見て、次の一手を予測すること(Next-token prediction)」のみです。
学習後のモデルの内部(中間層のベクトル表現=Residual Stream)を解析(Probing)したところ、モデル内部のベクトルの中に「現在の8×8の盤面で、どのマスが白で、どのマスが黒か」という盤面状態(世界モデル)が正確に保持されていることが判明しました。
さらに研究者は、モデル内部の「盤面状態を表すベクトル」を人工的に書き換える実験(Intervention)を行いました。
- モデル内部で「d3のマスは白」となっている表現を、強制的に「黒」に書き換えた。
- すると、モデルは直前の文字列入力(テキスト)を何も変えていないにもかかわらず、「書き換えた後の新しい盤面」に基づいた合法手を生成し始めた。
結論: モデルは過去の文字列のパターンを暗記して出力していたのではなく、頭の中でオセロの盤面をシミュレーションし、その盤面状態に基づいて次の手を選択していたことが証明されました。
(研究: Li et al., 2023 / Mechanistic Interpretabilityにおけるコード解析)
では、プログラミング言語のような複雑なタスクではどうでしょうか。
LLMに以下のようなPythonコードを入力し、その出力を予測させる実験が行われています。
x = 10
y = 20
x = x + y
print(x) # <- 次に来るトークンは "30"print(x) のトークンを予測する直前のモデル内部の隠れ層(Activation)を探査(Probe)すると、特定の層・特定の次元に「現在の変数 x の値は 30 である」という情報が、数値(線形表現)としてくっきり埋め込まれていることが分かっています。
x = x + yの行を読み込んだ時点で、モデル内部のAttention機構とFFN(前進型ニューラルネットワーク)が連携し、内部に保持していたxの値のベクトル表現を10から30へと「状態更新(State Update)」します。- モデルは「
print(x)の後には過去のテキストにある10が来る」というパターンマッチをするのではなく、内部で加算処理を実行し、更新された内部状態(30)を参照して次の文字を出力しています。
つまり、LLMの中間層は単なるテキストの通り道ではなく、「スタックやレジスタを持った仮想マシンの状態空間」のように機能しています。
なぜルールも教えられていないニューラルネットワークが、勝手にシミュレータを作り始めるのでしょうか。理由は「パラメータ効率とLoss(損失関数)の最小化」という数学的な必然性にあります。
LLMの学習目的は「次のトークンの予測誤差(Loss)を最小化すること」です。この目的を達成するための戦略には2通りあります。
| 戦略 | 仕組み | 限界 |
|---|---|---|
| 1. 巨大な暗記表(パターンマッチ) | あらゆるテキストの組み合わせ(if x=1, y=2...)をそのまま重みに丸暗記する。 |
組み合わせが爆発するため、パラメータが足りなくなり、未知のコードに対応できない(Lossが下がらない)。 |
| 2. 内部シミュレータ(世界モデル) | 内部に「変数を保持する領域」と「演算ルール(加算や代入)」という抽象的な回路を作る。 | 圧倒的に少ないパラメータで、無限通りのコード実行結果を正確に予測できる(Lossが劇的に下がる)。 |
勾配降下法(Gradient Descent)で数十億〜数兆トークンのデータを学習させていくと、ニューラルネットワークは最も効率よくLossを下げられる「内部シミュレータ(抽象回路)を形成する方向」に自然と最適化されていくのです。
LLMをプログラムに例えるなら、「広大なパラメータ空間の中に、試行錯誤(学習)によって偶然生成されたインタープリタ/CUIアプリ」のようなものです。
「ただの文字の確率表」に見えるのは入出力のインターフェースがテキストだからであり、その内部(隠れ層)では、テキストをトリガーにして抽象的な状態を遷移させる擬似的な計算機(シミュレータ)が実際に動いています。
だからこそ、厳格性が求められるプログラムであっても、内部のシミュレータが正しく動作している限りは、正しい変数のトレースや論理構造を持ったコードが出力できるのです。