JA EN
体系深層学習の基礎
·無料·論文·15分で読めます

活性化関数を1から — なぜ非線形が必要なのか

層を何段積んでも、活性化関数がなければ1段と同じことしかできない——その一行の証明から始めて、sigmoidが捨てられReLUが勝ち、いまのLLMがSiLU/SwiGLUに落ち着くまでを、傾きを実際に触れる動く図とともに辿ります。

対象textタスクtraining

Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification


何度両替しても、結局は1回の掛け算

円をドルに替え、ドルをユーロに替え、ユーロをポンドに替える。手数料を無視すれば、最後に手元へ残る額は「最初の円 × ある1つの倍率」で言い当てられます。3回の両替は、1回の両替に畳めてしまう。窓口を増やしても、できることは増えません。

ニューラルネットの層も、そのままでは同じ運命をたどります。層の中身は「入力に重みを掛けて足し、下駄を履かせる」——つまり倍率と平行移動だけです。これを何段重ねても、数学的には1段と同じことしかできない。100層積んでも、引ける境界線はまっすぐな1本のままです。

そこで、両替と両替のあいだにまっすぐでない処理を1つ挟む。これが活性化関数です。挟んだ瞬間に「層を重ねる」ことへ意味が生まれ、ネットワークは曲がった境界を描けるようになります。深層学習が深いことに意味を持つのは、この小さな関数が層と層のあいだに座っているからです。

この記事は、なぜ非線形が要るのかという一行の証明から始めて、sigmoid が退場し ReLU が勝ち、いまの大規模言語モデルが GELU・SiLU・SwiGLU に落ち着くまでを、傾きを実際に指で触りながら辿ります。前提知識はほとんど要りません。

活性化関数はどこに座っているのか

まず場所を確かめます。一段の層はこう書けます。

z=Wx+b,a=ϕ(z)z = Wx + b, \qquad a = \phi(z)

記号を1つずつ見ます。xx は入力ベクトル(数値の並び)、WW重み行列bbバイアスベクトル(下駄)、zz は活性化を通す前の値で、現場ではプリアクティベーションや単に「ロジット」と呼ばれます。そして ϕ\phi が活性化関数、aa が次の層への入力です。

つまり式は「入力に重みを掛けて足し合わせ、下駄を履かせた値 zz を作り、その zz を関数 ϕ\phi に通したものを次へ渡す」と言っているだけです。

ここで見落としやすいのが、ϕ\phi要素ごとに働くという点です。zz が1000個の数値の並びなら、1000個それぞれに同じ関数を独立に適用します。隣の数値を参照しません。だから活性化関数は、行列積に比べれば計算量としては誤差のような存在で、それでいてネットワークの性格をほぼ決めてしまいます。安上がりなのに効く、というのがこの部品の立ち位置です。

非線形がないとどうなるか(1行の証明)

活性化関数を外して、層を2段重ねてみます。1段目の出力をそのまま2段目に入れると、こうなります。

W2(W1x+b1)+b2=(W2W1)x+(W2b1+b2)=Wx+bW_2(W_1 x + b_1) + b_2 = (W_2 W_1)x + (W_2 b_1 + b_2) = W'x + b'
(1)

式(1) が言っているのは、2段ぶんの計算は、あらかじめ掛け合わせておいた1枚の重み行列 W=W2W1W' = W_2W_1 と1本のバイアス b=W2b1+b2b' = W_2b_1 + b_2 で完全に置き換えられるということです。両替を2回通しても倍率が1つに畳めるのと同じで、層を増やした意味が消えます。10段でも100段でも結論は変わりません。

逆に言えば、活性化関数に求められるのは「この畳み込みを壊すこと」だけです。畳めなくなりさえすれば、層を重ねた回数だけ表現力が積み上がります。理論の側でも、活性化関数が多項式でありさえしなければ、十分な幅の1隠れ層で連続関数をいくらでも細かく近似できることが知られています(万能近似定理)。ただしこれは「近似できる存在は保証する」だけで、「学習で見つかる」ことは何も保証しません。実務で活性化関数を選び分ける理由は、表現力ではなく勾配の通りやすさの側にあります。

直感: 折れ線があれば、たいていの形は描ける

非線形と聞くと滑らかな曲線を思い浮かべますが、実際に一番使われている ReLU は「負を0にする」だけの折れ線です。それで足りるのか、と思うところですが、折れ線を大量に足し合わせれば、円でも波でも好きなだけ近い形が作れます。多角形の頂点を増やすと円に見えてくるのと同じ理屈です。

ReLU を使ったネットワークが表す関数は、実は区分的に線形——入力空間を細かい領域に切り分け、各領域の中ではまっすぐな平面という形をしています。層を重ねるほど切れ目の本数が増え、細かい形を作れるようになる。曲がっているように見えるのは、切れ目が十分細かいからです。

第一世代: sigmoid と tanh、そして飽和

初期のニューラルネットは sigmoid を使いました。

σ(z)=11+ez\sigma(z) = \frac{1}{1 + e^{-z}}

これは「どんな実数を入れても 0 と 1 のあいだの値を返す」関数です。大きな正の値ならほぼ1、大きな負の値ならほぼ0、0付近では緩やかに立ち上がります。生物のニューロンが「発火する/しない」を確率で表しているようで直感的でした。

tanh は同じ形を上下に引き伸ばして 1-1 から 11 の範囲にしたもの(実は tanh(z)=2σ(2z)1\tanh(z) = 2\sigma(2z) - 1 という関係にあります)で、出力が0を中心に対称になるぶん sigmoid より扱いやすいことが知られています。

問題は、どちらも両端で平らになることです。学習は誤差を後ろから前へ伝えて重みを直しますが、伝わる量は各層での傾きの掛け算で決まります。sigmoid の傾きは最大でも 0.25 しかありません。10層通れば 0.25100.25^{10}、およそ百万分の1です。前のほうの層には信号がほぼ届かない——これが勾配消失で、2010年ごろまで深いネットが学習できなかった主因のひとつです。誤差がどう伝わるかの仕組みそのものは誤差逆伝播を1から解説で扱っています。

次の図で、関数のカーブ(濃い線)と傾きのカーブ(薄い金の線)を同時に見てください。グラフ上を横にドラッグすると入力の位置が動き、その点での出力と傾きが数値で出ます。sigmoid のまま入力を ±4\pm4 あたりまで引っ張ると、傾きがほぼ0に潰れることが確認できます。

FIG 1濃い線が関数、薄い金の線がその傾き。sigmoid の端では傾きがほぼ0 — ここに落ちた値は学習信号を後ろへ渡せません

ReLU: 負を捨てるだけの勝者

2010年代に主流を奪ったのが ReLU(Rectified Linear Unit、正規化線形ユニット)です。

ReLU(z)=max(0,z)\mathrm{ReLU}(z) = \max(0, z)

やっていることは「負の値は0にする、正の値はそのまま通す」だけです。

これが効いた理由は3つあります。第一に、正の側の傾きがちょうど1です。何層通しても1の掛け算なので、勾配が幾何級数的に痩せません。第二に、指数関数を計算しないので速い。第三に、負の入力が0になることで出力がスパース(多くの要素が0)になり、各ニューロンが特定の入力パターンにだけ反応する分業が生まれやすくなります。

代償もあります。負の側の傾きは0なので、あるニューロンの zz が全ての入力に対して負になってしまうと、勾配も0のまま二度と復活しません。これが死んだReLU(dying ReLU)です。学習率を大きくしすぎた直後に起きやすく、症状としては「損失が途中から下がらないのに発散もしない」という気づきにくい形で現れます。

この穴を塞ぐために一族が生まれました。

名前 負の側の扱い 主な狙い
Leaky ReLU αz\alpha zα\alpha は 0.01 など固定) 傾きを完全に0にしない
PReLU αz\alpha zα\alpha を学習する) 傾きをデータに決めさせる
ELU α(ez1)\alpha(e^{z}-1) 負側をなめらかにし、出力の平均を0へ寄せる
ReLU6 上を6で頭打ちにする 値域を固定して量子化しやすくする

ただし現場の感覚として、これらが ReLU を置き換えたわけではありません。画像系の畳み込みネットでは素の ReLU が今も広く使われています。落とし穴は ReLU そのものより、初期化を合わせ忘れることの側にあります。ReLU は入力の半分を捨てるので、そのぶん分散が縮みます。これを見越して重みの初期値を大きめに取るのが He 初期化(Kaiming 初期化)で、tanh 向けの Xavier 初期化のままにしておくと深い層で信号が痩せます。

GELU と SiLU: 「ゲート」としての活性化

Transformer 以降、ReLU の折れ目を滑らかにした関数が主流になりました。代表が GELUSiLU(別名 Swish)です。

GELU(z)=zΦ(z),SiLU(z)=zσ(z)\mathrm{GELU}(z) = z \cdot \Phi(z), \qquad \mathrm{SiLU}(z) = z \cdot \sigma(z)
(2)

式(2) の読み方はこうです。どちらも「入力 zz をそのまま通すか、捨てるか」を0〜1の連続的な割合で決めています。Φ\Phi は標準正規分布の累積分布関数(「標準正規分布からの値が zz 以下になる確率」)、σ\sigma はさきほどの sigmoid で、どちらも zz が大きいほど1に近づき、小さいほど0に近づきます。つまり ReLU が「通す/捨てる」を0か1で断ち切っていたところを、通す割合を滑らかに変える門番に置き換えたわけです。

滑らかにすると何が良いのか。ひとつは、z=0z=0 の周辺で傾きが不連続に飛ばないので、最適化が扱いやすい形になること。もうひとつは、負の側にも小さな出力が残ることです。GELU も SiLU も zz が少し負の領域でわずかに負の値を返し、そこでの傾きは0ではありません。死んだReLU が構造的に起きにくくなります。

実装上の注意点を1つ。GELU の Φ\Phi は誤差関数を含むので、高速な近似式がよく使われます。

GELU(z)0.5z(1+tanh ⁣(2/π(z+0.044715z3)))\mathrm{GELU}(z) \approx 0.5\,z\left(1 + \tanh\!\left(\sqrt{2/\pi}\,(z + 0.044715\,z^3)\right)\right)

これは「正確な Φ\Phi を、tanh を使った似た形のカーブで置き換えたもの」です。値はごく近いものの完全には一致しないため、厳密版と近似版が混ざると同じ重みでも出力がわずかにずれます。後述しますが、これは移植時の定番のつまずきどころです。

下の図でタブを GELU に切り替え、入力を負の側へドラッグしてみてください。ReLU なら真っ平らな領域で、GELU の傾きの線がまだ0から浮いていることが見えます。

FIG 2タブで4つを切り替えられます。ReLU の負側は関数も傾きも完全に0、GELU の負側はわずかに沈んでから戻る — この「浮いた傾き」が死んだニューロンを救います

SwiGLU: いまのLLMが使っている形

現在の大規模言語モデルの多くは、活性化関数を単体で使わずゲート付きの形に組み替えています。Transformer ブロックの中のフィードフォワード層を、次のように書き換えたものです。

SwiGLU(x)=(SiLU(xW)xV)W2\mathrm{SwiGLU}(x) = \big(\mathrm{SiLU}(xW) \odot xV\big)W_2

記号の意味です。WWVVW2W_2 は別々の重み行列、\odot要素ごとの掛け算(同じ位置同士を掛ける)です。同じ入力 xx から2本の枝を作り、片方だけを SiLU に通して0〜1寄りの「開き具合」にし、もう片方の生の値にその開き具合を掛けてから出力する——つまり値そのものを運ぶ枝と、どれだけ通すかを決める枝を分けた構造です。

枝が1本増えるので、素直に組むとパラメータが1.5倍になります。そのため実装では中間層の幅を 23\tfrac{2}{3} 程度に絞り、パラメータ数を揃えたうえで比較するのが通例です。この形は Shazeer (2020) が Transformer 向けに比較実験してから広まり、以後の主要なオープンモデルの多くが採用しています。

コードで確かめる

活性化関数の実装は、どれも数行です。

import numpy as np

def relu(z):  return np.maximum(0.0, z)
def leaky(z, a=0.01):  return np.where(z > 0, z, a * z)
def sigmoid(z):  return 1.0 / (1.0 + np.exp(-np.clip(z, -60, 60)))
def silu(z):  return z * sigmoid(z)
def gelu(z):  return 0.5 * z * (1 + np.tanh(np.sqrt(2/np.pi) * (z + 0.044715 * z**3)))

np.clip を挟んでいるのが実装のコツです。zz800-800 のような値になると np.exp が桁あふれして警告や inf を出します。±60\pm60 で切っても sigmoid の値は変わらない(すでに 0 か 1 に張り付いている)ので、安全に潰せます。数式には現れないのに実装には必ず要る、この種の安定化は深層学習の実装のあちこちに顔を出します。

選び方の早見表

現場ではこう使う

活性化関数を「選ぶ」場面は、実はそれほど多くありません。モデルを一から設計するとき以外、既定値を触る理由はほぼないからです。にもかかわらずこの知識が要るのは、事故が起きたときの原因がここにあることが多いからです。

どの職種が、いつ。学習を回す機械学習エンジニアが、損失が下がらない原因を切り分けるとき。研究者の実装を製品コードへ移す担当者が、出力が論文と一致しない理由を追うとき。推論を最適化する担当者が、量子化やカーネル融合の対象を決めるときです。

触るパラメータ名。PyTorch なら nn.ReLUnn.LeakyReLU(negative_slope=0.01)nn.SiLUnn.GELU(approximate='none' | 'tanh')。Hugging Face のモデルでは config.jsonhidden_act が実体で、ここに "gelu""silu" といった文字列が入っています。初期化は torch.nn.init.kaiming_normal_(w, nonlinearity='relu') のように、活性化の種類を引数で渡す形になっているものが多く、ここを既定のまま放置すると初期化と活性化がちぐはぐになります

知らないと事故になる落とし穴を4つ。

1つ目、sigmoid の二重掛け。モデルの最後に torch.sigmoid を掛けたうえで BCEWithLogitsLoss(内部で sigmoid を掛ける損失)に渡す事故です。エラーは出ず、学習も一応進むのに精度が頭打ちになります。損失関数の名前に WithLogits が入っていたら、活性化を掛けない生の値を渡すが鉄則です。

2つ目、GELU の版ずれ。厳密版と tanh 近似版が混在すると、同じ重みでも出力に小さなずれが出ます。単体では無視できる差でも、層を重ねた深いモデルでは末端で無視できない差になり、「移植したら精度が数%落ちた」の典型的な原因になります。Hugging Face が "gelu""gelu_new" を別物として持っているのはこのためで、チェックポイントを移すときは活性化の版まで一致させる必要があります。

3つ目、死んだReLU の見逃し。学習率を上げた直後に損失が停滞したら、各層の出力のうち0の割合を1バッチぶん測ってください。ある層で常時9割以上が0なら、その層はほぼ死んでいます。対処は学習率を下げるか、Leaky ReLU / SiLU に替えるか、初期化を見直すかです。

4つ目、並べる順番。畳み込みネットの標準は Conv → 正規化 → 活性化 の順で、この2つを入れ替えると正規化が整えたスケールを活性化が壊す(あるいはその逆になる)ため、収束の挙動が変わります。正規化レイヤー側の事情は正規化レイヤー全史にまとめてあります。

面接で問われたら。「なぜ ReLU で勾配消失が緩和されるのか」は定番です。答えの筋道は、正の側の傾きがちょうど1なので層を通るたびの掛け算で勾配が痩せない、という一点。そのうえで「ただし ReLU だけでは深いネットは学習できず、残差接続と正規化があって初めて数十層以上が回るようになった」まで言えると、歴史の順番を分かっていることが伝わります。

まとめ

次に読むなら、この活性化を挟んだ層がどう積み上がるかを扱うニューラルネットワークを1から理解するが地続きです。

参考文献

  1. Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification. arXiv:1502.01852論文ページ·PDF
  2. Gaussian Error Linear Units (GELUs). arXiv:1606.08415論文ページ·PDF
  3. Searching for Activation Functions. arXiv:1710.05941論文ページ·PDF
  4. GLU Variants Improve Transformer. arXiv:2002.05202論文ページ·PDF

本記事は上記論文の本文にもとづいて執筆しています。数値・主張は原典を優先してください。

コメント

コメントにはログインが必要です