(microgpt のユーティリティ関数)= # microgpt のユーティリティ関数 ## この章で学ぶこと - `linear` / `softmax` / `rmsnorm` の役割と実装の読み方 - {numref}`microgpt のモデル本体(gpt)` の `gpt` から何度も呼ばれる部品としての位置づけ ### `linear` 入力ベクトル `x` と重み行列 `w` から、行ごとに内積(線形変換)を計算します。出力の各成分は `Value` の演算でつながります。 ```{literalinclude} ../../../src/part3/microgpt.py :language: python :lines: 129-131 :lineno-match: ``` **行ごとの意味** - **L129** `def linear(x, w):` — 入力ベクトル `x` と重み `w`(行が出力次元、列が入力次元のリストのリスト)を受け取る。 - **L130** ドキュストリング — `y = Wx` の対応関係を説明。 - **L131** 戻り値 — 各行 `wo` について `sum(wi * xi)` を `Value` のまま計算し、出力ベクトルを返す。 **最小例で `Value` のつながりを追う** 入力が 2 次元で、出力が 1 ニューロンだけのとき、重みは 1 行 2 列の行列です。たとえば `x = [Value(1.0), Value(2.0)]`、`w = [[Value(3.0), Value(4.0)]]` とすると、`linear(x, w)` の唯一の成分は ```text y[0] = 3×1 + 4×2 = 11 ``` となります(実際の `.data` はこの値)。式の中身はすべて `Value` の `*` と `+` なので、`y[0]` は **`x` と `w` の各要素を子に持つ計算グラフの根**になります。損失から `backward()` すると、この内積に関わる重み・入力方向へ勾配が流れます。次元を増やしても「行ごとに同じ内積」が並ぶだけです。 ### `softmax` ここでいう **logits(ロジット)** は、「まだ確率になっていない、各候補に対する**生のスコア**」の列です。語彙サイズが N なら長さ N のベクトルで、成分が大きいほど「そのトークンらしい」という強さを表します。スコアは負でもよく、**足して 1 になる必要もありません**。学習では `lm_head` などの線形層が、隠れ状態からこのスコア列を計算します。`softmax` は、その logits を **0 以上で、すべて足すと 1 になる確率**に変換する役割です。 この関数は logits のベクトルを確率分布に変換します。最大値を引いてから指数するので、オーバーフローを抑えた実装です。 ```{literalinclude} ../../../src/part3/microgpt.py :language: python :lines: 133-138 :lineno-match: ``` **行ごとの意味** - **L133** `def softmax(logits):` — `Value` のリストを受け取る。 - **L134** ドキュストリング — logits を確率化し、`max` で引いてから `exp` する方針。 - **L135** `max_val = max(val.data for val in logits)` — 全 logits の最大(スカラー)を取る。 - **L136** `exps = [...]` — 各 logit から `max_val` を引いてから `exp`(`Value` の演算)。 - **L137** `total = sum(exps)` — 正規化の分母。 - **L138** 各指数を `total` で割り、和が 1 になる確率ベクトルを返す。 **最小例(`[Value(1.0), Value(2.0)]`):** ```text exps[0] ∝ exp(1 - 2) = exp(-1), exps[1] ∝ exp(2 - 2) = exp(0) = 1 total = exps[0] + exps[1] 出力[i] = exps[i] / total (和は 1) ``` `2` の方が大きいので確率もより大きくなります。`exp` / `+` / `/` は `Value` の演算なので確率ベクトルが損失へつながります。 ### `rmsnorm` ベクトルの RMS(二乗平均の平方根)でスケールし、ベクトルの大きさを揃えます。同じ「層の中でベクトルを正規化する」系に **LayerNorm** がありますが、本コードの RMSNorm はそこから**成分の平均を引く計算を省いた**簡略版です。LayerNorm そのものの説明は、**この項の末尾の注記**にまとめています。 ```{literalinclude} ../../../src/part3/microgpt.py :language: python :lines: 140-144 :lineno-match: ``` **行ごとの意味** - **L140** `def rmsnorm(x):` — `Value` のリスト(ベクトル)を受け取る。 - **L141** ドキュストリング — RMS 正規化(平均を引かない)の説明。LayerNorm との対比はこの項の注記参照。 - **L142** `ms = sum(xi * xi for xi in x) / len(x)` — 二乗の平均。 - **L143** `scale = (ms + 1e-5) ** -0.5` — 小さな定数 `1e-5` で除算を安定化し、RMS の逆数を得る。 - **L144** 各成分に `scale` を掛けて返す。 **最小例(`[Value(3.0), Value(4.0)]`):** ```text ms = (3² + 4²) / 2 = 12.5 scale = (12.5 + 1e-5)^(-1/2) ≈ 1 / √12.5 出力[i] = x[i] * scale ``` 各成分に `1/RMS` を掛けるので**正規化後の二乗平均がおおよそ 1** になり、大きすぎる活性を抑えます。`xi * scale` で各成分がグラフに入ります。 :::{note} **LayerNorm とは** **Layer Normalization(レイヤー正規化)**は、ニューラルネットの**ある一層の出力ベクトル**に対して、そのベクトルの成分だけを材料に「位置をそろえてから、幅をそろえる」処理です。Transformer では、**トークン位置ごとに**隠れベクトル(次元 `n_embd`)がひとつずつあり、そのベクトルに対して正規化をかけます。 ざっくりしたイメージは次のとおりです。 1. **中心化** — そのベクトルの成分の**平均**を求め、各成分から引く(ベクトルの重心を原点付近へ)。 2. **スケーリング** — 成分の**分散**(または標準偏差)で割る(ばらつきの大きさをおおよそ 1 に)。 3. (実装によっては)学習可能な**倍率とバイアス**を掛けて、モデルが正規化の強さを調整できるようにする。 目的は、層を重ねても**中間の値が極端に大きくなったり小さくなりすぎたりしにくくし**、勾配が安定して流れるようにすることです。 **RMSNorm** は、このうち「中心化(平均を引く)」を省略し、**二乗平均の平方根(RMS)だけ**で割ってスケールを揃えます。式は単純になり、計算も軽く、GPT-2 に近い設定の本コードでも採用されています。`microgpt.py` のモデル部コメント(GPT-2 との相違点として `LayerNorm`→`RMSNorm` と書かれている箇所)が指すのも、この違いです。 :::