JuliaでDeep Learningを理解する: 1.1 Background Mathematics

[Understanding Deep Learning](https://udlbook.github.io/udlbook/)の[ノートブック](https://github.com/udlbook/udlbook/blob/main/Notebooks/Chap01/1_1_BackgroundMathematics.ipynb)をJuliaで確認する。
# 1.1 -- Background Mathematics
Deep Learningを理解するために最低限必要な基礎的な数学を確認する。
## Linear functions
Deep Learningを小さい部品に分解していくと線形関数に行き着く。線形関数の利点は計算が速いことであり、欠点は単純すぎることである。単純すぎる欠点は活性化関数にReLUを用いることで解消される。(参照：[JuliaでDeep Learningを理解する: 3.1 Shallow neural networks I](https://qiita.com/dokudo9191/items/e3ce231fdb2ab36b5a98))

xが1次元の時、
```math
\begin{equation}y=\beta+\omega x,\end{equation}
```
と表される。Juliaで定義すると
```julia
linear_function(x, β, ω) = β + ω * x
```
となる。図で表示すると
```julia
# 図描画パッケージCairoMakieを使う
using CairoMakie
# 0から10までを0.01刻みとする配列として動作するRange
x = 0:0.01:10
β = 0.0
ω = 1.0
lines(x, linear_function.(x, β, ω))
```
![linear_function.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2279125/c2fb87f3-c16c-1d77-cbfb-0a943273ade7.png)
となる。Juliaはブロードキャストを言語としてサポートしているので、`linear_function.(x, β, ω)`として、関数名の後にドットを付けることで配列xの全ての要素にlinear_functionを適用している。

xが2次元になると
```math
\begin{equation}y=\beta+\omega_1 x_1 + \omega_2 x_2.\end{equation}
```
となる。Juliaで定義すると
```julia
linear_function(x1, x2, β, ω) = β + ω' * [x1, x2]
```
とできる。関数オーバーロードは許されている。`'`は転置(厳密には共役転置)を取っている。つまり、`ω' * [x1, x2]`は内積を計算している。
```julia
x1 = 0:0.1:10
x2 = 0:0.1:10
β = 0.0
ω = [1.0, -0.5]
y = linear_function.(x1', x2, β, ω |> Ref)
```
yは
```console
101×101 Matrix{Float64}:
  0.0    0.1    0.2    0.3    0.4    0.5    0.6    0.7    0.8   …  9.1   9.2   9.3   9.4   9.5   9.6   9.7   9.8   9.9   10.0
 -0.05   0.05   0.15   0.25   0.35   0.45   0.55   0.65   0.75     9.05  9.15  9.25  9.35  9.45  9.55  9.65  9.75  9.85   9.95
 -0.1    0.0    0.1    0.2    0.3    0.4    0.5    0.6    0.7      9.0   9.1   9.2   9.3   9.4   9.5   9.6   9.7   9.8    9.9
 -0.15  -0.05   0.05   0.15   0.25   0.35   0.45   0.55   0.65     8.95  9.05  9.15  9.25  9.35  9.45  9.55  9.65  9.75   9.85
  ⋮                                  ⋮                          ⋱                          ⋮                              ⋮
 -4.85  -4.75  -4.65  -4.55  -4.45  -4.35  -4.25  -4.15  -4.05     4.25  4.35  4.45  4.55  4.65  4.75  4.85  4.95  5.05   5.15
 -4.9   -4.8   -4.7   -4.6   -4.5   -4.4   -4.3   -4.2   -4.1      4.2   4.3   4.4   4.5   4.6   4.7   4.8   4.9   5.0    5.1
 -4.95  -4.85  -4.75  -4.65  -4.55  -4.45  -4.35  -4.25  -4.15     4.15  4.25  4.35  4.45  4.55  4.65  4.75  4.85  4.95   5.05
 -5.0   -4.9   -4.8   -4.7   -4.6   -4.5   -4.4   -4.3   -4.2   …  4.1   4.2   4.3   4.4   4.5   4.6   4.7   4.8   4.9    5.0
```
と出力される。`y = linear_function.(x1', x2, β, ω |> Ref)`は何をやっているかというとx1を転置することで1x101行列にして、ブロードキャストを適用している。1x101と101x1の行列のブロードキャストの出力は101x101の行列になる。`ω |> Ref`(`Ref(ω)`のシンタックスシュガー)としているのは、ωはブロードキャストの対象になってほしくないので、Refで配列の参照として渡している。

引数を色々変えて、図を表示する。
```julia
f = Figure(size=(600, 600))
ax1 = Axis(f[1, 1], title="β=0.0, ω=[1.0, -0.5]", aspect=1)
ax2 = Axis(f[1, 2], title="β=0.0, ω=[0, -0.5]", aspect=1)
ax3 = Axis(f[2, 1], title="β=0.0, ω=[1.0, 0]", aspect=1)
ax4 = Axis(f[2, 2], title="β=-5, ω=[1.0, -0.5]", aspect=1)

y = linear_function.(x1', x2, β, ω |> Ref)
contour!(ax1, x1, x2, y; labels=true, levels=-10:10)

y = linear_function.(x1', x2, β, [0, -0.5] |> Ref)
contour!(ax2, x1, x2, y; labels=true, levels=-10:10)

y = linear_function.(x1', x2, β, [1.0, 0] |> Ref)
contour!(ax3, x1, x2, y; labels=true, levels=-10:10)

y = linear_function.(x1', x2, -5, ω |> Ref)
contour!(ax4, x1, x2, y; labels=true, levels=-10:10)
```
![linear_function2D.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2279125/4ec83536-a281-8c02-5a37-ce2936737d95.png)

次はxが3次元の場合を考える。また、同時に2つの線形関数を定義する。
```math
\begin{align}y_1 &=& \beta_1 + \omega_{11} x_1 + \omega_{12} x_2 + \omega_{13} x_3\\
y_2 &=& \beta_2 + \omega_{21} x_1 + \omega_{22} x_2 + \omega_{23} x_3.
\end{align}
```
行列で書くと
```math
\begin{equation}
\begin{bmatrix} y_1\\ y_2 \end{bmatrix} = \begin{bmatrix}\beta_{1}\\\beta_{2}\end{bmatrix}+ \begin{bmatrix}\omega_{11}&\omega_{12}&\omega_{13}\\\omega_{21}&\omega_{22}&\omega_{23}\end{bmatrix}\begin{bmatrix}x_{1}\\x_{2}\\x_{3}\end{bmatrix},
\end{equation}
```
```math
\begin{equation}
\mathbf{y} = \boldsymbol\beta +\boldsymbol\Omega\mathbf{x}.
\end{equation}
```
と書ける。Juliaで定義すると
```julia
linear_function(x1, x2, x3, β, Ω) = β .+ Ω * [x1, x2, x3]
```
適当な値で計算する。
```julia
β = [0.5, 0.2]
Ω = [-1.0 0.1 -0.3; 0.4 0.1 1.2;]

y = linear_function(4, -1, 2, β, Ω)
```
出力は
```console
2-element Vector{Float64}:
 -4.199999999999999
  4.1
```
となる。Juliaは動的型付け言語であるが言語仕様上、型情報は重要な位置を占めているため、型推論は非常に強力に作用する。上記コードで型注釈は1つも書いていないがコンパイラは全ての値の型を推論できており、正しく型付けされたLLVMを出力する。Juliaの実行速度が速いのはこれが主因である。