FFN(Feed-Forward Network)――パラメータの半分を占める「知識の巨大金庫」。なぜAIはここで世界の事実を思い出すのか?
1. 素朴な疑問:花形のAttentionの裏で、パラメータの3分の2を喰らう「地味な巨漢」
AIの世界でTransformerを語るとき、主役として賞賛されるのは常に「Self-Attention(自己注意機構)」です。 「Q・K・Vの内積」「32個の複眼」「文脈のダイナミックな結合」――解説書の9割はAttentionのメカニズムに費やされます。
しかし、実際のLLM(Llama 3やGPT-4など)のパラメータ(重みファイル)の内訳を調べてみると、誰もが息を呑む奇妙な現実に直面します。
【Llama 3(8Bモデル)の1ブロックあたりのパラメータ内訳】
・Self-Attention層の重み(W_q, W_k, W_v, W_o) : 約 1,600 万パラメータ(約 35%)
・FFN層の重み(W_gate, W_up, W_down) : 約 2,900 万パラメータ(約 65%!)
なんと、**モデルの重みの約3分の2(半分以上)を独占しているのは、Attentionではなく、その直後に控えている「FFN(Feed-Forward Network / 別名: MLP)」**なのです。
教科書を開くと、FFNの説明は決まってあっさりしています。 「Attentionの後に、線形変換と活性化関数を通す2層の全結合ネットワークです」
なぜ、そんな一昔前の古典的なパーツが、世界最強の知能の心臓部でパラメータの大半を食い散らかしているのでしょうか? もしFFNを削ぎ落としたら、モデルには何が起きるのでしょうか?
その理由は極めて明快です。 **「Attentionは他の単語と会話する『通信』を担当し、FFNは世界の知識を蓄え引き出す『記憶(脳の長期記憶)』を担当しているから」**です。
2. 決定的な役割分担:Attentionは「通信」、FFNは「思考と記憶」
Transformerブロックの内部では、極めて合理的な「役割の二極化」が行われています。
Attentionだけでは「足し算のミキサー」に過ぎない
前話(第4・5話)で実装したSelf-Attentionの数式を思い出してください。
$$\text{Output} = \text{Softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) V$$
Attentionがやっていることの本質は、既存の単語ベクトル $V$ の**「加重平均(ブレンド)」でした。 「フランス」と「首都」という2つの単語があれば、その2つのベクトルを混ぜ合わせることはできます。しかし、混ぜ合わせただけでは「フランスの首都」という新しい合成ベクトルができるだけで、「パリ(Paris)」という具体的な事実(知識)を生み出すことはできません。**
単語同士の情報をいくら右から左へ動かしても、そこに「事実データ」を蓄える巨大なストレージがなければ、モデルは何も知らないまま空虚な言葉遊びを反復するだけです。
FFNは「他の単語を一切見ない」
ここで、FFNのテンソル計算の決定的な物理特性を確認します。
Attentionの計算 : 時間軸(Seq_Len)を跨いで、全単語が相互に干渉する [B, S, S]
FFNの計算 : 時間軸には一切干渉しない! 各単語が完全に独立して並列処理される
FFNに入力されたトークン列 [Batch, Seq_Len, Hidden_Dim] は、1単語ずつ別々の部屋に放り込まれます。
1単語目の処理に、2単語目の情報は1ビットも関与しません。
各トークンは、Attentionによって周囲から集めてきた文脈(例: 「フランス」「の」「首都」「は」)を手に持ち、FFNという名の**「巨大な知識の図書館」**の前に立ちます。 そして、図書館の分厚い本棚(重み行列)をめくって、「その文脈に合致する答えは『パリ』である」という事実を引き出し、自分のベクトルに注ぎ込んで部屋から出てくるのです。
- Attention = ルーティング(情報の交通整理・通信)
- FFN = コンピューティング(連想記憶・知識の呼び出し)
この両輪が揃って初めて、Transformerは「文脈を理解し、知識を語る」という知能を成立させています。
3. FFNの幾何学:なぜ一度「4倍(あるいは8/3倍)」に膨らませるのか?
では、FFNの内部ではどのような幾何学的変換が行われているのでしょうか? 元祖Transformer(Attention Is All You Need)の数式を見てみましょう。
$$\text{FFN}(x) = \max(0, x W_1 + b_1) W_2 + b_2$$
極めてシンプルです。
- 入力ベクトル $x$(4096次元)に、重み $W_1$ を掛ける。
- 活性化関数($\text{ReLU}$)を通す。
- 重み $W_2$ を掛けて、元の4096次元に戻す。
ここで最も重要な物理的パラメータが、**「中間層の広さ(Intermediate Dimension / $d_{ff}$)」**です。
なぜ真っ直ぐ通さず、わざわざ「4倍」に太らせるのか?
元祖Transformerでは、隠れ層次元 $d_{model} = 512$ に対し、中間層は 4倍 の $d_{ff} = 2048$ に拡大されました。 GPT-3でも、隠れ層 12,288 次元に対し、中間層は 4倍 の 49,152 次元です。
なぜわざわざ、一度4倍もの広大な空間に風船のように膨らませてから、元のサイズにしぼませるのでしょうか? 理由は、**「高次元空間に射影しなければ、複雑に絡み合った概念をハサミで切り分けられないから(線形分離可能性)」**です。
数学(機械学習)の基本定理である**「カバートの定理(Cover’s Theorem)」**は、次のように述べています。
「低次元空間では複雑に入り組んで分離できないデータパターンも、非線形変換を用いて十分な高次元空間へと持ち上げれば、高確率で真っ直ぐな平面(超平面)でスパッと綺麗に切り分けられるようになる」
4096次元の狭い部屋の中では、「物理」「歴史」「文学」「Pythonの構文」といった無数の知識が複雑に折り畳まれ、圧縮されています。 これを**16,384次元という広大な空間へ一気にアンパック(展開)**することで、1つひとつの概念が互いに邪魔をしない独立したスペースを獲得します。
そこで活性化関数によって不要なノイズをバッサリ切り捨て、本当に必要な知識の成分だけを厳選して、元の4096次元へとギュッと凝縮して持ち帰るのです。
4. 衝撃の研究:FFNは「Key-Value連想メモリ(辞書)」である
2021年、テルアビブ大学のモリ・ゲヴァ(Mor Geva)ら研究チームが発表した論文『Transformer Feed-Forward Layers Are Key-Value Memories』は、世界中のAI研究者に衝撃を与えました。
研究チームは、FFNの数式を線形代数的に分解し、それが**「コンピュータの連想配列(Key-Valueストア)」と数学的に完全に同一の構造であること**を暴き出したのです。
数式を一行ずつ解剖してみましょう。バイアスを省略すると、FFNの出力は次のように書き下せます。
$$\text{FFN}(x) = f(x W_1) W_2$$
ここで、行列 $W_1$ と $W_2$ をベクトルの集まりとして見ます。
- $W_1$ の各列を $\vec{k}_i$(Keyベクトル)とする。
- $W_2$ の各行を $\vec{v}_i$(Valueベクトル)とする。
すると、この計算は以下のようなベクトルの総和へと完全に分解できます。
$$\text{FFN}(x) = \sum_{i=1}^{d_{ff}} \underbrace{f(x \cdot \vec{k}i)}{m_i (\text{発火強度})} \underbrace{\vec{v}i}{\text{値ベクトル}}$$
辞書の引き当ての物理
- Keyとの照合($x \cdot \vec{k}_i$): 入力ベクトル $x$ と、第1層の重みベクトル $\vec{k}_i$ の内積を取る。 もし入力された文脈が「アインシュタインは [ ??? ] で生まれた」であれば、「人物の生誕地」を監視している特定のKeyニューロン $i$ が強烈に反応し、内積が大きなプラスになる。
- 係数の決定($m_i = f(x \cdot \vec{k}_i)$): 活性化関数 $f$ を通り、その事実をどれくらい強く思い出すかの係数 $m_i$ が決まる。関係ないKeyニューロンは $0$ に叩き落とされる。
- Valueの引き出し($m_i \vec{v}_i$): 発火したニューロンに対応する第2層の重みベクトル $\vec{v}_i$(そこには「ドイツ(Germany)」という意味成分が学習によって刻み込まれている)が引っこ抜かれ、係数 $m_i$ が掛け算される。
- 知識の合流: 引き出されたValueベクトルが、前話(第6話)で学んだ情報の大動脈「Residual Stream」へとドカンと足し算(Add)される。
FFNとは、抽象的なニューラルネットなどではありませんでした。 「16,384個(モデル全体で数百万個)もの『もしAならBを出力せよ』という知識のインデックスカードが隙間なく敷き詰められた、超高密度のKey-Value辞書データベース」、それこそがFFNの正体だったのです。
触って試す:キーバリュー・メモリの体験
🗝️ キーバリュー・メモリの体験(FFNは辞書引き)
入力パターンを選んでください。第1層の「キー」ニューロンが入力との内積で発火し(バーと数値が発火強度 m_i)、強く発火したキーに対応する第2層の「バリュー」が、係数 m_i 倍されて残差ストリームに加算されます。
まずは入力パターンをクリックしてみてください。
5. 活性化関数の進化論:なぜ現代LLMは「SwiGLU」一択なのか?
FFNの心臓部にある非線形活性化関数は、この数年で劇的な進化を遂げました。 Llama 3、Mistral、Gemmaなど、近年の主要なオープンウェイトLLMは例外なく「SwiGLU(スウィー・グルー)」という特殊な構造を採用しています。
なぜ、昔ながらのReLUやGELUは王座を奪われたのでしょうか?
SwiGLUの数理:門番(Gate)の導入
2020年、Googleの天才研究者ノーム・シャジール(Noam Shazeer)が発表した論文『GLU Variants Improve Transformer』によって、FFNの構造は決定的に塗り替えられました。
従来のFFNは「1本の道」を登って降りる構造でした。
$$\text{従来のFFN} = \text{GELU}(x W_1) W_2$$
これに対し、SwiGLUは入力を**「2本の並行するルート」**に分岐させます。
$$\text{SwiGLU}(x) = \Big( \underbrace{\text{Swish}(x W_{\text{gate}})}{\text{門番(フィルター)}} \odot \underbrace{(x W{\text{up}})}{\text{通過させたい情報}} \Big) W{\text{down}}$$
- アップ層($W_{\text{up}}$): 入力 $x$ を高次元(例: 11,008次元)へ持ち上げ、「候補となる知識情報」を生成する。
- ゲート層($W_{\text{gate}}$): 同じ入力 $x$ から、どの情報をどれくらい通すべきかを判断する「門番(フィルター)」を生成し、**Swish関数(別名: SiLU)**を通す。
- アダマール積($\odot$): アップ層の情報と、門番の数値を**「要素ごとの掛け算(Element-wise Multiplication)」**する。 門番が「0」と判定した次元は完全にシャットアウトされ、門番が「1」と判定した次元だけが鮮やかに通過する。
- ダウン層($W_{\text{down}}$): ゲートをくぐり抜けた精鋭の情報を、元の4096次元へ戻す。
なぜ掛け算(Gating)がこれほど強力なのか?
足し算だけの世界に「要素ごとの掛け算(双線形性)」を持ち込むと、モデルの表現力は爆発的に跳ね上がります。 門番(Gate)は、文脈に応じて**「今回の単語には、この知識スロットは100%開け、あっちの知識スロットは完全に鍵をかけろ」**という極めてシャープな動的スイッチングを行うことができます。
シャジールはこの構造を導入する際、パラメータ数が $1.5$ 倍に増えてしまわないよう、中間層の拡大率を従来の $4$ 倍から 約 $\frac{8}{3}$ 倍(約2.67倍:例 11,008次元) へと巧妙に絞り込みました。 総パラメータ数を元祖FFNと全く同じに揃えた比較実験において、SwiGLUはあらゆる下流タスクで圧倒的なスコア向上を叩き出し、現代LLMの絶対標準となったのです。
6. 完全実装:NumPyで作る「SwiGLU FFN」
それでは、ここまでの理論をコードで完全に再現してみましょう。 PyTorchなどのフレームワークは一切使いません。Llama 3の設計思想に準拠した**「SwiGLUスタイルのFFN」**をNumPyだけで実装します。
ファイル名を min_swiglu_ffn.py として保存してください。
import numpy as np
def silu(x: np.ndarray) -> np.ndarray:
"""SiLU (Swish-1) 活性化関数: x * sigmoid(x)"""
# 数値安定性を考慮したシグモイド
sigmoid = 1.0 / (1.0 + np.exp(-np.clip(x, -30, 30)))
return x * sigmoid
class SwiGLUFFN:
"""
Llama 3等で標準採用されているSwiGLUフィードフォワードネットワーク
"""
def __init__(self, hidden_dim: int, intermediate_dim: int):
self.hidden_dim = hidden_dim # 例: 4096 (実験では 8)
self.intermediate_dim = intermediate_dim # 例: 11008 (実験では 22)
np.random.seed(42)
# 3つの重み行列を初期化 (バイアスは最新LLMの慣例に従い全廃)
# 1. ゲート射影: [Hidden_Dim -> Intermediate_Dim]
self.W_gate = np.random.randn(hidden_dim, intermediate_dim) * 0.1
# 2. アップ射影: [Hidden_Dim -> Intermediate_Dim]
self.W_up = np.random.randn(hidden_dim, intermediate_dim) * 0.1
# 3. ダウン射影: [Intermediate_Dim -> Hidden_Dim]
self.W_down = np.random.randn(intermediate_dim, hidden_dim) * 0.1
def forward(self, x: np.ndarray) -> np.ndarray:
"""
x: [Batch_Size, Seq_Len, Hidden_Dim]
戻り値: [Batch_Size, Seq_Len, Hidden_Dim]
"""
# 1. ゲート側への射影とSiLU活性化
# [B, S, D] @ [D, I] -> [B, S, I]
gate = silu(x @ self.W_gate)
# 2. アップ側への射影(知識候補の生成)
# [B, S, D] @ [D, I] -> [B, S, I]
up = x @ self.W_up
# 3. 要素ごとの掛け算(門番による情報の動的フィルタリング!)
# [B, S, I] * [B, S, I] -> [B, S, I]
gated_hidden = gate * up
# 4. ダウン射影で元の次元へ凝縮
# [B, S, I] @ [I, D] -> [B, S, D]
output = gated_hidden @ self.W_down
return output
# -------------------------------------------------------------
# 実験:テンソルShapeの追跡とゲーティング動作の観察
# -------------------------------------------------------------
if __name__ == "__main__":
B = 1 # バッチサイズ
S = 3 # 系列長 ("Paris", "is", "capital")
D = 4 # 隠れ層次元 (小さく設定)
# Llamaスタイルの拡大率: 4 * 8/3 ≈ 11 次元へ拡大
I = int(D * 8 / 3) # 中間次元 10
# 仮想の入力テンソル(前段のAttention + 残差接続を抜けてきたデータ)
np.random.seed(100)
X = np.random.randn(B, S, D)
print("=== 入力テンソル X ===")
print(f"Shape: {X.shape} ([Batch={B}, Seq_Len={S}, Hidden_Dim={D}])")
print(f"トークン0 ('Paris') のベクトル:\n {X[0, 0]}\n")
# SwiGLU FFNの実行
ffn = SwiGLUFFN(hidden_dim=D, intermediate_dim=I)
output = ffn.forward(X)
print("=== SwiGLU FFN 内部の次元変化の軌跡 ===")
print(f"1. 入力次元 : {D}")
print(f"2. 中間層次元(拡大!) : {I} (W_gate, W_up により約2.67倍へアンパック)")
print(f"3. ゲートとアップの積 : {I} (要素ごとの掛け算により選別)")
print(f"4. 最終出力次元(凝縮!) : {D} (W_down により元の次元へ復帰)\n")
print("=== 最終出力テンソル Output ===")
print(f"Shape: {output.shape} ([Batch={B}, Seq_Len={S}, Hidden_Dim={D}])")
print(f"トークン0 ('Paris') の変換後ベクトル:\n {output[0, 0]}")
実行結果
=== 入力テンソル X ===
Shape: (1, 3, 4) ([Batch=1, Seq_Len=3, Hidden_Dim=4])
トークン0 ('Paris') のベクトル:
[-1.74976547 0.3426804 1.1530358 -0.25243604]
=== SwiGLU FFN 内部の次元変化の軌跡 ===
1. 入力次元 : 4
2. 中間層次元(拡大!) : 10 (W_gate, W_up により約2.67倍へアンパック)
3. ゲートとアップの積 : 10 (要素ごとの掛け算により選別)
4. 最終出力次元(凝縮!) : 4 (W_down により元の次元へ復帰)
=== 最終出力テンソル Output ===
Shape: (1, 3, 4) ([Batch=1, Seq_Len=3, Hidden_Dim=4])
トークン0 ('Paris') の変換後ベクトル:
[ 0.0039958 -0.00843475 -0.00755912 -0.0049285 ]
コードの計算手順を見てください。
Attentionの時のように transpose(1, 2) や [Seq_Len, Seq_Len] のような正方行列はどこにも登場しません。
純粋に各トークンが、W_gate と W_up によって10次元の広大な空間へと押し上げられ、要素ごとの掛け算で選別された後、W_down によって綺麗に元の4次元へと整流されて着地しています。
7. テンソルの視点:1トークン完結の「縦方向」パイプライン
前話(第5話)のMulti-Head Attentionと、本話のFFNの「テンソルの動き」を対比させてみましょう。 これこそが、Transformerが「計算機の極限」と呼ばれる構造的理由です。
| 比較項目 | Multi-Head Attention層 | FFN(SwiGLU)層 |
|---|---|---|
| 情報の流れる方向 | 横方向(トークン間) | 縦方向(トークン内部) |
| 主たる機能 | 通信(文脈のルーティング) | 記憶(知識の連想引き出し) |
| 時間軸(Seq_Len)の干渉 | あり(全単語と内積を取る) | 完全になし(1単語ずつ並列独立) |
| パラメータの割合 | 全体の約 33% | 全体の約 67%(大半を占有) |
| 計算のボトルネック | 系列長 $S$ の2乗($S^2$) | 中間層次元 $d_{ff}$ の巨大な行列積 |
この2つのブロックがペアとなり、1つの「Transformerレイヤー」を形成します。
- Attentionで周囲の単語から「状況の手がかり」を吸い集める(通信)。
- FFNでその手がかりを鍵(Key)にして、巨大な本棚から「事実」を引っ張り出す(記憶)。
- 残差ストリーム(Add)にその事実を足し算して、次の階層へエレベーターで昇っていく。
この「通信 $\to$ 記憶 $\to$ 通信 $\to$ 記憶」の反復横跳びを30回、80回と繰り返すことによって、最初はただの単語記号だったものが、高度に推敲された「深い思考と知識の結晶」へと練り上げられていくのです。
8. まとめ:FFNとは「パラメータで織り成された静的メモリ」である
FFNの真実を総括すると、以下のようになります。
- パラメータの過半を占める理由: Attentionは単なる通信路であり、世界の事実(知識)を重みとして物理的に保存しているのはFFNだからである。
- 高次元展開の必然: 4096次元から11,008次元や16,384次元へ一度広げることで、カバートの定理に基づき、複雑に絡み合った概念を線形分離可能な状態へとアンパックする。
- Key-Value連想メモリの構造: 第1層の重みが「文脈パターンを検出するKey」、第2層の重みが「加算される知識のValue」として機能し、巨大な辞書引きを行っている。
- SwiGLUの完成度: SiLU活性化関数と門番(Gate)の掛け算を組み合わせることで、不要な知識をシャープに遮断し、現代LLMの最高水準の推論効率を確立した。
これで、Transformerブロックの「二大巨頭(Attention と FFN)」の内部構造は完全に解剖されました。
しかし、ここまでの解説を注意深く読んできた読者なら、ある致命的な矛盾に気づいているはずです。
「Attentionは全単語と内積を取り、FFNは全単語を個別に処理する。……ということは、単語の順番をシャッフルしてバラバラに並び替えても、全く同じ計算結果になってしまうのではないか?」
その通りです。Transformerには、元来「語順(1番目、2番目……)」を理解する機構が1ミリも備わっていません。
「犬が人を噛んだ」と「人が犬を噛んだ」を区別するために、人類はモデルにどうやって「位置」を教え込んだのか? 初期の素朴な足し算方式から、現代LLMのデファクトスタンダードとなった回転幾何学「RoPE」まで。
次回、**「第8話:位置エンコーディングの進化――絶対位置から『RoPE(回転幾何学)』へ」**へと進みましょう。
📝 3行で頭に入る『試験・実務の要点まとめ』
- 通信と記憶の分業: Attentionがトークン間の文脈を混ぜ合わせる「横の通信路」であるのに対し、パラメータの約3分の2を占めるFFNはトークン単体で世界の知識を引き出す「縦の連想メモリ」である。
- Key-Value連想記憶の数理: FFNは $\sum f(x \cdot k_i) v_i$ に分解でき、第1層重み $W_1$ のKeyで文脈パターンを検知し、第2層重み $W_2$ のValueから対応する事実知識を引っこ抜いて残差ストリームに加算する。
- SwiGLUの優位性: 中間次元を約 $\frac{8}{3}$ 倍に拡大しつつ、門番層とアップ層の要素積($(\text{Swish}(x W_{\text{gate}})) \odot (x W_{\text{up}})$)で動的フィルタリングを行うことで、従来のReLU/GELUを凌駕する表現力を実現した。