Tech カテゴリのイメージ

活性化関数とは?ReLUをPythonで動かして「層を重ねる意味」を理解する

ニューラルネットワークに層を増やしても、重みを掛けてバイアスを足す計算だけを繰り返すなら、全体を1回の同じ形の計算にまとめられる。層を重ねる意味を作るのが、間に入る活性化関数だ。

前の「重み・バイアス」では、入力から z=wx+bz=wx+b を計算した。ここから先へ進むには、得られた zz をそのまま次の層へ渡すのか、条件に応じて形を変えるのかが重要になる。

線形層だけを重ねても、曲がらない

1つ目の層が z=w1x+b1z=w_1x+b_1、2つ目の層が y=w2z+b2y=w_2z+b_2 だとする。代入すると次のようになる。

y=w2(w1x+b1)+b2=(w2w1)x+(w2b1+b2)y=w_2(w_1x+b_1)+b_2=(w_2w_1)x+(w_2b_1+b_2)

xx は入力、w1,w2w_1,w_2 は重み、b1,b2b_1,b_2 はバイアスだ。括弧を外すと、新しい重みと新しいバイアスを持つ1つの層と同じ式になる。入力や出力がベクトルになっても、線形変換とバイアス加算だけの合成は、結局1つのアフィン変換として表せる。

このままでは「ある値を境に出力の変わり方を切り替える」ような形を表せない。そこで、層の間に非線形の活性化関数を入れる。

ReLUは負の値を0にする

最も分かりやすい例の1つがReLUである。

ReLU⁡(z)=max⁡(0,z)\operatorname{ReLU}(z)=\max(0,z)

zz が0以下なら出力は0、0より大きければ zz をそのまま出す。PyTorchの torch.nn.ReLU もこの式で定義されている。

たとえば前の層が z=3x+1z=3x+1 を出す場合、xx が −1/3-1/3 より小さい領域ではReLUの出力は0になり、それより大きい領域では 3x+13x+1 になる。境目で出力の傾きが変わる。これが、線形層だけでは作れなかった「曲がり目」だ。

ただし、ReLU自身が重みを学習するわけではない。境目の位置を動かすのは、ReLUへ入る前の重みとバイアスである。活性化関数は、受け取った値を規則に従って変換する。

NumPyで層の間に入れて比べる

下の例では、同じ2つの層を使い、間にReLUを入れる場合と入れない場合を比べる。学習済みモデルではなく、違いを見るために係数を固定した例である。

import numpy as np
x = np.array([-2.0, -1.0, 0.0, 1.0, 2.0])
first_layer = 3.0 * x + 1.0
without_activation = 2.0 * first_layer - 1.0
with_relu = 2.0 * np.maximum(0.0, first_layer) - 1.0
print('input: ', x)
print('without ReLU: ', without_activation)
print('with ReLU: ', with_relu)

実行結果は次のとおり。

input: [-2. -1. 0. 1. 2.]
without ReLU: [-11. -5. 1. 7. 13.]
with ReLU: [-1. -1. 1. 7. 13.]

ReLUがなければ、全体は y=6x+1y=6x+1 という1本の直線だ。ReLUを入れると、x=−2x=-2 と x=−1x=-1 はどちらも最初の層で負になり、ReLU後は0になる。最後の層を通すと、両方とも −1-1 だ。一方、x=0,1,2x=0,1,2 では値が変化する。1本の直線では表せない形になった。

この例の出力が一部で同じになるからといって、どんな入力でも「情報を捨てるほどよい」とは言えない。ReLUの負側では出力が0になり、その領域の勾配も0になる。学習の進み方や表現したい形に応じて、別の活性化関数を使うこともある。

LLMでも「間に非線形を入れる」

Transformerの原論文『Attention Is All You Need』では、位置ごとに適用するフィードフォワード部分を、2つの線形変換と、その間のReLUで構成している。したがって活性化関数は、注意機構とは別の場所でも表現の幅を作る要素だ。

ただし、すべてのLLMがReLUを使うわけではない。PyTorchにはGELUなど別の活性化関数もある。モデルの実装を読むときは「活性化関数があるか」だけでなく、「どの関数が、どの2つの変換の間にあるか」を確かめたい。

よくある誤解

活性化関数を「ニューロンをON/OFFにするだけの部品」と説明すると、ReLUの正側が入力値をそのまま通すことや、GELUのように連続的に変える関数を見落とす。ReLUを理解する入口としては便利な比喩だが、一般的な定義ではない。

もう1つ、活性化関数はモデルの最後に必ず置くものでもない。どの値を出力として使うかによって、最後の層の扱いは変わる。まずは「線形層を重ねただけでは作れない変化を、間に入れる関数が作る」と押さえるのがよい。

重みとバイアスが入力の組み合わせ方を決め、活性化関数がその結果を非線形に変える。次は、出力が望む答えからどれだけずれたかを測る損失関数へ進む。

参考資料:

講座の前後記事