ラビットチャレンジ 深層学習day1

##本記事について
ラビットチャレンジの深層学習day1のレポートです。
ラビットチャレンジはStudy-AI開講のE資格受験資格を得られる認定プログラムとなっています。

## 識別モデルと生成モデル

- 識別モデルと生成モデルの違い

|  | 識別(discriminative, backward) | 生成(generative, toward) |
|  :---: | :---: |  :---:|
| 目的 | データを目的のクラスに分類する <br> データ→クラス| 特定のクラスのデータを生成する　<br> クラス→データ|
| 例 | 犬や猫の画像データを識別(分類) | 犬らしい画像を生成 |
| 計算結果 | $p(C_{k}&#124;x)$ <br>あるデータ$x$が与えられたという条件の下でクラス$C_{k}$である確率 | $p(x&#124;C_{k})$ <br>あるクラス$y$に属するという条件の下でデータ$C_{k}$の分布 |

- 識別モデルと生成モデルの手法

|  | 識別(discriminative, backward) | 生成(generative, toward) |
|  :---: | :--- |  :---|
| モデル | ・決定木 <br> ・ロジスティック回帰<br>・SVM<br>・ニューラルネットワーク| ・隠れマルコフモデル<br>・ベイジアンネットワーク<br>・変分オートエンコーダ(VAE)<br>・GAN|
| 特徴 | ・高次元→低次元<br>・必要な学習データ：少 | ・低次元→高次元<br>・必要な学習データ：多 |
| 応用例 | ・画像認識 | ・画像の超解像<br>・テキスト生成 |

## ニューラルネットワーク

### ニューラルネットワークの全体像
- 特徴
    - 入力層、中間層、出力層からなる
    - 中間層が多層になるとディープニューラルネットワークと呼ばれる
- 確認テスト
    1. ディープラーニングは結局何をしようとしているのか二行以内で述べよ
    2. また、次の中のどの値の最適化が最終目的か。すべて選べ。  
①入力値[$x$]  
②出力値[$Y$]  
③重み[$W$]  
④バイアス[$b$]  
⑤総入力[$u$]  
⑥中間層入力[$z$]  
⑦学習率 [$p$]  
    3. 入力層が2ノード1層、中間層が3ノード2層、出力層が1ノード1層のネットワークを書け。 

- 確認テスト解答
    1. 明示的なプログラムの代わりに多数の中間層を持つニューラルネットワークを用いて入力値から目的とする出力値に変換できる数学モデルを構築すること。
    2. ③重み[$W$]、④バイアス[$b$]
    3. 下記の通り
![IMG_3799.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2235425/9144c6fd-bc23-94de-eeb8-218c1af9cd5e.jpeg)

### 入力層～中間層
- 入力層と中間層の関係は下記の式のようになる
$$u = w_{1}x_{1} + w_{2}x_{2} + w_{3}x_{3} + w_{4}x_{4} + b$$
$$ = Wx + b$$
（入力値：$x$、重み：$W$、バイアス：$b$、総入力：$u$、出力：$z$、活性化関数：$f$）
- 確認テスト
  1. 下の式をPythonで書け
$$u = w_{1}x_{1} + w_{2}x_{2} + w_{3}x_{3} + w_{4}x_{4} + b$$
$$ = Wx + b$$
  2. 添付のソースコード「1_1_forward_propagation.ipynb」から中間層の出力を定義しているソースを抜き出せ

- 確認テスト解答

```python
# 1. プログラムは下記の通り
u1 = np.dot(x, W1) + b

# 2. 抜き出したソースは下記の通り
# 重み
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)
```

- 実装演習結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2235425/d9fb8735-eacb-33fc-e215-a550288a7b0e.png)


### 活性化関数
- 活性化関数とは
  - ニューラルネットワークにおいて、次の層への出力の大きさを決める非線形の関数。
  - 入力の値によって、次の層への信号のON、OFFや強弱を定める働きを持つ。
- 活性化関数の種類
  - 中間層用
     - ReLU関数 
     - シグモイド(ロジスティック)関数
     - ステップ関数
  - 出力層用
     - ソフトマックス関数 
     - 恒等写像
     - シグモイド(ロジスティック)関数
- 確認テスト
  1. 線形と非線形の違いを図に書いて簡単に示せ
![0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2235425/b3538797-2e9e-b41d-c458-28876fdd3cda.jpeg)


#### ステップ関数 
- 数式

```math
\begin{equation} x(x)=\begin{cases} 1 & \text{$x\geq0$} \\ 0 & \text{$x<0$} \end{cases} \end{equation}
```

- サンプルコード

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

- グラフ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2235425/3991b3ee-9487-67a1-1652-9880cdcd1f05.png)

- 課題
  - 0か1しか表現できず、線形分離可能なものしか学習できなかった。

#### シグモイド関数
- 数式

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

- サンプルコード

```python
def sigmoid(x):
    return 1 / (1 + np.exp(-x))
```

- グラフ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2235425/fb6d318c-7064-e85c-2384-5915ae894ba7.png)

- 課題
  - 大きな値では出力の変化が微小なため、勾配消失問題を引き起こす事があった。

#### ReLU関数
- 数式

```math
\begin{equation} x(x)=\begin{cases} x & \text{($x>0$)} \\ 0 & \text{($x\leq0$)} \end{cases} \end{equation}
```

- サンプルコード

```python
def relu(x):
    return np.maximum(0, x)
```

- グラフ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2235425/4e88316a-3e8a-0861-251c-de0542db53cc.png)

- 特徴
  - 今最も使われている活性化関数。
  - 勾配消失問題の回避とスパース化に貢献することで良い成果をもたらしている。

#### 単層・複数ノード
- 全結合ニューラルネットワーク
  - 中間層の出力がさらに次の層に伝播
  - 活性化関数の効果で一部の出力は弱く、一部は強く伝播される
- 確認テスト
  1. 配布されたソースコード「1_1_forward_propagation.ipynb」より中間層の出力箇所を抜き出せ。

- 確認テスト解答

```python
# 順伝播（単層・複数ユニット）

# 重み
W = np.array([
    [0.1, 0.2, 0.3], 
    [0.2, 0.3, 0.4], 
    [0.3, 0.4, 0.5],
    [0.4, 0.5, 0.6]
])
print_vec("重み", W)

# バイアス
b = np.array([0.1, 0.2, 0.3])
print_vec("バイアス", b)

# 入力値
x = np.array([1.0, 5.0, 2.0, -1.0])
print_vec("入力", x)

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

# 中間層出力 ★ここが答え
z = functions.sigmoid(u)
print_vec("中間層出力", z)
```

- 実装演習結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2235425/5dbc0e60-2ed2-0d4e-807d-bbe5144be501.png)

### 出力層

#### 出力層の役割
- 各クラスに分類される確率を出力する

#### 誤差関数
- 誤差関数とは
  - 訓練データとニューラルネットワークで出力した結果を比較する関数
  - どのくらい正解していたかを評価できる
- 数式
$$ E_{n}(w) = \frac{1}{2} \sum_{j=1}^{J}(y_{i} - d_{j})^2 = \frac{1}{2} ||(y_{i} - d_{j})||^2 $$
- 確認テスト
  1. なぜ、引き算でなく二乗するか述べよ
  2. 下式の1/2はどういう意味を持つか述べよ 
- 確認テスト解答
  1. 引き算をすると誤差で正の値と負の値が発生し打ち消しあってしまうため、二乗して各ラベルの誤差が必ず正の値となるようにしている。
  2. 誤差伝播の計算で微分が必要となるため、降りてきた2をかけて1にできるよう1/2としている。

#### 数式とコード 
- 数式
$$ E_{n}(w) = \frac{1}{2} \sum_{j=1}^{J}(y_{i} - d_{j})^2 = \frac{1}{2} ||(y_{i} - d_{j})||^2 $$
- コード

```python 
loss = functions.mean_squared_error(d, y)
```
- 注意
  - 分類問題の場合は誤差関数にクロスエントロピー誤差を用いる

```python  
# クロスエントロピー誤差
loss =cross_entropy_error(d,y)
```

#### 活性化関数
- 出力層の中間層との違い
  - 値の強弱 
     - 中間層︓しきい値の前後で信号の強弱を調整
     - 出力層︓信号の大きさ（比率）はそのままに変換
  - 確率出力
     - 分類問題の場合、出力層の出力は0 ~ 1 の範囲に限定し、総和を１とする必要がある
- 出力層と中間層で利用される活性化関数が異なる

#### 恒等写像
- 目的
  - 回帰
- 誤差関数
  - 二乗誤差

#### シグモイド関数
- 目的
  - 二値分類
- 数式
$$ f(u) = \frac{1}{1+e^{-u}} $$
- 誤差関数
  - 交差エントロピー
- ソースコード

```python
def sigmoid(x):
    return 1 / (1 + np.exp(-x))
```


#### ソフトマックス関数
- 目的
  - 多クラス分類
- 数式
$$ f(i, u) = \frac{e^{u_{i}}}{\sum_{k=1}^{K}e^{u_{i}}} $$
- 誤差関数
  - 交差エントロピー
- ソースコード

```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.exp(x))
```

- 確認テスト
  - 上記のソースコードで下記の数式に当たる部分を示し内容を説明せよ。
$$ f(i, u) = \frac{e^{u_{i}}}{\sum_{k=1}^{K}e^{u_{i}}} $$

- 確認テスト解答

```python
# np.exp(x)で数式におけるeのui乗を示す
# np.sum(np.exp(x))で数式におけるeのuk乗のk=1からk=Kまでの和を示す
return np.exp(x) / np.sum(np.exp(x))
```

#### 二乗誤差
- 数式
$$ E_{n}(w) = \frac{1}{2} \sum_{j=1}^{J}(y_{i} - d_{j})^2 $$
- ソースコード

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

#### 交差エントロピー
- 数式
$$ E_{n}(w) = -\sum_{j=1}^{J}d_{i} - \log{y_{j}} $$
- ソースコード

```python
def cross_entropy_error(d, y):
    if y.ndim == 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
```

- 確認テスト
  - 上記のソースコードで下記の数式に当たる部分を示し内容を説明せよ。
$$ E_{n}(w) = -\sum_{j=1}^{J}d_{i} - \log{y_{j}} $$

- 確認テスト解答

```python
# np.log(y[np.arange(batch_size), d] + 1e-7)はdiとlog(yi)の掛け算
# -np.sum()で()内の数式を足したものの正負を逆にする
return -np.sum(np.log(y[np.arange(batch_size), d] + 1e-7)) / batch_size
```

### 勾配降下法
#### 勾配降下法
- 目的
  - 学習の誤差$E(w)$を最小にするパラメータ$w$を探す

- 数式
$$ w^{(t+1)} = w^{t} -	\varepsilon \nabla E $$
$$ \nabla E = \frac{\delta E}{\delta w} = [\frac{\delta E}{\delta w_{1}} ... \frac{\delta E}{\delta w_{M}}]$$
- 注意
  - 学習率$\varepsilon$の値によって学習の効率が大きく変わる
     - 大きすぎると最小値にいつまでもたどり着かず発散してしまう
     - 小さすぎると収束するまでに時間がかかってしまう(発散はしない)
- 確認テスト
  - 上式をソースコードから抜き出せ
- 確認テスト解答
$$ w^{(t+1)} = w^{t} -	\varepsilon \nabla E $$

```python
network[key]  -= learning_rate* grad[key]
```

$$ \nabla E = \frac{\delta E}{\delta w} = [\frac{\delta E}{\delta w_{1}} ... \frac{\delta E}{\delta w_{M}}]$$

```python
grad = backward(x, d, z1, y)
```
- ポイント
  - 勾配降下法の学習率の決定、収束性向上のためのアルゴリズムについて複数の論文が公開され、よく利用されている
  - 例
     - Momentum
     - AdaGrad
     - Adadelta
     - Adam

#### 確率的勾配降下法(SGD)
- 特徴
  - ランダムに抽出したサンプルの誤差を利用する

- メリット
  - データが冗⻑な場合の計算コストの軽減
  - 望まない局所極小解に収束するリスクの軽減
  - オンライン学習ができる

- 確認テスト
  - オンライン学習とは何か
- 確認テスト解答
  - 学習データが入ってくるたびに都度パラメータを更新し学習を進める方法
  - 一度にすべての学習データを使う方法はバッチ学習と呼ばれる

#### ミニバッチ勾配降下法
- 特徴
  - ランダムに分割したデータの集合(ミニバッチ)$D_{t}$に属するサンプルの平均誤差

- メリット
  - 確率的勾配降下法のメリットを損なわず、計算機の計算資源を有効利用できる
  - CPUを利用したスレッド並列化やGPUを利用したSIMD並列化

- 数式
$$ w^{(t+1)} = w^{t} -	\varepsilon \nabla E  $$
$$ E_{t} = \frac{1}{N_{t}} \sum_{n \in D_{t}} E_{n} $$
$$ N_{t} = |D_{t}| $$

- 確認テスト
  - 下記の数式の意味を図で説明せよ
$$ w^{(t+1)} = w^{t} -	\varepsilon \nabla E  $$

- 確認テスト解答
![0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2235425/53983171-15b7-e7fe-d65b-a3c9913cf78a.jpeg)

### 誤差逆伝播法
- 目的
  - 下の式であらわされる誤差$E$の微分を求めるため、もともとは数値微分が利用されていたが計算量が膨大だった
  - 誤差伝播法で最小限の計算での$E$の微分が可能になった

$$ \nabla E = \frac{\delta E}{\delta w} = [\frac{\delta E}{\delta w_{1}} ... \frac{\delta E}{\delta w_{M}}]$$

- 特徴
  - 算出された誤差を、出力層側から順に微分し、前の層前の層へと伝播する
  - 最小限の計算で各パラメータでの微分値を解析的に計算する手法
  - 計算結果（=誤差）から微分を逆算することで、不要な再帰的計算を避けて微分を算出できる

- 確認テスト
  1. 誤差逆伝播法では不要な再帰的処理を避ける事が出来る。既に行った計算結果を保持しているソースコードを抽出せよ。
  1. ソースコード内で、下記の二つの式に該当する部分を抜き出せ。
$$ (1) \frac{\delta E}{\delta y} \frac{\delta y}{\delta u} $$
$$ (2) \frac{\delta E}{\delta y} \frac{\delta y}{\delta u} \frac{\delta u}{\delta w_{ji}^{(2)}} $$

- 確認テスト解答

```python
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)

    #  中間層でのデルタ
    #  確認テスト(1)解答
    delta1 = np.dot(delta2, W2.T) * functions.d_relu(z1)
    # b1の勾配
    grad['b1'] = np.sum(delta1, axis=0)
    #  W1の勾配
    #  確認テスト(2)解答
    grad['W1'] = np.dot(x.T, delta1)
```

- 実装演習結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2235425/c0fa641a-8f5f-4aa0-7e13-fc6e84e744a3.png)