一文でまとめると: ニューラルネットワーク層とは、行列の掛け算と非線形性によってベクトルを変換する、学習された関数のことです。Transformer を理解するためには、それを「形を変えるブラックボックス」として扱うだけでも十分にやっていけます。
7.1 ニューラルネットワークの専門家になる必要はありません
先に率直に言わせてください。Transformer を理解するために、ニューラルネットワークを深く理解する必要はありません。
各 Transformer ブロックの内部にある Feed Forward Network (FFN) はニューラルネットワーク層の一種です。しかし、アーキテクチャ上の役割はとてもシンプルです。ベクトルを受け取り、それを変換して、同じサイズのベクトルを返す。Transformer 全体の動作を理解するという目的では、FFN を「形がわかっている学習済み関数」として扱えば十分です。
最低限知っておくべきことは次の3つだけです。
- どのような形のベクトルが入るのか。
- どのような形のベクトルが出てくるのか。
- 学習可能なパラメータがどこに住んでいるのか。
もう少し深い絵が見たい方のために、本章ではその先まで踏み込みます。ただし目標はあくまで「使えるメンタルモデル」を提供することであり、皆さんを誤差逆伝播の専門家に仕立てることではありません。
7.2 生物学的なたとえ話 (とその限界)
7.2.1 生物のニューラルネットワーク
人間の脳には、おおよそ次のような規模の構造があります。
- 約860億個のニューロン: それぞれが小さな処理ユニット
- 約100兆個のシナプス: ニューロン同士をつなぐ接続
ニューロンは上流のニューロンから電気信号を受け取り、入力の合計があるしきい値を超えると「発火」して下流に信号を送ります。
7.2.2 人工ニューラルネットワーク
人工ニューラルネットワークは、語彙だけ生物学から借りてきていますが、生物学そのものを写し取っているわけではありません。
- ノード: ニューロンに相当
- 重み: シナプスの強さに相当
- 活性化: 入力の重み付き和がしきい値を超えると、ノードが活性化する
ここで重要な但し書きがあります。人工ニューラルネットワークはあくまで数学的なモデルであり、脳のシミュレーションではありません。名前と大まかな比喩を借りているだけで、実際の計算は「行列の掛け算 + 非線形関数」です。
「ニューラル」という言葉に引きずられて神秘的に感じる必要はありません。これは少しひねりを加えた線形代数なのです。
7.3 ニューラルネットワークが学習することの正体
7.3.1 自動的な特徴の発見
ニューラルネットワークの面白い性質は、「何を見るべきか」を教えなくても、有用な表現を自分で学んでくれるところにあります。
古典的なデモンストレーションを紹介しましょう。手書き数字の画像10,000枚 (MNIST データセット) でネットワークを訓練します。各画像は 28×28 ピクセル、つまり 784 次元です。訓練後、学習された表現を 2 次元に射影してみると、数字が自然にクラスタを作ります。「3」の画像同士が集まり、「7」の画像同士が集まる、といった具合です。
「3」がどんな見た目なのかを誰も教えていません。それでもネットワークは、訓練信号からその構造を発見してしまうのです。
7.3.2 言語にも同じことが起きる
同じ原理は言語にも当てはまります。十分なテキストで訓練すれば、
- どの単語同士が共起しやすいかを学習します。
- 分布パターンから文法構造を学習します。
- 「エージェント」と「レビュアー」が似たような構文上の位置を占めることを学習します。
訓練後、「pull request」と「code review」は埋め込み空間内の近い領域に配置されます。これは誰かが手で関係を書き込んだからではなく、訓練信号がそれらを似た文脈に繰り返し置いたからです。
7.4 ニューラルネットワークの基本構造
7.4.1 3つのレイヤー
最小構成のニューラルネットワークは、3つのレイヤーから成ります。
- 入力層: 生のデータを受け取ります。本書の文脈ではトークンのベクトルです。
- 隠れ層: 変換を行います。学習可能な重みが住んでいる場所です。
- 出力層: 結果を返します。
「隠れ」というのは、入力でも出力でもなく、計算の途中で外からは直接観測されない、という意味にすぎません。
7.4.2 具体的な特徴量で考える例
入力ベクトルが商品リスティングを表しているとしましょう。隠れ層は、たとえば次のような特徴量を抽出するように学習します。
- 価格帯
- ターゲット層
- カテゴリ
抽出された特徴量から、出力層がラベルを予測します。
ネットワークが学ぶのは次の2点です。
- 各特徴量を作るために、どの入力次元を組み合わせるか。
- どの特徴量の組み合わせが、どのラベルを予測するか。
中間特徴量を人間が手作業で設計したわけではありません。訓練データ上の予測誤差を最小化する過程で、ネットワーク自身が発見したのです。
7.5 数学的な核: 行列の掛け算
7.5.1 1レイヤー = 1回の行列の掛け算
1つの全結合層 (dense layer) の核となる計算は、次の式で書けます。
y = xW + b
ここで、
xは入力ベクトル。Wは重み行列。これが学習可能なパラメータです。bはバイアスベクトル。これも学習可能です。yは出力ベクトル。
具体例として、入力が 2 次元、出力が 2 次元の場合を見てみましょう。
input vector × weight matrix = output vector
[0.54] [w₁ w₃] [0.91]
[0.84] × [w₂ w₄] = [0.90]
出力の i 番目の要素は、入力ベクトルと W の i 列目との内積になります。
7.5.2 複数レイヤー
レイヤーを重ねると、次のようになります。
layer 1 -> layer 2 -> layer 3 -> layer 4
矢印の1本1本がそれぞれ行列の掛け算1回分です。「ディープラーニング」とは要するに、こうしたレイヤーをたくさん重ねたニューラルネットワークの呼び名にすぎません。
図の中でノード同士をつなぐ線は重みを表しています。1本1本の線が、重み行列のひとつの要素です。レイヤーが増える = 行列が増える = 学習可能なパラメータが増える、ということになります。
7.5.3 これがパラメータ数の話につながる
「GPT-3 は1750億パラメータ」というとき、そのパラメータの大半は、ここで見たような重み行列の中の数値です。どこかに別途用意された知識ベースに格納されているわけではありません。各レイヤーの W の学習済みの値そのものです。
7.6 活性化関数: 非線形性という材料
7.6.1 なぜ非線形性が必要なのか
線形なレイヤーだけを重ねるとどうなるでしょうか。
y₂ = (x W₁) W₂ = x (W₁ W₂) = x W₃
複数の行列の掛け算は、結局1つの行列の掛け算にまとまってしまいます。何枚レイヤーを積もうと、全体としては1枚のレイヤーと同じことしかできません。これでは複雑なパターンを表現できないのです。
そこで活性化関数をレイヤーの間に挟み、非線形性を持ち込むことで、この「つぶれ」を防ぎます。
7.6.2 ReLU
最もシンプルでよく使われている活性化関数が ReLU (Rectified Linear Unit) です。
ReLU(x) = max(0, x)
正の値はそのまま通し、負の値は 0 にします。
import torch
import torch.nn as nn
x = torch.tensor([-2.0, -1.0, 0.0, 1.0, 2.0])
print(nn.functional.relu(x)) # tensor([0., 0., 0., 1., 2.])
7.6.3 GELU と SwiGLU
最近の LLM ではもっと滑らかな派生形がよく使われます。
- GELU (Gaussian Error Linear Unit): GPT-2、BERT などで採用されています。0 付近で ReLU より滑らかです。
- SwiGLU: LLaMA をはじめ近年のモデルで多用されているゲート付きの派生形で、経験的に良い結果が出ます。
アーキテクチャを理解するために、これらを暗記する必要はありません。重要なポイントは1つだけです。「すべてのレイヤーは行列の掛け算のあとに非線形な活性化関数を持つ。そして活性化関数の選び方は、想像以上にモデルの品質に効いてくる」。
7.7 PyTorch での実装
7.7.1 シンプルなネットワーク
import torch.nn as nn
model = nn.Sequential(
nn.Linear(2, 3), # 入力層: 2次元 → 3次元
nn.ReLU(), # 活性化関数
nn.Linear(3, 1), # 出力層: 3次元 → 1次元
)
空行とコメントを含めても 7 行。これで 1 つのフィードフォワードネットワークが完成します。
nn.Linear(in_features, out_features) は、形が [in_features, out_features] の重み行列と、形が [out_features] のバイアスベクトルを持つレイヤーを作ります。
7.7.2 次元の変化
データがネットワークを通っていくときの形は、こうなります。
input (1, 2) @ weight (2, 3) = hidden (1, 3) @ weight (3, 1) = output (1, 1)
行列の掛け算のルールは (a, b) @ (b, c) = (a, c)。内側の次元が一致していなければならず、結果の形は外側の次元になります。
次元の変化を追えるようになることが、Transformer のコードを読むうえでのカギです。
7.8 Transformer ブロック内の FFN
7.8.1 拡張してから縮約するパターン
各 Transformer ブロックの内部で、Feed Forward Network (FFN) は決まった形をしています。
[d_model] → [4 × d_model] → [d_model]
ベクトルがいったん 4 倍の幅まで拡張され、非線形な活性化関数を通り、ふたたび d_model まで縮約されます。
ffn = nn.Sequential(
nn.Linear(d_model, 4 * d_model), # 拡張
nn.GELU(), # 活性化関数
nn.Linear(4 * d_model, d_model), # 縮約
)
これが GPT-2 系モデルにおける標準的な FFN です。LLaMA は SwiGLU を使うため行列が 2 つではなく 3 つになりますが、「拡張してから縮約する」という大筋は変わりません。
7.8.2 ブロック全体の構造
入力
↓
LayerNorm
↓
Masked Multi-Head Attention
↓
残差接続
↓
LayerNorm
↓
Feed Forward Network (FFN) <- これがニューラルネットワーク層
↓
残差接続
↓
出力
Attention は系列内の位置をまたいで情報を混ぜ合わせ、FFN は各位置の表現を独立に処理します。この2つのサブレイヤーは、相補的な役割を担っています。
- Attention が問うのは「系列内の他のトークンのうち、このトークンにとって関係があるのはどれか?」。
- FFN が問うのは「その文脈を踏まえると、このトークンの表現はどう変えるべきか?」。
7.8.3 なぜトークンごとに独立に処理するのか
FFN は同じ変換を各トークン位置に適用し、位置をまたいだ混合は行いません。混合は Attention 側で起きます。役割をはっきり分けておくことで、アーキテクチャはスケールしやすく、改造しやすくなります。
7.9 パラメータはどこに住んでいるのか
7.9.1 学習可能な重み行列の地図
Transformer の中で、パラメータがどこに住んでいるかをまとめると次のようになります。
| コンポーネント | パラメータ数 |
|---|---|
| 埋め込みテーブル | vocab_size × d_model |
| FFN (1レイヤーあたり) | d_model × 4d_model + 4d_model × d_model |
| Attention の Q, K, V, O (1レイヤーあたり) | 4 × d_model² |
| LayerNorm (1レイヤーあたり) | 2 × d_model (γ と β) |
| LM Head (最終射影) | d_model × vocab_size |
7.9.2 現実的なモデルでのパラメータ数
LLaMA-7B (d_model = 4096、d_ff = 11008、32 レイヤー、vocab_size = 32,000) で具体的に計算してみましょう。
| コンポーネント | パラメータ数 |
|---|---|
| 埋め込み | 32,000 × 4,096 ≈ 131M |
| FFN 1レイヤーあたり (SwiGLU、行列3つ) | 3 × 4,096 × 11,008 ≈ 135M |
| Attention 1レイヤーあたり | 4 × 4,096² ≈ 67M |
| LayerNorm 1レイヤーあたり | 2 × 4,096 ≈ 8K (ごく僅か) |
*LLaMA は SwiGLU 活性化を使うため、従来の FFN の 2 つの行列ではなく、gate・up・down という 3 つの射影行列を必要とします。
7.9.3 FFN にまつわる意外な事実
「Attention の方がパラメータが多そう」と思っている方は多いはずです。なにしろ、いかにも面白そうな計算は Attention の側で起きていますから。しかし数字を見てください。1レイヤーあたりの FFN のパラメータは、1レイヤーあたりの Attention のパラメータのおよそ2倍です。
これが 32 レイヤー積み重なるのですから、パラメータ予算の大半は FFN が占めることになります。
最近の研究によれば、これにはちゃんと意味があるようです。Attention は情報のルーティング (どのトークンがどのトークンに注意を向けるか) を専門としており、FFN は訓練データから学んだ事実関係の連想を保持することを専門としている。モデルの「知識」が住んでいるのは、おもに FFN の中なのです。
7.10 章のまとめ
7.10.1 重要な概念
| 概念 | 意味 |
|---|---|
| ニューラルネットワーク層 | 行列の掛け算 + バイアス + 活性化関数 |
| 隠れ層 | 中間の変換レイヤー |
| 活性化関数 | 行列の掛け算のあとに適用される非線形関数 (ReLU、GELU、SwiGLU) |
| FFN | 各 Transformer ブロック内部にあるニューラルネットワーク部分 |
| 拡張してから縮約 | FFN のパターン: d_model → 4d_model → d_model |
7.10.2 中心となる式
output = activation(input × W₁ + b₁) × W₂ + b₂
PyTorch だとこうなります。
ffn = nn.Sequential(
nn.Linear(d_model, 4 * d_model),
nn.GELU(),
nn.Linear(4 * d_model, d_model),
)
7.10.3 結局、押さえるべきこと
- 形: FFN は
[seq_len, d_model]を受け取り、[seq_len, 4 × d_model]まで拡張し、[seq_len, d_model]まで縮約します。 - 位置ごと: FFN は各トークンを独立に処理し、位置をまたいだ混合はしません。
- パラメータ: FFN は Attention よりもパラメータを多く抱えており、ブロックあたりおおむね 2〜3 倍にもなります。
- 役割: Attention は情報を混ぜ合わせ、FFN はトークンごとの表現を変換し、学習した連想を蓄えます。
Transformer の中のニューラルネットワーク層は、行列の掛け算と非線形性を組み合わせたものに過ぎません。「各トークンの表現の形を変える、学習された関数」と捉えてください。それだけで、アーキテクチャ全体について十分に推論できます。
第2部のまとめ
これで 第2部: 中核コンポーネント が終わりました。
| 章 | コンポーネント | 中心的な役割 |
|---|---|---|
| 第4章 | トークナイゼーション + Embedding | テキスト → トークン ID → ベクトル |
| 第5章 | 位置エンコーディング | 各ベクトルに位置情報を付与する |
| 第6章 | LayerNorm + Softmax | 活性化を安定させ、スコアを確率に変換する |
| 第7章 | Feed Forward Network (FFN) | トークンごとの情報を変換し、保持する |
これでコンポーネントは出そろいました。第3部では、それらを束ねて Transformer の力の源泉となるメカニズム、つまり Attention に踏み込みます。
章のチェックリスト
本章を読み終えたあと、次のことができるようになっていれば合格です。
-
y = xW + bが何を計算しているか、学習可能なパラメータがどこにあるかを説明できる。 - レイヤーの間に活性化関数が必要な理由を説明できる。
- FFN の「拡張してから縮約する」パターンと、その次元変化を記述できる。
- Transformer ブロック内における Attention の役割と FFN の役割の違いを述べられる。
- FFN が Attention よりも多くのパラメータを持つ理由と、それが「知識」の住処について何を示唆しているかを説明できる。
次の章でお会いしましょう
これでニューラルネットワーク層についてはひと区切りです。これまでに、トークナイゼーション、Embedding、位置エンコーディング、LayerNorm、Softmax、そして FFN という、すべての構成要素が手に入りました。
第8章では、Attention に踏み込む前に、ほんの少しだけ幾何学に寄り道します。具体的に答えるのは、Attention に初めて触れた人がほぼ全員つまずく、こんな問いです。「行列の掛け算は、幾何学的にいったい何をやっているのか?」。この絵さえ手に入れば、Attention の中心にある内積の意味がすっと腑に落ちるはずです。
それでは、また次の章で。お疲れさまでした。