JuliaでDeep Learningを理解する: 3.2 Shallow neural networks II

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

# 3.2 Shallow neural networks II
[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)より引用)
## 入力2次元、出力1次元
2次元の浅いニューラルネットワークを確認する。
![ShallowNetTwoInputs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2279125/1d749b38-b6b8-f28d-1c08-c333bebeff1f.png)
(画像は[udlbook](https://github.com/udlbook/udlbook/tree/main/PDFFigures)より引用)
式で表すと
```math
\begin{align*}
h_1 &= a[\theta_{10} + \theta_{11}x_1 + \theta_{12}x_2] \\
h_2 &= a[\theta_{20} + \theta_{21}x_1 + \theta_{22}x_2] \\
h_3 &= a[\theta_{30} + \theta_{31}x_1 + \theta_{32}x_2]
\end{align*}
```
```math
y = \phi_0 + \phi_1h_1 + \phi_2h_2 + \phi_3h_3
```
となる。
Juliaで実装する。
```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
shallow(x1, x2, activation_fn, phi, theta) = shallow([x1, x2], activation_fn, phi, theta)
```
[JuliaでDeep Learningを理解する: 3.1 Shallow neural networks I](https://qiita.com/dokudo9191/items/e3ce231fdb2ab36b5a98)で実装したshallowのphiが行列に対応するように転置した。他は変更してない。
```julia
using CairoMakie
phi = [
    0.0 -2.0 2.0 1.5
]
theta = [
    -4.0 0.9 0.0
    5.0 -0.9 -0.5
    -7 0.5 0.9
]
ReLU(preactivation) = ifelse(preactivation < 0, zero(preactivation), preactivation)
results = shallow.(x1', x2, ReLU, phi |> Ref, theta |> Ref)
ys = getindex.(results, 1)
fig = Figure()
ax = Axis(fig[1, 1], xlabel="x1", ylabel="x2", title="y=ϕ₀+ϕ₁h₁+ϕ₂h₂+ϕ₃h₃")
co = contourf!(ax, x1, x2, ys, levels=-10:10)
Colorbar(fig[1, 2], co)
```
![shallow213.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2279125/0378435e-ae88-fe8c-87c3-a8ec8ea8b248.png)
活性化関数はReLUをそのまま使う。得られた等高線は境界線がいくつか見える。
足し合わせる前の$\phi_0,\ \phi_1h_1,\ \phi_2h_2,\ \phi_3h_3$をそれぞれ表示してみる。
```julia
w_acts = getindex.(results, 4)
fig = Figure()
axs = [Axis(fig[i, j]) for i in 1:2 for j in 1:2]
for i in eachindex(axs)
    contourf!(axs[i], x1, x2, getindex.(w_acts, i), levels=-10:10)
end
```
![shallow213_w_acts.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2279125/c0e4eb59-7fb6-97c2-2aa9-aa4290d74a95.png)
![shallow213.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2279125/0378435e-ae88-fe8c-87c3-a8ec8ea8b248.png)
境界線は活性化関数により0にされた部分から来ていることがわかる。言い換えると隠れユニット数の線を引いて領域を分割する効果が活性化関数にはある。
## Universal approximation theorem
領域が分割されて何が嬉しいのだろうか？普遍近似定理(Universal approximation theorem)によると任意の関数がこの分割により近似可能となるらしい(意訳)。この定理は隠れユニットを増やせば任意の関数に任意の精度で近似可能と言っているが、深さに関しては何も述べていない。そして、Deep Learningにおける深さの効果はまだ数学的に説明されていない。
## 入力2次元、出力2次元
入力２、出力２のニューラルネットワークを確認する。

```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, dims=1)
    y, pre, act, w_act
end
phi = [
    0.0 -2.0 2.0 1.5
    -2.0 -1.0 -2.0 0.8
]
theta = [
    -4.0 0.9 0.0
    5.0 -0.9 -0.5
    -7 0.5 0.9
]
x1 = x2 = 0:0.1:10
results = shallow.(x1', x2, ReLU, phi |> Ref, theta |> Ref)
```
shallow関数はyが２つ返せるように`y = sum(w_act)`を`y = sum(w_act, dims=1)`にした。
```julia
ys = getindex.(results, 1)
y1 = getindex.(ys, 1)
y2 = getindex.(ys, 2)
contourf(x1, x2, y1, levels=-10:10)
contourf(x1, x2, y2, levels=-10:10)
```
y1は
![shallow_y1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2279125/7063cb18-03fe-e6c2-aa62-c3ab0f320329.png)
y2は
![shallow_y2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2279125/f1739e09-5d17-4d6a-a699-4a48f0fb107e.png)
となる。境界線は同じで値の異なる等高線が得られる。