Understanding Deep LearningのノートブックをJuliaで確認する。
3.1 Shallow neural networks I
ニューラルネットワークを深くする前に、浅いニューラルネットワークモデルに関して理解を深めておく。浅いニューラルネットワークモデルは以下のような式で表される。
\begin{align*}
y &= f[x, \phi] \\
&= \phi_0 + \phi_1a[\theta_{10} + \theta_{11}x] + \phi_2a[\theta_{20} + \theta_{21}x] + \phi_3a[\theta_{30} + \theta_{31}x].
\end{align*}
モデルパラメータ$\phi$は
\phi = \{\phi_0, \phi_1, \phi_2, \phi_3, \theta_{10}, \theta_{11}, \theta_{20}, \theta_{21}, \theta_{30}, \theta_{31}\}
で、aは活性化関数である。
ReLU
活性化関数はReLUを使う。ReLUは以下のように定義される。
a[z] = \text{ReLU}[z] = \begin{cases}
0 & \text{if } z < 0 \\
z & \text{if } z \geq 0
\end{cases}
Juliaで書くと
ReLU(z) = ifelse(z < 0, zero(z), z)
とできる。
ReLU(z) = ifelse(z < 0, 0, z)
としないのは、型安定性を考慮したためである。0の型はInt64なので、zがFloat64の時、ReLU関数はInt64とFloat64のどちらかを返すかコンパイル時に決定できない。
この場合、ReLU関数はInt64とFloat64の共通の抽象型であるReal型を返す関数と解釈される。zero関数は引数の型の0を返す関数である(zがFloat64ならFloat64の0.0を返す)。
zとzero(z)は必ず同じ型を返すので、コンパイラは型を推論でき、型安定性が得られる。
私は怠惰なプログラマーなので、何がなんでも型安定にすることはない。常にコード容易性、速度の必要性と天秤に掛けている(今回は容易に型安定が得られるのでそうしている)。
using CairoMakie
z = -5:0.1:5
lines(z, ReLU.(z))
浅いニューラルネットワークモデル
\begin{align*}
y &= f[x, \phi] \\
&= \phi_0 + \phi_1a[\theta_{10} + \theta_{11}x] + \phi_2a[\theta_{20} + \theta_{21}x] + \phi_3a[\theta_{30} + \theta_{31}x].
\end{align*}
をJuliaで実装する。
function shallow(x, activation_fn, phi, theta)
pre = theta * [1; x]
act = activation_fn.(pre)
w_act = phi .* [1; act]
y = sum(w_act)
y, pre, act, w_act
end
図で確認してみる。
x = 0:0.01:1
phi = [-0.3, 2.0, -1.0, 7.0]
theta = [
0.3 -1.0
-1.0 2.0
-0.5 0.65
]
results = shallow.(x, ReLU, phi |> Ref, theta |> Ref)
ys = getindex.(results, 1)
lines(x, ys)
活性化関数にReLUを用いたニューラルネットワークは1階微分不連続点が活性化関数を適用したユニット数(この例では3)存在する連続関数となる。
JuliaでDeep Learningを理解する: 1.1 -- Background Mathematicsで言及した線形関数の単純さを殺さずにモデルの表現力が向上している。(活性化関数にsigmoid、tanhを使っていた初期はそこまで性能は出せなかった。)