サンプリングの物理工学――Temperature, Top-p, Top-kとは何の確率操作か? なぜ最高確率を選び続けるとAIは狂うのか
1. 素朴な疑問:なぜ「確率1位の単語」を選び続けると、AIは壊れるのか?
ChatGPTやClaudeのAPIパラメータ、あるいはローカルLLMの設定画面を開くと、必ず目にする調整ツマミがあります。
{
"temperature": 0.7,
"top_p": 0.9,
"top_k": 40
}
この「Temperature(温度)」というパラメータを 0 に近づけると、AIはまるで几帳面な官僚のように、何度質問しても寸分違わず同じ回答を、極めて論理的かつ硬い言葉で返してきます。
逆に数値を 1.5 や 2.0 に引き上げると、AIは突然泥酔した詩人のように饒舌になり、独創的なメタファーを語り出したかと思えば、最後には意味不明な造語を叫び始めます。
なぜ、コンピュータの計算に「温度」という物理学の言葉が使われているのでしょうか?
そして、立ち止まって考えてみてください。 「AIが最も確からしい次の1単語を計算しているのなら、常に確率が第1位(最大)の単語だけを愚直に選び続ければ、最も賢く正確な文章になるはずではないか?」
実は、常に確率1位の単語を選び続けるアルゴリズム(貪欲法:Greedy Search)を実行すると、現代の超巨大LLMであっても、数行進んだところで同じ言葉を狂ったように反復する「無限ループ」に陥るか、極めて退屈で平坦な文章しか出力できなくなります。
知的な文章を生み出すためには、**「あえて確率1位を捨て、2位や3位の単語を拾い上げる『確率のサイコロ』」**を振らなければなりません。
モデルが算出した生の数字から、いかにして人間らしい自然な言葉を選び取るのか? 統計力学から直輸入された「温度」の物理と、ハルシネーション(嘘)の引き金となる危険なノイズを刈り取るサンプリングの工学を徹底解剖します。
2. 出力層(LM Head)の物理:生の自信度「Logits(ロジット)」の誕生
サンプリングを理解するために、まずはモデルの最も出口にある**「LM Head(言語モデル出力ヘッド)」**のテンソル計算を確認しましょう。
第6話・第7話で解説した数十層に及ぶTransformerブロックを駆け抜けたデータは、最終的に以下の形状(Shape)をした1本のベクトルとして射出されます。
最終隠れ層ベクトル x_last : [Batch, 1, Hidden_Dim]
例: [1, 1, 4096] (直前の単語が持つ、文脈を吸い尽くした4096次元の表現)
この4096次元のベクトルを、辞書にある全単語(語彙数:約32,000〜128,000語)に対するスコアへと変換するのが、出力層の重み行列 $W_{\text{head}}$ です。
$$\text{Logits} = x_{\text{last}} \times W_{\text{head}}$$
$$\underbrace{x_{\text{last}}}{[1 \times 4096]} \times \underbrace{W{\text{head}}}{[4096 \times 100,000]} = \underbrace{\text{Logits}}{[1 \times 100,000]}$$
Logits(ロジット)とは何か?
出力された10万個の数値の配列、これを専門用語で**「Logits(ロジット)」**と呼びます。 Logitsは、まだ確率(0%〜100%)ではありません。 単語ごとに「$+12.4$」「$+8.2$」「$-4.5$」といった、**正から負までバラバラの実数(生の自信度のスコア)**が並んでいるだけの状態です。
この10万個の凸凹した数値を、合計がちょうど「1.0(100%)」になる綺麗な確率の山へと変換し、そこから運命の1単語を釣り上げる道具こそが、サンプリングアルゴリズムなのです。
3. 統計力学からの直輸入:Temperature(温度)とボルツマン分布
ここで、世界中のAIエンジニアが日常的に使っている「Temperature(温度)」の数学的定義を暴きましょう。
Logitsの配列を $z = [z_1, z_2, \dots, z_V]$、温度パラメータを $T$ としたとき、単語 $i$ が選ばれる確率 $P_i$ は以下の修正されたSoftmax関数で計算されます。
$$P_i = \frac{\exp\left(\frac{z_i}{T}\right)}{\sum_{j=1}^{V} \exp\left(\frac{z_j}{T}\right)}$$
数式の構造を見てください。 各単語の生スコア $z_i$ を、Softmaxに通す直前に「温度 $T$」で割り算しているだけです。
150年前の物理学「ボルツマン分布」との完全一致
この数式を見て、物理学科の学生なら誰もが叫びます。 「これは、統計力学の**ボルツマン分布(カノニカル分布)**そのものではないか!」
19世紀、物理学者ルートヴィヒ・ボルツマンは、気体分子が温度 $T$ の環境下でエネルギー状態 $E_i$ を取る確率が以下の式に従うことを導き出しました。
$$P_i \propto \exp\left(-\frac{E_i}{k_B T}\right)$$
- 機械学習のスコア $z_i$ は、物理学における「負のエネルギー(安定度)」に相当します。
- そして、数式の分母にあるパラメータ $T$ は、物理学における**「絶対温度(熱エネルギー)」**と数学的に寸分違わず完全に一致しているのです。
温度 $T$ を変化させたとき、物理的に何が起きるのか?
具体的な数字で体感してみましょう。 2つの単語のLogitsが、それぞれ「$z_1 = 10$」「$z_2 = 8$」だったとします(差はわずか 2 です)。
① 低温の世界($T = 0.2$:絶対零度への冷却)
数値を $T=0.2$ で割ると、スコアはそれぞれ $\frac{10}{0.2} = 50$ と $\frac{8}{0.2} = 40$ になります。 差が「2」から「10」へと5倍も極端に拡大されました。
これを指数関数 $e^x$ に入れると:
- $e^{50} \approx 5.18 \times 10^{21}$
- $e^{40} \approx 2.35 \times 10^{17}$
差は2万倍以上に開き、確率を計算すると $P_1 \approx 99.995%$、$P_2 \approx 0.005%$ となります。 最大値以外の候補が完全に凍りつき、**「常に1位の単語しか選ばれない決定論的マシン(Greedy Search)」**へと収縮するのです。
② 高温の世界($T = 2.0$:熱暴走)
逆に、数値を $T=2.0$ で割ると、スコアは $\frac{10}{2} = 5$ と $\frac{8}{2} = 4$ になります。 差が「2」から「1」へと半分に圧縮されました。
これを指数関数に入れると:
- $e^5 \approx 148.4$
- $e^4 \approx 54.6$
確率は $P_1 \approx 73%$、$P_2 \approx 27%$ となり、2位の単語が当選する確率が跳ね上がります。 さらに温度を $T \to \infty$ に上げると、すべての単語のスコアがゼロに近づき、全語彙が $\frac{1}{100,000}$ の等しい確率を持つ**「ただの無作為な乱数発生器(ホワイトノイズ)」**へと自壊します。
熱力学が「温度の上昇とともに分子の乱雑さ(エントロピー)が増大する」と説くように、LLMもまた、温度パラメータ $T$ によって文章のエントロピー(予測不可能性・自由度)を物理的に直接制御されているのです。
触って試す:温度とサンプリングの体験
🌡️ 温度とサンプリングの体験
モデルが出した6単語のLogits(生スコア)は固定です。温度ボタンを押すと、Softmax( P_i = exp(z_i / T) ÷ Σ exp(z_j / T) )で確率の山がどう変形するかが見えます。「サンプリングする」で、その確率に従ってサイコロを実際に振ります。
まずは温度ボタンを切り替えて、確率の山の形の変化を見てください。
4. 危険な裾野を切り落とす:Top-k と Top-p(核サンプリング)
「ならば、温度を $T=0.7$ くらいにしておけば、すべて解決するのではないか?」
そうはいきません。ここに、自然言語処理の最も恐ろしい罠、**「ロングテール(裾野)の毒」**が潜んでいます。
語彙数は10万語もあります。 どれほど確率分布を綺麗に整えても、辞書の下位には「文脈と1ミリも関係のない単語」や「難解な専門記号」が何万個も並んでいます。
【ロングテールの恐怖】
文脈: 「空が青く澄み渡り、心地よい風が [ ??? ]」
上位候補:
1位: 『吹いている』 (確率 60%)
2位: 『通り抜けた』 (確率 25%)
3位: 『流れる』 (確率 10%)
...
98,542位: 『核融合炉』 (確率 0.00001%)
「確率が 0.00001% なら、無視できるほど小さいから大丈夫だろう」と人間は考えます。 しかし、本1冊、あるいは数千文字の文章を出力するとき、AIは何千回も連続してサイコロを振り続けます。
10万個のゴミ単語の極小確率を合計すると、「確率の裾野(ロングテール)」全体では数%のまとまった確率の塊になってしまいます。 数千回サイコロを振るうちに、どこかで不運にも「0.00001%の地雷(核融合炉)」を引き当ててしまうのです。
1度文脈が狂うと、次のステップではその狂った単語を入力として計算するため、ドミノ倒しのように文章全体が崩壊し、意味不明なハルシネーション(幻覚)の泥沼へと沈んでいきます。
この「危険なロングテール」を物理的に遮断するために生まれたのが、Top-k と Top-p という2つの防壁です。
① Top-kサンプリング(固定数の足切り)
ルールは極めてシンプルです。 「確率が高い順にソートし、上位 $K$ 個(例: 40個)以外の単語の確率を、すべて強制的に『0』に書き換えて捨てる」
残った上位 $K$ 個の中だけで確率を再正規化(合計1.0にし直す)してサイコロを振ります。 これにより、下位99,960個の危険な地雷単語を引く確率は物理的に完全ゼロになります。
しかし、Top-kには**「文脈の柔軟性がない」**という致命的な欠陥がありました。
- 選択肢が1つしかない文脈: 「ワシントンD.C.は、アメリカ合衆国の [ ??? ] である」 $\to$ 答えは「首都」のほぼ一択。上位40個も残すと、30位あたりの不適切な単語まで候補に入ってしまう。
- 選択肢が何百もある文脈: 「彼女の好きな色は [ ??? ] だった」 $\to$ 色の名前は無数にある。上位40個で切ってしまうと、美しい色の名前が不当に切り捨てられてしまう。
② Top-p(Nucleus / 核サンプリング):究極の動的適応
このTop-kの硬直性を打ち破り、2019年にアリ・ホルツマン(Ari Holtzman)らが発表した現代のデファクトスタンダード、それが**「Top-p(核サンプリング:Nucleus Sampling)」**です。
個数を固定するのをやめろ。「確率の総和(累積確率)」で足切りしろ!
- 確率が高い順に単語を並べる。
- 上から順に確率を足し算(累積)していく。
- 累積確率が $p$(例: 0.9 = 90%)を超えた瞬間に、それ以降の単語をすべて切り捨てる!
この「Top-p」の導入によって、モデルは:
- 自信があるときは、自発的に候補を1〜2個にギュッと絞り込んで誤答を防ぎ、
- 自由度が高いときは、候補を何十個にも広げて豊かな語彙を紡ぎ出す、 という知的な適応能力を手に入れたのです。
5. 悪魔のループを断ち切る:Repetition Penalty(繰り返しペナルティ)
サンプリングを語る上で、実務上決して無視できないもう一つのパラメータがあります。 それが**「Repetition Penalty(繰り返しペナルティ)」**や「Frequency / Presence Penalty」です。
なぜAIは、放置すると「〜ということである。ということである。ということである。」と同じフレーズを壊れたレコードのようにリピートし始めるのでしょうか?
理由は、自己回帰のフィードバックループにあります。
一度同じ単語を出力してしまうと、直前の文脈にその単語が存在することになり、モデルのAttention回路は「あ、さっきもこの言葉を使ったな」と過剰に反応して、次のステップでも同じ単語の確率(Logit)をさらに高く見積もってしまうのです。
対処の物理:過去に出現した単語のLogitsを叩き落とす
この悪魔のループを外科手術のように切り裂くのが、Repetition Penaltyです。
$$\text{もし単語 } i \text{ が過去の文脈にすでに出現していたら:}$$
$$z_i^{\text{new}} = \begin{cases} z_i / \text{penalty} & (z_i > 0) \ z_i \times \text{penalty} & (z_i \leq 0) \end{cases} \quad (\text{penalty} > 1.0)$$
過去の生成履歴を監視し、すでに出現した単語のLogitsスコアを機械的に一定比率(例: 1.1〜1.2倍)で引き下げます(ペナルティを与える)。
これにより、モデルが同じフレーズを反復しようとしても、確率の山が人為的に削り取られ、モデルは嫌でも別の言い回しや次の展開を選ばざるを得なくなります。退屈なリピート地獄は、このシンプルなスコア減衰によって完全に封殺されているのです。
6. 完全実装:NumPyで作る「最小のフルサンプリングエンジン」
それでは、ここまでの理論(Logits $\to$ Repetition Penalty $\to$ Temperature $\to$ Top-k $\to$ Top-p $\to$ 確率的抽出)を1本のパイプラインとして完全にコード化しましょう。
PyTorchやTransformersを一切使わず、NumPyの基本関数だけで商用LLMと同一のサンプリング処理を実装します。
ファイル名を min_sampler.py として保存してください。
import numpy as np
def sample_next_token(
logits: np.ndarray,
generated_tokens: list[int] = None,
temperature: float = 1.0,
top_k: int = 0,
top_p: float = 1.0,
repetition_penalty: float = 1.0
) -> int:
"""
商用推論エンジンと同等の完全なサンプリングパイプライン
logits: [Vocab_Size] の1次元実数配列
generated_tokens: これまでに生成されたトークンIDのリスト
"""
# 浮動小数点の精度コピー
logits = logits.astype(np.float64).copy()
# ---------------------------------------------------------
# 1. Repetition Penalty(繰り返しペナルティの適用)
# ---------------------------------------------------------
if repetition_penalty != 1.0 and generated_tokens:
for token_id in set(generated_tokens):
if logits[token_id] > 0:
logits[token_id] /= repetition_penalty
else:
logits[token_id] *= repetition_penalty
# ---------------------------------------------------------
# 2. Temperature(温度スケーリングによる確率密度の変調)
# ---------------------------------------------------------
if temperature <= 1e-5:
# 絶対零度(T -> 0): 完全な貪欲法 (Greedy Search)
return int(np.argmax(logits))
# スコアを温度で割る(ボルツマン分布の物理)
logits = logits / temperature
# ---------------------------------------------------------
# 3. Top-k サンプリング(固定枠によるロングテールの足切り)
# ---------------------------------------------------------
if top_k > 0:
# 上位 top_k 番目の境界スコアを見つける
top_k = min(top_k, len(logits))
# np.argpartition で上位 k 個以外を効率的に特定
indices_to_remove = np.argpartition(logits, -top_k)[:-top_k]
logits[indices_to_remove] = -np.inf
# ---------------------------------------------------------
# 4. Softmax による確率化(数値安定性を考慮)
# ---------------------------------------------------------
# 最大値を引いて exp のオーバーフローを防止
shifted_logits = logits - np.max(logits)
probs = np.exp(shifted_logits)
probs = probs / np.sum(probs)
# ---------------------------------------------------------
# 5. Top-p サンプリング(累積確率による動的足切り:Nucleus)
# ---------------------------------------------------------
if top_p < 1.0:
# 確率が高い順にソートしたインデックスを取得
sorted_indices = np.argsort(probs)[::-1]
sorted_probs = probs[sorted_indices]
# 累積確率の計算
cumulative_probs = np.cumsum(sorted_probs)
# 累積確率が top_p を超えたインデックスを特定
# 最初の1単語がすでに top_p を超えていても、少なくとも1単語は残す
sorted_indices_to_remove = cumulative_probs > top_p
sorted_indices_to_remove[0] = False # 1位は絶対に保護
# 切り捨てるインデックスの確率をゼロにする
indices_to_remove = sorted_indices[sorted_indices_to_remove]
probs[indices_to_remove] = 0.0
# 確率の再正規化(合計を 1.0 に戻す)
probs_sum = np.sum(probs)
if probs_sum > 0:
probs = probs / probs_sum
else:
# 万が一ゼロサムになった場合は最大確率の単語を返す
return int(sorted_indices[0])
# ---------------------------------------------------------
# 6. 多項分布からの確率的サンプリング(サイコロを振る!)
# ---------------------------------------------------------
next_token = np.random.choice(len(probs), p=probs)
return int(next_token)
# -------------------------------------------------------------
# 実験:異なる温度とパラメータでサイコロを10,000回振ってみる
# -------------------------------------------------------------
if __name__ == "__main__":
vocab = ["東京", "大阪", "京都", "福岡", "札幌", "謎の呪文"]
# モデルが算出した仮想のLogits(生スコア)
# 「東京」が本命だが、「大阪」「京都」も有力
mock_logits = np.array([5.0, 4.0, 3.5, 2.0, 1.0, -2.0])
print("=== 各単語の生スコア (Logits) ===")
for w, logit in zip(vocab, mock_logits):
print(f" {w:<6}: Logit = {logit:5.1f}")
print()
N_TRIALS = 10000
np.random.seed(42)
def run_experiment(title, **kwargs):
counts = {w: 0 for w in vocab}
for _ in range(N_TRIALS):
token_id = sample_next_token(mock_logits, **kwargs)
counts[vocab[token_id]] += 1
print(f"--- 【{title}】の抽選結果 (試行 {N_TRIALS} 回) ---")
for w in vocab:
pct = (counts[w] / N_TRIALS) * 100
bar = "█" * int(pct / 2)
print(f" {w:<6}: {pct:5.1f}% {bar}")
print()
# 実験1: 極低温(T = 0.1:決定論)
run_experiment("極低温 (Temperature = 0.1)", temperature=0.1)
# 実験2: 標準温度(T = 0.8:バランス)
run_experiment("標準温度 (Temperature = 0.8)", temperature=0.8)
# 実験3: 超高温(T = 2.0:カオス)
run_experiment("超高温 (Temperature = 2.0)", temperature=2.0)
# 実験4: Top-p = 0.8(上位80%の核サンプリング:下位の完全排除)
run_experiment("Top-p = 0.8 (危険な裾野の遮断)", temperature=1.0, top_p=0.8)
実行結果
=== 各単語の生スコア (Logits) ===
東京 : Logit = 5.0
大阪 : Logit = 4.0
京都 : Logit = 3.5
福岡 : Logit = 2.0
札幌 : Logit = 1.0
謎の呪文 : Logit = -2.0
--- 【極低温 (Temperature = 0.1)】の抽選結果 (試行 10000 回) ---
東京 : 100.0% ██████████████████████████████████████████████████
大阪 : 0.0%
京都 : 0.0%
福岡 : 0.0%
札幌 : 0.0%
謎の呪文 : 0.0%
--- 【標準温度 (Temperature = 0.8)】の抽選結果 (試行 10000 回) ---
東京 : 62.1% ███████████████████████████████
大阪 : 21.2% ██████████
京都 : 11.5% █████
福岡 : 3.6% █
札幌 : 1.3%
謎の呪文 : 0.0%
--- 【超高温 (Temperature = 2.0)】の抽選結果 (試行 10000 回) ---
東京 : 38.7% ███████████████████
大阪 : 23.8% ███████████
京都 : 18.6% █████████
福岡 : 9.2% ████
札幌 : 5.6% ██
謎の呪文 : 4.1% █
--- 【Top-p = 0.8 (危険な裾野の遮断)】の抽選結果 (試行 10000 回) ---
東京 : 68.2% ██████████████████████████████████
大阪 : 31.8% ███████████████
京都 : 0.0%
福岡 : 0.0%
札幌 : 0.0%
謎の呪文 : 0.0%
実行結果が暴く確率操作のリアル
- 極低温(T = 0.1): 10,000回サイコロを振って、10,000回すべて(100.0%)「東京」が選ばれました。 1位以外の選択肢が完全に凍結されています。
- 標準温度(T = 0.8): 「東京」が約62%で本命を維持しつつ、「大阪」が21%、「京都」が11%と、文脈に応じた健全な揺らぎが生まれています。
- 超高温(T = 2.0): 確率が均一化し、スコアが圧倒的に低かったはずの「謎の呪文」が 4.1%(400回以上!) も当選してしまっています。これが、温度を上げすぎたAIが支離滅裂なハルシネーションを吐き出す物理的瞬間です。
- Top-p = 0.8:
「東京(約68%)」と「大阪(約32%)」を足した時点で累積確率が100%に達するため、3位の「京都」以下が完全に
0.0%としてバッサリ切り落とされました。 下位のノイズが完全に遮断されていることが一目瞭然です。
7. テンソルの視点:円環の完成(閉環)
サンプリングによって次のトークンID(例: 3797 ('cat'))が1つ決定されたその瞬間、LLMの推論パイプラインは**「完全な円環(ループ)」**を閉じます。
第2話で文字をトークンに刻み、 第3話で数千次元の空間座標へ埋め込み、 第4・5話でAttentionの視線を交わし、 第6話で残差ストリームの大動脈を抜け、 第7話でFFNの知識金庫から事実を引き出し、 第8話でRoPEの時計の針を回し、 第9話でKVキャッシュに歴史を刻み込み、 そして第10話で、統計力学のサイコロを振って次の1ピースを確定させた。
そして、その確定した1ピースが、再び第3話の埋め込み層へと飛び込んでいく。 この壮大で美しいテンソルの円環運動が、秒間何十回という猛スピードで回転し続けることによって、私たちの目の前に「生きた知能の言葉」が立ち現れているのです。
8. まとめ:知能とは「決定論と偶然性の調停」である
サンプリングの物理工学を総括すると、以下のようになります。
- Greedy Searchの限界: 常に確率1位を選び続けると、言語の持つ確率的な幅を失い、反復ループや硬直化に陥って文章が死ぬ。
- 温度(Temperature)の物理: 統計力学のボルツマン分布に基づき、Softmaxの直前でスコアを $T$ で割ることで、確率分布のエントロピー(乱雑さ・創造性)を連続的に制御する。
- Top-p(核サンプリング)の英断: 個数固定のTop-kの弱点を克服し、累積確率90%などの閾値で動的に足切りすることで、文脈の確信度に応じた柔軟なノイズ除去を実現した。
- Repetition Penaltyの解剖: 過去に出現したトークンのLogitsを人為的に減衰させることで、自己回帰ループ特有の「同じ言葉の連呼」を物理的に遮断する。
これで、Transformerという知能の「論理」と「推論」の数理は、完全に解き明かされました。
しかし、理論がどれほど美しくても、現実の半導体には過酷な制約があります。 「70B(700億パラメータ)のモデルは、140GBもの重みデータがある。なぜ、それがわずか24GBの家庭用グラフィックボード(RTX 4090)や、16GBメモリのMacBookでサクサク動いているのか?」
その魔法を可能にした、重みの圧縮技術――**「量子化(Quantization)」の極限工学。 次回、「第11話:量子化(Quantization)の物理工学――なぜ70Bモデルが家庭用PCで動くのか?」**へと進みましょう。
📝 3行で頭に入る『試験・実務の要点まとめ』
- 温度(Temperature)の本質: ボルツマン分布 $\exp(z_i / T)$ に基づき、Logitsを $T$ で除算することで、$T < 1.0$ では最大確率を鋭利化させ(決定論化)、$T > 1.0$ では分布を平坦化(ランダム化)させる。
- Top-p(Nucleus)の動的切断: 確率上位から累積和を計算し閾値 $p$(例: 0.9)で裾野を足切りするため、モデルの確信度が高い時は少数、不確実な時は多数へと候補枠が文脈適応的に自動伸縮する。
- 閉環(自己回帰ループ): サンプリングされたトークンIDは即座に次ステップの入力ベクトルとして埋め込み層へフィードバックされ、KVキャッシュを伸長させながら次のLogits計算へと円環を描く。