第14章 出力層:ロジット、Softmax、そして温度パラメータ

こんにちは。ゆうせいです。

第13章までで、Transformerブロックの中身がすべて揃いました。GPT-2(small)では、そのブロックが12回繰り返されます。第14章では、12個のブロックを通過した後、最終的に「次に来るトークンは何か」という予測が作られる部分を扱います。Transformer Explainerの画面右端で、単語の候補が確率とともに並んでいる、あの表示を作る処理です。そして、画面上部にある温度のスライダーが、この確率をどう変えるのかも解説します。第2章のトークン化から始まった処理が、この章で一つの予測として結実します。連載の最終章です。

出力層の処理の流れ

12個のブロックを通過した後、次の三段階の処理が行われます。

第一に、最終的なレイヤー正規化を適用します。

第二に、768次元のベクトルを、語彙数である50257次元に変換します。この結果をロジットと呼びます。

第三に、Softmax関数を適用して、確率に変換します。

このうち、第一の処理は第12章で説明したとおりですので、第二以降を詳しく見ていきます。

最後のトークンだけを使う

出力層に入る前に、重要な点を確認します。

12個のブロックを通過した時点では、すべてのトークンに対応するベクトルが存在します。5トークンの文章であれば、5本のベクトルです。

しかし、次のトークンを予測する際に使うのは、最後のトークンに対応するベクトル1本だけです。

なぜでしょうか。

第8章で扱った因果マスクを思い出してください。各トークンは、自分より前のトークンだけを参照できます。

したがって、最後のトークンに対応するベクトルだけが、文章全体の情報を含んでいます。それより前のトークンのベクトルは、途中までの情報しか持っていません。

「The cat sat on the」という入力に対して、次に来る単語を予測したいのであれば、「the」に対応するベクトルを使う必要があります。

学習時には全トークンを使う

ただし、これは推論時の話です。学習時には、すべてのトークンのベクトルが使われます。

「The cat sat on the mat」という文章があれば、次の五つの予測課題を、同時に学習できます。

「The」の次は「cat」である。

「The cat」の次は「sat」である。

「The cat sat」の次は「on」である。

「The cat sat on」の次は「the」である。

「The cat sat on the」の次は「mat」である。

因果マスクがあるおかげで、一度の計算で、これら五つの課題をまとめて学習できるのです。1トークンずつ処理していたのでは、これほど効率よく学習できません。

因果マスクは制約であると同時に、学習効率を高める仕組みでもある、と理解してください。

ロジットの計算

768次元のベクトルを、語彙数である50257次元に変換します。

ロジット = h \times W_{出力}

hは最終的な768次元のベクトル、W_{出力} は768行50257列の行列です。

結果として、50257個の数値が得られます。各数値が、語彙の各トークンに対応しています。

この段階の値をロジットと呼びます。第9章で扱ったSoftmaxの入力にあたる、確率になる前の生の数値です。値の範囲に制限はなく、マイナスの値も含まれます。

重み共有(Weight Tying)

第3章の最後で予告した内容を、ここで説明します。

GPT-2では、この出力用の行列 W_{出力} として、入力の埋め込み行列と同じものが使われています。正確には、埋め込み行列の転置が使われます。

埋め込み行列は50257行768列でした。これを転置すると768行50257列になり、出力層で必要な形と一致します。

この工夫を、重み共有(Weight Tying)と呼びます。

なぜ共有できるのか

直感的な説明は、次の通りです。

入力の埋め込みでは、「このトークンは、意味空間のこの位置にある」という対応が記録されています。

出力では、「意味空間のこの位置にあるベクトルは、どのトークンに近いか」を知りたいわけです。

同じ対応表を、逆向きに使っているだけだと考えれば、共有が自然に思えます。

実際の計算では、最終ベクトルと、各トークンの埋め込みベクトルとの内積を取っていることになります。第6章で確認したとおり、内積は類似度を表します。最終ベクトルに最も近い埋め込みを持つトークンが、最も高いロジットを得るのです。

共有の効果

重み共有には、二つの効果があります。

一つ目は、パラメータ数の削減です。共有しなければ、出力層に約3860万個の追加パラメータが必要になります。全体の約1億2400万個に対して、大きな削減です。

二つ目は、性能の向上です。入力と出力で同じ表現を使うことで、学習が効率化されることが報告されています。

全体のパラメータ内訳

ここで、GPT-2(small)のパラメータ数を整理します。

構成要素パラメータ数の目安割合
トークン埋め込み(出力層と共有)約3860万個約31パーセント
位置埋め込み約79万個約1パーセント
注意機構(12ブロック分)約2831万個約23パーセント
MLP層(12ブロック分)約5662万個約46パーセント
レイヤー正規化約4万個約0.03パーセント

バイアス項を省略した概算ですが、おおよその内訳が把握できます。

Softmaxによる確率への変換

50257個のロジットに対して、Softmax関数を適用します。

確率_i = \frac{e^{ロジット_i}}{\sum_j e^{ロジット_j}}

計算方法は、第9章で扱ったものと同じです。

結果として、50257個の確率が得られます。すべて0から1の範囲にあり、合計は1になります。

「次のトークンが『mat』である確率は32パーセント、『floor』である確率は8パーセント」といった形です。

第9章のSoftmaxとの違い

第9章では、注意の重みを計算するためにSoftmaxを使いました。この章では、出力の確率を計算するために使います。

適用箇所は異なりますが、関数としてはまったく同じものです。

違いは、対象の数です。注意の重みでは、トークン数(多くても1024)が対象でした。出力層では、語彙数である50257個が対象です。

温度パラメータ

ここが、Transformer Explainerで最も体感しやすい部分です。

温度(Temperature)とは、Softmaxに入力する前に、ロジットを割る値です。

確率_i = \frac{e^{ロジット_i / T}}{\sum_j e^{ロジット_j / T}}

Tが温度です。

温度を低くすると、最も可能性の高い予測を選ぶことに近づきます。より創造的な答えが欲しい場合には、温度を上げます。

温度が確率分布に与える影響

三つのトークンのロジットが、次の値だとします。

[4.0, 2.0, 0.0]

温度を変えて、確率を計算してみます。

温度が1.0の場合(そのまま)。

e^{4.0} \approx 54.6e^{2.0} \approx 7.39e^{0.0} = 1.0

合計は約62.99です。

確率は、[0.867, 0.117, 0.016] となります。

温度が0.5の場合(ロジットを0.5で割る、つまり2倍にする)。

[8.0, 4.0, 0.0]

e^{8.0} \approx 2981e^{4.0} \approx 54.6e^{0.0} = 1.0

合計は約3036.6です。

確率は、[0.982, 0.018, 0.0003] となります。

温度が2.0の場合(ロジットを2で割る)。

[2.0, 1.0, 0.0]

e^{2.0} \approx 7.39e^{1.0} \approx 2.72e^{0.0} = 1.0

合計は約11.11です。

確率は、[0.665, 0.245, 0.090] となります。

結果の整理

温度1位の確率2位の確率3位の確率分布の性質
0.50.9820.0180.000尖っている
1.00.8670.1170.016標準
2.00.6650.2450.090なだらか

温度が低いほど、1位に確率が集中します。温度が高いほど、確率が分散します。

温度と生成される文章の関係

温度を低く設定すると、常に最も確率の高いトークンが選ばれやすくなります。

生成される文章は、安定し、破綻が少なくなります。一方で、同じ入力に対して常に似た出力になり、単調になります。事実に基づく回答や、要約など、正確さが求められる用途に向いています。

温度を高く設定すると、確率の低いトークンも選ばれやすくなります。

生成される文章は、多様性が増し、予想外の表現が現れます。一方で、文脈から外れた不自然な文章になるリスクも高まります。創作や、アイデア出しなど、発想の幅が求められる用途に向いています。

温度0の扱い

温度を0にすると、式の上ではゼロ除算が発生します。

実装上は、温度0は特別扱いされ、単に最も確率の高いトークンを選ぶ処理に置き換えられます。この選び方を、貪欲法(Greedy Decoding)と呼びます。

同じ入力に対して、常に同じ出力が返ることになります。再現性が必要な場面で使われます。

第7章のスケーリングとの関係

第7章で扱ったスケーリングと、この温度パラメータは、構造がまったく同じです。

どちらも、Softmaxに入力する前に、定数で割る処理です。

違いは二点です。

スケーリングは、\sqrt{d_k} という固定値で割ります。モデルの設計として組み込まれており、利用者が変更するものではありません。

温度は、利用者が推論時に自由に設定できます。生成の性質を、その場で調整するためのものです。

同じ数学的操作が、モデル内部の安定化と、利用者による出力制御という、二つの異なる目的に使われている、という点は興味深い対応です。

トークンを選ぶ方法

確率が得られた後、実際にどのトークンを選ぶかには、複数の方法があります。

貪欲法は、最も確率の高いトークンを常に選ぶ方法です。安定しますが、同じ表現の繰り返しに陥りやすいという欠点があります。

サンプリングは、確率に従ってランダムに選ぶ方法です。確率が32パーセントのトークンは、32パーセントの割合で選ばれます。多様性が生まれますが、確率の非常に低い不適切なトークンが選ばれる可能性もあります。

Top-kサンプリングは、確率の高い上位k個だけを候補とし、その中からサンプリングする方法です。極端に不適切なトークンを排除できます。

Top-p(核)サンプリングは、確率の合計が一定値(たとえば0.9)に達するまでの候補だけを対象とする方法です。候補の数が、状況に応じて変動します。

温度は、これらの方法と組み合わせて使われます。温度で分布の形を調整し、選び方でどこから選ぶかを決める、という役割分担です。

文章生成の繰り返し

第1章で説明したとおり、1回の計算で予測されるのは、次の1トークンだけです。

文章を生成する際は、次の手順が繰り返されます。

第一に、現在の入力から、次のトークンを予測します。

第二に、予測されたトークンを、入力の末尾に追加します。

第三に、更新された入力で、また次のトークンを予測します。

この繰り返しにより、文章が1トークンずつ伸びていきます。

終了の条件は、文章の終わりを表す特殊トークンが生成されるか、あらかじめ設定した最大長に達するか、のいずれかです。

KVキャッシュによる高速化

第8章で触れたKVキャッシュが、ここで効いてきます。

新しいトークンを追加しても、それより前のトークンのKeyとValueは変化しません。因果マスクによって、後ろのトークンが前のトークンに影響しないためです。

したがって、過去のKeyとValueを保存しておけば、新しいトークンの分だけを計算すればよいことになります。

この最適化により、長い文章の生成が、現実的な速度で行えるようになります。

Transformer Explainerで観察できること

Transformer Explainerの出力部分では、次の点を確認してみてください。

一つ目は、確率の高い順に、候補となるトークンが並んでいるという点です。

二つ目は、温度のスライダーを動かすと、確率の分布が即座に変化するという点です。温度を下げると1位に集中し、上げると分散する様子が、リアルタイムで観察できます。

三つ目は、候補として表示されているのは上位の数個だけだという点です。実際には、50257個すべてに確率が割り当てられています。

四つ目は、入力する文章を変えると、候補の顔ぶれが大きく変わるという点です。文脈が予測に反映されていることが、直接確認できます。

演習

演習1:温度による確率の変化を計算する

次のロジットについて、温度が0.5、1.0、2.0の場合の確率を、それぞれ計算してください。

[2.0, 1.0]

必要な指数関数の値は、電卓または次の近似値を使ってください。

e^{0.5} \approx 1.649e^{1.0} \approx 2.718e^{2.0} \approx 7.389e^{4.0} \approx 54.598

演習2:温度の効果を確認する

import torch
import torch.nn.functional as F

logits = torch.tensor([4.0, 2.0, 1.0, 0.0, -1.0])
labels = ["mat", "floor", "table", "chair", "sky"]

print(f"{'温度':>6} | " + " | ".join(f"{l:>8}" for l in labels))
print("-" * 60)

for temp in [0.2, 0.5, 1.0, 1.5, 2.0, 5.0]:
    probs = F.softmax(logits / temp, dim=0)
    row = " | ".join(f"{p:>8.4f}" for p in probs.tolist())
    print(f"{temp:>6.1f} | {row}")

# エントロピー(分布の広がり)を計算
print("\n=== 分布の広がり(エントロピー)===")
for temp in [0.2, 0.5, 1.0, 2.0, 5.0]:
    probs = F.softmax(logits / temp, dim=0)
    entropy = -(probs * torch.log(probs + 1e-10)).sum()
    print(f"温度 {temp:>4.1f}: エントロピー {entropy.item():.4f}")

演習3:GPT-2で実際に予測する

from transformers import GPT2Tokenizer, GPT2LMHeadModel
import torch
import torch.nn.functional as F

tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
model = GPT2LMHeadModel.from_pretrained("gpt2")
model.eval()

text = "The cat sat on the"
inputs = tokenizer(text, return_tensors="pt")

with torch.no_grad():
    outputs = model(**inputs)
    logits = outputs.logits

print(f"入力: {text}")
print(f"ロジットの形: {logits.shape}")

# 最後のトークンのロジットだけを使う
last_logits = logits[0, -1, :]
print(f"最後のトークンのロジットの形: {last_logits.shape}")

# 温度を変えて上位5候補を表示
for temp in [0.5, 1.0, 2.0]:
    probs = F.softmax(last_logits / temp, dim=0)
    top_probs, top_ids = torch.topk(probs, 5)
    print(f"\n=== 温度 {temp} ===")
    for p, i in zip(top_probs, top_ids):
        token = tokenizer.decode([i])
        print(f"  {token!r:>12}: {p.item():.4f}")

# 重み共有を確認
print("\n=== 重み共有の確認 ===")
print(f"埋め込み行列の形: {model.transformer.wte.weight.shape}")
print(f"出力層の重みの形: {model.lm_head.weight.shape}")
print(f"同一のオブジェクトか: {model.lm_head.weight is model.transformer.wte.weight}")

演習の解答例と解説

演習1の解答は、次の通りです。

温度が1.0の場合。

\frac{7.389}{7.389 + 2.718} = \frac{7.389}{10.107} \approx 0.731

確率は、[0.731, 0.269] です。

温度が0.5の場合。ロジットは [4.0, 2.0] になります。

\frac{54.598}{54.598 + 7.389} = \frac{54.598}{61.987} \approx 0.881

確率は、[0.881, 0.119] です。

温度が2.0の場合。ロジットは [1.0, 0.5] になります。

\frac{2.718}{2.718 + 1.649} = \frac{2.718}{4.367} \approx 0.622

確率は、[0.622, 0.378] です。

温度が下がるほど1位の確率が高まり、上がるほど両者の差が縮まることが、数値で確認できます。

演習2では、温度0.2でほぼ1位に確率が集中し、温度5.0でほぼ均等に近づくことが観察できます。エントロピーは、分布の広がりを表す指標であり、温度が高いほど大きくなります。

演習3では、実際のGPT-2の予測を確認できます。温度を変えると、上位候補の確率がどう変わるかを観察してください。また、重み共有により、埋め込み行列と出力層の重みが同一のオブジェクトであることも確認できます。

講師向けの補足

最終章では、必ず第1章からの流れを振り返る時間を設けてください。

トークン化から始まり、埋め込み、位置エンコーディング、12個のブロック、そして出力層。この一連の流れを、一枚の図で示し、各章がどこに対応していたかを確認すると、断片的だった知識が一つにつながります。

温度パラメータについては、Transformer Explainerのスライダーを実際に動かしてもらう時間を、必ず取ってください。この連載で扱った内容の中で、最も直感的に変化が体感できる部分です。

また、第7章のスケーリングと温度が同じ構造であることに触れると、受講者から驚きの反応が得られます。異なる文脈で学んだ二つのことが、実は同じ仕組みだったという発見は、理解の定着に効果的です。

理解度を確認する問い

第14章の内容を、次の問いで確認してみてください。

推論時に、最後のトークンに対応するベクトルだけを使う理由は何でしょうか。

重み共有(Weight Tying)とは何でしょうか。また、それにどのような効果があるでしょうか。

温度を下げると、生成される文章はどのように変わるでしょうか。また、その理由をSoftmaxの計算から説明してみてください。

第7章のスケーリングと、この章の温度パラメータの、共通点と相違点は何でしょうか。

連載全体のまとめ

第2章から第14章まで、Transformer Explainerで可視化されている要素技術を、順に見てきました。全体を振り返ります。

入力された文章は、まずトークンに分割され、それぞれに番号が割り当てられます。その番号は、埋め込み行列を通じて768次元のベクトルに変換され、位置情報が加算されます。

そのベクトルは、12個のTransformerブロックを順に通過します。各ブロックでは、まずレイヤー正規化を経て、マルチヘッド自己注意によって他のトークンから情報が集められます。その結果は残差接続によって元のベクトルに加算されます。続いて、再びレイヤー正規化を経て、MLP層によってトークン内部の情報が加工され、これも残差接続によって加算されます。

自己注意の内部では、Query、Key、Valueが生成され、QueryとKeyの内積によってスコアが計算されます。そのスコアは \sqrt{d_k} でスケーリングされ、因果マスクによって未来への参照が遮断され、Softmaxによって確率に変換されます。その確率を重みとして、Valueが合成されます。この処理が12個のヘッドで並列に行われ、結果が結合されて出力射影を通ります。

12個のブロックを通過した後、最後のトークンに対応するベクトルが、最終的なレイヤー正規化を経て、埋め込み行列と共有された重みによって50257次元のロジットに変換されます。温度で調整されたロジットにSoftmaxを適用することで、次のトークンの確率が得られます。

この予測が繰り返されることで、文章が生成されます。

最後に

この連載で扱ったのは、GPT-2(small)という、1億2400万個のパラメータを持つモデルです。現在の大規模言語モデルと比べれば、規模は小さなものです。

しかし、ここで学んだ構成要素の多くは、最新のモデルにもそのまま引き継がれています。位置エンコーディングの方式が変わり、活性化関数が変わり、正規化の細部が変わっていても、自己注意と残差接続とMLP層を積み重ねるという骨格は共通しています。

新しいモデルの論文や解説を読む際には、「GPT-2のどこが、どう変わっているのか」という視点で読むことができます。土台があれば、差分だけを追えばよいのです。

次のステップとして、Transformer Explainerで、これまで学んだ各段階を、あらためて順に追ってみてください。第1章で開いたときには意味不明だった数値の並びが、それぞれ何を表しているのか、説明できるようになっているはずです。そのうえで、Andrej Karpathy氏のnanoGPTのように、実際に動くコードを読んでみることをお勧めします。本連載で扱った内容が、驚くほど短いコードで実装されていることに気づくはずです。

長い連載におつきあいいただき、ありがとうございました。

セイ・コンサルティング・グループでは新人エンジニア研修のアシスタント講師を募集しています。

投稿者プロフィール

山崎講師
山崎講師代表取締役
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
海外放浪の末、2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。

学生時代は趣味と実益を兼ねてリゾートバイトにいそしむ。長野県白馬村に始まり、志賀高原でのスキーインストラクター、沖縄石垣島、北海道トマム。高じてオーストラリアのゴールドコーストでツアーガイドなど。現在は野菜作りにはまっている。