【AI初心者向け】ニューラルネットワークの基礎を完全に理解した気分になろう(完全に理解できるとは言ってない)

### はじめに
初心者がざっとニューラルネットワークを理解していくという記事です。
前提として今は研究をしていない学士卒が書いた記事ということは念頭においてください🙇

# ニューラルネットワーク(NN)について
深層学習(ディープラーニング)に必要。もともと機械学習の手法の一つだったが、ハード機器の発達で深層学習ができるようになることでニューラルネットワークという手法が発達した。

## まずは一次関数で直感的にNNを理解しよう！
　入力値```x```に対して、パラメータ```a```が最適化された(*)関数```y```の出力は
```math
y^* = a^*x + b
```
と表せます。
![nn.drawio (7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472015/6bba1f3f-b8bd-ad47-26b1-0922ce2e4c83.png)
※ u|hについては後ほど解説します
　どのように最適化されるかは、例えば```y = ax + b```において```y```の目標値は```t```であるとします(```y = t```の時、$y = y^*$)。目標値に近づくようにパラメータ```a```を最適化していきますというお話です。今回、```t = b```であるとします。

　ここでの最適化とは、どの入力値```x```に対しても(任意の```x```に対して)```y```の値ができる限り```b```に近づくようパラメータ```a```を最適化しようっていうお話しです。
では、```y = ax + b```の学習の進み方を見てみましょう。

### 学習の進み方(一次)
##### 順伝播(一次)
![nn.drawio (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472015/4ff85aee-6f39-e97b-a2b9-56889501566e.png)

　最初に```x```が入力された時、```u = ax + b```が計算されます。__(線形変換)__
```u```が```0```以上で結果をそのまま、小さいと```0```に変換 __(非線形変換、活性化処理)__
```math
\begin{equation}
h =m(u) = ReLU(u) = 
\begin{cases}
u & (u \geqq 0) \\
0 & (u \lt 0)
\end{cases}
\end{equation}
```
※ 活性化関数```m```がReLUの場合
```y = h```出力層に中間層の計算結果が渡されます。
```y```と目標値（今回は最小値）```b```の差の二乗をとります。__(目的化処理)__
```math
l = L(y) = (b - y)^2
```

```y```を目標値```b```に近づける処理をこの後するためです。この時の```L```を**目的関数**と呼びます。この後の操作では```l```の最小化問題と考えてパラメータが更新されます。

※ 便宜上、目的化処理と呼んでいますが、単に、出力層に目的関数を設定するっていう認識で大丈夫です。```

##### 逆伝播(一次)
![nn_r.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472015/0f35c0f4-7fdd-fe3e-1ee2-8d2f436f5fa9.png)

この```b - y```から```a```を最適化します(最適化処理)
```math
a \leftarrow a -\eta\frac{d l}{d a} = a - \eta\frac{d l}{d y}\frac{d y}{d a} (\etaは学習率)
```
```a```の値を更新します。(```a - dl/da```の部分は最適化の方法でまた変わります。)
学習率を設定することで徐々に目標値に近づくことを可能とします。パラメータによっては学習が収束しないなんてことも良くあるのでこういう値は大事です。
そして再度、順伝播...
この順伝播、逆伝播を繰り返します。

※ Δとdの違いについて、Δ（デルタ）は変化や差分を表すための演算子であり、微小変化を表現するためには不適切です。機械学習の文脈では、微分(dy/dx)を使用してパラメータを更新することが一般的です。今回の場合だと```dy/da```の部分はΔでも対応可能かもしれませんが、```dl/dy```の部分ではΔだと正確に表現できません。詳しくは[微分の定義](https://math-masteeer.com/basic-knowledge/definition-of-differential.html#i-2)

### 最適化後(一次)
![nn.drawio (7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472015/6bba1f3f-b8bd-ad47-26b1-0922ce2e4c83.png)

学習が終わったら、(目標値に限りなく近づいたら)
```y```、```a```が最小値```b```に近づくように最適化(*)されています。この時
```math
y^* = a^* x + b
```
これで任意の入力値に対して、```y```が最小値```b```付近の値を出力するようになりました！
やったね！

※ $y^*$の値についてですが、$a^*x+b$という一連の操作の結果であると認識してください。これが結構抜けてる人が多いので、抜けてたなという人は最初から読んでください。数式を言語化して理解できているかは結構重要です。この後もそれを意識してみてください。

※ 本来は```b```も最適化の対象とすることがありますが、今回の場合、簡単のために```a```に絞っています。以下でも、```b```値は[ハイパーパラメータ](https://www.codexa.net/hyperparameter-tuning-python/)として扱います。

## ベクトル、行列で理解してみよう！
同様に、入力ベクトル```x```に対して最適化(*)されたパラメータ関数```y```の出力ベクトルは
```math
\begin{align}
\mathbb{y}^* = \boldsymbol{W}^{*} \mathbb{x} + \mathbb{b}
\end{align}
```

```W```は重み行列、```b```はバイアスベクトルです。簡単のためにこれらの値は
```math
\begin{align}
    \mathbb{y}^* = (y_1^*, y_2^*),
    \boldsymbol{W}^* =
    \begin{pmatrix}
    w_{11}^{\,*} & w_{12}^{\,*} \\
    w_{21}^{\,*} & w_{22}^{\,*}
    \end{pmatrix}, 
    \mathbb{x} = (x_1, x_2),
    \mathbb{b} = (b_1, b_2)
\end{align}
```
とします。
![nn_2d.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472015/f1994cae-8899-d917-66b9-d4bed97d8d59.png)

目的関数もしっかり定義してみましょう。今回は単なる差ではなく平均二乗誤差(MSE)を用います。
```math
L = \frac{1}{N} \sum_{i=1}^{N} {(t_{\,i} - y_{\,i})^2}
```
```N```はベクトルの要素数で、```t```は目標値です。今回は```N = 2```で定義します。

一応言語化してみましょう。目標ベクトル$(t_1, t_2)$に出力ベクトルを近づけたい。みたいな感じですかね。

### 学習の進み方(vec)
##### 順伝播(vec)
![nn_2d.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472015/f1d60844-d0e3-9e4b-e451-48c96f6593fb.png)

__線形変換__
```math
\begin{align}
u_1 &= \omega_{\,11}\,x_1 + \omega_{\,21}\,x_2 + b_1 \\
u_2 &= \omega_{\,12}\,x_1 + \omega_{\,22}\,x_2 + b_2
\end{align}
```
__非線形変換__
今回も活性化関数はReLUでいきましょう
```math
\begin{align}
h_1 &= ReLU(u_1) \\
h_2 &= ReLU(u_2)
\end{align}
```
※ 活性化関数は、ベクトルを受け取ったとしても基本的には要素ごとに適用されます
```math
y_1 = h_1, y_2 = h_2
```
__目的化処理__
```math
\begin{align}
l = L(y) &= \frac{1}{2} \sum_{i=1}^{2} {(t_{\,i} - y_{\,i})^2} \\
&= \frac{1}{2}\{(t_1 - y_1)^2 + (t_2 - y_2)^2\}
\end{align}
```
##
##### 逆伝播(vec)
![nn_2d.drawio (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472015/a9de48d6-ffc3-ac1a-4e5f-399278d8c150.png)

パラメータが一気に増えます。ベクトルになると更新したいパラメータが複数あるので、微分ではなく[勾配](https://mathlandscape.com/grad/)で逆伝播します。
```math
w_{11} \leftarrow w_{11} - \eta\frac{∂l}{∂w_{11}} = w_{11} - \eta\frac{∂l}{∂y_1}\frac{∂y_1}{∂w_{11}}
```
他同様なので省略します。

※このように書けるのは、lを関数で表したときは合成関数になるのですが、この関数一つ一つが微分可能であるからです。一部ReLUなどは微分不可能な点(x=0)がありますが、これは[劣微分](https://qiita.com/wosugi/items/8d5a407a0a0434aaabeb)などで解消します。

### 最適化後(vec)
![nn_2d.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472015/f1994cae-8899-d917-66b9-d4bed97d8d59.png)
入力ベクトル```x```に対して目標値に近づくように最適化(*)されたパラメータ関数```y```の出力ベクトルは
```math
\begin{align}
\mathbb{y}^* = \boldsymbol{W}^{*} \mathbb{x} + \mathbb{b}
\end{align}
```

##
普段私たちが使ってるAIはこの最適化がされ続けて一旦学習を終えたもの、というのが大まかな認識です。※大まかです。
最適化されたパラメータによって構成された「関数」は普段私たちが何気なく使用してる関数と見かけ上何も変わりません。※見かけ上。

ここで疑問、「NNじゃなくても普通の関数(アルゴリズム)でいけるのでは？」
全くもってその通りです。NNは手法の一つで場合によっては既存のアルゴリズム使ったほうがいい場合もあります。(例えば[ルービックキューブ](https://qiita.com/7y2n/items/24785b985e9c30862014)とか、AI使うまでもなく状態管理のアルゴリズムでいけますね。)
何が便利なのって人間では思いつかないようなアルゴリズムを学習によって導いてくれる点。

## NNについて本題
### 構造のお話
入力層、中間層、出力層はもちろんスカラー、ベクトル、行列だけじゃないです。テンソルで表現されます。
０次元テンソルがスカラー、
１次元テンソルがベクトル※、
２次元テンソルが行列...(以下略)
みたいなのです。

※３次元ベクトルは1次元テンソルのベクトルの要素数が３というイメージです。ここ間違いやすいです。

　この辺りは学習対象に依存してきます。画像や音声、それぞれで次元が変わるので、、
学習対象にあった入力層、出力層を設計したり、学習対象のデータを加工して入出力に合うようにしたり(データの前処理)して、中間層は通常、特徴量を抽出し、入力データをより表現力の高い特徴量空間にマッピングするために使用されます。中間層は複数の層にまたがることがほとんどです。
 この辺りは有名どころの論文とか、その解説記事を読んで感覚(本質)を掴んでくしかないです。頑張ってください。ざっと基礎を数ヶ月でさらいたい人には個人的には[ゼロから作るdeep learning](https://www.oreilly.co.jp/books/9784873117584/)シリーズがおすすめ。基礎がないのに最新技術追っても余程の人じゃない限りわからないと思うので基礎はおろそかにしたらダメだと思います。

### 層と層の関係のお話
　層と層の結合については、全結合や畳み込み、再帰が挙げられます。

__全結合__： k層目のn個のノードとk+1層目のj個のノード同士が全て(n×j個)繋がってるイメージ。上の例ではこれを採用

__畳み込み__: k層目の距離が近いノードを関連づけ、k+1層のノードに渡す。k層目の特徴量がk+1層目に表れているイメージ。画像向け

__再帰__：　 k層目のノードの出力結果を何度かk層目に入れるイメージです。時刻を保持するのに向いてる。

### 目的関数
　目的関数には損失関数や誤差関数などがある。筆者は損失関数しか知りません。
学習の最終的な目標値に近づけるために必要な関数というイメージ。
学習における何を最小化したいのとか、どういうふうに学習を進めればいいのっていう指標です。

損失関数には、MSE(平均二乗誤差）とかがあります。
詳しくは、調べてみてください。

https://qiita.com/Hatomugi/items/d00c1a7df07e0e3925a8

### 基本的な計算(順伝播)
![nuron.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472015/1e5df6b8-3e48-cd99-3c14-f83aea4312b1.png)

線形変換と非線形変換をこの順で繰り返し計算していきます。　
一つの層には複数のニューロンがあります。
その層が複数あり、ちょうど下の記事の最初にある図みたいなイメージです。

https://qiita.com/osadataku/items/fa38cfc697611783d465

##### 線形変換
直感で理解しようの項目に載せてるのでなんとなく分かってると思ってますが、
入力ベクトル$h$と重み行列```W```、バイアスベクトル$b$を使って
```math
\boldsymbol{W}\mathbb{h} + \mathbb{b}
```
と変換すること。[詳しくはこちら](https://tutorials.chainer.org/ja/13_Basics_of_Neural_Networks.html#%E7%B7%9A%E5%BD%A2%E5%A4%89%E6%8F%9B)

厳密にはアフィン変換ですが、ディープラーニングの文脈では+bを含む操作も線形変換と呼ばれることが多いです。

##### 非線形変換
Sigmoid関数とか、ReLUとかありますね。
[非線形変換の説明](https://tutorials.chainer.org/ja/13_Basics_of_Neural_Networks.html#%E9%9D%9E%E7%B7%9A%E5%BD%A2%E5%A4%89%E6%8F%9B)に詳しく載ってますが、活性化処理というのをすることで、このニューロンで計算された線形変換がどれくらい妥当な値かというのを数値化していると筆者は認識しています。

### 計算(逆伝播)
ここは勾配で重みを更新するやつですね。微分ではなく厳密には[勾配](https://mathlandscape.com/grad/)ですので勾配と呼びましょう。目的関数の最適化、重みの更新にもいろいろと方法があります。最適化手法、最適化アルゴリズムと呼ばれることが多いです。有名な記事を載せときます。

https://qiita.com/omiita/items/1735c1d048fe5f611f80

### そもそもなんで学習に逆伝播が必要か？
一つに、メモリ問題で学習時にニューラルネットワークの一つ一つのニューロン全ての計算結果を保持できないことがほどんどです。更新したいパラメータのみを記憶しておき、それを最終的な計算結果から勾配で導く逆伝播はメモリ問題を解決してくれます。
また、[解析的に解けない](https://jp.quora.com/%E6%96%B9%E7%A8%8B%E5%BC%8F%E3%82%92-%E4%BB%A3%E6%95%B0%E7%9A%84%E3%81%AB%E8%A7%A3%E3%81%8F-%E3%81%AE%E3%81%A8-%E8%A7%A3%E6%9E%90%E7%9A%84%E3%81%AB%E8%A7%A3%E3%81%8F-%E3%81%AE%E3%81%A8-%E6%95%B0%E5%80%A4%E7%9A%84)問題を解いてくれるというのも強みです。

# 完全に理解した
とは言ってません。筆者も完全に理解はしてないです。

# 参考にしたものなど

https://tutorials.chainer.org/ja/13_Basics_of_Neural_Networks.html

https://www.oreilly.co.jp/books/9784873119069

## 終わりに
(ただの感想というか所感なので読み飛ばして大丈夫です。お疲れ様でした！！！！)

数式なしでもわかる！という記事も散見されますが、違うと思います。
　言葉を概念に当てて、概念をなんとなく理解した気になってる。もちろん概念という領域に、言葉という一つ一つの言語領域によって近づけていくことで理解することもある。しかし、それには多くの試行錯誤が必要でそれこそ、私たちが機械学習的に大量のデータ、事例を帰納的にを学習する必要がある。せっかく数式という明確な定義があるのだからそれを使って理解した方が早いと思います。また、数式なしで言葉のみで正確に定義しようとするとかなりの文字数になります。友達が強化学習系の卒業論文を数式なしで書いてましたが、本来３０ページ相当のものを５０ページ強に拡張しており、「読むのに時間がかかった」「嫌がらせだよね笑」と教授が苦悶していたのを覚えています。数式は言葉で表すことができますが、数式それ自体が言語なのでわざわざテキストに変換せずに一発で脳内にぶち込む方が絶対に気持ちいいです。食わず嫌いせずに数学の基礎から学びましょう。ちなみに筆者も基礎から学び直し中です。

最後まで読んでいただき、ありがとうございました。