【ラビットチャレンジ】 stage3 - 深層学習前編 (day1)

#ラビットチャレンジ

ラビットチャレンジはE資格の受験に必要な協会認定講座のプログラムです。
ラビットチャレンジでは認定プログラムを修了してE資格の受験資格を習得するために、指定のレポートとテストで合格点を取得しなければなりません。

https://ai999.careers/rabbit/

今回はStage3深層学習前編(day1)についてまとめます。

- [ ] Stage1 応用数学
- [ ] Stage2 機械学習
- [x] stage3 深層学習前編(day1, day2)
- [ ] stage4 深層学習後編(day3, day4)

---

# ニューラルネット

ニューラルネットワークは大きく分けて「入力層」「中間層」「出力層」に分かれます。
入力層ではパラメータなど値を入力し、中間層で処理し、出力層で結果を処理します。
例）体長、体重、ひげの本数・・・を入力し結果として犬っぽさ0.1、猫っぽさ0.05、ねずみっぽさ0.85などの値を得る

### 確認テスト① P5
Q01. ディープラーニングは、結局何をやろうとしてるか2行以内で述べよ。
Q02. また、次の中のどの値の最適化が最終目的か全て選べ
　　①入力値、②出力地、③重み、④バイアス、⑤総入力、⑥中間層入力、⑦学習率

A01. 入力されたパラメータから出力値に対する最適な変換値を自動で求めようとしていA02. 重みとバイアス

### 確認テスト② P7
Q03. 次のネットワークを紙にかけ。
　　入力層：2ノード1層、出力層：3ノード2層、出力層1ノード1層
A03. ![img1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/e0945b5a-c923-4750-4d42-07ff11fa1807.png)


### ニューラルネットでできること
・分類：猫写真の判定や手書き文字の認識などの離散値もを求める
・回帰：売上予測や株価予想、順位予想等の連続値を求める
※4つ以上の中間層をもつものを深層ニューラルネットワークと呼ぶ

### 回帰
連続する実数値をとる関数の近似
回帰分析には「線形回帰」「回帰木」「ランダムフォレスト」「ニューラルネットワーク」などの手法がある

### 分類
性別や動物の種類など離散的な結果を予想するための分析
分類分析には「ベイズ分類」「ロジスティック回帰」「決定木」「ランダムフォレスト」「ニューラルネットワーク」などの手法がある

# Section1 入力層～中間層

$u = w_1x_1 + w_2x_2 + w_3x_3 + w_4x_4 + b$
$z = f(u)$

・$x$:入力
・$w$:重み
・$b$:バイアス
・$u$:総入力
・$z$:出力
・$f$:活性化関数
・$i$:入力層のインデックス

### 確認テスト③ P13
Q04. 入力層から中間層の図式に動物分類の実例をいれよ
A04. ![img2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/ac3dcf6e-bc2c-3d12-0866-48c3b97a2486.png)

### 確認テスト④ P15
Q05. $u = w_1x_1 + w_2x_2 + w_3x_3 + w_4x_4 + b$ この式をPythonできじゅせよ
A05.
 
```Python
u = np.dot(x, w)+b
```

dot関数は行列の積和を行う

### 確認テスト⑤ P17
Q06. 指定のファイルから中間層の出力を定義しているソースコードを抜き出せ
A06. 

```Python
z = functions.relu(u)
```

### 実装演習 (順伝播)

```Python
# 重み
W = np.array([[0.1], [0.2]])
print_vec("重み", W)

# バイアス
b = 0.5
print_vec("バイアス", b)

# 入力値
x = np.array([2, 3])
print_vec("入力", x)

# 総入力
u = np.dot(x, W) + b
print_vec("総入力", u)

# 中間層出力
z = functions.relu(u)
print_vec("中間層出力", z)
```

出力結果

```
*** 重み ***
[[0.1]
 [0.2]]

*** バイアス ***
0.5

*** 入力 ***
[2 3]

*** 総入力 ***
[1.3]

*** 中間層出力 ***
[1.3]
```

---

# Section2 活性化関数
活性化関数とはニューラルネットワークおいて、次の層への出力の大きさを決める非線形の関数。
入力値の値によって、次の層への信号のON/OFFや強弱を定める働きをもつ。

### 確認テスト⑥ P20
Q07. 線形と非線形の違いを図に書いて簡単に説明せよ
A07. 線形は直線で表され、非線形は曲線で表される

|線形|非線形|
|:---:|:---:|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/c6f0cae1-0e79-ceed-5868-867a1f8bdbe2.png)|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/9516c7d1-b970-de5b-8301-0537885a832b.png)
|
## 中間層で用いられる活性化関数
###ステップ関数
・しきい値を超える発火する間であり、出力は常に1か0
・課題としては、0と1の間を表現することができず、線形分離可能なものしか学習できない

数式

```math
f(x) = \left\{
\begin{array}{ll}
1 & (x\geqq 0)\\
0 & (x \lt 0)
\end{array}
\right.
```

サンプルコード

```Python
import matplotlib.pyplot as plt
import numpy as np

def step_function(x):
    return 1 if x > 0 else 0

x = np.linspace(-10, 10, 100)
y = [step_function(i) for i in x]
plt.plot(x, y)
plt.show()
```

![step_func.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/a565919b-1f04-d8c6-9f9a-9165a9260bae.png)

---

###シグモイド関数
・0~1の間を緩やかに変化する関数で、信号の強弱を伝えることができる。
・課題としては、大きな値では出力の変化が微小なため勾配消失問題を引き起こすことがある

数式

```math
f(u) = \frac{1}{1+e^{-u}}
```

サンプルコード

```Python
import matplotlib.pyplot as plt
import numpy as np

def sigmoid_function(x):
    return 1 / (1 + np.exp(-x))

x = np.linspace(-10, 10, 100)
y = [sigmoid_function(i) for i in x]
plt.plot(x, y)
plt.show())
```

![sigmoid_func.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/37a03cf6-1534-32a9-7617-da1e3f1f834d.png)

---

###ReLU関数
・0以下では0を返し、0より大きい場合には入力値を返す。
・勾配消失問題の回避とスパース化に貢献することで良い成果をもたらしている。

数式

```math
f(x) = \left\{
\begin{array}{ll}
x & (x > 0)\\
0 & (x \leq 0)
\end{array}
\right.
```

サンプルコード

```Python
import matplotlib.pyplot as plt
import numpy as np

def relu_function(x):
    return np.maximum(0, x)

x = np.linspace(-10, 10, 100)
y = [relu_function(i) for i in x]
plt.plot(x, y)
plt.show()
```

![relu_function.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/48318ad3-4f60-2abe-f506-4b3723f83c47.png)

---

### 確認テスト⑦ P28
Q08. 配布されたコードより活性化関数に該当する箇所を抜き出せ
A08. 

```Python
# 中間層出力
z = functions.sigmoid(u)
print_vec("中間層出力", z)
```

# Section3 出力層

###誤差関数
訓練データとはデータと正解値とのペアでNNの出力結果と正解ラベルと答え合わせを行い、その誤差を得る

###二乗誤差 MSE （Mean square error)
```math
En(w) = \frac{1}{2}\sum_{j=1}^{1}(y_j - d_j)^2=\frac{1}{2}||y-d||^2
```
例）犬0.82，猫0.07、ネズミ0.11で正解が犬の場合

```math
En(w) = \frac{1}{2}((1-0.82)^2+(0-0.07)^2+(0-0.11)^2)=0.01871

```

正解の犬は$y$が1となり、不正解の猫とネズミは0となる

###確認テスト⑧ 38P
Q08. なぜ引き算ではなく、2乗するのか述べよ
Q09. 二乗誤差の1/2はどういう意味を持つか述べよ

A08. 2乗することで常に正となり符号の計算処理が不要になるため。
A09. 誤差逆伝播の計算時の微分が簡単になるため


##出力層の活性化関数
中間層ではしきい値の前後で信号の強弱を調整するが、出力層では信号の大きさ(比率)はそのままに変換する
分類問題の場合には、出力層の出力は0~1に限定し、総和を1(100%)とする必要がある
つまり使用される活性関数が中間層と出力層では異なる


||回帰|二値分類|他クラス分類|
|:---:|:---:|:---:|:---:|
|活性化関数|恒等写像|シグモイド関数|ソフトマックス関数|
|誤差関数|二乗誤差|交差エントロピー|交差エントロピー|

---

###シグモイド関数

数式

```math
f(u) = \frac{1}{1+e^{-u}}
``` 

実装演習

```Python
def sigmoid_function(x):
    return 1 / (1 + np.exp(-x))
```

---

###ソフトマックス関数

数式

```math
f(i, u) = \frac{e^{u_i}}{\sum_{k = 1}^{K}e^{u_k}}
``` 

実装演習

```Python
def softmax(x):
    if x.ndim == 2:
        x = x.T
        x = x - np.max(x, axis = 0)
        y = np.exp(x) / np.sum(np.exp(x), axis = 0)
        return y.T
    x = x - np.max(x)
    return np.exp(x) / np.sum(np.ext(x))
```

---

###二乗誤差

数式

```math
En(W) = \frac{1}{2}\sum_{j=1}^{1}(y_j - d_j)^2
```

実装演習

```Python
def mean_squared_error(d, y):
    return np.mean(np.square(d - y)) / 2
```

---

###交差エントロピー

数式

```math
En(W) = -\sum_{i=1}^{I}d_i \log y_i
```

実装演習

```Python
def cross_entropy(d, y):
    if y.ndim == 1:
        d = d.reshape(1, d.size)
        y = y.reshape(1, y.size)
    if d.size == y.size:
        d = d.argmax(axis = 1)
    batch_size = y.shape[0]
    return -np.sum(np.log(y[np.arange(batch_size), d] + 1e-7)) / batch_size
```

###確認テスト⑨ 45P
Q10. ソフトマックス関数について説明せよ
A10. 

```math
①f(i, u) = \frac{②e^{u_i}}{③\sum_{k = 1}^{K}e^{u_k}}
``` 

```Python
def softmax(x):
    if x.ndim == 2:                   # 2次元である場合
        x = x.T                       # xを転置
        x = x - np.max(x, axis = 0)   # オーバーフロー対策のため最大値で減算
        y = np.exp(x) / np.sum(np.exp(x), axis = 0) # ソフトマックス本体数式処理
        return y.T                    # 転置してリターン
    x = x - np.max(x)                 # オーバーフロー対策のため最大値で減算
    return np.exp(x) / np.sum(np.ext(x)) # ソフトマックス本体数式処理
```

① softmax(x)
② np.exp(x)
③ np.sum(np.ext(x))



###確認テスト⑩ 47P
Q11. 交差エントロピーについて説明せよ
A11. 

```math
①En(W) = ②-\sum_{i=1}^{I}d_i \log y_i
```

① cross_entropy(d,y)
② -np.sum(np.log(y[np.arange(batch_size), d] + 1e-7)) / batch_size

```Python
def cross_entropy(d, y):
    if y.ndim == 1:                 # yが1次元である場合それぞれ1次元にする
        d = d.reshape(1, d.size) 
        y = y.reshape(1, y.size) 
    # 教師データがone-hot-vectorの場合、正解ラベルのインデクスに変換
    if d.size == y.size:
        d = d.argmax(axis = 1)
    batch_size = y.shape[0]
    return -np.sum(np.log(y[np.arange(batch_size), d] + 1e-7)) / batch_size # 数式本体
# 1e-7はマイナス無限大は発生させないため追加
```


# Section4 勾配降下法
深層学習の目的は、学習を通して誤差を最小にするネットワークを作成すること。
つまり、誤差E(W)を最小化するパラメータWを発見することになる。
そのためには勾配降下法を用いてパラメータを最適化する

###勾配降下法

```math
w^{(t+1)} = w^{(t)} - \epsilon\nabla E\\
\nabla E = \frac{\partial E}{\partial w} = \left[\frac{\partial E}{\partial w_1}, \dots, \frac{\partial E}{\partial w_M} \right]\\
\epsilon:学習率
```

###確認テスト⑪ 50P
Q11. 上記のコードを示せ
A11. 

```python
grad = backward(x, d, z1, y)
for key in ('W1', 'W2', 'b1', 'b2'):
    network[key]  -= learning_rate * grad[key]
```

学習率が大きすぎる場合には最小値にいつまでもたどり着かずに発散してしまう
また、学習率が小さい場合には発散することはないが、収束までに時間がかかる
そこで勾配降下法の学習率の決定にいろいろなアルゴリズムが使用されている
・Momentum/AdaGrad/Adadelta/Adamなど

入力層⇒中間層⇒出力層⇒誤差関数⇒W,Bの更新
この一周をエポックと呼ぶ

###確率的勾配降下法 (SDG)

```math
w^{(t+1)} = w^{(t)} - \epsilon\nabla E_n
```
通常の勾配降下法では全てのサンプルの平均誤差を求めるが、確率的勾配降下法ではランダムに抽出したサンプルの誤差を求める
データが冗長な場合の計算コスト削減、局所極小解に収束するリスクの軽減、オンライン学習ができるなどのメリットがある

###確認テスト⑫ 59P
Q12. オンライン学習とは何か？
A12. 新しいデータが入ってきたときに、そのデータのみで学習を行う
　　 対するバッチ学習は毎回全てのデータで学習を行う

###ミニバッチ勾配降下法
```math
{\begin{array}{ii}
\boldsymbol{w}^{(t+1)} = \boldsymbol{w}^{(t)} - \epsilon\nabla E_t\\
E_t=\frac{1}{N_t}\sum_{n \in D_t}E_n\\
N_t=|D_t|
\end{array}
}
```

ランダムに分割したデータの集合(ミニバッチ)$D_t$に属するサンプルの平均誤差を求める。
CPUを利用したスレッド並列化やGPUを利用したSIMD並列化により確率的勾配降下法のメリットを損なわず、計算機の計算資源を有効利用することができる。

###確認テスト⑬ 62P
Q13. 以下の数式の意味を図に書いて説明せよ
A13.
```math
w^{(t+1)} = w^{(t)} - \epsilon\nabla E_n
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1883771/2da128e3-b317-2460-7c9b-59f42ca26fc8.png)

# Section5 誤差逆伝播法
数値微分式・・・プログラムで微小な数値を生成し疑似的に微分を計算する一般的な手法
これだと各パラメータ$W_m$それぞれについて$E(Wm+h)$や$E(W_m-h)$を計算するために、準伝播の計算を繰り返し行う必要があり負荷が大きい。
そこで、誤差逆伝播法を使用する

誤差逆伝播法では算出された誤差を、出力層側から順に微分し、前の層へと伝播する。最小限の計算で各パラメータでの微分値を解析的に計算する

計算結果(=誤差)から微分を逆残することで不要な再帰的計算を避けて微分を算出できる。

###確認テスト⑭ 71P
Q14. 誤差逆伝播法では不要な再帰処理を避けることができる。
　　 既に行った計算結果を保持しているソースコードを抽出せよ。
A14.

```Python
delta2 = functions.d_mean_squared_error(d, y)
```

###確認テスト⑭ 76P
Q15. 次に該当するコードを抽出せよ

```math
\frac{\partial E}{\partial y} \times \frac{\partial y}{\partial u} 
```

A15. 

```Python
delta2 = functions.d_mean_squared_error(d, y)
```

Q16. 次に該当するコードを抽出せよ

```math
\frac{\partial E}{\partial y} \times \frac{\partial y}{\partial u} \times  \frac{\partial u}{\partial w_{ji}^{(2)}}
```

A16. 

```Python
grad['W2'] = np.dot(z1.T, delta2)
```

### 実装演習
```Python
# coding: utf-8

import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import numpy as np
from common import functions
import matplotlib.pyplot as plt

# 行列を1行で表示
def _print_matrix(name, mat):
    mat_dim1 = mat.reshape(mat.size)
    mat_size = mat_dim1.size
    print(name + ':[', end = '')
    for i in range(mat_size):
        print('{0:.2f}'.format(mat_dim1[i]), end = '')
        if i != mat_size - 1:
            print(', ', end = '')
    print('] ', end = '')

# ウェイトとバイアスを初期化
def init_network():
    print("##### ネットワークの初期化 #####")

    network = {}
    network['W1'] = np.array([
        [0.1, 0.3, 0.5],
        [0.2, 0.4, 0.6]
    ])

    network['W2'] = np.array([
        [0.1, 0.4],
        [0.2, 0.5],
        [0.3, 0.6]
    ])

    network['b1'] = np.array([0.1, 0.2, 0.3])
    network['b2'] = np.array([0.1, 0.2])
    
    _print_matrix("w1", network['W1'])
    _print_matrix("w2", network['W2'])
    _print_matrix("b1", network['b1'])
    _print_matrix("b2", network['b2'])
    print('')

    return network

# 順伝播
def forward(network, x):
    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']
    
    u1 = np.dot(x, W1) + b1
    z1 = functions.relu(u1)
    u2 = np.dot(z1, W2) + b2
    y = functions.softmax(u2)
    
    return y, z1

# 誤差逆伝播
def backward(x, d, z1, y):
    grad = {}

    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']
    # 出力層でのデルタ
    delta2 = functions.d_sigmoid_with_loss(d, y)
    # b2の勾配
    grad['b2'] = np.sum(delta2, axis=0)
    # W2の勾配
    grad['W2'] = np.dot(z1.T, delta2)
    # 中間層でのデルタ
    delta1 = np.dot(delta2, W2.T) * functions.d_relu(z1)
    # b1の勾配
    grad['b1'] = np.sum(delta1, axis=0)
    # W1の勾配
    grad['W1'] = np.dot(x.T, delta1)
    return grad
    
# 訓練データ
x = np.array([[1.0, 5.0]])
# 目標出力
d = np.array([[0, 1]])
#  学習率
learning_rate = 0.01
network =  init_network()

print("##### 結果表示 #####")    
for i in range(100):
    y, z1 = forward(network, x)

    # 誤差
    loss = functions.cross_entropy_error(d, y)

    grad = backward(x, d, z1, y)
    for key in ('W1', 'W2', 'b1', 'b2'):
        network[key]  -= learning_rate * grad[key]

    print('# 更新{0:4d}回目'.format(i + 1), end = '')
    _print_matrix('W1', network['W1'])
    _print_matrix('W2', network['W2'])
    _print_matrix('b1', network['b1'])
    _print_matrix('b2', network['b2'])
    print('')
```

結果

```
##### ネットワークの初期化 #####
w1:[0.10, 0.30, 0.50, 0.20, 0.40, 0.60] w2:[0.10, 0.40, 0.20, 0.50, 0.30, 0.60] b1:[0.10, 0.20, 0.30] b2:[0.10, 0.20]
##### 結果表示 #####
# 更新   1回目W1:[0.10, 0.30, 0.50, 0.20, 0.40, 0.60] W2:[0.10, 0.40, 0.20, 0.50, 0.30, 0.60] b1:[0.10, 0.20, 0.30] b2:[0.10, 0.20]
# 更新   2回目W1:[0.10, 0.30, 0.50, 0.20, 0.40, 0.60] W2:[0.10, 0.40, 0.20, 0.50, 0.29, 0.61] b1:[0.10, 0.20, 0.30] b2:[0.10, 0.20]

・・・途中略・・・

# 更新 998回目W1:[0.12, 0.33, 0.53, 0.31, 0.54, 0.76] W2:[0.01, 0.49, 0.03, 0.67, 0.05, 0.85] b1:[0.12, 0.23, 0.33] b2:[0.04, 0.26]
# 更新 999回目W1:[0.12, 0.33, 0.53, 0.31, 0.54, 0.76] W2:[0.01, 0.49, 0.03, 0.67, 0.05, 0.85] b1:[0.12, 0.23, 0.33] b2:[0.04, 0.26]
# 更新1000回目W1:[0.12, 0.33, 0.53, 0.31, 0.54, 0.76] W2:[0.01, 0.49, 0.03, 0.67, 0.05, 0.85] b1:[0.12, 0.23, 0.33] b2:[0.04, 0.26]

```

値が変化していっているのは確認できた