FFN(Feed-Forward Network)――パラメータの半分を占める「知識の巨大金庫」。なぜAIはここで世界の事実を思い出すのか?

2026-11-05 / シリーズ「LLM・AIの内臓」第7話 / LLM, FFN, MLP, SwiGLU, Transformer, 連想メモリ, Python

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)」**なのです。

Transformerブロック内部のパラメータ占有率 Attention 約35% FFN 約65%! Attention:文脈を混ぜる通信路(約35%) FFN:巨大なパラメータを抱え込む巨漢(約65%) 教科書では「ただの2層ニューラルネット」と数行で片付けられるこの層が、なぜ半分以上を占めるのか?

教科書を開くと、FFNの説明は決まってあっさりしています。 「Attentionの後に、線形変換と活性化関数を通す2層の全結合ネットワークです」

なぜ、そんな一昔前の古典的なパーツが、世界最強の知能の心臓部でパラメータの大半を食い散らかしているのでしょうか? もしFFNを削ぎ落としたら、モデルには何が起きるのでしょうか?

その理由は極めて明快です。 **「Attentionは他の単語と会話する『通信』を担当し、FFNは世界の知識を蓄え引き出す『記憶(脳の長期記憶)』を担当しているから」**です。


2. 決定的な役割分担:Attentionは「通信」、FFNは「思考と記憶」

Transformerブロックの内部では、極めて合理的な「役割の二極化」が行われています。

Attentionの仕事(横方向の通信) 単語 1 単語 2 単語 3 トークン間で情報を混ぜ合わせる。 自分自身の中身を深く掘り下げることはしない FFNの仕事(縦方向の思考と記憶) 単語 1 FFN 単語 1' 単語 2 FFN 単語 2' 単語 3 FFN 単語 3' 他の単語は一切見ない! 1トークン完結で、蓄えた知識と照合する

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$$

極めてシンプルです。

  1. 入力ベクトル $x$(4096次元)に、重み $W_1$ を掛ける。
  2. 活性化関数($\text{ReLU}$)を通す。
  3. 重み $W_2$ を掛けて、元の4096次元に戻す。

ここで最も重要な物理的パラメータが、**「中間層の広さ(Intermediate Dimension / $d_{ff}$)」**です。

入力ベクトル x Shape: [Seq_Len, 4096] 重み W₁ を掛ける 中間隠れ層(4倍に拡大!) Shape: [Seq_Len, 16384] ― 情報のアンパック 活性化関数 (ReLU / GELU) 不要なニューロンをゼロで間引く 重み W₂ を掛ける 出力ベクトル Shape: [Seq_Len, 4096] ― 情報の蒸留 + 残差接続で合流 入力 x がそのまま出力に足し算される

なぜ真っ直ぐ通さず、わざわざ「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ストア)」と数学的に完全に同一の構造であること**を暴き出したのです。

入力トークン x 例:「アインシュタインは [???] で生まれた」 第1層 W₁(Keyメモリ群)と照合 内積 x · k_i を全探索 ― 事実パターンに合致するKeyが強く発火! 活性化係数 m_i が決まる その事実をどれくらい強く思い出すか。関係ないKeyは 0 に叩き落とされる 第2層 W₂(Valueメモリ群)から引き出す 係数 m_i に応じて、Valueベクトル v_i を引き出し加重する 残差ストリームへ知識を書き込み! v_i に刻まれた知識(『ドイツ』『物理学者』)が噴出する FFN(x) = Σ f(x · k_i) v_i ― 辞書引きそのものの数式

数式を一行ずつ解剖してみましょう。バイアスを省略すると、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{値ベクトル}}$$

辞書の引き当ての物理

  1. Keyとの照合($x \cdot \vec{k}_i$): 入力ベクトル $x$ と、第1層の重みベクトル $\vec{k}_i$ の内積を取る。 もし入力された文脈が「アインシュタインは [ ??? ] で生まれた」であれば、「人物の生誕地」を監視している特定のKeyニューロン $i$ が強烈に反応し、内積が大きなプラスになる。
  2. 係数の決定($m_i = f(x \cdot \vec{k}_i)$): 活性化関数 $f$ を通り、その事実をどれくらい強く思い出すかの係数 $m_i$ が決まる。関係ないKeyニューロンは $0$ に叩き落とされる。
  3. Valueの引き出し($m_i \vec{v}_i$): 発火したニューロンに対応する第2層の重みベクトル $\vec{v}_i$(そこには「ドイツ(Germany)」という意味成分が学習によって刻み込まれている)が引っこ抜かれ、係数 $m_i$ が掛け算される。
  4. 知識の合流: 引き出された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は王座を奪われたのでしょうか?

1. ReLU: max(0, x) シンプルだが、マイナス側で傾きがゼロになり ニューロンが二度と起き上がらない(Dying ReLU問題) 2. GELU: x × Φ(x) 正規分布の確率で滑らかに落とす(GPT-2/GPT-3標準) しなやかだが、単一の線形変換に依存 3. SwiGLU: (Swish(x W_gate)) ⊙ (x W_up) ★現代LLMの絶対標準! 門番(Gate)による掛け算制御

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}}$$

入力 x: [Seq_Len, 4096] 重み W_gate 重み W_up ゲート層 (W_gate) 門番(フィルター)を生成 アップ層 (W_up) 候補となる知識情報を生成 Swish (SiLU) 活性化: x · σ(x) 要素ごとの掛け算 ⊙(アダマール積) 不要な情報は門番が完全にゼロへ押し潰す! 重み W_down 出力: [Seq_Len, 4096]
  1. アップ層($W_{\text{up}}$): 入力 $x$ を高次元(例: 11,008次元)へ持ち上げ、「候補となる知識情報」を生成する。
  2. ゲート層($W_{\text{gate}}$): 同じ入力 $x$ から、どの情報をどれくらい通すべきかを判断する「門番(フィルター)」を生成し、**Swish関数(別名: SiLU)**を通す。
  3. アダマール積($\odot$): アップ層の情報と、門番の数値を**「要素ごとの掛け算(Element-wise Multiplication)」**する。 門番が「0」と判定した次元は完全にシャットアウトされ、門番が「1」と判定した次元だけが鮮やかに通過する。
  4. ダウン層($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が「計算機の極限」と呼ばれる構造的理由です。

1. Attention(横の結合:通信) 入力: [B, S, D] アテンション行列: [B, H, S, S] 出力: [B, S, D] 計算量 O(S² · D)/時間軸 S を跨いで全単語が絡み合う 2. SwiGLU FFN(縦の跳躍:記憶) 入力: [B, S, D] 中間展開: [B, S, d_ff] 出力: [B, S, D] 計算量 O(S · D · d_ff)/時間軸 S は一切不変!
比較項目Multi-Head Attention層FFN(SwiGLU)層
情報の流れる方向横方向(トークン間)縦方向(トークン内部)
主たる機能通信(文脈のルーティング)記憶(知識の連想引き出し)
時間軸(Seq_Len)の干渉あり(全単語と内積を取る)完全になし(1単語ずつ並列独立)
パラメータの割合全体の約 33%全体の約 67%(大半を占有)
計算のボトルネック系列長 $S$ の2乗($S^2$)中間層次元 $d_{ff}$ の巨大な行列積

この2つのブロックがペアとなり、1つの「Transformerレイヤー」を形成します。

  1. Attentionで周囲の単語から「状況の手がかり」を吸い集める(通信)。
  2. FFNでその手がかりを鍵(Key)にして、巨大な本棚から「事実」を引っ張り出す(記憶)。
  3. 残差ストリーム(Add)にその事実を足し算して、次の階層へエレベーターで昇っていく。

この「通信 $\to$ 記憶 $\to$ 通信 $\to$ 記憶」の反復横跳びを30回、80回と繰り返すことによって、最初はただの単語記号だったものが、高度に推敲された「深い思考と知識の結晶」へと練り上げられていくのです。


8. まとめ:FFNとは「パラメータで織り成された静的メモリ」である

FFNの真実を総括すると、以下のようになります。

  1. パラメータの過半を占める理由: Attentionは単なる通信路であり、世界の事実(知識)を重みとして物理的に保存しているのはFFNだからである。
  2. 高次元展開の必然: 4096次元から11,008次元や16,384次元へ一度広げることで、カバートの定理に基づき、複雑に絡み合った概念を線形分離可能な状態へとアンパックする。
  3. Key-Value連想メモリの構造: 第1層の重みが「文脈パターンを検出するKey」、第2層の重みが「加算される知識のValue」として機能し、巨大な辞書引きを行っている。
  4. 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を凌駕する表現力を実現した。

確認クイズ

📝 確認クイズ(3問)