Skip to content
第10回:思考の連鎖 ~推論の軌跡~

第10回:思考の連鎖 ~推論の軌跡~

注意事項

  • CoTを「多様体上の探索軌跡」と見る解釈は比喩であり、厳密な数学的対応ではない。
  • Test-time compute scalingの効果はタスク・モデル依存であり、普遍的ではない。
  • 「離散トークン列が連続軌跡を近似」という見方は、分析の枠組みとして有用だが証明されたものではない。
  • Model CollapseとDimensional Collapseは関連するが異なる現象であり、混同に注意。
  • 本回の「軌跡」「経路」は、物理的な軌道ではなく、表現空間内での状態遷移の比喩である。

導入:推論という旅

前回、拡散モデルにおいて「ノイズからデータへの軌道」を学習する枠組みを見た。時間発展を明示的に扱うことで、生成プロセスを制御できるようになった。

本回では、同様の視点を 推論 に適用する。

Chain of Thought(CoT)は、近年の大規模言語モデル(LLM)において、 特定クラスの推論タスクで顕著な性能向上をもたらす 技術として注目されている。「ステップバイステップで考えよう」という単純なプロンプトが、なぜ効果的な場合があるのか。

従来の理解では、CoTは「中間ステップを出力することで、モデルが複雑な推論を分解できる」と説明される。しかし、幾何学的な視点から見ると、より深い構造が見えてくる。

推論とは、表現空間上を移動する「旅」である。 (これは比喩的な見方である)

質問から答えへ至る過程は、高次元の意味空間における軌跡として捉えられる。One-shotで答える場合、その軌跡は短く直接的である。CoTを使う場合、中間ステップという「足場」を経由しながら、より複雑な経路を辿る。

NOTE

比喩としての「軌跡」: 本回で使う「軌跡」「経路」「旅」といった表現は、幾何学的な直感を与えるための比喩である。Transformerの内部状態が文字通り連続的な軌道を描いているわけではなく、離散的なトークン生成の過程を、連続空間での移動に見立てている。この見方は分析の枠組みとして有用だが、数学的に証明されたものではない。

IMPORTANT

CoTの効果はタスク依存: CoTが効果的かどうかは、タスクの種類(算術、論理推論、常識推論など)、評価方法、モデルサイズ、プロンプト設計に大きく依存する。すべての問題でCoTが有効というわけではなく、単純なパターン認識タスクや、そもそもモデルの能力を超える問題では効果が限定的である。例えば、短い回答が求められる形式(採点が最終答のみ等)では、長いCoTが不利になることも考えられる。

本回では、この「推論の幾何学」を探求する。CoTの本質、Test-time compute scaling、そしてModel CollapseやDimensional Collapseといった表現の崩壊現象を、幾何学的な視点から理解することを目指す。

推論の二つのモード:One-shot vs Multi-step

One-shot推論:直接の飛躍

最も単純な推論は、入力から出力への直接的なマッピングである。

txt
質問:「12 × 15 は?」
回答:「180」

この場合、モデルは質問を受け取り、内部で何らかの計算を行い、直接答えを出力する。幾何学的には、質問の埋め込みベクトルから答えの埋め込みベクトルへの短い経路を辿っている。

txt
[質問] ────────────→ [答え]
        (直接ジャンプ)

単純な問題であれば、この直接的な経路で十分である。しかし、問題が複雑になると、この「一足飛び」は困難になる。

Multi-step推論:足場を使った旅

複雑な問題では、中間ステップを経由するMulti-step推論が有効である。

txt
質問:「12 × 15 は?」
思考:「12 × 15 = 12 × 10 + 12 × 5 = 120 + 60 = 180」
回答:「180」

幾何学的には、質問から答えへ直接ジャンプする代わりに、中間的な「足場」を経由しながら進む。

txt
[質問] ──→ [中間1] ──→ [中間2] ──→ [中間3] ──→ [答え]
           12×10      12×5       120+60

各中間ステップは、表現空間における waypoint(経由点) の連続的なベクトル表現として機能する。この経由点があることで、一度には難しい大きなジャンプを、複数の小さなステップに分解できる。

推論モード経路の長さ適した問題
One-shot短い(直接)単純、パターン認識的
Multi-step長い(経由)複雑、論理的推論

なぜ「遠回り」が必要か

直感的には、「最短経路」が最も効率的に思える。しかし、表現空間の地形を考えると、遠回りの必要性が理解できる。

山岳地帯を移動する場合を考えよう。直線距離では近くても、間に険しい山脈があれば、直接越えるのは困難である。峠を経由する「遠回り」の方が、実際には効率的なことがある。

同様に、意味空間にも「険しい領域」がある。One-shotで直接ジャンプしようとすると、この険しい領域を越える必要があり、誤りやすい。中間ステップを経由することで、より「なだらかな」経路を辿れる。

NOTE

難易度と経路長の関係: 経験的に、問題が難しいほど、CoTによる改善効果が大きい傾向がある。これは「難しい問題ほど、直接経路が険しく、迂回路が必要」という幾何学的直感と整合する。ただし、この関係は普遍的ではなく、タスクやモデルに依存する。

Chain of Thoughtの本質

CoTとは何か

Chain of Thought(CoT)(Wei et al., 2022)は、LLMに「ステップバイステップで考える」よう促すプロンプティング技術である。

txt
[プロンプト]
問題を解くときは、まず問題を分解し、各ステップを順番に考え、
最後に答えを導いてください。

[質問]
あるお店で、リンゴが1個80円、オレンジが1個120円で売られています。
リンゴを5個とオレンジを3個買うと、合計いくらになりますか?

[CoTによる回答]
まず、リンゴの合計金額を計算します。
リンゴ1個80円 × 5個 = 400円

次に、オレンジの合計金額を計算します。
オレンジ1個120円 × 3個 = 360円

最後に、両方を合計します。
400円 + 360円 = 760円

答え:760円

連続的思考と離散的出力

ここで、Transformerの内部動作を思い出そう。

Hidden states(隠れ状態) は、各トークン位置での連続的なベクトル表現である。これらは高次元空間(例:4096次元)内の点であり、層を通過するごとに変化する。

出力トークンは、この連続的な表現から離散的な単語へのマッピングである。Softmaxを通じて、語彙全体にわたる確率分布が計算され、そこからトークンが選択される。

txt
連続空間(hidden states)      離散空間(トークン)
    ●                              [まず]
     \                               ↓
      ●                            [リンゴ]
       \                             ↓
        ●  ────────────────→       [の]
         \                           ↓
          ●                        [合計]
           \                         ↓
            ●                      [を]

CoTの本質は、この連続的な思考過程を、中間的な離散トークンで「足場」を作りながら進めることにある、と解釈できる。

足場としての中間トークン(仮説的なメカニズム)

なぜ中間トークンが「足場」として機能するのか。以下はもっともらしい仮説であり、確立したメカニズム説明ではないことに注意が必要である。実際には複数のメカニズムが複合的に働いている可能性が高い。

1. 作業記憶の外部化(仮説):Transformerは固定長のコンテキストを持つ。複雑な推論では、中間結果を「覚えておく」必要がある。中間トークンとして出力することで、この情報がコンテキストに残り、後続の推論で参照できる、と考えられる。

2. 軌跡の安定化(仮説):連続空間での長い軌跡は、途中で「迷子」になりやすい。中間トークンは、軌跡上の「チェックポイント」として機能し、正しい方向への進行を確認する、という見方ができる。

3. 探索空間の構造化(仮説):中間ステップを出力することで、探索空間が階層的に構造化される。大きな問題が小さな部分問題に分解され、各部分問題は独立に解きやすくなる、と解釈できる。

その他の可能性

  • 形式的制約による誘導:CoTフォーマットが、モデルを特定の推論パターンに誘導する
  • 学習データとの整合:学習時に見た推論パターンとの類似性が高まる
  • 注意機構の活用:中間ステップがAttentionの参照先を提供する

IMPORTANT

CoTは単なる「説明」ではない: CoTの効果は、「モデルが思考過程を説明している」だけでは説明できない。実際には、中間トークンの生成がモデルの次のトークン分布(生成挙動)を変え、後続の推論を変化させている。ただし、そのメカニズムの詳細は依然として研究対象であり、上記の仮説は一つの見方に過ぎない。

Test-time Compute Scaling

推論時の計算量と性能

近年、Test-time compute scalingという現象が注目されている。これは、推論時の計算量を増やすことで、モデルの性能が向上する現象である。

従来のスケーリング則(Kaplan et al., 2020; Hoffmann et al., 2022)は、主に学習時のパラメータ数・データ量・計算量に焦点を当てていた。Test-time compute scalingは、推論時にも同様のスケーリング効果があることを示唆する。

スケーリングの種類対象増やすもの
学習時スケーリングモデル学習パラメータ、データ、計算量
Test-time スケーリング推論推論時の計算量(後述)

計算量を増やす方法(分類)

「Test-time compute」として括られる手法は、実際には異なるメカニズムで計算量を増やしている。これらを混同すると、「同じ原理で一律に伸びる」という誤解を招く。

手法カテゴリ具体例増やすもの主な効果
シーケンス長の拡張CoT, Scratchpad生成トークン数中間表現の明示化
サンプリング回数Self-consistency独立した試行回数多数決による安定化
探索アルゴリズムTree of Thoughts, MCTS探索幅・深さ経路の比較・選択
検証・リランキングSelf-reflection, Verifier検証ステップ誤り検出・修正

1. シーケンス長の拡張(CoT等):中間ステップを多く生成することで、より複雑な推論を段階的に行う。計算量は生成トークン数に比例して増加。

2. サンプリング回数の増加(Self-consistency):同じ問題に対して複数回(独立に)推論を行い、多数決や集約で最終回答を決定する。計算量は試行回数に比例。

3. 探索アルゴリズムの適用(Tree of Thoughts):複数の思考経路を木構造で並行して探索し、評価関数で最も有望な経路を選択する。計算量は探索幅×深さに依存。

4. 検証と修正(Self-reflection, Verifier):生成した回答を別のモデルや同一モデルで検証し、必要に応じて修正・再生成する。計算量は検証ステップ数に依存。

NOTE

手法ごとに効果の出方が異なる: これらの手法は、すべてが同じ問題に対して同じように効くわけではない。例えば、Self-consistencyは「複数の妥当な解法がある」問題で効きやすく、Tree of Thoughtsは「探索が必要な」問題で効きやすい。

幾何学的解釈(比喩)

Test-time compute scalingを幾何学的に解釈すると、以下のような見方ができる(ただし、これは直感的な比喩であり、厳密な対応ではない)。

探索空間のより広範な探索:計算量を増やすことで、表現空間内のより多くの経路を探索できる。これにより、「良い経路」を見つける確率が上がる。

軌跡の精緻化:より多くのステップを踏むことで、軌跡がより滑らかになり、「険しい領域」を避けやすくなる。

検証による経路の選別:複数の経路を探索し、最も「安定した」経路を選択することで、誤りを減らせる。

txt
         ┌──→ 経路A ──→ [答えA]

[質問] ──┼──→ 経路B ──→ [答えB] ✓ (最も信頼性が高い)

         └──→ 経路C ──→ [答えC]

NOTE

限界と注意: Test-time compute scalingの効果は、タスクやモデルに依存する。すべての問題で計算量を増やせば性能が上がるわけではない。特に、モデルの能力を超える問題や、そもそも情報が不足している問題では、計算量を増やしても改善しない。

HMMとの対比:局所から大域へ

隠れマルコフモデル(HMM)

言語モデルの歴史を振り返ると、 隠れマルコフモデル(HMM) が重要な位置を占める。

HMMは、観測されない「隠れ状態」の系列が、観測される「出力」を生成するというモデルである。

P(x1,,xT,z1,,zT)=P(z1)t=2TP(zt|zt1)t=1TP(xt|zt)

ここで:

  • zt :時刻 t の隠れ状態(離散)
  • xt :時刻 t の観測(出力)
  • P(zt|zt1) :状態遷移確率(マルコフ性:直前の状態のみに依存)

マルコフ性の限界

HMMの核心的な仮定はマルコフ性である。現在の状態は、直前の状態のみに依存し、それ以前の履歴には依存しない。

これは計算上は便利だが、言語モデルとしては大きな制約となる。文の意味は、直前の単語だけでなく、文全体の文脈に依存することが多いからである。

NOTE

対比の単純化について: ここでのHMMは「一次マルコフモデル」の基本形を指している。実際には、n-gram化(高次マルコフ)、状態の設計の工夫、階層的HMMなど、文脈を拡張する手法も多数存在する。本対比は、「局所的依存 vs 大域的依存」という本質的な違いを強調するための単純化である。

txt
「昨日、公園で会った友人が、今日も同じ場所にいた。彼は...」

HMM(基本形):「彼は」の予測に使えるのは直前の状態のみ
Transformer:文全体の文脈(「昨日」「友人」「同じ場所」など)を参照可能

TransformerとCoT:大域的な文脈

TransformerのAttention機構は、系列全体を参照できる。これにより、マルコフ性の制約を超えた、大域的な依存関係を捉えられる。

CoTは、この能力をさらに活用する。中間ステップをコンテキストに追加することで、後続の推論が参照できる情報が増える。

観点HMMCoT (Transformer)
状態空間離散(有限個)連続(高次元ベクトル)
遷移の依存関係局所的(直前のみ)大域的(系列全体)
文脈の利用なし(マルコフ性)全系列を参照可能
表現力限定的豊か

幾何学的な対比

幾何学的に見ると、両者の違いは「状態空間の構造」に現れる。

HMM:状態空間は離散的なグラフ。各状態はノードであり、遷移はエッジ。移動は「ノードからノードへのジャンプ」。

Transformer:状態空間は連続的な多様体。hidden statesは高次元空間内の点であり、推論は「空間内を滑らかに移動する軌跡」(比喩的に)。

txt
HMM(離散グラフ):
    ●───●───●
    │   │   │
    ●───●───●

Transformer(連続空間、比喩):
    ~~~~~~~~
   /        \
  /   ~~~~   \
 /  /      \  \
●──────────────●

ビームサーチの幾何学

貪欲法の限界

最も単純なデコーディング戦略は 貪欲法(greedy decoding) である。各ステップで、最も確率の高いトークンを選択する。

x^t=argmaxxP(x|x1:t1)

しかし、貪欲法は局所最適に陥りやすい。各ステップで最良の選択をしても、全体として最良の系列になるとは限らない。

txt
貪欲法の落とし穴:

ステップ1:選択肢A (確率0.6) vs 選択肢B (確率0.4)
           → Aを選択

ステップ2:Aの後、最良の選択肢の確率 = 0.3
           Bの後、最良の選択肢の確率 = 0.8

全体:A経路 = 0.6 × 0.3 = 0.18
      B経路 = 0.4 × 0.8 = 0.32  ← こちらが良い

ビームサーチ:複数経路の同時探索

ビームサーチは、この問題に対処するために、複数の候補経路を同時に追跡する。

各ステップで、上位 k 個(ビーム幅)の候補を保持し、次のステップに進む。最終的に、最も確率の高い完全な系列を選択する。

txt
ビーム幅 k=2 の例:

ステップ1:[A, B] を保持

ステップ2:A → [A1, A2]
           B → [B1, B2]
           上位2つを選択 → [B1, A1]

ステップ3:B1 → [B1a, B1b]
           A1 → [A1a, A1b]
           上位2つを選択 → [B1a, A1b]

最終:B1a を出力

幾何学的解釈:経路の競争

ビームサーチを幾何学的に解釈すると、複数の経路が表現空間内で競争している姿が見える。

各候補は、異なる経路を辿って「目的地」(答え)に向かう。ビームサーチは、これらの経路を並行して追跡し、「最も良さそうな経路」を選び出す。

txt
表現空間内での経路の競争(比喩):

[質問] ──┬──→ 経路1 ──→ ●
         │              ↘
         ├──→ 経路2 ──→ ● → [答え] ✓
         │              ↗
         └──→ 経路3 ──→ ●

サンプリング戦略

ビームサーチ以外にも、様々なサンプリング戦略がある。

Top-k sampling:上位 k 個のトークンからランダムにサンプリング。多様性と品質のバランスを取る。

Top-p (Nucleus) sampling:累積確率が p を超えるまでのトークン集合からサンプリング。動的にサンプリング範囲を調整。

Temperature sampling:Softmaxの温度パラメータを調整。高温で多様性増加、低温で決定論的に近づく。

戦略特徴用途
貪欲法決定論的、高速翻訳など正確性重視
ビームサーチ複数経路探索翻訳、要約
Top-k確率的、多様創作、対話
Top-p動的範囲調整汎用

Model Collapse:生成データの罠

現象の説明

Model Collapseは、生成モデルが自己生成データで再学習すると、生成の多様性が失われる現象である(Shumailov et al., 2023)。

txt
世代1:モデルM₁を実データで学習
       → 多様な生成が可能

世代2:M₁の生成データでM₂を学習
       → やや多様性が減少

世代3:M₂の生成データでM₃を学習
       → さらに多様性が減少

...

世代N:ほぼ同じような出力しかできない
       → Model Collapse

幾何学的解釈

Model Collapseを幾何学的に解釈すると、データ多様体の縮退として理解できる。

元のデータは、高次元空間内の複雑な多様体(「曲がった面」)上に分布している。世代を重ねるごとに、この多様体が収縮し、最終的には低次元の部分空間(線や点)に縮退する。

txt
世代1:データが多様体全体に分布
      ~~~~~~~~~~~~
     /            \
    /    ●  ●  ●   \
   /  ●    ●    ●   \
  /●    ●     ●    ●\
  ~~~~~~~~~~~~~~~~~~~~

世代N:データが一点に集中
      ~~~~~~~~~~~~
     /            \
    /              \
   /       ●●●      \
  /                  \
  ~~~~~~~~~~~~~~~~~~~~

原因の分析

Model Collapseの原因は、主に以下の2点である。

1. 生成データのバイアス:モデルは「得意な」パターンを生成しやすい。生成データは、元のデータ分布の「モード」(確率が高い領域)に偏る。

2. フィードバックループ:偏ったデータで再学習すると、モデルはさらにその偏りを強化する。世代を重ねるごとに、この効果が累積する。

幾何学的には、「確率密度の高い領域への収縮力」が働いているようなものである。各世代で、分布の「裾」が切り捨てられ、中心部への集中が進む。

対策

Model Collapseを防ぐための対策として、以下が提案されている。

1. 元データの保持:再学習時に、元の実データを一定割合混ぜる。これにより、分布の多様性が維持される。

2. 多様性の明示的な報酬化:Entropy regularizationなど、生成の多様性を促す項を損失関数に追加する。

3. データのフィルタリング:生成データの中から、元の分布に近いものを選別して使用する。

NOTE

実世界での含意: インターネット上のコンテンツに占めるAI生成コンテンツの割合が増加する中、Model Collapseは実際的な懸念となっている。将来のモデルが、AI生成コンテンツを含むデータで学習される場合、この問題に対処する必要がある。

Dimensional Collapse:表現の縮退

NOTE

文献の射程について: Dimensional Collapseに関する代表的な分析(Jing et al., 2022)は、主に対照学習(contrastive learning)における表現を対象としている。LLMの埋め込みや内部表現に同様の現象が起こるかどうかは、学習方法(言語モデリング vs 対照学習)や表現の種類(入力埋め込み、中間層、最終層)によって異なる可能性がある。本節の議論は、表現学習一般に対する幾何学的な直感を与えることを目的としており、LLMに直接当てはまると断定するものではない。

現象の説明

Dimensional Collapse は、表現空間の 有効次元が減少 する現象である。

モデルの埋め込み空間は、例えば4096次元かもしれない。しかし、学習されたデータの表現が、実際にはその一部の次元しか使っていない場合がある。これがDimensional Collapseである。

txt
名目上:4096次元空間
実効上:数十〜数百次元の部分空間に集中

     [使われている次元]
     ████████░░░░░░░░░░░░░░░░░░░░░░
     ↑                            ↑
     有効次元                    名目次元

Model Collapseとの違い

Model CollapseとDimensional Collapseは関連するが、異なる現象である。

観点Model CollapseDimensional Collapse
対象生成の多様性表現の次元
原因自己生成データでの再学習学習ダイナミクス、正則化
結果似たような出力ばかり表現空間の有効次元減少
検出生成サンプルの多様性評価埋め込み行列のSVD

幾何学的解釈

Dimensional Collapseを幾何学的に解釈すると、 高次元空間内での「平坦化」 として理解できる。

データは高次元空間に埋め込まれているが、実際にはその一部の方向にしか「広がり」がない。残りの次元は、ほとんど使われていない。

txt
3次元空間にデータがあるが、実際には2次元平面に集中:

      z

      │    ← この方向にはほとんど広がりがない

      ●───●───●
     /│  /│  /│
    ●─┼─●─┼─●─┼─ y
      │      │
      ●───●───●
     /
    x

有効次元 = 2(x-y平面)

検出方法:特異値分解

Dimensional Collapseの検出には、 特異値分解(SVD) が有効である。

埋め込み行列 ERn×dn 個のデータ、 d 次元)をSVDすると:

E=UΣV

特異値 σ1σ2σd の分布を見ることで、有効次元を推定できる。

健全な場合:特異値が緩やかに減衰し、多くの次元が寄与している。

Dimensional Collapse:特異値が急激に減衰し、少数の次元のみが支配的。

有効ランク

有効次元を定量化する指標として、 有効ランク(effective rank) がある。

effective rank=exp(ipilogpi),pi=σijσj

これは、特異値分布のエントロピーに基づく指標であり、「実質的に何次元を使っているか」を表す。

なぜ問題か

Dimensional Collapseは、以下の問題を引き起こす。

1. 表現力の低下:使える次元が減ると、異なる概念を区別する能力が低下する。

2. 過学習リスク:低次元部分空間に集中すると、学習データへの過適合が起きやすい。

3. 転移の柔軟性低下:特定のタスクに最適化された低次元表現は、他のタスクへの転移が難しくなることがある。

まとめ

概念定義本回での役割
Chain of Thought中間ステップを出力する推論軌跡の「足場」として機能
Test-time compute推論時の計算量増加探索空間のより広範な探索
ビームサーチ複数経路の同時探索経路の競争と選択
Model Collapse生成の多様性低下データ多様体の縮退
Dimensional Collapse有効次元の減少表現空間の平坦化

本回のポイント

推論を「表現空間上の軌跡」として捉える視点は、CoTやTest-time compute scalingの効果を理解するための有用な枠組みを提供する(ただし、これは比喩的な理解であり、数学的に厳密な対応ではない)。

One-shot vs Multi-step:単純な問題は直接経路で解けるが、複雑な問題は中間ステップという「足場」を経由する必要がある。CoTは、連続的な思考過程を離散トークンで「アンカー」しながら進める技術である。

Test-time compute scaling:推論時の計算量を増やす(より長い思考、複数回の試行、探索の拡張)ことで、より多くの経路を探索し、良い解を見つけやすくなる。ただし、効果はタスク依存である。

HMMとの対比:HMMの離散状態・局所遷移に対し、Transformerは連続表現・大域的注意を持つ。これにより、より豊かな推論が可能になる。

Model CollapseとDimensional Collapseは、表現や生成の多様性が失われる現象である。幾何学的には「多様体の縮退」や「空間の平坦化」として理解できる。これらは、持続可能なモデル開発において注意すべき問題である。

「賢さ」= 探索空間の形状を効率的に探索する能力、とも解釈できる。

次回予告

第11回「感覚の統合」では、異なるモダリティの空間をどう接続するかを考える。

これまで、主にテキストの表現空間を議論してきた。しかし、現代のAIシステムは、画像、音声、動画など、複数のモダリティを扱う。これらの異なる「感覚」は、それぞれ異なる多様体上に存在する。マルチモーダルモデルは、これらの多様体をどのように「結婚」させているのか。異種多様体の融合という幾何学的課題を探求する。

実装ノート

有効ランクの計算

コード例: 10_effective_rank.py
python
import numpy as np
import torch


def compute_effective_rank(embeddings, eps=1e-10):
    """埋め込み行列の有効ランクを計算

    有効ランク = exp(entropy of normalized singular values)

    Args:
        embeddings: 埋め込み行列 [n_samples, dim]
        eps: 数値安定性のための小さな値

    Returns:
        effective_rank: 有効ランク(スカラー)
        singular_values: 特異値の配列
    """
    # 中心化(平均を引く)
    embeddings_centered = embeddings - embeddings.mean(dim=0, keepdim=True)

    # SVD
    U, S, Vh = torch.linalg.svd(embeddings_centered, full_matrices=False)

    # 特異値を正規化(確率分布に)
    S_normalized = S / (S.sum() + eps)

    # エントロピー計算(0を避けるためにeps追加)
    entropy = -(S_normalized * torch.log(S_normalized + eps)).sum()

    # 有効ランク = exp(エントロピー)
    effective_rank = torch.exp(entropy)

    return effective_rank.item(), S.numpy()


def analyze_collapse(embeddings, threshold_ratio=0.9):
    """Dimensional Collapseの分析

    Args:
        embeddings: 埋め込み行列 [n_samples, dim]
        threshold_ratio: 累積寄与率の閾値

    Returns:
        analysis: 分析結果の辞書
    """
    eff_rank, singular_values = compute_effective_rank(embeddings)

    # 累積寄与率
    total_variance = (singular_values**2).sum()
    cumulative_ratio = np.cumsum(singular_values**2) / total_variance

    # 閾値を超える最小の次元数
    dims_for_threshold = np.searchsorted(cumulative_ratio, threshold_ratio) + 1

    # 最大特異値の支配度
    dominance_ratio = singular_values[0] / singular_values.sum()

    analysis = {
        "effective_rank": eff_rank,
        "nominal_rank": len(singular_values),
        "rank_ratio": eff_rank / len(singular_values),
        "dims_for_90_percent": dims_for_threshold,
        "top_singular_dominance": dominance_ratio,
        "singular_values": singular_values,
        "cumulative_ratio": cumulative_ratio,
    }

    return analysis


def diagnose_collapse(analysis):
    """Collapseの診断メッセージを生成

    Args:
        analysis: analyze_collapse の出力

    Returns:
        diagnosis: 診断メッセージ
    """
    rank_ratio = analysis["rank_ratio"]
    dominance = analysis["top_singular_dominance"]

    if rank_ratio < 0.1:
        severity = "重度"
    elif rank_ratio < 0.3:
        severity = "中程度"
    elif rank_ratio < 0.5:
        severity = "軽度"
    else:
        severity = "なし"

    diagnosis = f"""
Dimensional Collapse 診断結果:
================================
有効ランク: {analysis["effective_rank"]:.1f} / {analysis["nominal_rank"]} 次元
ランク比率: {rank_ratio:.2%}
90%分散に必要な次元: {analysis["dims_for_90_percent"]}
最大特異値の支配度: {dominance:.2%}

診断: Collapse {severity}
"""

    if severity != "なし":
        diagnosis += """
推奨対策:
- 正則化の見直し(weight decay, dropout)
- 対照学習の導入
- バッチ正規化の確認
"""

    return diagnosis


# 使用例
if __name__ == "__main__":
    # 健全な埋め込み(多次元を使用)
    torch.manual_seed(42)
    healthy_embeddings = torch.randn(1000, 256)

    # Collapseした埋め込み(低次元に集中)
    collapsed_base = torch.randn(1000, 10)
    padding = torch.zeros(1000, 246)
    collapsed_embeddings = torch.cat([collapsed_base, padding], dim=1)
    # 少しノイズを追加
    collapsed_embeddings += 0.01 * torch.randn_like(collapsed_embeddings)

    print("=== 健全な埋め込み ===")
    analysis_healthy = analyze_collapse(healthy_embeddings)
    print(diagnose_collapse(analysis_healthy))

    print("\n=== Collapseした埋め込み ===")
    analysis_collapsed = analyze_collapse(collapsed_embeddings)
    print(diagnose_collapse(analysis_collapsed))

Model Collapseのシミュレーション

コード例: 10_model_collapse_simulation.py
python
"""Model Collapseのシミュレーション

注意: これは概念的なデモであり、実際のLLMでのModel Collapseとは
単純化された類似物である。
"""

import matplotlib.pyplot as plt
import torch
import torch.nn as nn


class SimpleGenerator(nn.Module):
    """シンプルな生成モデル(デモ用)"""

    def __init__(self, latent_dim=2, output_dim=2, hidden_dim=64):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(latent_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, output_dim),
        )

    def forward(self, z):
        return self.net(z)

    def generate(self, n_samples, device="cpu"):
        z = torch.randn(n_samples, 2, device=device)
        return self.forward(z)


def train_generator(model, data, epochs=100, lr=0.01):
    """生成モデルを訓練(MSE損失で簡略化)"""
    optimizer = torch.optim.Adam(model.parameters(), lr=lr)

    for _epoch in range(epochs):
        # ランダムな潜在ベクトル
        z = torch.randn(len(data), 2)
        generated = model(z)

        # 最近傍へのMSE(簡略化された損失)
        # 実際のGANやVAEとは異なる
        loss = ((generated.unsqueeze(1) - data.unsqueeze(0)) ** 2).sum(dim=-1).min(dim=1)[0].mean()

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    return model


def simulate_model_collapse(initial_data, n_generations=5, samples_per_gen=500):
    """Model Collapseをシミュレート

    Args:
        initial_data: 初期の実データ [n_samples, dim]
        n_generations: 世代数
        samples_per_gen: 各世代で生成するサンプル数

    Returns:
        history: 各世代のデータと統計
    """
    history = []
    current_data = initial_data.clone()

    for gen in range(n_generations):
        # 統計を記録
        mean = current_data.mean(dim=0)
        std = current_data.std(dim=0)
        cov = torch.cov(current_data.T)
        eigenvalues = torch.linalg.eigvalsh(cov)

        history.append(
            {
                "generation": gen,
                "data": current_data.clone(),
                "mean": mean.numpy(),
                "std": std.numpy(),
                "eigenvalues": eigenvalues.numpy(),
                "effective_dim": (eigenvalues > 0.01 * eigenvalues.max()).sum().item(),
            }
        )

        # 新しいモデルを作成して訓練
        model = SimpleGenerator()
        model = train_generator(model, current_data)

        # 次世代のデータを生成
        with torch.no_grad():
            current_data = model.generate(samples_per_gen)

    return history


def visualize_collapse(history):
    """Model Collapseの可視化"""
    n_generations = len(history)

    fig, axes = plt.subplots(2, n_generations, figsize=(4 * n_generations, 8))

    for i, gen_data in enumerate(history):
        # 上段:データ分布
        ax1 = axes[0, i]
        data = gen_data["data"].numpy()
        ax1.scatter(data[:, 0], data[:, 1], alpha=0.5, s=10)
        ax1.set_xlim(-4, 4)
        ax1.set_ylim(-4, 4)
        ax1.set_title(f"Generation {gen_data['generation']}")
        ax1.set_aspect("equal")

        # 下段:固有値分布
        ax2 = axes[1, i]
        eigenvalues = gen_data["eigenvalues"]
        ax2.bar(range(len(eigenvalues)), eigenvalues)
        ax2.set_xlabel("Component")
        ax2.set_ylabel("Eigenvalue")
        ax2.set_title(f"Eff. dim: {gen_data['effective_dim']}")

    plt.tight_layout()
    plt.savefig("model_collapse_simulation.png", dpi=150)
    plt.close()

    print("Saved: model_collapse_simulation.png")


# 使用例
if __name__ == "__main__":
    # 初期データ:2つのクラスタを持つ分布
    torch.manual_seed(42)
    n_samples = 500
    cluster1 = torch.randn(n_samples // 2, 2) * 0.5 + torch.tensor([-1.5, 0.0])
    cluster2 = torch.randn(n_samples // 2, 2) * 0.5 + torch.tensor([1.5, 0.0])
    initial_data = torch.cat([cluster1, cluster2], dim=0)

    # シミュレーション実行
    history = simulate_model_collapse(initial_data, n_generations=5)

    # 可視化
    visualize_collapse(history)

    # 統計の表示
    print("\nModel Collapse Simulation Results:")
    print("=" * 50)
    for gen_data in history:
        print(
            f"Generation {gen_data['generation']}: "
            f"std = {gen_data['std'].mean():.3f}, "
            f"effective_dim = {gen_data['effective_dim']}"
        )

CoT軌跡の可視化(概念デモ)

コード例: 10_cot_trajectory_visualization.py
python
"""CoT軌跡の可視化(概念的なデモ)

注意: これは実際のLLMの内部状態を可視化しているわけではない。
CoTの「軌跡」という比喩を直感的に理解するためのデモである。
"""

import matplotlib.pyplot as plt
import numpy as np


def generate_cot_trajectory(start, end, n_steps, noise_level=0.1):
    """CoT軌跡を生成(概念的なシミュレーション)

    Args:
        start: 開始点(質問の埋め込み)
        end: 終了点(答えの埋め込み)
        n_steps: 中間ステップ数
        noise_level: ノイズの強度

    Returns:
        trajectory: 軌跡の点列 [n_steps+2, dim]
    """
    # 直線経路
    t = np.linspace(0, 1, n_steps + 2).reshape(-1, 1)
    linear_path = start + t * (end - start)

    # ノイズを追加(中間ステップのみ)
    noise = np.random.randn(n_steps + 2, len(start)) * noise_level
    noise[0] = 0  # 開始点はノイズなし
    noise[-1] = 0  # 終了点はノイズなし

    trajectory = linear_path + noise

    return trajectory


def generate_difficult_trajectory(start, end, n_steps, detour_strength=1.0):
    """難しい問題の軌跡(迂回が必要)

    Args:
        start: 開始点
        end: 終了点
        n_steps: 中間ステップ数
        detour_strength: 迂回の強さ

    Returns:
        trajectory: 軌跡の点列
    """
    dim = len(start)
    trajectory = [start]

    # 迂回点を生成
    mid_point = (start + end) / 2
    # 直線に垂直な方向に迂回
    direction = end - start
    perpendicular = np.array([-direction[1], direction[0], 0] if dim >= 3 else [-direction[1], direction[0]])
    perpendicular = perpendicular[:dim]
    if np.linalg.norm(perpendicular) > 0:
        perpendicular = perpendicular / np.linalg.norm(perpendicular)
    detour_point = mid_point + detour_strength * perpendicular

    # スプライン的な経路
    for i in range(1, n_steps + 1):
        t = i / (n_steps + 1)
        # 二次ベジェ曲線的な補間
        p = (1 - t) ** 2 * start + 2 * (1 - t) * t * detour_point + t**2 * end
        # 少しノイズを追加
        p += np.random.randn(dim) * 0.05
        trajectory.append(p)

    trajectory.append(end)
    return np.array(trajectory)


def visualize_cot_comparison():
    """One-shot vs CoT の軌跡比較を可視化"""
    np.random.seed(42)

    # 3次元空間での可視化
    fig = plt.figure(figsize=(15, 5))

    # 開始点と終了点
    start = np.array([0.0, 0.0, 0.0])
    end = np.array([3.0, 2.0, 1.0])

    # 1. One-shot(直接経路)
    ax1 = fig.add_subplot(131, projection="3d")
    oneshot = np.array([start, end])
    ax1.plot(oneshot[:, 0], oneshot[:, 1], oneshot[:, 2], "b-", linewidth=2, label="One-shot")
    ax1.scatter(*start, color="green", s=100, marker="o", label="Question")
    ax1.scatter(*end, color="red", s=100, marker="*", label="Answer")
    ax1.set_title("One-shot (Direct Path)")
    ax1.legend()
    ax1.set_xlabel("Dim 1")
    ax1.set_ylabel("Dim 2")
    ax1.set_zlabel("Dim 3")

    # 2. 簡単な問題のCoT(ほぼ直線)
    ax2 = fig.add_subplot(132, projection="3d")
    easy_cot = generate_cot_trajectory(start, end, n_steps=4, noise_level=0.1)
    ax2.plot(easy_cot[:, 0], easy_cot[:, 1], easy_cot[:, 2], "b-", linewidth=2)
    ax2.scatter(easy_cot[1:-1, 0], easy_cot[1:-1, 1], easy_cot[1:-1, 2], color="blue", s=50, marker="o", label="Steps")
    ax2.scatter(*start, color="green", s=100, marker="o", label="Question")
    ax2.scatter(*end, color="red", s=100, marker="*", label="Answer")
    ax2.set_title("Easy Problem CoT")
    ax2.legend()
    ax2.set_xlabel("Dim 1")
    ax2.set_ylabel("Dim 2")
    ax2.set_zlabel("Dim 3")

    # 3. 難しい問題のCoT(迂回が必要)
    ax3 = fig.add_subplot(133, projection="3d")
    hard_cot = generate_difficult_trajectory(start, end, n_steps=6, detour_strength=1.5)
    ax3.plot(hard_cot[:, 0], hard_cot[:, 1], hard_cot[:, 2], "b-", linewidth=2)
    ax3.scatter(hard_cot[1:-1, 0], hard_cot[1:-1, 1], hard_cot[1:-1, 2], color="blue", s=50, marker="o", label="Steps")
    ax3.scatter(*start, color="green", s=100, marker="o", label="Question")
    ax3.scatter(*end, color="red", s=100, marker="*", label="Answer")
    # 直接経路も点線で表示
    ax3.plot(
        [start[0], end[0]],
        [start[1], end[1]],
        [start[2], end[2]],
        "r--",
        linewidth=1,
        alpha=0.5,
        label="Direct (blocked)",
    )
    ax3.set_title("Hard Problem CoT (Detour)")
    ax3.legend()
    ax3.set_xlabel("Dim 1")
    ax3.set_ylabel("Dim 2")
    ax3.set_zlabel("Dim 3")

    plt.tight_layout()
    plt.savefig("cot_trajectory_comparison.png", dpi=150)
    plt.close()

    print("Saved: cot_trajectory_comparison.png")


def visualize_beam_search():
    """ビームサーチの軌跡を可視化"""
    np.random.seed(42)

    fig, ax = plt.subplots(figsize=(10, 8))

    start = np.array([0.0, 0.5])
    end = np.array([5.0, 0.5])

    # 複数の候補経路
    n_beams = 4
    colors = plt.cm.viridis(np.linspace(0, 1, n_beams))

    all_trajectories = []
    scores = []

    for i in range(n_beams):
        # 各経路に異なる迂回を追加
        detour = 0.5 * (i - n_beams / 2)
        traj = []
        for t in np.linspace(0, 1, 10):
            x = start[0] + t * (end[0] - start[0])
            y = start[1] + detour * np.sin(np.pi * t) + 0.1 * np.random.randn()
            traj.append([x, y])
        traj = np.array(traj)
        all_trajectories.append(traj)

        # スコアを計算(終点への近さ + 滑らかさ)
        final_dist = np.linalg.norm(traj[-1] - end)
        smoothness = np.mean(np.linalg.norm(np.diff(traj, axis=0), axis=1))
        score = 1 / (final_dist + 0.5 * smoothness + 0.1)
        scores.append(score)

    # スコアでソート
    sorted_indices = np.argsort(scores)[::-1]

    # 描画
    for rank, idx in enumerate(sorted_indices):
        traj = all_trajectories[idx]
        alpha = 1.0 if rank == 0 else 0.3
        linewidth = 3 if rank == 0 else 1
        label = f"Beam {idx + 1} (score: {scores[idx]:.2f})"
        if rank == 0:
            label += " ✓ Selected"
        ax.plot(traj[:, 0], traj[:, 1], color=colors[idx], alpha=alpha, linewidth=linewidth, label=label)
        ax.scatter(traj[1:-1, 0], traj[1:-1, 1], color=colors[idx], alpha=alpha, s=20)

    ax.scatter(*start, color="green", s=200, marker="o", zorder=5, label="Start")
    ax.scatter(*end, color="red", s=200, marker="*", zorder=5, label="Goal")

    ax.set_xlabel("Position")
    ax.set_ylabel("State")
    ax.set_title("Beam Search: Multiple Path Exploration")
    ax.legend(loc="upper left")
    ax.grid(True, alpha=0.3)

    plt.tight_layout()
    plt.savefig("beam_search_visualization.png", dpi=150)
    plt.close()

    print("Saved: beam_search_visualization.png")


# 実行
if __name__ == "__main__":
    visualize_cot_comparison()
    visualize_beam_search()

ビームサーチの実装

コード例: 10_beam_search.py
python
from dataclasses import dataclass

import torch
import torch.nn.functional as F


@dataclass
class BeamHypothesis:
    """ビームサーチの仮説(候補系列)"""

    tokens: list[int]  # トークン列
    score: float  # 対数確率の累積
    finished: bool = False  # 終了フラグ


def beam_search(
    model,
    start_tokens: torch.Tensor,
    beam_width: int = 4,
    max_length: int = 50,
    eos_token_id: int = 2,
    length_penalty: float = 1.0,
):
    """ビームサーチによるデコーディング

    Args:
        model: 言語モデル(next token確率を返す)
        start_tokens: 開始トークン列 [seq_len]
        beam_width: ビーム幅
        max_length: 最大生成長
        eos_token_id: 終了トークンID
        length_penalty: 長さペナルティ(>1で長い系列を好む)

    Returns:
        best_hypothesis: 最良の仮説
        all_hypotheses: すべての完了した仮説
    """
    device = start_tokens.device

    # 初期仮説
    initial_hyp = BeamHypothesis(tokens=start_tokens.tolist(), score=0.0)
    active_hypotheses = [initial_hyp]
    finished_hypotheses = []

    for _step in range(max_length):
        if not active_hypotheses:
            break

        all_candidates = []

        for hyp in active_hypotheses:
            if hyp.finished:
                finished_hypotheses.append(hyp)
                continue

            # 現在の系列に対する次トークン確率を取得
            input_ids = torch.tensor([hyp.tokens], device=device)

            with torch.no_grad():
                logits = model(input_ids)  # [1, seq_len, vocab_size]
                next_token_logits = logits[0, -1, :]  # [vocab_size]
                log_probs = F.log_softmax(next_token_logits, dim=-1)

            # Top-k の候補を取得
            topk_log_probs, topk_indices = torch.topk(log_probs, beam_width)

            for log_prob, token_id in zip(topk_log_probs.tolist(), topk_indices.tolist(), strict=True):
                new_tokens = hyp.tokens + [token_id]
                new_score = hyp.score + log_prob
                is_finished = token_id == eos_token_id

                new_hyp = BeamHypothesis(tokens=new_tokens, score=new_score, finished=is_finished)
                all_candidates.append(new_hyp)

        # スコアでソートして上位を選択
        # 長さペナルティを適用
        def score_with_penalty(hyp):
            length = len(hyp.tokens)
            return hyp.score / (length**length_penalty)

        all_candidates.sort(key=score_with_penalty, reverse=True)
        active_hypotheses = all_candidates[:beam_width]

        # 完了した仮説を分離
        new_active = []
        for hyp in active_hypotheses:
            if hyp.finished:
                finished_hypotheses.append(hyp)
            else:
                new_active.append(hyp)
        active_hypotheses = new_active

    # 残りの仮説も完了扱いに
    finished_hypotheses.extend(active_hypotheses)

    # 最良の仮説を選択
    if finished_hypotheses:
        best = max(finished_hypotheses, key=lambda h: h.score / (len(h.tokens) ** length_penalty))
    else:
        best = BeamHypothesis(tokens=start_tokens.tolist(), score=float("-inf"))

    return best, finished_hypotheses


# ダミーモデル(デモ用)
class DummyLanguageModel:
    """デモ用のダミー言語モデル"""

    def __init__(self, vocab_size=100):
        self.vocab_size = vocab_size

    def __call__(self, input_ids):
        batch_size, seq_len = input_ids.shape
        # ランダムなロジットを返す
        logits = torch.randn(batch_size, seq_len, self.vocab_size)
        return logits


# 使用例
if __name__ == "__main__":
    model = DummyLanguageModel(vocab_size=100)
    start_tokens = torch.tensor([1])  # 開始トークン

    best, all_hyps = beam_search(model, start_tokens, beam_width=4, max_length=10, eos_token_id=2)

    print(f"Best hypothesis: {best.tokens}")
    print(f"Score: {best.score:.4f}")
    print(f"Total hypotheses: {len(all_hyps)}")

参考文献

Chain of Thought

  • Wei, J., Wang, X., Schuurmans, D., Bosma, M., Ichter, B., Xia, F., Chi, E., Le, Q., & Zhou, D. (2022). Chain-of-Thought Prompting Elicits Reasoning in Large Language Models. NeurIPS 2022. arXiv: arXiv:2201.11903.
    • CoTの原論文。中間ステップを出力することで推論能力が向上。
  • Kojima, T., Gu, S. S., Reid, M., Matsuo, Y., & Iwasawa, Y. (2022). Large Language Models are Zero-Shot Reasoners. NeurIPS 2022. arXiv: arXiv:2205.11916.
    • 「Let's think step by step」というシンプルなプロンプトでもCoT効果が得られる。

推論と探索

  • Yao, S., Yu, D., Zhao, J., Shafran, I., Griffiths, T. L., Cao, Y., & Narasimhan, K. (2023). Tree of Thoughts: Deliberate Problem Solving with Large Language Models. NeurIPS 2023. arXiv: arXiv:2305.10601.
    • 複数の思考経路を木構造で探索。
  • Wang, X., Wei, J., Schuurmans, D., Le, Q., Chi, E., Narang, S., Chowdhery, A., & Zhou, D. (2023). Self-Consistency Improves Chain of Thought Reasoning in Language Models. ICLR 2023. arXiv: arXiv:2203.11171.
    • 複数回のCoTサンプリングと多数決による精度向上。

スケーリング則

  • Kaplan, J., McCandlish, S., Henighan, T., Brown, T. B., Chess, B., Child, R., Gray, S., Radford, A., Wu, J., & Amodei, D. (2020). Scaling Laws for Neural Language Models. arXiv: arXiv:2001.08361.
    • ニューラル言語モデルのスケーリング則。
  • Hoffmann, J., Borgeaud, S., Mensch, A., Buchatskaya, E., Cai, T., Rutherford, E., ... & Sifre, L. (2022). Training Compute-Optimal Large Language Models. NeurIPS 2022. arXiv: arXiv:2203.15556.
    • Chinchillaスケーリング則。パラメータとデータの最適バランス。

Model Collapse

  • Shumailov, I., Shumaylov, Z., Zhao, Y., Gal, Y., Papernot, N., & Anderson, R. (2023). The Curse of Recursion: Training on Generated Data Makes Models Forget. arXiv: arXiv:2305.17493.
    • Model Collapseの分析。自己生成データでの再学習による多様性低下。

Dimensional Collapse

  • Jing, L., Vincent, P., LeCun, Y., & Tian, Y. (2022). Understanding Dimensional Collapse in Contrastive Self-Supervised Learning. ICLR 2022. arXiv: arXiv:2110.09348.
    • 対照学習におけるDimensional Collapseの分析。