LlamaやPaLM、Mistral、Gemmaといった近年の大規模言語モデルのソースコードを開くと、Feed-Forward Network(FFN)の中身が教科書の図とは違うことに気づきます。教科書には「線形変換 → ReLU → 線形変換」と書いてあるのに、実装では線形変換が3つあり、片方の出力がもう片方に掛け算されています。そして、そこで使われている活性化関数はReLUでもtanhでもなく SiLU(PyTorchでは nn.SiLU、論文では Swish と呼ばれます)です。
なぜ、わざわざ行列を1つ増やしてまで掛け算を入れるのでしょうか。そして、なぜReLUではなくSiLUなのでしょうか。この2つの問いに答えるのが本記事の目的です。

この図がSiLUの発想のすべてです。左のReLUは、入力が正なら蛇口を全開、負なら完全に閉める。開き具合は0%か100%しかありません。右のSiLUは、流れ込んだ信号 $x$ 自身がシグモイドを通って「開き具合」に変換され、その開き具合で自分をスケールします。入力 $-1.0$ に対してReLUは問答無用で $0$ を返しますが、SiLUは開き $27\%$ を掛けて $-0.27$ を返す。この「連続的な蛇口」が、以降で見る滑らかさ・非単調性・自己ゲーティングのすべての出発点になります。
SiLUは $\text{SiLU}(x) = x\,\sigma(x)$ という、拍子抜けするほど単純な式で定義されます。ところがこの単純な式には、「ReLUのハードなスイッチをシグモイドで滑らかにした」「入力自身がゲートの開き具合を決める(自己ゲーティング)」「負側で少しだけ谷を作る非単調性」という3つの性質が同時に詰まっています。この性質は、次のような場面でそのまま効いてきます。
- 大規模言語モデルのFFN — SwiGLU $(\bm{x}\bm{W}) \otimes \text{SiLU}(\bm{x}\bm{V})$ として、パラメータ数を揃えてもReLU FFNより低い損失に速く到達する構成要素になっています。本記事の実験でも、テストMSEの中央値で約4倍の差がつきました。
- 画像認識のバックボーン — EfficientNet や YOLO 系の検出器では、ReLUをSwishに置き換えるだけで精度が上がることが報告され、標準的な選択肢になりました。モバイル向けには計算を軽くした hard-swish も使われます。
「たかが活性化関数」と侮ると、こうしたモデルのコードが読めません。逆に、SiLUの導関数を自分の手で微分して形を眺めると、ReLUの何が不満で、GELUと何が違い、SwiGLUがどこで得をしているのかが一本の線でつながります。
本記事の内容
- SiLUの直感(自己ゲーティング)と数学的定義
- 導関数 $\sigma(x)\left(1 + x(1-\sigma(x))\right)$ の導出と、その形が持つ意味
- 非単調性 — 最小値がランベルトW関数を使って厳密に $-W(1/e) = -0.278$ と書けること
- GELUとの関係 — $\text{GELU}(x) \approx x\,\sigma(1.702x)$ の由来と、2つの関数の差の実測
- SwiGLUがなぜ同パラメータ数のReLU FFNより表現力で有利なのか
- Pythonによる可視化と、小規模MLP・FFNでの学習比較実験
前提知識
この記事を読む前に、以下の記事を読んでおくと理解が深まります。
- ReLU・GELU・SiLUを数式から比較して理解する
- モダン活性化関数の完全ガイド — SiLU・GELU・Mishを数式から理解する
- Transformer Encoderの構造と実装を完全理解する
シグモイド関数 $\sigma(x) = 1/(1+e^{-x})$ の形と、合成関数・積の微分ができれば十分です。後半のSwiGLUの節では、Transformerのブロック内でFFNがどこに挟まっているかを知っていると読みやすくなります。
SiLUとは — 「自分で自分のゲートを開ける」活性化関数
活性化関数の役割を、水道の蛇口にたとえてみましょう。ニューロンに流れ込んだ信号 $x$ を、そのまま次の層へ流すのか、それとも止めるのか。この判断を担うのが活性化関数です。
ReLUの判断は極端です。$x > 0$ なら蛇口を全開(そのまま通す)、$x \le 0$ なら完全に閉める。ゼロか1かの二択で、その間がありません。この潔さがReLUの強さでもあるのですが、「$x = 0.001$ なら通すが $x = -0.001$ なら完全遮断」というのは、よく考えると乱暴な話です。入力がほんの少し揺れただけで、そのニューロンの出力も、そこを通る勾配も、断崖絶壁のように切り替わってしまいます。
SiLUの発想は「蛇口の開き具合を、0か1ではなく 0から1までの連続値 にしよう。しかもその開き具合は、流れ込んできた信号 $x$ 自身に決めさせよう」というものです。開き具合を表すのにシグモイド $\sigma(x)$ ほど都合のいい関数はありません。$\sigma$ は $x$ が大きいと1に、小さいと0に近づき、その間を滑らかにつなぎます。
そこで、信号 $x$ に「$x$ 自身が決めた開き具合 $\sigma(x)$」を掛けます。
$$ \begin{equation} \text{SiLU}(x) = x \cdot \sigma(x) = \frac{x}{1 + e^{-x}} \end{equation} $$

グラフを見ると、SiLU(太い青線)が「灰色の破線 $x$」と「オレンジの $\sigma(x)$」の積として作られている様子がはっきりわかります。$x=+2.5$ では開きが $92\%$ なので出力は $x$ よりわずかに小さい $+2.31$、$x=-1.5$ では開きが $18\%$ しかないので出力は $-0.27$ まで潰れます。注目してほしいのは、$x$ が負のとき「小さい正の数(開き具合)× 負の数(信号)」となるため、出力が負の小さな値になる点です。この一見なんでもない事実が、あとで非単調性という形で表に出てきます。
これがSiLU(Sigmoid-weighted Linear Unit、シグモイド重み付き線形ユニット)です。$x$ が大きければ $\sigma(x) \approx 1$ なので出力は $x$ にほぼ等しく、恒等写像として振る舞います。$x$ が小さければ $\sigma(x) \approx 0$ なので出力はほぼ0に潰れます。つまり ReLUと同じ「大きい入力は通す、小さい入力は止める」という挙動を、崖ではなく坂で実現しているわけです。
このように「自分自身から作った重みで、自分自身をスケールする」構造を自己ゲーティング(self-gating)と呼びます。LSTMの入力ゲートや忘却ゲートは、別の入力から作ったゲートで信号を制御しますが、SiLUのゲートは自分から作られます。だからパラメータが増えず、活性化関数として1入力1出力のまま使えるのです。
名前の歴史も少し混乱しているので整理しておきます。この関数は複数の研究グループが独立に見つけました。Hendrycks と Gimpel が2016年のGELUの論文の中で $x\sigma(x)$ を SiL(Sigmoid Linear Unit)として言及し、Elfwing らが2017年に強化学習の文脈で SiLU という名前で提案し、Ramachandran らが2017年に強化学習ベースの自動探索で「良い活性化関数」を探した結果として再発見し、Swish と名付けました。中身は同じ関数です。PyTorch は nn.SiLU を、TensorFlow は tf.nn.silu / swish を提供しています。本記事では以降 SiLU で統一します。
さて、直感は掴めました。次はこの関数を数学的に丁寧に定義し、極限や単調性といった基本性質を確認していきましょう。「滑らかにした」という言い方が具体的に何を意味するのかは、微分してみて初めてはっきりします。
SiLUの数学的定義と基本性質
改めて定義を書き下します。シグモイド関数
$$ \sigma(x) = \frac{1}{1 + e^{-x}} $$
に対して、SiLUは
$$ \begin{equation} \text{SiLU}(x) = x\,\sigma(x) = \frac{x}{1 + e^{-x}} \end{equation} $$
と定義されます。ここから基本性質を1つずつ確認します。
性質1: 正側の漸近挙動 — ReLUに一致する
$x \to +\infty$ のとき $e^{-x} \to 0$ なので $\sigma(x) \to 1$、したがって
$$ \text{SiLU}(x) = \frac{x}{1+e^{-x}} \to x \quad (x \to +\infty) $$
より正確に誤差を評価すると、$\frac{1}{1+e^{-x}} = 1 – e^{-x} + O(e^{-2x})$ なので
$$ \text{SiLU}(x) = x – x e^{-x} + O(x e^{-2x}) $$
となり、$x$ からのずれは $x e^{-x}$ のオーダーで指数的に小さくなります。$x = 5$ なら $5e^{-5} \approx 0.034$、$x = 10$ なら $10e^{-10} \approx 4.5\times 10^{-4}$。実質的に、正の大きな領域ではSiLUは恒等写像です。ここはReLUと同じで、「上に有界でない」という活性化関数として重要な性質を保っています。上に有界だと(tanhやシグモイドのように)出力が飽和して勾配が消えてしまうので、この性質は譲れません。
性質2: 負側の漸近挙動 — 0に落ちるが、落ち方が緩い
$x \to -\infty$ のとき $\sigma(x) \approx e^{x}$ なので
$$ \text{SiLU}(x) \approx x e^{x} \to 0 \quad (x \to -\infty) $$
$x e^x$ は $x \to -\infty$ で0に収束しますが、$x$ が負なので負の側から0に近づきます。つまりSiLUの出力は負の領域でわずかに負の値を取り、しかも下に有界です(後で示すように最小値は $-0.278$)。ReLUが「負側は問答無用でゼロ」なのに対し、SiLUは「負側は小さな負の値を返してから、ゆっくりゼロへ」という振る舞いをします。この「ゆっくり」が後で効いてきます。

左の片対数プロットでは、$x$ からのずれが直線に乗っています。片対数で直線ということは指数的な減衰そのもので、しかも青い実測線が赤い破線 $xe^{-x}$ にぴったり重なることから、上で導いた漸近評価が正しいと確認できます。右のパネルは負側の拡大図で、SiLU(青)が $x=-1.278$ で底を打ってから0へ戻るのに対し、ReLU(灰の破線)は $x<0$ で完全に0に張り付いています。$x=-6$ でもSiLUは $-0.0148$ という値を返していて、「潰れきらない」ことが目で見て取れます。
性質3: 原点近傍のテイラー展開
原点近傍の振る舞いを見るために、シグモイドを展開します。$\sigma$ の3次までの展開は
$$ \sigma(x) = \frac{1}{2} + \frac{x}{4} – \frac{x^3}{48} + O(x^5) $$
です($\sigma$ は $(0, 1/2)$ について点対称なので偶数次の項が消えます)。両辺に $x$ を掛けると
$$ \begin{equation} \text{SiLU}(x) = \frac{x}{2} + \frac{x^2}{4} – \frac{x^4}{48} + O(x^6) \end{equation} $$
原点付近では 「傾き $1/2$ の直線 + 上に開いた放物線」 です。ReLUが原点で折れ曲がる(左からの傾き0、右からの傾き1)のに対し、SiLUは原点をなめらかに通り、その傾きはちょうど左右の平均 $1/2$ になっています。二次の項 $x^2/4$ があるおかげで、$x$ が正側に増えると傾きが徐々に1へ、負側に減ると傾きが徐々に0へ近づきます。ReLUの折れ線を、二次関数で丸めたものというイメージがぴったりです。

左の図で、赤い一点鎖線(2次まで)が原点近傍でSiLU(青)にぴったり張り付き、$|x|$ が大きくなるにつれて放物線らしく上に離れていくのがわかります。ReLU(灰の破線)と比べると、SiLUは原点の折れ目を放物線で丸めた形になっていることが視覚的に確認できます。右の対数軸プロットはその精度を定量化したもので、$x=1$ における打ち切り誤差は1次で $0.231$、2次で $0.0189$、4次で $0.00189$ と、次数を上げるたびに1桁ずつ小さくなっています。
性質4: 無限回微分可能($C^\infty$)
$\sigma$ は $C^\infty$ 級で、その積 $x\sigma(x)$ も $C^\infty$ 級です。ReLUが $x=0$ で微分不可能($C^0$ だが $C^1$ ではない)なのに対し、SiLUは何回でも微分できます。最適化の観点では、これは損失地形が滑らかになることを意味します。勾配法は関数の局所的な滑らかさを前提に動くので、活性化関数に折れ目があると、そこを跨いだ瞬間に勾配が不連続に変わります。層を重ねるとこの折れ目は指数的に増え、損失地形は無数の小さな面がつなぎ合わさった多面体のようになります。SiLUを使えば、その多面体が丸みを帯びた曲面になる、というわけです。
ここまでで関数そのものの姿は掴めました。しかし活性化関数の本質は「学習中に勾配をどう通すか」にあります。次は導関数を実際に手で計算し、その形が何を語っているかを読み解きましょう。
導関数の導出
まずシグモイドの微分を確認します。$\sigma(x) = (1+e^{-x})^{-1}$ に合成関数の微分を適用すると
$$ \sigma'(x) = -(1+e^{-x})^{-2} \cdot (-e^{-x}) = \frac{e^{-x}}{(1+e^{-x})^2} $$
ここで $\dfrac{e^{-x}}{1+e^{-x}} = 1 – \dfrac{1}{1+e^{-x}} = 1 – \sigma(x)$ という書き換えを使うと、有名な形
$$ \begin{equation} \sigma'(x) = \sigma(x)\left(1 – \sigma(x)\right) \end{equation} $$
が得られます。この式は「シグモイドの傾きは、シグモイドの値だけで決まる」という便利な性質を表しています。
では本題です。$\text{SiLU}(x) = x \cdot \sigma(x)$ に積の微分則 $(fg)’ = f’g + fg’$ を適用します。$f = x$、$g = \sigma(x)$ とすれば $f’ = 1$、$g’ = \sigma(1-\sigma)$ なので
$$ \text{SiLU}'(x) = 1\cdot\sigma(x) + x\cdot\sigma(x)\left(1-\sigma(x)\right) $$
右辺の2項に共通する $\sigma(x)$ をくくり出すと、教科書でよく見る形になります。
$$ \begin{equation} \text{SiLU}'(x) = \sigma(x)\Bigl(1 + x\bigl(1 – \sigma(x)\bigr)\Bigr) \end{equation} $$
この式には別の書き方もあります。$\sigma(x) = \text{SiLU}(x)/x$ を使う代わりに、括弧を展開してから $x\sigma(x) = \text{SiLU}(x)$ で置き換えてみましょう。
$$ \text{SiLU}'(x) = \sigma(x) + x\sigma(x) – x\sigma(x)^2 = \sigma(x) + \text{SiLU}(x) – \text{SiLU}(x)\,\sigma(x) $$
最後の2項を $\text{SiLU}(x)$ でくくり、$\sigma$ の項を整理すると
$$ \begin{equation} \text{SiLU}'(x) = \text{SiLU}(x) + \sigma(x)\bigl(1 – \text{SiLU}(x)\bigr) \end{equation} $$
という自己参照的な形が出てきます。順伝播で計算した $\sigma(x)$ と $\text{SiLU}(x)$ をそのまま使い回せるので、実装上は指数関数を追加で呼ぶ必要がありません。深層学習フレームワークのバックワード実装はこの形を使っています。
導関数の値をいくつか押さえる
$x = 0$ を代入すると $\sigma(0) = 1/2$ なので
$$ \text{SiLU}'(0) = \frac{1}{2}\left(1 + 0\right) = \frac{1}{2} $$
原点での傾きはちょうど $1/2$ — ReLUの左右の傾き $0$ と $1$ の平均です。テイラー展開 $\text{SiLU}(x) \approx x/2 + x^2/4$ の一次係数と一致しています。
$x \to +\infty$ では $\sigma \to 1$、$x(1-\sigma) \to 0$($x e^{-x} \to 0$ より)なので $\text{SiLU}'(x) \to 1$。$x \to -\infty$ では $\sigma(x) \to 0$ が支配的で $\text{SiLU}'(x) \to 0$。両端の挙動はReLUの導関数(0または1)と一致します。

左の図でReLUの導関数(灰の破線)は $x=0$ で0から1へ垂直に跳ぶ階段ですが、SiLUの導関数は連続な曲線で、$x \approx 2.4$ で $1.0998$ という1を超えるピークを作ってから1へ収束します。負側では $x \approx -2.4$ で $-0.0998$ の谷を作っており、導関数が負になる区間の存在がひと目でわかります。右のパネルは次に示す対称性の数値的な検証で、$\text{SiLU}'(x)$ と $\text{SiLU}'(-x)$ を足した赤い点線が全域でぴったり $1$ の水平線に重なっています(最大誤差 $6.7\times10^{-16}$、つまり機械精度)。
隠れた対称性: $\text{SiLU}'(x) + \text{SiLU}'(-x) = 1$
導関数を $\text{SiLU}'(x) = \sigma(x) + x\,\sigma(x)(1-\sigma(x))$ の形で眺めると、きれいな対称性が見えてきます。第2項の $\sigma(x)(1-\sigma(x))$ は $\sigma’$ そのもので、$\sigma$ が点対称なことから 偶関数 です。したがって $x\,\sigma(x)(1-\sigma(x))$ は「奇関数 × 偶関数」で奇関数になります。一方 $\sigma(x) + \sigma(-x) = 1$ は $\sigma$ の基本性質です。この2つを足すと
$$ \text{SiLU}'(x) + \text{SiLU}'(-x) = \underbrace{\sigma(x)+\sigma(-x)}_{=1} + \underbrace{\bigl[x\sigma'(x) + (-x)\sigma'(-x)\bigr]}_{=0} = 1 $$
つまり 導関数のグラフは点 $(0, 1/2)$ について点対称 です。「$x$ での勾配の通しやすさ」と「$-x$ での通しやすさ」を足すとちょうど1になる、という美しい関係が成り立っています。この対称性から、導関数の最大値と最小値を足すと必ず1になることも即座にわかります。
導関数の最大・最小 — 二階微分から
導関数の極値を求めるため、もう一度微分します。$\text{SiLU}'(x) = \sigma + x\sigma(1-\sigma)$ の各項を微分すると、第1項は $\sigma(1-\sigma)$、第2項は積の微分で
$$ \frac{d}{dx}\bigl[x\sigma(1-\sigma)\bigr] = \sigma(1-\sigma) + x\frac{d}{dx}\bigl[\sigma(1-\sigma)\bigr] $$
ここで $\dfrac{d}{dx}[\sigma(1-\sigma)] = \sigma'(1-\sigma) + \sigma(-\sigma’) = \sigma'(1-2\sigma) = \sigma(1-\sigma)(1-2\sigma)$ を代入します。全部まとめて $\sigma(1-\sigma)$ をくくり出すと
$$ \begin{equation} \text{SiLU}”(x) = \sigma(x)\bigl(1-\sigma(x)\bigr)\Bigl(2 + x\bigl(1 – 2\sigma(x)\bigr)\Bigr) \end{equation} $$
$\sigma(1-\sigma) > 0$ は常に成り立つので、$\text{SiLU}”(x) = 0$ となるのは
$$ 2 + x\left(1 – 2\sigma(x)\right) = 0 $$
のときです。ここで $1 – 2\sigma(x) = -\tanh(x/2)$ という関係(シグモイドとtanhの関係 $\sigma(x) = \frac{1}{2}(1+\tanh(x/2))$ から従います)を使うと、条件は驚くほどシンプルになります。
$$ x\tanh\!\left(\frac{x}{2}\right) = 2 $$
左辺は偶関数なので、解は $\pm x_c$ の形で対になります。数値的に解くと $x_c \approx 2.3994$ です。このとき
$$ \text{SiLU}'(2.3994) \approx 1.0998, \qquad \text{SiLU}'(-2.3994) \approx -0.0998 $$
先ほどの対称性 $\text{SiLU}'(x)+\text{SiLU}'(-x)=1$ が、$1.0998 + (-0.0998) = 1$ としてきちんと成り立っていますね。
この2つの数値には重要な意味があります。導関数の最大値が1を超える(約1.10)こと、そして導関数が負の値(約 $-0.10$)を取ることです。前者は、SiLUが入力の一部の範囲で勾配をわずかに増幅することを意味します。後者は、SiLUが単調増加関数ではないことの直接的な証拠です。
導関数が負になる区間があるということは、関数そのものがどこかで減少して谷を作っているということです。次はその谷の底を、数値解ではなく厳密な閉形式で求めてみましょう。
非単調性 — 最小値をランベルトW関数で厳密に求める
ReLU、Leaky ReLU、ELU、softplus。定番の活性化関数はどれも単調増加です。「入力が大きいほど出力も大きい」というのは自然な要請に見えます。ところがSiLUはこれを破ります。$x$ が $-\infty$ から増えていくと、いったん出力は下がっていき、$x \approx -1.28$ で底を打ち、そこから上昇に転じるのです。この「負側の小さな谷」がSiLUの特徴的な性質で、GELUやMishも共有しています。
谷の底を求めましょう。$\text{SiLU}'(x) = 0$ とおきます。$\sigma(x) > 0$ は常に成り立つので、括弧の中がゼロになる条件を解けばよいことになります。
$$ 1 + x\bigl(1-\sigma(x)\bigr) = 0 $$
ここで $1 – \sigma(x) = \sigma(-x) = \dfrac{1}{1+e^{x}}$ を代入すると
$$ 1 + \frac{x}{1+e^{x}} = 0 \quad \Longleftrightarrow \quad x = -\left(1 + e^{x}\right) $$
移項して整理します。
$$ x + 1 = -e^{x} $$
ここで $u = x+1$ と置き換えると $x = u – 1$ なので
$$ u = -e^{u-1} = -\frac{e^{u}}{e} $$
両辺に $e^{-u}$ を掛けて $u$ の項を左辺に集めます。
$$ u\,e^{-u} = -\frac{1}{e} $$
さらに両辺に $-1$ を掛け、$t = -u$ と置くと $u e^{-u} = (-t)e^{t}$ なので
$$ t\,e^{t} = \frac{1}{e} $$
この形はランベルトW関数の定義そのものです。$W(z)$ は $we^{w} = z$ を満たす $w$ として定義されるので
$$ t = W\!\left(\frac{1}{e}\right) \approx 0.278465 $$
$u = -t$、$x = u – 1$ と戻すと、最小値を取る点は
$$ \begin{equation} x^{*} = -1 – W\!\left(\frac{1}{e}\right) \approx -1.278465 \end{equation} $$
と厳密に書けます。
最小値そのものはもっと簡単に出る
さて、最小値 $\text{SiLU}(x^*) = x^*\sigma(x^*)$ を計算するには $\sigma(x^*)$ が要りそうですが、実は先ほどの条件式を使えば一発で出ます。停留点の条件は $x(1-\sigma(x)) = -1$、すなわち
$$ x – x\sigma(x) = -1 \quad \Longrightarrow \quad x\,\sigma(x) = x + 1 $$
左辺はまさに $\text{SiLU}(x)$ です。つまり停留点においては $\text{SiLU}(x^*) = x^* + 1$ が成り立つという、うまい関係が使えます。$x^* = -1 – W(1/e)$ を代入すると
$$ \begin{equation} \min_{x} \text{SiLU}(x) = x^* + 1 = -W\!\left(\frac{1}{e}\right) \approx -0.278465 \end{equation} $$
最小値は $-W(1/e) \approx -0.2785$、それを取る点は $-1-W(1/e) \approx -1.2785$。「最小値の座標」と「最小値」がちょうど1だけずれているという、覚えやすい関係になっているのはこのためです。数値実験でも scipy.optimize.minimize_scalar が $x = -1.278465$、値 $-0.278465$ を返し、閉形式と小数点以下6桁まで一致します(このあと実験2で12桁まで確かめます)。

左の図は、置換を重ねてたどり着いた方程式 $te^t = 1/e$ を素直に描いたものです。単調増加する $te^t$ と水平線 $1/e$ の交点がただ1つ(正の側で)あり、その $t$ 座標が $W(1/e) = 0.278465$ であることが見て取れます。右のパネルでは、SiLU(青)の谷の底で導関数(緑の点線)がちょうどゼロを横切っており、同時に谷の底が直線 $x+1$(オレンジ)の上に乗っています。この交点こそが「停留点では $\text{SiLU}(x^*) = x^*+1$」という関係の視覚的な意味で、最小値と最小点がちょうど1だけずれる理由がこの一枚に集約されています。
なぜ非単調性が役に立つのか
では、この谷は学習に何をもたらすのでしょうか。3つの見方があります。
(1)弱い入力に対する「ソフトな抑制」。$x$ が $-3$ から $-1.28$ の範囲では、SiLUは入力が大きくなるほど出力が小さくなります。これは「中途半端に負の信号は、強く負の信号よりも強く抑え込む」という挙動です。ReLUがこの範囲を一律ゼロで潰すのに対し、SiLUは「どのくらい負か」の情報を(符号を反転させた形で)わずかに残します。
(2)ゼロでない勾配による「復活可能性」。ReLUにはdying ReLUという有名な病があります。あるニューロンの入力が学習中にすべて負の領域に入ってしまうと、そのニューロンの勾配は完全にゼロになり、以後どんなデータが来ても重みが更新されなくなります。文字通り「死ぬ」わけです。SiLUの導関数は $x=-3$ で $-0.088$、$x=-6$ で $-0.012$、$x=-10$ で $-4.1\times10^{-4}$。小さいですが厳密にゼロではありません。負の領域に落ちたニューロンにも細い勾配が流れ続けるので、原理的にはいつでも生き返れます。
(3)出力分布の中心化。谷があることでSiLUの出力は小さな負の値を取れます。ReLUの出力は必ず非負なので、次の層への入力の平均が正にバイアスされ、勾配の方向が偏る(すべての重みが同じ符号方向に押される)ことが知られています。ELUなどが負側の値を許すのはこの偏りを減らすためで、SiLUの谷も同じ効果を持ちます。
ただし、注意も必要です。非単調性そのものが精度向上の主因かどうかは、実は決着していません。Ramachandranらの論文は自動探索で $x\sigma(\beta x)$ を見つけたと報告していますが、「なぜ効くのか」は仮説の域を出ないと述べています。後の節の実験でも、SiLUがReLUに常に勝つわけではないことを確認します。「滑らかで、負側に少し漏らして、上に有界でない」という組み合わせが経験的に良いという以上のことは、あまり強く主張しないほうが誠実です。
ここまでは $\beta = 1$ に固定した $x\sigma(x)$ を見てきました。実は $\sigma$ の内側にスケールを入れると、SiLUは線形関数とReLUを結ぶ「連続した橋」になります。次はその一般形を見ましょう。
Swish(β付きの一般形)— 線形関数とReLUを結ぶ橋
Ramachandranらが提案した Swish は、シグモイドの中にスケールパラメータ $\beta$ を持つ形でした。
$$ \begin{equation} \text{Swish}_\beta(x) = x\,\sigma(\beta x) = \frac{x}{1+e^{-\beta x}} \end{equation} $$
$\beta = 1$ がSiLUです。$\beta$ を動かすと何が起きるか、両極端を調べてみましょう。
$\beta \to 0$ の極限。$\sigma(0) = 1/2$ なので
$$ \lim_{\beta \to 0}\text{Swish}_\beta(x) = x \cdot \frac{1}{2} = \frac{x}{2} $$
単なる線形関数(傾き $1/2$)になります。非線形性が完全に消えるわけです。
$\beta \to \infty$ の極限。$x > 0$ なら $\beta x \to +\infty$ で $\sigma(\beta x) \to 1$、$x < 0$ なら $\sigma(\beta x) \to 0$。したがって
$$ \lim_{\beta \to \infty}\text{Swish}_\beta(x) = \begin{cases} x & (x > 0) \\ 0 & (x \le 0)\end{cases} = \text{ReLU}(x) $$
ReLUに一致します。つまり $\beta$ は「線形($\beta=0$)とReLU($\beta=\infty$)の間を連続的に補間するツマミ」なのです。SiLUはその中間、$\beta=1$ に位置します。この見方をすると、Swishは「ReLUの滑らかな緩和(smooth relaxation)」であることがはっきりします。
スケーリング則
$\beta$ を変えたときの形の変化には、簡単な相似関係があります。
$$ \text{Swish}_\beta(x) = x\,\sigma(\beta x) = \frac{1}{\beta}\bigl[(\beta x)\,\sigma(\beta x)\bigr] = \frac{1}{\beta}\,\text{SiLU}(\beta x) $$
つまり $\text{Swish}_\beta$ は SiLU を横に $1/\beta$ 倍、縦に $1/\beta$ 倍したものです。この関係から、最小値の位置と値も即座にわかります。
$$ x^*_\beta = \frac{-1-W(1/e)}{\beta} \approx \frac{-1.2785}{\beta}, \qquad \min \text{Swish}_\beta = \frac{-W(1/e)}{\beta} \approx \frac{-0.2785}{\beta} $$
$\beta = 0.5$ なら最小値 $-0.557$(谷が深い)、$\beta = 2$ なら $-0.139$(谷が浅い)。数値的にも $\beta=0.5$ で $x=-2.557$、値 $-0.557$、$\beta=2$ で $x=-0.639$、値 $-0.139$ と一致します。$\beta$ が大きいほどReLUに近づき、谷が浅くなって原点近くに寄る、という直感どおりの挙動です。

左の図では、$\beta$ を $0$ から $5$ まで動かすと曲線が直線 $x/2$ から黒い破線のReLUへ連続的に変形していく様子が見えます。$\beta=1$(太い線)がその中間、SiLUの位置です。右のパネルは、各 $\beta$ で数値最適化して求めた谷の位置(丸)と深さ(四角)を、理論値 $x^*/\beta$ と $-W(1/e)/\beta$ の曲線に重ねたもので、実測と理論のズレは最大でも $4\times10^{-8}$ しかありません。スケーリング則 $\text{Swish}_\beta(x) = \text{SiLU}(\beta x)/\beta$ が数値でも厳密に成り立っていることの確認になっています。
実装上は $\beta$ を学習可能なパラメータにすることもできます(チャネルごとに1つ持たせる、など)。ただし実務では $\beta=1$ 固定のSiLUが圧倒的によく使われます。パラメータを増やすコストに見合う改善が安定して得られないためです。
ここまでで、SiLUは「ReLUの滑らかな緩和」であると整理できました。ところで、同じく「ReLUの滑らかな緩和」を名乗る関数がもう1つあります。BERTやGPTで使われてきたGELUです。この2つはどういう関係にあるのでしょうか。
GELUとの関係 — 「確率的なゲート」という共通の見方
GELU(Gaussian Error Linear Unit)は次のように定義されます。
$$ \begin{equation} \text{GELU}(x) = x\,\Phi(x) = x\cdot\frac{1}{2}\left[1 + \text{erf}\!\left(\frac{x}{\sqrt{2}}\right)\right] \end{equation} $$
ここで $\Phi$ は標準正規分布の累積分布関数、$\text{erf}$ はガウス誤差関数です。
SiLUの定義 $x\sigma(x)$ と並べてみてください。構造がまったく同じです。どちらも「$x$ に、$[0,1]$ の値を取る何らかの累積分布関数を掛ける」形をしています。違いは掛ける分布だけです。
$$ \text{SiLU}(x) = x \cdot F_{\text{logistic}}(x), \qquad \text{GELU}(x) = x \cdot F_{\text{normal}}(x) $$
実は $\sigma(x)$ は標準ロジスティック分布の累積分布関数そのものです。となると、両者を統一的に解釈できます。ある確率変数 $Z$ を用意し、$Z \le x$ なら信号を通す、そうでなければ止める、というランダムなゲートを考えます。このゲートの出力の期待値は
$$ \mathbb{E}\bigl[x \cdot \mathbb{1}\{Z \le x\}\bigr] = x\,P(Z \le x) = x\,F_Z(x) $$
$Z$ を標準正規分布にすればGELU、標準ロジスティック分布にすればSiLUです。「入力が大きいほど高い確率でそのニューロンを生かす」という確率的な仕組みの期待値、というのが両者に共通する解釈です。GELUの原論文がドロップアウトとの類似から出発しているのは、この見方に基づいています。

左の図が「掛けるゲートの正体」です。ReLUは $x=0$ で0から1へ跳ぶ階段(灰の破線)を掛けているのに対し、SiLUはロジスティック分布のCDF(青)、GELUは正規分布のCDF(赤)という滑らかな確率を掛けています。青が赤より寝ているのは、ロジスティック分布の標準偏差が $1.814$ と正規分布の1より大きいためで、これが2つの関数の性格の違いを生みます。右のパネルは実際の関数の形で、SiLU・GELU・Mishのいずれも負側に谷を持ち、その深さが $-0.278$ / $-0.170$ / $-0.309$ と違うこと、$x=-4$ ではSiLUが $-0.0719$ を返す一方GELUは $-0.0001$ とほぼゼロに張り付くことが読み取れます。
なぜ数値的に近いのか
正規分布とロジスティック分布は、どちらも釣鐘型で左右対称です。ただしスケールが違います。標準ロジスティック分布の標準偏差は
$$ \text{sd}(Z_{\text{logistic}}) = \frac{\pi}{\sqrt{3}} \approx 1.8138 $$
で、標準正規分布の1よりかなり大きい。だから $\sigma(x)$ は $\Phi(x)$ より「なだらか」に立ち上がります。この差を埋めるには、シグモイドの中に係数を入れて横に縮めればよいわけです。すると
$$ \Phi(x) \approx \sigma(\beta x), \qquad \text{GELU}(x) \approx x\,\sigma(\beta x) $$
という近似が成り立ちます。$\beta$ の値は「何を合わせるか」で変わり、代表的なものは次の3通りです。
(a)分散を合わせる。ロジスティック分布の標準偏差を1にするには $\beta = \pi/\sqrt{3} \approx 1.8138$。
(b)原点での2次までのテイラー展開を合わせる。$\Phi(x) \approx 1/2 + x/\sqrt{2\pi}$ なので $\text{GELU}(x) \approx x/2 + x^2/\sqrt{2\pi}$。一方 $x\sigma(\beta x) \approx x/2 + \beta x^2/4$。二次の係数を等しくおくと
$$ \frac{\beta}{4} = \frac{1}{\sqrt{2\pi}} \quad \Longrightarrow \quad \beta = \frac{4}{\sqrt{2\pi}} \approx 1.5958 $$
(c)全区間での最大誤差を最小化する。数値的に $\beta$ を最適化すると $\beta \approx 1.773$、そのときの最大絶対誤差は約 $0.014$ です。
そして実際によく引用されるのが $\beta = 1.702$ です。
$$ \begin{equation} \text{GELU}(x) \approx x\,\sigma(1.702\,x) \end{equation} $$
これはHendrycksとGimpelが原論文で示した数値フィットの値で、上の(a)(b)(c)のどれとも微妙に違います。実測すると、この近似の最大絶対誤差は $x \approx \pm 2.27$ 付近で約 $0.020$(あとで見るように、差は偶関数なので山が左右に1つずつできます)。$\beta$ を最適化した $1.773$ の $0.014$ よりわずかに大きいものの、実用上は十分な精度です。要するに $1.702$ という数字に深い理論的必然性はなく、「だいたい合う値」 と理解しておけば十分です。
なお、GELUのもう1つのよく使われる近似は tanh を使うもので
$$ \text{GELU}(x) \approx \frac{1}{2}x\left[1 + \tanh\!\left(\sqrt{\tfrac{2}{\pi}}\left(x + 0.044715x^3\right)\right)\right] $$
こちらの最大誤差は約 $4.7 \times 10^{-4}$ と、シグモイド近似より2桁近く高精度です。PyTorchの nn.GELU(approximate='tanh') はこれを使っています。

左の差分プロットで、4本の曲線がすべて左右対称(偶関数)になっていることに注目してください。この対称性はあとで証明します。生のSiLUとGELUの差(青の破線)は $x=\pm 1.97$ で $0.193$ の山を2つ作り、$\beta$ を入れた近似(オレンジ)はその1/10以下に潰れています。右の棒グラフは同じ情報を対数軸でまとめたもので、$0.193 \to 0.0203 \to 0.0139 \to 0.000473$ と、選ぶ近似によって精度が3桁近く変わることが一目でわかります。
では SiLU と GELU はどのくらい違うのか
$\beta=1.702$ ではなく $\beta=1$(つまり生のSiLU)とGELUを比べるとどうなるでしょうか。数値的に調べると、両者の差の最大値は $x \approx 1.97$ 付近で約 $0.193$ もあります。決して無視できる差ではありません。具体的な値を並べると差の性格が見えます。
| $x$ | ReLU | SiLU | GELU |
|---|---|---|---|
| $-3$ | 0.000 | $-0.142$ | $-0.004$ |
| $-2$ | 0.000 | $-0.238$ | $-0.046$ |
| $-1$ | 0.000 | $-0.269$ | $-0.159$ |
| $0$ | 0.000 | 0.000 | 0.000 |
| $1$ | 1.000 | 0.731 | 0.841 |
| $2$ | 2.000 | 1.762 | 1.955 |
| $3$ | 3.000 | 2.858 | 2.996 |
読み取れることは2つです。正側ではGELUのほうがReLUに近い($x=2$ でGELU 1.955 対 SiLU 1.762)。ロジスティック分布のほうが裾が広いぶん、SiLUのゲートは同じ入力でも開き切らないためです。一方 負側の遠方ではSiLUのほうがよく漏らす($x=-3$ でSiLU $-0.142$ 対 GELU $-0.004$)。GELUの谷は $x=-0.752$ で深さ $-0.170$ と浅く、$-3$ まで行くとほぼゼロに張り付きます。SiLUは $-3$ でもまだ $-0.142$ を返し、勾配も $-0.088$ 残っています。
「GELUは正側でReLUに忠実、SiLUは負側で情報を残す」 — これが両者の性格の違いです。どちらが良いかはタスク次第で、実務では「BERT系はGELU、Llama系はSiLU(SwiGLU)」のように、モデルファミリの慣習で決まっている面が大きいのが実情です。
さて、ここまでは活性化関数を「1入力1出力の関数」として見てきました。しかし冒頭で触れたSwiGLUは、その枠を超えて層の構造そのものを変えます。次はその話に進みましょう。
SwiGLU — SiLUをゲートとして使うFFN
従来のFFNの構造と、その限界
Transformerの各ブロックには、Attentionの後にFFN(Feed-Forward Network、position-wise MLP)が入っています。標準的な形は
$$ \begin{equation} \text{FFN}(\bm{x}) = f(\bm{x}\bm{W}_1 + \bm{b}_1)\bm{W}_2 + \bm{b}_2 \end{equation} $$
で、$\bm{x} \in \mathbb{R}^{d}$、$\bm{W}_1 \in \mathbb{R}^{d\times d_{ff}}$、$\bm{W}_2 \in \mathbb{R}^{d_{ff}\times d}$、活性化 $f$ は元論文ではReLUでした。$d_{ff} = 4d$ が慣例です。
この構造をよく見ると、隠れ層の各ユニット $j$ が計算しているのは
$$ h_j = f\bigl(\bm{w}_j^\top \bm{x}\bigr) $$
です。$\bm{w}_j$ は $\bm{W}_1$ の $j$ 列目。つまり 各ユニットは入力 $\bm{x}$ を「たった1つの方向」に射影した値だけを見ている。こういう関数をリッジ関数と呼びます。FFN全体の出力は、リッジ関数の線形和です。
$$ \text{FFN}(\bm{x}) = \sum_{j=1}^{d_{ff}} f\bigl(\bm{w}_j^\top\bm{x}\bigr)\,\bm{u}_j $$
ここで $\bm{u}_j$ は $\bm{W}_2$ の $j$ 行目です。理論上はユニット数を増やせば任意の連続関数を近似できます(普遍近似定理)が、「入力の異なる2成分の掛け算」のような相互作用を作るのが構造的に苦手という弱点があります。
掛け算がなぜ高くつくか — 分極恒等式で見る
具体的に $x_1 x_2$ という単純な積を作ることを考えましょう。リッジ関数の和で作るには、分極恒等式
$$ x_1 x_2 = \frac{1}{4}\left[(x_1+x_2)^2 – (x_1-x_2)^2\right] $$
を使うのが定石です。つまり $\bm{w} = (1,1)$ 方向と $\bm{w}=(1,-1)$ 方向の2つのユニットを使い、それぞれで二乗を作って引き算する必要があります。1つの積を作るのに2ユニット。これが $d$ 次元の全ペア $\binom{d}{2}$ について必要だとすると、コストは膨らみます。
GLUとSwiGLU
Dauphinらが2017年に提案したGLU(Gated Linear Unit)は、この問題に直接手を打ちます。
$$ \text{GLU}(\bm{x}) = (\bm{x}\bm{W} + \bm{b}) \otimes \sigma(\bm{x}\bm{V} + \bm{c}) $$
$\otimes$ は要素ごとの積(アダマール積)です。射影を2本用意し、片方を「値」、もう片方を「ゲート」として掛け合わせます。Shazeerが2020年に、このゲートの活性化関数をシグモイドからSiLUに替えたのがSwiGLUです。
$$ \begin{equation} \text{SwiGLU}(\bm{x}) = (\bm{x}\bm{W}) \otimes \text{SiLU}(\bm{x}\bm{V}) \end{equation} $$
これをFFNに組み込むと
$$ \begin{equation} \text{FFN}_{\text{SwiGLU}}(\bm{x}) = \Bigl[(\bm{x}\bm{W}) \otimes \text{SiLU}(\bm{x}\bm{V})\Bigr]\bm{W}_2 \end{equation} $$
となります。行列が $\bm{W}, \bm{V}, \bm{W}_2$ の3つに増えました(バイアス項は省略されるのが一般的です)。

2つの構成を並べると、違いが構造として見えます。左の従来型は「線形 → 活性化 → 線形」の一本道で、活性化はその途中に挟まる関数にすぎません。右のSwiGLUでは入力が2本に分岐し、片方(値)はそのまま、もう片方(ゲート)はSiLUを通ってから、要素ごとの積で合流します。つまりSiLUは「途中に挟む関数」ではなく「掛け算の相手を作る部品」として使われている。この配置の違いが、次に見る表現力の差を生みます。
なぜ表現力で有利なのか
隠れユニット $j$ が何を計算しているかを比べます。
- ReLU FFN: $h_j = \text{ReLU}(\bm{w}_j^\top\bm{x})$ — 1方向への射影の関数
- SwiGLU: $h_j = (\bm{w}_j^\top\bm{x}) \cdot \text{SiLU}(\bm{v}_j^\top\bm{x})$ — 2方向への射影の積
原点近傍で $\text{SiLU}(z) \approx z/2 + z^2/4$ を代入して展開してみましょう。$a = \bm{w}_j^\top\bm{x}$、$b = \bm{v}_j^\top\bm{x}$ と置くと
$$ h_j \approx a\left(\frac{b}{2} + \frac{b^2}{4}\right) = \frac{1}{2}ab + \frac{1}{4}ab^2 $$
第1項の $ab$ は、行列の言葉で書けば
$$ ab = (\bm{w}_j^\top\bm{x})(\bm{v}_j^\top\bm{x}) = \bm{x}^\top\bigl(\bm{w}_j\bm{v}_j^\top\bigr)\bm{x} $$
1ユニットで、任意のランク1行列 $\bm{w}_j\bm{v}_j^\top$ による二次形式が作れます。$\bm{w}_j = \bm{e}_1$、$\bm{v}_j = \bm{e}_2$ と選べば $x_1x_2$ そのものです。1ユニットで1つのクロス項。
一方、素のFFNのユニットはどうでしょうか。滑らかな活性化 $f$ を使う場合、原点まわりで $f(z) = f(0) + f'(0)z + \frac{1}{2}f”(0)z^2 + \cdots$ と展開できるので、二次の項は
$$ \frac{1}{2}f”(0)\bigl(\bm{w}_j^\top\bm{x}\bigr)^2 = \frac{1}{2}f”(0)\,\bm{x}^\top\bigl(\bm{w}_j\bm{w}_j^\top\bigr)\bm{x} $$
に限られます。行列 $\bm{w}_j\bm{w}_j^\top$ は対称なランク1行列で、しかも半正定値です。出力側の重み $\bm{u}_j$ の符号を反転させれば負定値にもできますが、いずれにせよ1ユニットが生む二次形式は符号が定まったランク1形式にしかなりません。ところが $x_1x_2$ に対応する行列 $\frac{1}{2}(\bm{e}_1\bm{e}_2^\top + \bm{e}_2\bm{e}_1^\top)$ は固有値が $+1/2$ と $-1/2$ の不定符号です。したがって1ユニットでは絶対に作れず、正の寄与と負の寄与を持つ2ユニットを重ねる必要があります — これが先ほどの分極恒等式の正体です。
ReLUは原点で微分できないのでこのテイラー展開はそのまま使えませんが、「各ユニットが1方向への射影しか見ていない」というリッジ関数の性質は変わりません。任意のリッジ関数 $f(\bm{w}^\top\bm{x})$ は $\bm{w}$ と直交する方向の変化にまったく反応しないので、単独では $x_1x_2$ のような「2方向が同時に効く」項を表現できないのです。
まとめると SwiGLUは、乗法的な相互作用を作るコストが素のFFNの半分になります。第2項の $ab^2$ に至っては3次の相互作用で、これも1ユニットで出てきます。「掛け算が構造として組み込まれている」ことの効き目は、こういうところに現れます。

4枚のヒートマップを見比べると、議論の中身が絵になります。左上が目標の $x_1x_2$ で、対角方向に正、反対角方向に負という鞍型の模様です。右上のSwiGLU 1ユニット $2x_1\text{SiLU}(x_2)$ はこれとよく似た鞍型で、相関係数 $0.844$ が出ています。ところが左下のリッジ関数 $\text{ReLU}((x_1+x_2)/\sqrt{2})$ は $x_1+x_2$ に直交する方向にまったく反応せず、縞模様にしかなりません。鞍型を作るには右下のように符号違いの2ユニットの差が必要で、これが分極恒等式の正体です。
もう1つの見方は動的な計算です。ゲート $\text{SiLU}(\bm{x}\bm{V})$ は固定の重みではなく、入力によって最小 $-0.278$ から上は青天井まで変わるデータ依存の係数です。だから「このトークンではこのチャネルを大きく増幅し、別のトークンでは0付近まで抑え込む」という切り替えが、追加のパラメータなしに実現します。Attentionが「どのトークンを見るか」を入力に応じて決めるのに対し、SwiGLUは「どのチャネルをどれだけ通すか」を入力に応じて決めている、と捉えられます。Transformerが強い理由の一つは、こうした乗法的・データ依存の演算をブロックの隅々に配置したことだと考えると、SwiGLUの位置づけがすっきり見えてきます。
パラメータ数を揃える
行列が3つになったので、素朴に $d_{ff} = 4d$ のままだとパラメータが1.5倍に増えてしまいます。公平な比較のため、隠れ次元を $2/3$ に縮めます。
- ReLU FFN: $2 d\, d_{ff}$ 個のパラメータ
- SwiGLU FFN: $3 d\, d_{ff}’$ 個のパラメータ
これらを等しくおくと $d_{ff}’ = \frac{2}{3}d_{ff}$。$d_{ff}=4d$ に対しては
$$ d_{ff}’ = \frac{2}{3}\times 4d = \frac{8}{3}d \approx 2.667\,d $$
Llamaの実装で hidden_dim = int(2 * 4 * dim / 3) のような式(さらに実装効率のため64などの倍数に丸める)が出てくるのは、この計算のためです。Shazeerの論文は、このパラメータ数と計算量を揃えた条件でGLUの各変種を比較し、SwiGLUを含むGLU系がベースラインより一貫して良いperplexityに到達したと報告しています。
理屈の説明はここまでです。ここからは実際にコードを動かして、これまでの主張を1つずつ確かめていきましょう。
Pythonでの実装と可視化
実験1: 各活性化関数とその導関数を重ね描きする
まずはSiLU・ReLU・GELU・Mishを同じ軸に描き、続けて導関数も描いて、これまで導いた性質(谷の位置、導関数の最大1.10・最小 $-0.10$、原点での傾き $1/2$)を目で確認します。
import numpy as np
import matplotlib, matplotlib.pyplot as plt
from scipy.special import erf
# 日本語フォント設定
for cand in ["Hiragino Sans", "Yu Gothic", "Noto Sans CJK JP", "IPAexGothic", "Meiryo"]:
if any(cand == f.name for f in matplotlib.font_manager.fontManager.ttflist):
plt.rcParams["font.family"] = cand
break
plt.rcParams["axes.unicode_minus"] = False
sigmoid = lambda x: 1.0 / (1.0 + np.exp(-x))
silu = lambda x: x * sigmoid(x)
relu = lambda x: np.maximum(x, 0.0)
gelu = lambda x: x * 0.5 * (1.0 + erf(x / np.sqrt(2.0)))
softplus= lambda x: np.log1p(np.exp(-np.abs(x))) + np.maximum(x, 0.0) # 数値安定版
mish = lambda x: x * np.tanh(softplus(x))
# 導関数(解析形)
d_silu = lambda x: sigmoid(x) * (1.0 + x * (1.0 - sigmoid(x)))
d_relu = lambda x: (x > 0).astype(float)
d_gelu = lambda x: 0.5 * (1.0 + erf(x / np.sqrt(2.0))) \
+ x * np.exp(-x**2 / 2.0) / np.sqrt(2.0 * np.pi)
x = np.linspace(-6, 4, 1200)
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
axes[0].plot(x, relu(x), "--", lw=1.8, label="ReLU")
axes[0].plot(x, silu(x), lw=2.4, label="SiLU(Swish)")
axes[0].plot(x, gelu(x), lw=1.8, label="GELU")
axes[0].plot(x, mish(x), ":", lw=1.8, label="Mish")
axes[0].scatter([-1.278465], [-0.278465], color="crimson", zorder=5,
label="SiLUの最小点 $(-1.278,\\,-0.278)$")
axes[0].set_title("活性化関数の比較:負側にできる小さな谷")
axes[0].set_xlabel("入力 $x$"); axes[0].set_ylabel("出力")
axes[0].axhline(0, color="gray", lw=0.6); axes[0].axvline(0, color="gray", lw=0.6)
axes[0].legend(); axes[0].grid(alpha=0.3)
続けて右側のパネルに導関数を描きます。ここが「勾配がどう流れるか」を直接示す図です。
axes[1].plot(x, d_relu(x), "--", lw=1.8, label="ReLU の導関数(0/1 の階段)")
axes[1].plot(x, d_silu(x), lw=2.4, label="SiLU の導関数")
axes[1].plot(x, d_gelu(x), lw=1.8, label="GELU の導関数")
axes[1].axhline(1.0, color="gray", lw=0.6, ls=":")
axes[1].scatter([2.3994, -2.3994], [1.0998, -0.0998], color="crimson", zorder=5,
label="SiLU導関数の最大 1.100 / 最小 $-0.100$")
axes[1].scatter([0.0], [0.5], color="darkgreen", zorder=5, label="原点の傾き $1/2$")
axes[1].set_title("導関数の比較:ReLUは崖、SiLUは滑らかな坂")
axes[1].set_xlabel("入力 $x$"); axes[1].set_ylabel("導関数の値")
axes[1].legend(fontsize=9); axes[1].grid(alpha=0.3)
plt.tight_layout(); plt.show()
# 数値チェック
for v in [-3.0, -2.3994, -1.278465, 0.0, 2.3994, 3.0]:
print(f"x={v:9.4f} SiLU={silu(v):9.5f} SiLU'={d_silu(v):9.5f}")
出力は次のようになります。
x= -3.0000 SiLU= -0.14228 SiLU'= -0.08810
x= -2.3994 SiLU= -0.19967 SiLU'= -0.09984
x= -1.2785 SiLU= -0.27846 SiLU'= -0.00000
x= 0.0000 SiLU= 0.00000 SiLU'= 0.50000
x= 2.3994 SiLU= 2.19973 SiLU'= 1.09984
x= 3.0000 SiLU= 2.85772 SiLU'= 1.08810
左の図から、SiLU・GELU・Mishのいずれも負側に谷を持つ非単調な関数であること、そして谷の深さと位置が関数ごとに違うこと(SiLU: $-0.278$ / GELU: $-0.170$ / Mish: $-0.309$)が読み取れます。SiLUの谷はGELUより深く、$x=-4$ 付近でもまだゼロ軸から目に見えて離れています($\text{SiLU}(-4)=-0.072$ に対し $\text{GELU}(-4)=-0.0001$)。これが「SiLUのほうが負側の情報をよく残す」ことの視覚的な証拠です。
右の導関数の図はさらに雄弁です。ReLUの導関数は $x=0$ で0から1へ垂直にジャンプする階段ですが、SiLUの導関数は連続曲線で、しかも $x \approx 2.4$ で $1.0998$ という1を超えるピークを作ってから1に収束します。負側では $x \approx -2.4$ で $-0.0998$ の谷を作り、そこから0に漸近します。数値出力でも $\text{SiLU}'(-1.278465) = -0.00000$(機械精度でゼロ)となっており、ランベルトW関数で求めた停留点が正しいことが確認できました。また $1.09984 + (-0.09984) = 1$ という対称性も数値で成立しています。
この図で「ReLUは崖、SiLUは坂」という表現の中身が具体的になりました。次は、閉形式で求めた最小値が本当に正しいかを、独立な方法で検証します。
実験2: 最小値の閉形式をランベルトW関数で検証する
$-W(1/e)$ という閉形式は、数値最適化の結果と一致するでしょうか。SciPyの lambertw と minimize_scalar を突き合わせます。
import numpy as np
from scipy.special import lambertw
from scipy.optimize import minimize_scalar
sigmoid = lambda x: 1.0 / (1.0 + np.exp(-x))
silu = lambda x: x * sigmoid(x)
# 閉形式:x* = -1 - W(1/e), min = -W(1/e)
W = lambertw(np.exp(-1.0)).real
x_star_closed = -1.0 - W
min_closed = -W
# 数値最適化(区間 [-5, 0] で下に凸な谷を探す)
res = minimize_scalar(silu, bounds=(-5.0, 0.0), method="bounded",
options={"xatol": 1e-12})
print(f"W(1/e) = {W:.12f}")
print(f"閉形式 x* = -1-W(1/e) = {x_star_closed:.12f}")
print(f"数値解 x* = {res.x:.12f}")
print(f"閉形式 min = -W(1/e) = {min_closed:.12f}")
print(f"数値解 min = {res.fun:.12f}")
print(f"停留点の恒等式 SiLU(x*) == x*+1 : "
f"{silu(x_star_closed):.12f} vs {x_star_closed + 1.0:.12f}")
# Swish_beta のスケーリング則も確認
for beta in [0.5, 1.0, 1.5, 2.0]:
f = lambda x, b=beta: x * sigmoid(b * x)
r = minimize_scalar(f, bounds=(-8.0, 0.0), method="bounded")
print(f"beta={beta:4.1f} 数値 x*={r.x:8.5f} min={r.fun:8.5f} "
f"理論 x*={x_star_closed/beta:8.5f} min={min_closed/beta:8.5f}")
実行結果は次の通りです。
W(1/e) = 0.278464542761
閉形式 x* = -1-W(1/e) = -1.278464542761
数値解 x* = -1.278464542621
閉形式 min = -W(1/e) = -0.278464542761
数値解 min = -0.278464542761
停留点の恒等式 SiLU(x*) == x*+1 : -0.278464542761 vs -0.278464542761
beta= 0.5 数値 x*=-2.55693 min=-0.55693 理論 x*=-2.55693 min=-0.55693
beta= 1.0 数値 x*=-1.27847 min=-0.27846 理論 x*=-1.27846 min=-0.27846
beta= 1.5 数値 x*=-0.85231 min=-0.18564 理論 x*=-0.85231 min=-0.18564
beta= 2.0 数値 x*=-0.63923 min=-0.13923 理論 x*=-0.63923 min=-0.13923
最小点 $x^*$ は数値最適化の打ち切り精度の分だけ末尾が違いますが($-1.2784645427$ 対 $-1.2784645426$)、最小値そのものは小数点以下12桁まで完全に一致しました。谷の底では傾きがゼロなので、$x^*$ が10桁目でずれても関数値は動かない — 極値まわりの二次的な振る舞いがそのまま出ています。導出の途中で使った置換 $u = x+1$、$t = -u$ と、$te^t = 1/e$ からのランベルトW関数への帰着が正しかったことになります。「停留点では $\text{SiLU}(x^*) = x^*+1$」という恒等式も、$-0.278464542761 = -1.278464542761 + 1$ として厳密に成立しています。
$\beta$ を変えたスケーリング則も、理論値 $x^*_\beta = x^*/\beta$、$\min_\beta = \min/\beta$ と数値解がぴったり一致します。$\beta$ が大きいほど谷が浅く原点寄りになり、$\beta\to\infty$ でReLUに収束していく様子が数字で追えます。
閉形式の検証はできました。次はGELUとの近さを実測してみましょう。
実験3: GELU $\approx x\sigma(1.702x)$ の精度を測る
$1.702$ という係数がどこまで正確なのか、そして生のSiLU($\beta=1$)とGELUの差がどのくらいあるのかを、同じ図の上で比較します。
import numpy as np
import matplotlib.pyplot as plt
from scipy.special import erf
from scipy.optimize import minimize_scalar
sigmoid = lambda x: 1.0 / (1.0 + np.exp(-x))
gelu = lambda x: x * 0.5 * (1.0 + erf(x / np.sqrt(2.0)))
swish_b = lambda x, b: x * sigmoid(b * x)
gelu_th = lambda x: 0.5 * x * (1.0 + np.tanh(np.sqrt(2/np.pi)*(x + 0.044715*x**3)))
xs = np.linspace(-6, 6, 20001)
# 最大絶対誤差を最小にする beta を数値探索
obj = lambda b: np.max(np.abs(gelu(xs) - swish_b(xs, b)))
best = minimize_scalar(obj, bounds=(1.0, 2.5), method="bounded")
for name, y in [("SiLU (beta=1)", swish_b(xs, 1.0)),
("x*sigmoid(1.702x)", swish_b(xs, 1.702)),
(f"x*sigmoid({best.x:.3f}x)", swish_b(xs, best.x)),
("tanh近似", gelu_th(xs))]:
e = np.abs(gelu(xs) - y)
print(f"{name:24s} 最大誤差={e.max():.6f} (x={xs[e.argmax()]:+.3f})")
出力は次のようになります。
SiLU (beta=1) 最大誤差=0.193002 (x=+1.966)
x*sigmoid(1.702x) 最大誤差=0.020335 (x=-2.270)
x*sigmoid(1.773x) 最大誤差=0.013922 (x=+2.426)
tanh近似 最大誤差=0.000473 (x=+2.699)
数字がはっきり語っています。生のSiLUとGELUは最大 $0.193$ もずれており、「ほぼ同じ関数」とは言えません。ずれが最大になるのは $x \approx 1.97$、つまり正側の中程度の入力で、SiLUのほうが小さい値を返す領域です。一方、シグモイドの中に $1.702$ を入れると誤差は一気に $0.020$ まで下がります。最大誤差を最小化する最適な $\beta$ は $1.773$ で誤差 $0.0139$。$1.702$ は最適値ではないものの、実用上は十分近いことがわかります。tanh近似の誤差 $0.00047$ は別格で、GELUを高速に計算したいときの第一選択です。
これらを図にすると、差の分布まで見えてきます。
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
axes[0].plot(xs, gelu(xs), lw=2.4, label="GELU(厳密)")
axes[0].plot(xs, swish_b(xs, 1.0), "--", lw=1.8, label="SiLU $x\\sigma(x)$")
axes[0].plot(xs, swish_b(xs, 1.702), ":", lw=2.0, label="$x\\sigma(1.702x)$")
axes[0].set_xlim(-4, 4); axes[0].set_ylim(-0.45, 4)
axes[0].set_title("GELU とシグモイド近似の重ね描き")
axes[0].set_xlabel("入力 $x$"); axes[0].set_ylabel("出力")
axes[0].legend(); axes[0].grid(alpha=0.3)
axes[1].plot(xs, gelu(xs) - swish_b(xs, 1.0), "--", lw=1.8, label="GELU $-$ SiLU")
axes[1].plot(xs, gelu(xs) - swish_b(xs, 1.702), lw=2.0, label="GELU $-$ $x\\sigma(1.702x)$")
axes[1].plot(xs, gelu(xs) - gelu_th(xs), ":", lw=2.0, label="GELU $-$ tanh近似")
axes[1].axhline(0, color="gray", lw=0.6)
axes[1].set_xlim(-6, 6)
axes[1].set_title("GELU との差(縦軸のスケールに注目)")
axes[1].set_xlabel("入力 $x$"); axes[1].set_ylabel("差")
axes[1].legend(fontsize=9); axes[1].grid(alpha=0.3)
plt.tight_layout(); plt.show()
右の差分プロットを見ると、3本の曲線がどれも左右対称(偶関数)になっていることに気づきます。GELUとSiLUの差(破線)は $x=\pm1.97$ で $+0.193$ の山を2つ作り、原点でゼロに戻ります。実際に数値で確認すると $\text{GELU}(-2)-\text{SiLU}(-2) = +0.193$、$\text{GELU}(2)-\text{SiLU}(2) = +0.193$ と、符号まで含めてぴったり一致します。
これは偶然ではなく、証明できます。$f(x) = x F(x)$ で $F$ が原点対称な分布のCDFなら $F(x)+F(-x)=1$ なので
$$ f(x) – f(-x) = xF(x) + xF(-x) = x\bigl(F(x)+F(-x)\bigr) = x $$
つまり $f(x) – x/2$ は必ず偶関数です。SiLUもGELUも $x\sigma(1.702x)$ も、すべて「$x/2$(共通の奇な部分)+ それぞれの偶関数」という形をしている。したがって差を取ると $x/2$ が消え、偶関数だけが残るわけです。「原点での傾きがどれも $1/2$」という事実も、この分解の直接の帰結です。
差の大きさに戻ると、$\beta=1.702$ の近似(実線)は全域で $\pm 0.02$ の帯に収まり、tanh近似(点線)に至っては目盛りの上ではほぼ直線に見えるほど小さい誤差です。
つまり 「SiLUとGELUは似ている」というのは定性的な話であって、数値的にはスケール係数を入れて初めて一致する。この区別を押さえておくと、モデルの活性化をSiLUとGELUで入れ替えたときに結果が変わっても驚かずに済みます。
関数の性質は十分に調べました。次は「では実際の学習でどう違うのか」を、小規模なMLPで確かめます。
実験4: 小規模MLPでReLU / GELU / SiLU / Mishを比較する
6層64ユニットのMLPで、2次元入力から $\sin(3x_1)\cos(2x_2) + 0.3x_1x_2$ を回帰します。乱数シードを5通り振って平均を取り、学習曲線とテスト誤差を比較します。
import numpy as np, torch, torch.nn as nn
def make_mlp(act, din=2, h=64, depth=6):
layers, d = [], din
for _ in range(depth):
layers += [nn.Linear(d, h), act()]
d = h
return nn.Sequential(*layers, nn.Linear(h, 1))
def target(x):
return torch.sin(3*x[:, :1]) * torch.cos(2*x[:, 1:2]) + 0.3*x[:, :1]*x[:, 1:2]
torch.manual_seed(0)
X, Xte = torch.rand(2000, 2)*4 - 2, torch.rand(1000, 2)*4 - 2
Y, Yte = target(X), target(Xte)
acts = {"ReLU": nn.ReLU, "GELU": nn.GELU, "SiLU": nn.SiLU, "Mish": nn.Mish}
result = {}
for name, A in acts.items():
curves, tests, gnorms = [], [], []
for seed in range(5):
torch.manual_seed(seed)
model = make_mlp(A)
opt = torch.optim.Adam(model.parameters(), lr=3e-3)
c, g = [], []
for ep in range(400):
opt.zero_grad()
loss = ((model(X) - Y)**2).mean()
loss.backward()
if ep >= 50: # 初期の過渡を除いて勾配ノルムを記録
g.append(model[0].weight.grad.norm().item())
opt.step()
c.append(loss.item())
with torch.no_grad():
tests.append(((model(Xte) - Yte)**2).mean().item())
curves.append(c); gnorms.append(g)
result[name] = (np.array(curves), np.array(tests), np.array(gnorms))
reach = [int(np.argmax(np.array(ci) < 0.01)) for ci in curves]
print(f"{name:5s} 訓練損失(最終)={np.mean([c[-1] for c in curves]):.5f} "
f"テストMSE={np.mean(tests):.5f}±{np.std(tests):.5f} "
f"損失0.01到達epoch(中央値)={int(np.median(reach)):4d} "
f"勾配ノルム中央値={np.median(gnorms):.4f}")
実行結果は次の通りでした。
ReLU 訓練損失(最終)=0.00131 テストMSE=0.00184±0.00121 損失0.01到達epoch(中央値)= 96 勾配ノルム中央値=0.0352
GELU 訓練損失(最終)=0.00077 テストMSE=0.00063±0.00017 損失0.01到達epoch(中央値)= 131 勾配ノルム中央値=0.0205
SiLU 訓練損失(最終)=0.00120 テストMSE=0.00123±0.00057 損失0.01到達epoch(中央値)= 192 勾配ノルム中央値=0.0257
Mish 訓練損失(最終)=0.00261 テストMSE=0.00262±0.00194 損失0.01到達epoch(中央値)= 177 勾配ノルム中央値=0.0219
この結果は正直に読む価値があります。序盤の収束速度ではReLUが最速で、損失 $0.01$ に到達するのに中央値96エポックしかかかっていません。SiLUは192エポックとその2倍です。ReLUの導関数が正側で常に1なので、初期の粗い当てはめでは勾配が素直に大きく流れるためだと考えられます。
ところが最終的な精度では順序が入れ替わります。テストMSEはGELU $0.00063$ < SiLU $0.00123$ < ReLU $0.00184$ < Mish $0.00262$。ReLUの $0.00184$ に対しSiLUは $0.00123$ で約33%小さく、GELUはさらにその約半分(SiLU比で51%)です。しかもシード間のばらつき(標準偏差)はReLUが $0.00121$ と平均と同程度の大きさなのに対し、GELUは $0.00017$、SiLUは $0.00057$ と小さい。滑らかな活性化のほうが、最終的な当てはめが良く、かつ結果が安定しているという傾向が読み取れます。

この図は5シードの結果を1点ずつ描いたものです。ReLU(灰)は最良のシードで $0.00033$ という良い値を出しているのに、最悪のシードは $0.00384$ と11倍以上悪く、点が縦に大きく散らばっています。GELU(赤)は $0.00043$ 〜 $0.00090$ という狭い帯に5点すべてが収まっており、平均が良いだけでなく「どのシードでもだいたい同じところに落ち着く」ことが視覚的にわかります。平均だけを見て活性化関数を選ぶと、この安定性の差を見落とします。
同時に、SiLUがすべての指標で勝つわけではないことも明確です。この設定ではGELUのほうが良く、Mishは最下位でした。活性化関数の優劣はタスク・深さ・最適化設定に強く依存します。「SwiGLUが使われているからSiLUが最強」といった単純な結論を出すべきではありません。
勾配ノルムの分布も見ておきましょう。第1層の重みの勾配ノルムをヒストグラムにします。
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
for name, (curves, tests, gnorms) in result.items():
axes[0].plot(np.arange(400), curves.mean(axis=0), lw=1.9, label=name)
axes[0].set_yscale("log")
axes[0].set_xlabel("エポック"); axes[0].set_ylabel("訓練損失(MSE, 対数軸)")
axes[0].set_title("学習曲線:ReLUは立ち上がりが速く、滑らか系は最終値が低い")
axes[0].legend(); axes[0].grid(alpha=0.3, which="both")
for name, (curves, tests, gnorms) in result.items():
axes[1].hist(gnorms.ravel(), bins=60, range=(0, 0.25), histtype="step",
lw=1.9, density=True, label=name)
axes[1].set_xlabel("第1層 重み勾配のノルム"); axes[1].set_ylabel("確率密度")
axes[1].set_title("勾配ノルムの分布:ReLUは裾が重く暴れやすい")
axes[1].legend(); axes[1].grid(alpha=0.3)
plt.tight_layout(); plt.show()
for name, (_, _, g) in result.items():
print(f"{name:5s} 勾配ノルム 中央値={np.median(g):.4f} 90%点={np.percentile(g,90):.4f}")
出力は次の通りです。
ReLU 勾配ノルム 中央値=0.0352 90%点=0.1368
GELU 勾配ノルム 中央値=0.0205 90%点=0.0471
SiLU 勾配ノルム 中央値=0.0257 90%点=0.0641
Mish 勾配ノルム 中央値=0.0219 90%点=0.0648

左の学習曲線(対数軸)では、序盤はReLUの線が圧倒的に下にあります。50エポック時点の平均損失はReLU $0.054$ に対しGELU $0.187$、SiLU $0.250$ と、実に5倍近い差です。ところがこの優位は続きません。GELUは157エポック、SiLUは209エポックでReLUを追い抜き、そこから先は差が開く一方になります。300エポック時点ではGELU $0.00087$ < Mish $0.00194$ ≈ ReLU $0.00205$ ≈ SiLU $0.00249$、400エポックではSiLU $0.00120$ < ReLU $0.00131$。「ReLUは速く粗く収束し、滑らかな活性化は遅く精密に収束する」という対照的な振る舞いです。ReLUの曲線に細かいギザギザが目立つのも特徴で、これは活性化の折れ目を跨ぐたびに損失地形の傾きが不連続に変わるためです。$C^\infty$ のSiLU/GELU/Mishの曲線はずっと滑らかに下っていきます。
右のヒストグラムはさらに示唆的です。ReLUの勾配ノルムは中央値 $0.0352$ に対し90%点が $0.1368$ と、中央値の約3.9倍まで裾が伸びています。SiLUは中央値 $0.0257$ に対し90%点 $0.0641$(2.5倍)、GELUは $0.0205$ に対し $0.0471$(2.3倍)。ReLUの勾配は「たまに非常に大きい値が出る」重い裾を持ち、滑らかな活性化は分布がコンパクトです。学習率を上げたときにReLUのほうが発散しやすい、という経験則の背景にはこの分布の違いがあります。
活性化関数を1つ差し替えるだけでも、これだけの違いが出ます。では、構造そのものを変えるSwiGLUではどうでしょうか。
実験5: SwiGLU FFN と ReLU FFN をパラメータ数を揃えて比較する
$\bm{x}$ の成分どうしの積を含むターゲット $y = x_1x_2 + \sin(3x_3)x_4$ を、Transformerのブロックを模した残差+LayerNorm+FFNの4段構成で学習させます。ReLU FFNは隠れ次元128、SwiGLU FFNは $2/3$ の86としてパラメータ数を揃えます。シードは8通り振り、平均だけでなく中央値と全シードの値も見ます(あとで理由がわかります)。
import numpy as np, torch, torch.nn as nn
d, h_relu, h_swi = 32, 128, 86 # 86 ≈ 128 * 2/3
class ReLUFFN(nn.Module):
def __init__(s, d, h):
super().__init__(); s.a, s.b, s.f = nn.Linear(d, h), nn.Linear(h, d), nn.ReLU()
def forward(s, x): return s.b(s.f(s.a(x)))
class GELUFFN(ReLUFFN):
def __init__(s, d, h):
super().__init__(d, h); s.f = nn.GELU()
class SwiGLUFFN(nn.Module):
def __init__(s, d, h):
super().__init__()
s.W, s.V, s.b, s.f = nn.Linear(d, h), nn.Linear(d, h), nn.Linear(h, d), nn.SiLU()
def forward(s, x): return s.b(s.W(x) * s.f(s.V(x))) # ゲート付き
class Block(nn.Module):
def __init__(s, ffn, d):
super().__init__(); s.n, s.ffn = nn.LayerNorm(d), ffn
def forward(s, x): return x + s.ffn(s.n(x)) # 残差接続
def build(kind, depth=4):
mk = {"relu": lambda: ReLUFFN(d, h_relu),
"gelu": lambda: GELUFFN(d, h_relu),
"swiglu": lambda: SwiGLUFFN(d, h_swi)}[kind]
return nn.Sequential(nn.Linear(4, d), *[Block(mk(), d) for _ in range(depth)],
nn.LayerNorm(d), nn.Linear(d, 1))
続いてデータを作って学習します。ターゲットの第1項 $x_1x_2$ が、まさに前節で議論した「クロス項」です。
torch.manual_seed(1)
Z = torch.rand(4000, 4)*2 - 1 # 訓練入力
Zte = torch.rand(2000, 4)*2 - 1 # テスト入力
f_true = lambda z: z[:, :1]*z[:, 1:2] + torch.sin(3*z[:, 2:3])*z[:, 3:4]
T, Tte = f_true(Z), f_true(Zte) # 第1項がクロス項 x1*x2
out = {}
for kind in ["relu", "gelu", "swiglu"]:
curves, tests, npar = [], [], None
for seed in range(8):
torch.manual_seed(seed)
model = build(kind)
npar = sum(p.numel() for p in model.parameters())
opt = torch.optim.Adam(model.parameters(), lr=3e-3)
c = []
for ep in range(600):
opt.zero_grad(); loss = ((model(Z) - T)**2).mean()
loss.backward(); opt.step(); c.append(loss.item())
with torch.no_grad():
tests.append(((model(Zte) - Tte)**2).mean().item())
curves.append(c)
curves, tests = np.array(curves), np.array(tests)
out[kind] = (curves, tests, npar)
reach = [int(np.argmax(ci < 0.001)) if (ci < 0.001).any() else 999 for ci in curves]
print(f"{kind:7s} パラメータ数={npar:6d} 訓練損失={curves[:, -1].mean():.5f} "
f"テストMSE 平均={tests.mean():.5f} 中央値={np.median(tests):.5f} "
f"最悪={tests.max():.5f} 損失1e-3到達epoch(中央値)={int(np.median(reach)):3d}")
print(" 全シードのテストMSE:", np.round(tests, 5).tolist())
実行結果は次の通りです。
relu パラメータ数= 33921 訓練損失=0.00022 テストMSE 平均=0.00039 中央値=0.00031 最悪=0.00083 損失1e-3到達epoch(中央値)=209
全シードのテストMSE: [0.00025, 0.00031, 0.00047, 0.0003, 0.00036, 0.00027, 0.00083, 0.00031]
gelu パラメータ数= 33921 訓練損失=0.00030 テストMSE 平均=0.00030 中央値=0.00023 最悪=0.00091 損失1e-3到達epoch(中央値)=293
全シードのテストMSE: [0.00019, 0.00016, 0.00025, 0.00026, 0.00021, 0.00013, 0.00027, 0.00091]
swiglu パラメータ数= 34353 訓練損失=0.00007 テストMSE 平均=0.00046 中央値=0.00008 最悪=0.00281 損失1e-3到達epoch(中央値)=133
全シードのテストMSE: [0.00045, 6e-05, 0.0001, 0.00281, 6e-05, 5e-05, 5e-05, 0.0001]
平均値だけを見ていたら、危うく逆の結論を出すところでした。テストMSEの平均はSwiGLU $0.00046$ > ReLU $0.00039$ > GELU $0.00030$ で、SwiGLUが最下位に見えます。ところが中央値を見ると順序が完全にひっくり返り、SwiGLU $0.00008$ < GELU $0.00023$ < ReLU $0.00031$。全シードの値を並べれば理由は一目瞭然で、SwiGLUは8シード中6つで $5\times10^{-5}$ 〜 $1.0\times10^{-4}$ という圧倒的な精度を出しているのに、4番目のシードだけが $0.00281$ と35倍悪い(1番目のシードも $0.00045$ とやや悪い)。この外れ値が平均を引きずり上げていたわけです。
まずパラメータ数を確認しておきます。ReLU/GELU版が33,921、SwiGLU版が34,353で、その差はわずか1.3%。隠れ次元を128から86へ $2/3$ に縮めた効果で、行列が3つになってもコストはほぼ揃っています。
その上で典型的な挙動(中央値)を比べると、SwiGLUのテストMSE $0.00008$ はReLU FFNの $0.00031$ の約1/4です。しかもGELU FFNの中央値 $0.00023$ はReLU FFNの $0.00031$ と大差ありません。「活性化をReLUからGELUに替える」だけでは3割ほどしか変わらないのに、「ゲート構造を入れる」と4倍近く変わる — これがこの実験の核心です。
これは前節の理論的な議論と整合します。ターゲットに含まれる $x_1x_2$ というクロス項は、素のFFNなら分極恒等式のために符号違いの2ユニットを消費しますが、SwiGLUなら1ユニットの $(\bm{w}^\top\bm{x})\cdot\text{SiLU}(\bm{v}^\top\bm{x})$ で直接表現できます。乗法的な相互作用を含む関数では、この構造上の差がそのまま性能差として現れる、というのが読み取りです。
一方で外れシードも見逃せません。SwiGLUの出力はゲートと値の積なので、両方が同時に大きくなると出力が二乗のオーダーで暴れます。学習率が高いと、この乗法項が発散のきっかけになりやすい。ReLU FFNにも $0.00083$、GELU FFNにも $0.00091$ という悪いシードがあり(それぞれ中央値の2.7倍・4.0倍)、この課題設定自体がやや不安定なのですが、SwiGLUの外れ方は中央値の35倍と桁違いに大きい。「ゲート構造は表現力を上げる代わりに、最適化を難しくする方向にも働く」という副作用です。実際のLLM学習でSwiGLU採用時に学習率やウォームアップを慎重に設定するのは、この性質と無関係ではありません。
なお、この差は「掛け算を含むターゲットを選んだから」でもあります。純粋に加法的な関数を近似させれば差は縮まるはずで、実際の言語モデルでSwiGLUが効く理由も「トークン表現に乗法的な相互作用が本質的に必要だから」という仮説に依存しています。
最後に学習曲線を描いて、収束の様子の違いを確認します。
import matplotlib.pyplot as plt
plt.figure(figsize=(9, 5.5))
labels = {"relu": "ReLU FFN(隠れ128)", "gelu": "GELU FFN(隠れ128)",
"swiglu": "SwiGLU FFN(隠れ86、パラメータ数ほぼ同一)"}
for kind, (curves, tests, npar) in out.items():
med = np.median(curves, axis=0) # 外れシードに強い中央値
lo, hi = curves.min(axis=0), curves.max(axis=0)
plt.plot(np.arange(len(med)), med, lw=2.0,
label=f"{labels[kind]} 中央値の最終={med[-1]:.5f}")
plt.fill_between(np.arange(len(med)), lo, hi, alpha=0.15) # 8シードの最小〜最大
plt.yscale("log")
plt.xlabel("エポック"); plt.ylabel("訓練損失(MSE, 対数軸)")
plt.title("クロス項 $x_1x_2$ を含むターゲットの学習:ゲート付きFFNの効果")
plt.legend(); plt.grid(alpha=0.3, which="both")
plt.tight_layout(); plt.show()

学習曲線を見ると、SwiGLUの優位は最終値だけでなく収束の速さにも現れています。訓練損失 $10^{-3}$ に到達するまでのエポック数は、SwiGLUが中央値133、ReLU FFNが209、GELU FFNが293。100エポック時点の平均損失で比べても、SwiGLU $0.0037$ 対 ReLU $0.0100$ 対 GELU $0.0146$ と、SwiGLUが序盤から一貫して下を走っています。ゲートを入れると収束が速くなり、しかも到達点も低くなるわけです。
同時に、塗りつぶした帯(8シードの最小〜最大)はSwiGLUが最も縦に広くなります。下端は他の2つよりはるかに下まで伸び、上端は外れシードのせいで大きく張り出す。「うまくいけば圧勝、こけると大きくこける」というSwiGLUの二面性が、この一枚の図に集約されています。右のパネルはそれを最終的なテスト誤差で見たもので、SwiGLU(紫)の実線(中央値)は他の2つよりはっきり下にあるのに、破線(平均)だけが1つの外れ点に引っ張られて最上位に跳ね上がっています。中央値の線を追えばSwiGLUの優位は明らかですが、平均だけを見ていると見誤る、という教訓でもあります。
前の実験(実験4)でSiLU単体がReLUに対して微妙な差しか出せなかったことと合わせると、SwiGLUの効果の主役は「SiLUという活性化関数」よりも「ゲートによる乗法構造」のほうにある、と結論づけるのが妥当です。SiLUはそのゲートを滑らかに開閉するための部品として、うまく噛み合っているという位置づけになります。
実験は以上です。最後に、SiLUを実務で使うときに気をつける点をまとめておきます。
実務上の注意点
計算コスト
SiLUは1回の指数関数評価を必要とします。ReLUは比較と選択だけなので、単体の演算コストではReLUが圧倒的に軽い。ただし現代のTransformerでは、活性化関数の計算時間よりも行列積とメモリ帯域が支配的なので、実測での差は小さいことが多いです。それでもエッジデバイスやモバイル推論では効いてきます。
そこで使われるのが hard-swish です。
$$ \text{h-swish}(x) = x \cdot \frac{\text{ReLU6}(x+3)}{6} = x \cdot \frac{\min(\max(x+3, 0), 6)}{6} $$
シグモイドを区分線形関数で置き換えたもので、指数関数が消えます。MobileNetV3で採用されました。SiLUとの最大絶対誤差は $x = -3$ でちょうど約 $0.142$ になります(h-swishはそこで厳密に0になるのに対しSiLUは $-0.142$)。誤差はそこをピークに原点へ近づくほど小さくなり、$|x| < 1$ なら $0.065$ 以下です。整数量子化との相性も良好です。
数値安定性
$x\sigma(x)$ を素直に実装すると、$x$ が大きな負の値のときに $e^{-x}$ がオーバーフローします。np.exp(-x) で $x = -800$ を入れると inf になり、警告が出ます。ライブラリの実装は内部で安定化しているので通常は問題ありませんが、自前で書くときは注意してください。上のコード例で softplus に np.log1p(np.exp(-np.abs(x))) + np.maximum(x, 0) という書き方をしているのも同じ理由です。
初期化との相性
SiLUは原点近傍で傾き $1/2$ なので、信号の分散を保つという意味ではReLU向けのHe初期化(分散 $2/n_{\text{in}}$)と厳密には整合しません。ReLUは入力の半分を0にするので分散が半減し、それを補うために係数2が入っています。SiLUは負側を完全には潰さないため、実効的な減衰はReLUよりやや弱い。実用上はHe初期化で問題なく学習しますが、非常に深いネットワークでは活性の分散が層ごとにどう推移するかを一度確認しておくと安心です。
いつSiLUを選ぶか
- Transformer系のFFN: SwiGLUの一部として使うのが現在の主流。単体のSiLU FFNより、ゲート構造とセットで使うほうが利得が大きい。
- CNNバックボーン: EfficientNet系やYOLO系では標準。ReLUからの置き換えで小さいが安定した改善が報告されています。
- とにかく速度優先・エッジ推論: ReLUまたはhard-swish。
- 既存モデルからの移行: 活性化関数を変えると最適な学習率が変わります。SiLU/GELUは勾配ノルムの裾が軽いので、ReLU時代より学習率を上げられる場合があります。差し替えたら学習率を再探索してください。
こうして見ると、SiLUは「ReLUの上位互換」ではなく、速さと滑らかさのトレードオフの上で別の位置を取る選択肢だとわかります。最後に、ここまでの議論を要点として整理しておきましょう。
まとめ
本記事では、SiLU(Swish)活性化関数を定義から実験まで通して見てきました。
- 定義: $\text{SiLU}(x) = x\sigma(x)$。入力自身がシグモイドでゲートの開き具合を決める自己ゲーティング。正側では $x$ に指数的に近づき、負側では0に落ちるが完全には潰れない。
- 導関数: 積の微分から $\text{SiLU}'(x) = \sigma(x)\left(1+x(1-\sigma(x))\right)$。原点で $1/2$、$x=\pm2.399$ で最大 $1.0998$・最小 $-0.0998$ を取り、$\text{SiLU}'(x)+\text{SiLU}'(-x)=1$ という点対称性を持つ。
- 非単調性: $\text{SiLU}'(x)=0$ を解くとランベルトW関数に帰着し、最小点 $x^*=-1-W(1/e) \approx -1.2785$、最小値 $-W(1/e)\approx -0.2785$ という閉形式が得られる。数値最適化の結果と小数点以下12桁まで一致することを確認した。
- 一般形: $\text{Swish}_\beta(x)=x\sigma(\beta x)$ は $\beta\to0$ で線形関数 $x/2$、$\beta\to\infty$ でReLUに収束する連続的な橋。$\text{Swish}_\beta(x) = \text{SiLU}(\beta x)/\beta$ というスケーリング則が成り立つ。
- GELUとの関係: 両者は「$x\times$(対称分布のCDF)」という同じ構造で、違いはロジスティック分布か正規分布かだけ。生のSiLUとGELUは最大 $0.193$ ずれるが、$x\sigma(1.702x)$ にすると誤差 $0.020$ に縮む。tanh近似なら $0.00047$。
- SwiGLU: $(\bm{x}\bm{W})\otimes\text{SiLU}(\bm{x}\bm{V})$ は1ユニットで任意のランク1二次形式 $\bm{x}^\top(\bm{w}\bm{v}^\top)\bm{x}$ を作れる。素のFFNは符号が定まったランク1形式しか作れず、不定符号のクロス項を作るのに2ユニット必要。パラメータ数を揃えるため隠れ次元は $2/3$($d_{ff}=\frac{8}{3}d$)にする。
- 実験の教訓(活性化関数): 小規模MLPではReLUの初期収束が最速(損失 $0.01$ 到達が96エポック、SiLUは192)だが、最終テスト誤差と安定性では滑らかな活性化が優位(テストMSE GELU $0.00063$ < SiLU $0.00123$ < ReLU $0.00184$)。ReLUの勾配ノルムは90%点が中央値の3.9倍と裾が重く、SiLU・GELUは2.3〜2.5倍に収まる。ただしSiLUがGELUに常に勝つわけではなく、優劣はタスク依存である。
- 実験の教訓(SwiGLU): クロス項を含むターゲットでは、パラメータ数を1.3%差に揃えてもSwiGLUのテストMSE中央値 $0.00008$ がReLU FFNの $0.00031$ の約1/4。一方で8シード中1つが $0.00281$ と中央値の35倍に外れ、平均で見ると順位が逆転する。ゲートの乗法項は表現力を上げると同時に最適化を不安定にもする。
活性化関数の話は、一見すると細部のチューニングに見えます。しかしSiLUを掘り下げると、「滑らかさが最適化にどう効くか」「ゲート構造が表現力をどう変えるか」「確率的な解釈が活性化関数を統一的に眺めさせてくれること」といった、深層学習の設計思想そのものが見えてきます。
次のステップとして、以下の記事も参考にしてください。
- ReLU・GELU・SiLUを数式から比較して理解する — 主要な活性化関数を横断的に比較
- モダン活性化関数の完全ガイド — SiLU・GELU・Mishを数式から理解する — Mishやその他の非単調活性化まで含めた全体像
- Transformer Encoderの構造と実装を完全理解する — SwiGLUが入るFFNの位置づけを構造から確認
参考文献
- P. Ramachandran, B. Zoph, Q. V. Le, “Searching for Activation Functions,” arXiv:1710.05941, 2017.(Swishの提案)
- S. Elfwing, E. Uchibe, K. Doya, “Sigmoid-Weighted Linear Units for Neural Network Function Approximation in Reinforcement Learning,” Neural Networks, 2018.(SiLUの提案)
- D. Hendrycks, K. Gimpel, “Gaussian Error Linear Units (GELUs),” arXiv:1606.08415, 2016.(GELUと $x\sigma(1.702x)$ 近似)
- Y. N. Dauphin et al., “Language Modeling with Gated Convolutional Networks,” ICML 2017.(GLU)
- N. Shazeer, “GLU Variants Improve Transformer,” arXiv:2002.05202, 2020.(SwiGLU)
- A. Howard et al., “Searching for MobileNetV3,” ICCV 2019.(hard-swish)