本稿はDeep Learningの勉強メモです。
活性化関数とは
ニューラルネットワークでは、各ニューロンが複数のニューロンからの信号の入力を受けて、その総和をもって発火するかどうかを決めます。その際に入力の総和に対して、どのように発火させるか(活性化)させるかを決めるのが活性化関数です。
活性化関数は非線形関数を使用することで、ニューラルネットワークの表現力が向上します。表現力が上がることで複雑なパターン認識ができるようになります。
理論的にはニューラルネットワークは活性化関数に非線形関数を用いることで、任意の連続関数を近似できる「万能近似定理」が証明されています。
以下にニューラルネットワークで一般的に用いられる活性化関数を紹介します。
シグモイド関数
特徴
- 0から1の値を取る
- 2値分類の出力層によく使用される。(例:閾値0.5を超えたらTrue, 0.5以下ならFalse)
- 微分後は最大値は0.25になり、繰り返すと勾配消失につながるので、深いNeural Networkには向かない
数式
$$
y = \frac{1}{1 + e^{-x}}
$$
import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-x))
sigmoid関数の微分(逆伝搬時)
$$
\frac{\partial y}{\partial x} = y(1-y) になる
$$
def deriv_sigmoid(x):
sig = sigmoid(x)
return sig * (1 - sig)
点線がsigmoid関数を微分したグラフです。
tanh関数 (ハイパボリックタンジェント)
特徴
- -1から1の値を取る
- 中間層の活性関数に使われる
- 微分後は0~1の値になりsigmoid関数よりは勾配消失しにくい
数式
$$
y = \frac{e^{x}-e^{-x}}{e^{x}+e^{-x}}
$$
import numpy as np
def tanh(x):
return (np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x))
tanh関数の微分
$$
\frac{\partial y}{\partial x} = 1 - y^2
$$
def deriv_tanh(x):
t = tanh(x)
return 1 - t**2
LeRU関数 (Rectified Linear Unit関数)
特徴
- 0~♾️の値を取る
- sigmoid関数、tanh関数よりも勾配消失に強い
- 負の入力に対しては常に0を返すため、あるユニットが一度死ぬと(出力が常に0)学習が止まる可能性がある
- 計算コストが低い
数式
$$
y = \begin{cases}
0 & (x \leq 0)
\\
x & (x > 0)
\end{cases}
$$
def leru(x):
return np.maximum(0, x)
LeRU関数の微分
$$
\frac {\partial y}{\partial x} = \begin{cases}
0 & (x \leq 0)
\\
1 & (x > 0)
\end{cases}
$$
def deriv_leru(x):
return np.where(x > 0, 1, 0)
Leaky ReLU関数
特徴
- ReLU関数の改良版
- 負の入力に対しては学習が停止しない
- -♾️~♾️の値を取る
- 計算コストが低い
数式
$$
y = \begin{cases}
αx & (x \leq 0)
\\
x & (x > 0)
\end{cases}
$$
$$αは小さいな正の数(例:0.01や0.1)$$
a = 0.05
def leaky_leru(x):
return np.maximum(a*x, x)
Leaky ReLU関数の微分
$$
\frac {\partial y}{\partial x} = \begin{cases}
α & (x \leq 0)
\\
1 & (x > 0)
\end{cases}
$$
$$αは小さいな正の数(例:0.01や0.1)$$
a = 0.05
def deriv_leaky_leru(x):
return np.where(x > 0, 1, a)
GELU(Gaussian Error Linear Unit)関数
特徴
- ReLU関数の改良版
- 負の入力に対しては「出力するかどうか」を滑らかに判断し、少しだけ値が残るので、の小さな値でも ニューロンが「死なない」
- ReLUよりは計算コストが高め
数式
$$
𝑦 = 0.5𝑥 (1 + tanh( \sqrt{ \frac{2}{\pi}} (𝑥 + 0.044715𝑥3)))
$$
def gelu(x):
return 0.5 * x * (1 + np.tanh(np.sqrt(2 / np.pi) * (x + 0.044715 * x**3)))
GELU関数の微分
$$
\frac{\partial y}{\partial x} =
0.5 \left(1 + \tanh(u)\right) + 0.5x \left(1 - \tanh^2(u)\right)
\cdot \sqrt{\frac{2}{\pi}} \left(1 + 0.134145x^2\right)
$$
def deriv_gelu(x):
sqrt_2_over_pi = np.sqrt(2 / np.pi)
x_cubed = x**3
u = sqrt_2_over_pi * (x + 0.044715 * x_cubed)
tanh_u = np.tanh(u)
# du/dx
du_dx = sqrt_2_over_pi * (1 + 3 * 0.044715 * x**2)
# derivative of GELU
dgelu = 0.5 * (1 + tanh_u) + 0.5 * x * (1 - tanh_u**2) * du_dx
return dgelu
Softmax関数
- 最後の出力層で使用される
- 0~1の値を出力する
- 多クラス分類(例:画像が「犬・猫・鳥」のどれかを判定)に使用される
- 各クラスに属する確率を出力する。全クラスの出力値を足したものが1になる。
数式
$$
y(z_i) = \frac{e^{z_i}}{\sum_{j=1}^{K} e^{z_j}}
$$
def softmax(z):
z = z - np.max(z, axis=-1, keepdims=True) # オーバーフロー対策
return np.exp(z) / np.sum(np.exp(z), axis=-1, keepdims=True)
(注意) 上記の実装はnumpyを使っていてzは実はスカラーではなく行列になっている。以下の図に示すような処理になっている。
Softmax関数の微分(ヤコビアン行列)
$$
\frac{\partial y_i}{\partial z_j} =
\begin{cases}
y_i (1 - y_i) & \text{if } i = j
\\
-y_i y_j & \text{if } i \neq j
\end{cases}
$$
def softmax_jacobian(z):
s = softmax(z).reshape(-1, 1) # (n, 1) ベクトルに変形
return np.diagflat(s) - np.dot(s, s.T)
多クラス分類で、損失関数(例えば交差エントロピー)と一緒に使うと微分が簡略化されるため、通常はヤコビアンを直接使わず、交差エントロピーの勾配を一気に導出することが多いです。