Understanding Deep LearningのノートブックをJuliaで確認する。
3.4 Activation functions
活性化関数を色々変えてどのような結果が得られるか見てみる。モデルはJuliaでDeep Learningを理解する: 3.1 Shallow neural networks Iで作った浅いニューラルネットワークを入力出力1次元で使う。
(画像はudlbookより引用)
using CairoMakie
function shallow(x, activation_fn, phi, theta)
pre = theta * [1; x]
act = activation_fn.(pre)
w_act = phi' .* [1; act]
y = sum(w_act, dims=1)
y, pre, act, w_act
end
phi = [
-0.3 2.0 -1.0 7.0
]
theta = [
0.3 -1.0
-1.0 2.0
-0.5 0.65
]
ReLU
zの値が負の時は0となり、正の時はzをそのまま返す。
a[z] = \text{ReLU}[z] = \begin{cases}
0 & \text{if } z < 0 \\
z & \text{if } z \geq 0
\end{cases}
ReLU(z) = ifelse(z < 0, zero(z), z)
x = 0:0.01:1
results = shallow.(x, ReLU, phi |> Ref, theta |> Ref)
ys = first.(getindex.(results, 1))
lines(x, ys)
ニューラルネットワークは非線形だが部分的に線形な関数となる。
シグモイド関数
z→-∞で0にz→∞で1に近づく関数。-10倍しているが本質的には変わらない。
\begin{equation}
f[z] = \frac{1}{1+\exp{[-10 z ]}}
\end{equation}
sigmoid(z) = 1 / (1 + exp(-10z))
results = shallow.(x, sigmoid, phi |> Ref, theta |> Ref)
ys = first.(getindex.(results, 1))
lines(x, ys)
Heaviside関数
負の時0、正の時1となる関数。
\begin{equation}
\text{heaviside}[z] = \begin{cases} 0 & \quad z <0 \\ 1 & \quad z\geq 0\end{cases}
\end{equation}
heaviside(z) = ifelse(z < 0, 0, 1)
results = shallow.(x, heaviside, phi |> Ref, theta |> Ref)
ys = first.(getindex.(results, 1))
lines(x, ys)
線形関数
活性化関数を線形関数にするとどうなるか?
\begin{equation}
\text{lin}[z] = a + bz
\end{equation}
lin(z) = 0.5 - 0.4z
results = shallow.(x, lin, phi |> Ref, theta |> Ref)
ys = first.(getindex.(results, 1))
lines(x, ys)
良い性質を持つ活性化関数
シグモイド関数は良さそうに見える。各点で無限回微分可能であり、数学的感覚からするとこれは良い性質だ。しかも非線形であり、JuliaでDeep Learningを理解する: 3.2 Shallow neural networks IIで言及した普遍近似定理により任意の関数に近似できる。(そもそもシグモイド関数で証明されている。)線形関数を活性化関数に用いるのは普遍近似定理の観点から良くない。Heaviside関数を使った場合も任意の関数に近似できる。しかし、シグモイド関数とHeaviside関数はDeep Learningの活性化関数としてはうまくいかない。
JuliaでDeep Learningを理解する: 2.1 Supervised Learningで手動でモデルパラメーターを"訓練"したが、実際は勾配に沿ってパラメーターを更新して訓練することになる。シグモイド関数のzが大きい値を取った時に勾配が0になる性質はモデルパラメーターを更新できなくしてしまう。zが大きな値をとっても常に勾配を持つReLU(とその発展形)が訓練の観点で良い性質を持つ活性化関数となる。