JuliaでDeep Learningを理解する: 3.4 Activation functions

[Understanding Deep Learning](https://udlbook.github.io/udlbook/)の[ノートブック](https://github.com/udlbook/udlbook/blob/main/Notebooks/Chap03/3_4_Activation_Functions.ipynb)をJuliaで確認する。

# 3.4 Activation functions
活性化関数を色々変えてどのような結果が得られるか見てみる。モデルは[JuliaでDeep Learningを理解する: 3.1 Shallow neural networks I](https://qiita.com/dokudo9191/items/e3ce231fdb2ab36b5a98)で作った浅いニューラルネットワークを入力出力1次元で使う。
![ShallowNet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2279125/7e19cb4e-ea12-cd88-b9dd-33cf2d275d9a.png)
(画像は[udlbook](https://github.com/udlbook/udlbook/tree/main/PDFFigures)より引用)
```julia
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をそのまま返す。
```math
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.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2279125/c8290da4-8453-0175-4919-e55fed67ebfe.png)
```julia
x = 0:0.01:1
results = shallow.(x, ReLU, phi |> Ref, theta |> Ref)
ys = first.(getindex.(results, 1))
lines(x, ys)
```
![shallow_ReLU.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2279125/98cb0ccd-345e-4283-828a-8323457650e0.png)
ニューラルネットワークは非線形だが部分的に線形な関数となる。

## シグモイド関数
z→-∞で0にz→∞で1に近づく関数。-10倍しているが本質的には変わらない。
```math
\begin{equation}
f[z] = \frac{1}{1+\exp{[-10 z ]}}
\end{equation}
```
```julia
sigmoid(z) = 1 / (1 + exp(-10z))
```
![sigmoid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2279125/27e86c1b-b72b-b9d9-1f5e-deb3436811d1.png)
```julia
results = shallow.(x, sigmoid, phi |> Ref, theta |> Ref)
ys = first.(getindex.(results, 1))
lines(x, ys)
```
![shallow_sigmoid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2279125/96796fb0-11ce-e3bd-1e88-41abedf8019d.png)
ニューラルネットワークは非線形な滑らかな関数になる。

## Heaviside関数
負の時0、正の時1となる関数。
```math
\begin{equation}
\text{heaviside}[z] = \begin{cases} 0 & \quad z <0 \\ 1 & \quad z\geq 0\end{cases}
\end{equation}
```
```julia
heaviside(z) = ifelse(z < 0, 0, 1)
```
![heaviside.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2279125/48aa8568-e97d-84c2-324d-359debbb9b25.png)
```julia
results = shallow.(x, heaviside, phi |> Ref, theta |> Ref)
ys = first.(getindex.(results, 1))
lines(x, ys)
```
![shallow_heaviside.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2279125/b15d2c8d-f46f-2732-3c18-865a8ab676ac.png)
ニューラルネットワークは非線形なステップ関数になる。

## 線形関数
活性化関数を線形関数にするとどうなるか？
```math
\begin{equation}
\text{lin}[z] = a + bz
\end{equation}
```
```julia
lin(z) = 0.5 - 0.4z
```
![lin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2279125/5c26daf1-fd9f-b72f-16c5-dbe8d4818c04.png)
```julia
results = shallow.(x, lin, phi |> Ref, theta |> Ref)
ys = first.(getindex.(results, 1))
lines(x, ys)
```
![shallow_lin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2279125/88635467-f0c8-e058-2021-20c488f1665d.png)
ニューラルネットワークは線形関数になる。

## 良い性質を持つ活性化関数
シグモイド関数は良さそうに見える。各点で無限回微分可能であり、数学的感覚からするとこれは良い性質だ。しかも非線形であり、[JuliaでDeep Learningを理解する: 3.2 Shallow neural networks II](https://qiita.com/dokudo9191/items/c5539cd5e97131b4f1d4#universal-approximation-theorem)で言及した普遍近似定理により任意の関数に近似できる。(そもそもシグモイド関数で証明されている。)線形関数を活性化関数に用いるのは普遍近似定理の観点から良くない。Heaviside関数を使った場合も任意の関数に近似できる。しかし、シグモイド関数とHeaviside関数はDeep Learningの活性化関数としてはうまくいかない。
[JuliaでDeep Learningを理解する: 2.1 Supervised Learning](https://qiita.com/dokudo9191/items/144b94bf615818c01fed)で手動でモデルパラメーターを"訓練"したが、実際は勾配に沿ってパラメーターを更新して訓練することになる。シグモイド関数のzが大きい値を取った時に勾配が0になる性質はモデルパラメーターを更新できなくしてしまう。zが大きな値をとっても常に勾配を持つReLU(とその発展形)が訓練の観点で良い性質を持つ活性化関数となる。