ディープラーニング講座「深層学習：Day1」要点まとめ＆実装演習


私は、2020年7月に<font color="red">__「G検定（2020 #2）」__</font>に合格した。
次は、2021年2月実施の「E資格（2021 #1）」受験資格である__JDLA認定プログラム「３カ月で現場で潰しが効くディープラーニング講座」__に2020年8月から挑戦中。

本記事では、__JDLA認定プログラム「3カ月で現場で潰しが効くディープラーニング講座」__の課題であり、__「深層学習：Day1」__実装演習結果をまとめる。


## 1.「深層学習：Day1」講義動画の要点まとめ

<dl>
<dt>ディープラーニングは何をしようとしているのか？</dt>
<dd>明示的なプログラムの代わりに多数の中間層を持つニューラルネットワークを用いて、<br>
入力値から目的とする出力値に変換する数学モデルを構築すること</dd>

<br>
<dt>ニューラルネットワークの全体像</dt>
<dd>・入力層～中間層～出力層の３パートに分かれる</dd>
<dd>・重み$w$とバイアス$b$を自力で学習し、最適化することが目的</dd>


<br>
<dt>ニューラルネットワークが解決できる問題</dt>
<dd>・回帰：連続的な数字を予測する　例：売上予想、売価</dd>
<dd>・分類：離散的な結果を予想する　例：性別、動物の種類</dd>
</dl>


### Section1）入力層～中間層

![deep1-01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/ec776a88-ac7a-0cdc-5322-b55bfca6aa19.jpeg)

複数の入力$Xi$、重み$Wi$、バイアス$b$により、中間層の総入力$u$が導き出される。

```math
W＝
\left(
\begin{array}{cc}
w_1 \\ 
\vdots \\
w_i \\
\end{array}
\right),\;
X=
\left(
\begin{array}{cc}
x_1 \\ 
\vdots \\
x_i \\
\end{array}
\right)\\
\;\\
u=w_1 x_1+w_2 x_2+w_3 x_3+w_4 x_4 +b\\
=WX+b
```

中間層を複数重ねることで、より深く学習ができるニューラルネットワークを作ることができる。図は、入力層から1つ目の中間層への図となっており、総入力$u$から活性化関数$f$により出力$z$を導き出し、その$z$が次の中間層の入力となる。


### Section2）活性化関数

活性化関数とは、次の層への出力の大きさを決める非線形の関数。入力値の値によって、次の層への信号のON/OFFや強弱を定める働きをもつ。

* 非線形な関数は加法性を満たさない<br>
　　加法性：$f(x+y)=f(x)+f(y)$<br>
* 非線形な関数は斉次性を満たさない<br>
　　斉次性：$f(kx)=kf(x)$<br>
* 活性化関数$f$の種類<br>
・中間層用：ReLU関数、シグモイド（ロジスティック）関数、ステップ関数<br>
・出力層用：ソフトマックス関数、恒等写像、シグモイド（ロジスティック）関数<br>
* 活性化関数の効果で一部の出力は弱く、一部は強く伝播される

### Section3）出力層
![deep1-02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/e5f069bf-0fd9-e2e0-1216-2d9179e6b731.jpeg)

出力層の役割とは、ほしいデータをズバリ出すこと。
　　分類：各クラスの確率

* 誤差関数：<br>
　　入力データ⇒ニューラルネットワーク⇒出力結果<br>
　　　出力結果と訓練データ（正解値）を比較し、どれくらい合っていたかを誤差関数で表現する
　　　　例：二乗和誤差関数<br>
　　　　例：分類問題：クロスエントロピー誤差（cross_entropy_error）、<br>
　　　　　　回帰問題：平均二乗差誤差（mean_squared_error）

```math
\begin{align}
誤差&関数\\
&E_n(W)＝\frac{1}{2}\sum_{j=1}^{J} (y_j-d_j)^2= \frac{1}{2}||(y-d)||^2
\end{align}
```

```python

# 誤差（主に回帰問題の際に利用される）
loss = functions.mean_squared_error(d, y)
```


* 出力層の中間層との違い<br>
　　【値の強弱】<br>
　　　 ・中間層：しきい値の前後で信号の強弱を調整<br>
　　　 ・出力層：信号の大きさ（比率）はそのままに変換<br>
　　【確率出力】<br>
　　　・分類問題の場合、出力層の出力は０～１の範囲に限定し、総和を1とする必要がある<br>
* 活性化関数　回帰　二値分類　多クラス分類<br>
　　　・恒等写像<br>
　　　・シグモイド関数<br>
　　　・ソフトマックス関数：3クラス以上の分類の際に全てを足したら1になるように変換<br>
　　　・誤差関数<br>
　　　・平均二乗誤差<br>
　　　・交差エントロピー<br>
![deep1-03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/3f94c9db-d20f-a6c2-b9ea-3ec1ae68e984.jpeg)

### Section4）勾配降下法
* 勾配降下法はニューラルネットワークを学習させる方法
* 勾配降下法は3種類ある
 * 勾配降下法
 * 確率的勾配降下法
 * ミニバッチ勾配降下法　
* 深層学習の目的：学習を通して誤差を最小にするネットワークを作成すること<br>
　→誤差E(w)を最小化するパラメータw,bを発見すること
* 勾配降下法：

```math

w^{(t+1)}=w^{(t)}-\epsilon \nabla E　\epsilon ：学習率\\
\nabla E=\frac{ \partial E }{\partial w } = \biggl[ \frac{ \partial E }{\partial w_1 } \cdots \frac{ \partial E }{\partial w_M }  \biggr]
```

* 学習率εを適切に設定することで効率的に学習できる<br>
　学習率εが大きすぎた場合、最小値にたどり着かず発散してしまう<br>
　学習率εが小さすぎた場合、発散することはないが、収束するまでに時間がかかる<br>
　また途中にある少し小さな値（極小値）に誤収束してしまう<br>
* 勾配降下法の学習率の決定、収束性向上のためのアルゴリズム
 * Momentum
 * AdaGrad
 * Adadelta
 * Adam（よく使われる）
* 入力→NN⇒出力→訓練データと比較：誤差関数からw,bを更新⇒次の周（エポック）に反映

* 確率的勾配降下法（SGD）
 * 学習全データからランダムに抽出したサンプルの誤差<br> 
　　　（⇔勾配降下法：全サンプルの平均誤差）
 * メリット<br>
　　　・リアルタイムデータでオンライン学習ができる<br>
　　　・望まない局所極小解に収束するリスク軽減<br>
　　　・データが冗長な場合の計算コストの軽減
* 確率的勾配降下法：

```math

w^{(t+1)}=w^{(t)}-\epsilon \nabla E_n　\epsilon ：学習率\\
```
* オンライン学習<br>
　　学習データが入ってくるたびに都度パラメータ(w,b)を更新し学習を進めていく方法<br>
　　（⇔バッチ学習）
* バッチ学習<br>
　　一度にすべての学習データを使ってパラメータ更新を行う方法（⇔オンライン学習）<br>
　　バッチ学習は全データの前処理をするには大量なメモリが必要<br>
　　都度学習ができるオンライン学習が有効的

* ミニバッチ勾配降下法（オンライン学習）
 * ランダムに分割したデータの集合（ミニバッチ）$D_t$に属するサンプルの平均誤差<br> 
　　　（⇔確率的勾配降下法：ランダムに抽出したサンプルの誤差）
 * 例：10万枚を500枚ずつに小分け（ミニバッチ）して学習させていく。メモリへの負荷も少なく効率的な学習法
 * メリット<br>
　　　・確率的勾配降下法のメリットを損なわず、計算機の計算資源を有効利用できる<br>
　　　  →CPUを利用したスレッド並列化やGPUを利用したSIMD並列化<br>
　　　　 500枚ずつを並行して計算・学習できる＝SIMD(Single Instruction Multi Data)
* ミニバッチ勾配降下法：

```math

\begin{align}
&w^{(t+1)}=w^{(t)}-\epsilon \nabla E_t　\epsilon ：学習率\\
&E_t=\frac{ 1 }{N_t } \sum_{n \in D_t} E_n \\
&N_t=|D_t|
\end{align}
```

### Section5）誤差逆伝播法

* 誤差勾配の計算方法

 * 数値微分：プログラムで微小な数値を生成し疑似的に微分を計算する一般的な手法<br>
　　　　　　　デメリット：計算量が多い
 * 誤差逆伝播法：算出された誤差を、出力層側から順に微分し、前の層前の層へと伝播。<br>
　　　　　　　最小限の計算で各パラメータでの微分値を解析的に計算する手法。<br>
　　　　　　　微分の連鎖律を用いて計算結果（＝誤差）から微分を逆算することで、<br>
　　　　　　　不要な再帰的計算を避けて微分を算出できる。


## 2.「深層学習：Day1」確認テスト考察

__★確認テスト1__

ディープラーニングは、結局何をやろうとしているか2行以内で述べよ。
また、次の中のどの値の最適化が最終目的か。全て選べ。
①入力値$[X]$　②出力値$[Y]$　③重み$[W]$　④バイアス$[b]$
⑤総入力$[u]$　⑥中間層入力$[z]$　⑦学習率$[ρ]$


<details><summary>解答</summary><div>

* ディープラーニングとは、明示的なプログラムを利用せず、中間層を持つニューラルネットワークを用いて入力値から目的とする出力値に変換する数学モデルを構築すること。
* 最適化の最終目的
   
   * ③重み$[W]$
   * ④バイアス$[b]$

</div></details>

----
__★確認テスト2__

次のネットワークを紙にかけ。
　入力層：2ノード1層
　中間層：3ノード2層
　出力層：1ノード1層


<details><summary>解答</summary><div>
![kakunin_test2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/a11024bd-c0de-fa46-dc9d-91b7e244bbde.jpeg)



</div></details>

----
__★確認テスト3__

以下の図に動物分類の実例を入れてみよう。
![kakunin_test3q.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/3f7f112c-0d44-2dcb-53fd-3b7a989c1d81.jpeg)


<details><summary>解答</summary><div>
![kakunin_test3a.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/9e1c9462-21fb-4286-03ee-5e5b2fba341e.jpeg)




</div></details>

----
__★確認テスト4__

この数式をPythonで書け。
![kakunin_test4q.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/5c6724e8-f738-337f-7344-87b9c4950131.jpeg)



<details><summary>解答</summary><div>

```python

# 1層の総入力
    u1 = np.dot(x, W1) + b1
```

</div></details>

----
__★確認テスト5__

`1-1_forward_propagation.ipynb` から中間層の出力を定義しているソースを抜き出せ。
![kakunin_test5q2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/168fc4b8-314e-1996-9bff-169e7559fd93.jpeg)


<details><summary>解答</summary><div>

```python

# 2層の総出力
z2 = functions.relu(u2)
```

</div></details>

----
__★確認テスト6__

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


<details><summary>解答</summary><div>

![kakunin_test6a.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/ff5ae86c-9b4b-51af-a488-e42eeca54a0f.jpeg)


</div></details>

----
__★確認テスト7__

`1-1_forward_propagation.ipynb` から該当する箇所を抜き出せ。
![kakunin_test7q.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/5b9872da-a536-6424-e298-a553a1e6b56d.jpeg)


<details><summary>解答</summary><div>

```python
# ReLU関数
def relu(x):
    return np.maximum(0, x)

# 2層の総出力
z2 = functions.relu(u2)
```

</div></details>

----
__★確認テスト8__

* 誤差関数の式がなぜ、引き算ではなく、二乗するか述べよ。
* 1/2はどういう意味を持つか述べよ。

```math
\begin{align}
誤差&関数\\
&E_n(W)＝\frac{1}{2}\sum_{j=1}^{J} (y_j-d_j)^2= \frac{1}{2}||(y-d)||^2
\end{align}
```
<details><summary>解答</summary><div>

* 二乗する理由
　　各ラベルでの誤差に正負の値が発生し、全体の誤差を正しく表せないため、2乗してラベルの誤差を正の値になるようにしている。

* 1/2する理由
　　ネットワーク学習をする際に行う誤差逆伝播の計算で、誤差関数の微分を用いるが、2乗関数の微分で1/2により係数が打ち消しあうなど、計算式を簡単にするため。

</div></details>

----
__★確認テスト9__

下図①～③の数式に該当するソースコードを示し、一行ずつ処理の説明をせよ。
![kakunin_test9q.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/1265fb3b-2b40-dcdc-83e9-8490643fe824.jpeg)

```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))
```
<details><summary>解答</summary><div>

![kakunin_test9a.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/81c17943-962f-c353-dd48-577d21626c70.jpeg)


</div></details>

----
__★確認テスト10__

下図①～②の数式に該当するソースコードを示し、一行ずつ処理の説明をせよ。
![kakunin_test10q.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/cf051568-f8a6-210e-7956-d8a8e113bbff.jpeg)


```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
```
<details><summary>解答</summary><div>

![kakunin_test10a.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/e0882fb3-d495-d29f-30aa-dae7dfc392fc.jpeg)

</div></details>

----
__★確認テスト11__

`1_3_stochastic_gradient_descent.ipynb` から勾配降下法の記載に該当する箇所を抜き出せ。

```math
w^{(t+1)}=w^{(t)}-\epsilon \nabla E　\epsilon ：学習率\\
```


<details><summary>解答</summary><div>

![kakunin_test11a.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/a22f8185-6f9c-02ae-41b4-d572045ea90d.jpeg)


</div></details>

----
__★確認テスト12__

オンライン学習とは何か。2行でまとめよ。

<details><summary>解答</summary><div>

オンライン学習とは、学習データが入ってくるたびに都度パラメータ(w,b)を更新し学習を進めていく方法（⇔バッチ学習）

＜参考＞バッチ学習
　　一度にすべての学習データを使ってパラメータ更新を行う方法
　　バッチ学習は全データの前処理をするには大量なメモリが必要
　　都度学習ができるオンライン学習が有効的

</div></details>

----
__★確認テスト13__

以下、ミニバッチ勾配学習法の数式の意味を図に書いて説明せよ。

```math

\begin{align}
&w^{(t+1)}=w^{(t)}-\epsilon \nabla E_t　\epsilon ：学習率\\
\end{align}
```

<details><summary>解答</summary><div>

![kakunin_test13a.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/69b9988f-3f4a-46ce-67dc-e100b89dbaad.jpeg)


</div></details>

----
__★確認テスト14__

誤差逆伝播法では不要な再帰的処理を避けることが出来る。
`1_3_stochastic_gradient_descent.ipynb` から既に行った計算結果を保持しているソースコードを抽出せよ。


<details><summary>解答</summary><div>

![kakunin_test14a2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/93742909-7f89-c910-ff27-e3662940cbed.jpeg)


</div></details>

----
__★確認テスト15__

`1_3_stochastic_gradient_descent.ipynb` から以下の2つの空欄に該当するソースコードを探せ。
![kakunin_test15q.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/6e4e3573-3a78-1423-406a-2c675c0871cd.jpeg)


<details><summary>解答</summary><div>

![kakunin_test15a.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/19e6635b-e39c-de6a-621e-9994397949c6.jpeg)


</div></details>

----

## 3.「深層学習：Day1」実装演習結果と考察

### 3-1.順伝播・逆伝播

`1_2_back_propagation.ipynb`の順伝播・逆伝播ニューラルネットワークを実装する。

#### 3-1-1.　importと関数定義

importと関数の定義を実施する。

使用するライブラリは__numpy__と__matplotlib__。
また、定義したのは、指定する変数を`print`文にて出力する関数。

```python
import numpy as np
from common import functions
import matplotlib.pyplot as plt

def print_vec(text, vec):
    print("*** " + text + " ***")
    print(vec)
    #print("shape: " + str(x.shape))
    print("")
```

続いて、重み__W__、バイアス__b__の初期値を設定する関数`init_network()`の定義。

```python

# ウェイトとバイアスを設定
# ネートワークを作成
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_vec("重み1", network['W1'])
    print_vec("重み2", network['W2'])
    print_vec("バイアス1", network['b1'])
    print_vec("バイアス2", network['b2'])

    return network
```

次は、順伝播の`forward()`関数の定義。
入力層、中間層1層、出力層への重み__w1,w2__、バイアス__b1,b2__、中間層、出力層への入力を__u1,u2__、中間層、出力層の出力を__z1,y__とし、順伝播のネットワークを定義している。
また、活性化関数は、中間層出力に__シグモイド関数__、出力層の出力には、__ソフトマックス関数__を指定。

```python

# 順伝播
def forward(network, x):
    print("##### 順伝播開始 #####")

    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']
    
    u1 = np.dot(x, W1) + b1
    z1 = functions.sigmoid(u1)
    u2 = np.dot(z1, W2) + b2
    y = functions.softmax(u2)
    
    print_vec("総入力1", u1)
    print_vec("中間層出力1", z1)
    print_vec("総入力2", u2)
    print_vec("出力1", y)
    print("出力合計: " + str(np.sum(y)))

    return y, z1
```

最後に、逆伝播の`backward()`関数の定義。
連鎖律を活用し、各層の偏微分を計算、`grad`に代入している。

```python

# 誤差逆伝播
def backward(x, d, z1, y):
    print("\n##### 誤差逆伝播開始 #####")

    grad = {}

    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']
    #  出力層でのデルタ
    delta2 = functions.d_softmax_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_sigmoid(z1)
    # b1の勾配
    grad['b1'] = np.sum(delta1, axis=0)
    #  W1の勾配
    grad['W1'] = np.dot(x.T, delta1)
        
    print_vec("偏微分_dE/du2", delta2)
    print_vec("偏微分_dE/du1", delta1)

    print_vec("偏微分_重み1", grad["W1"])
    print_vec("偏微分_重み2", grad["W2"])
    print_vec("偏微分_バイアス1", grad["b1"])
    print_vec("偏微分_バイアス2", grad["b2"])

    return grad
```

#### 3-1-2.　学習開始

初期値設定や順伝播、逆伝播のプログラムを見てわかるように、このネットワークは、以下のようなノード数、層数となっている。

* 入力層：2ノード1層
* 中間層：3ノード1層
* 出力層：2ノード1層

また、出力層のノード数は2、誤差は__交差エントロピー__で示すように2クラス分類の学習である。そのため、出力層での活性化関数は__ソフトマックス関数__を使っている。

```python

# 訓練データ
x = np.array([[1.0, 5.0]])
# 目標出力
d = np.array([[0, 1]])
# 学習率
learning_rate = 0.01

# ネットワーク初期化
network =  init_network()

# 順伝播
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("##### 結果表示 #####")    
print("loss =",loss,"\n")

print("##### 更新後パラメータ #####") 
print_vec("重み1", network['W1'])
print_vec("重み2", network['W2'])
print_vec("バイアス1", network['b1'])
print_vec("バイアス2", network['b2'])
```

訓練データや目標出力（期待値）を決め、順伝播、誤差逆伝播の学習を行い、結果を表示する。
![deep1-05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/1eed5dcd-a62e-2eda-f259-de521ee98f3a.jpeg)


順伝播後出力__$y$__は［0.29,0.71]、誤差__$loss$__は0.34、重みやバイアスも逆伝播を実施することにより初期よりも変化していることがわかる。目標出力__$d$__は[0,1]のため、まだ学習不足。このまま繰り返し100回の学習を実施してみる。

#### 3-1-3.　繰り返し学習

```python

# 繰り返し学習 +100回
loss_np = np.array(loss)
y_np = np.array(y)

for i in range(100):
  y, z1 = forward(network, x)
  loss = functions.cross_entropy_error(d, y)
  loss_np = np.append(loss_np,loss)
  y_np = np.append(y_np,y, axis=0)

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

print("##### 結果表示 #####")
print("y =",y)
print("loss =",loss,"\n")

print("##### 更新後パラメータ #####") 
print_vec("重み1", network['W1'])
print_vec("重み2", network['W2'])
print_vec("バイアス1", network['b1'])
print_vec("バイアス2", network['b2'])
```
![deep1-06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/d4c1a598-b0bb-5439-06d3-d375ac78b4c9.jpeg)
![deep1-07.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/2cdaa205-0d5e-9824-a1a8-5e1d0b0e9064.jpeg)

繰り返し学習することで、順伝播後出力が目標出力に、誤差が0に近づき、学習ができていることがわかる。

ちなみに1000回学習するとさらに目標出力や誤差0に近づく結果となった。
![deep1-08.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/e20d1ed8-c2c7-0a8f-4d5e-eadd1f9ef03b.jpeg)
![deep1-09.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/7c3c86bb-2ac7-fd64-871f-9f688a65a788.jpeg)

今回は、学習率を0.01として学習を行っている。10倍の0.1に変更し、100回学習を行うと、学習回数を減らすことが可能。今回は訓練データが1つ、テストデータも無しで行っているため、学習率を大きくすることによる他への影響は無いと言える。
![deep1-10.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/ad32db7e-f324-ecdb-5d5b-0631ae28a113.jpeg)
![deep1-11.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/22ebbae9-5191-ff78-0bc1-7980b4fcd528.jpeg)


### 3-2.確率勾配降下法

`1_3_stochastic_gradient_descent.ipynb`の確率勾配降下法ニューラルネットワークを実装する。

#### 3-2-1.　importと関数定義

importと関数の定義を実施する。

使用するライブラリは__numpy__と__matplotlib__。
また、定義したのは、指定する変数を`print`文にて出力する関数。

```python
import numpy as np
from common import functions
import matplotlib.pyplot as plt

def print_vec(text, vec):
    print("*** " + text + " ***")
    print(vec)
    #print("shape: " + str(x.shape))
    print("")
```

続いて、今回サンプルとして使用する関数`f(x)`と、重み__W__、バイアス__b__の初期値を設定する関数`init_network()`の定義。

```python

# サンプルとする関数
#yの値を予想するAI

def f(x):
    y = 3 * x[0] + 2 * x[1]
    return y

# 初期設定
def init_network():
    print("##### ネットワークの初期化 #####")
    network = {}
    nodesNum = 10
    network['W1'] = np.random.randn(2, nodesNum)
    network['W2'] = np.random.randn(nodesNum)
    network['b1'] = np.random.randn(nodesNum)
    network['b2'] = np.random.randn()

    print_vec("重み1", network['W1'])
    print_vec("重み2", network['W2'])
    print_vec("バイアス1", network['b1'])
    print_vec("バイアス2", network['b2'])

    return network
```

次は、順伝播の`forward()`関数の定義。
入力層、中間層1層、出力層への重み__w1,w2__、バイアス__b1,b2__、中間層、出力層への入力を__u1,u2__、中間層、出力層の出力を__z1,y__とし、順伝播のネットワークを定義している。
また、活性化関数は、中間層出力に__ReLU関数__、出力層の出力には、__恒等写像__を指定。

```python

# 順伝播
def forward(network, x):
    # print("##### 順伝播開始 #####")
    
    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 = u2 
    
    return z1, y
```

最後に、逆伝播の`backward()`関数の定義。
連鎖律を活用し、各層の偏微分を計算、`grad`に代入している。

```python

# 誤差逆伝播
def backward(x, d, z1, y):
    # print("\n##### 誤差逆伝播開始 #####")    

    grad = {}
    
    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']

    # 出力層でのデルタ
    delta2 = functions.d_mean_squared_error(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)

    delta1 = delta1[np.newaxis, :]
    # b1の勾配
    grad['b1'] = np.sum(delta1, axis=0)
    x = x[np.newaxis, :]
    # W1の勾配
    grad['W1'] = np.dot(x.T, delta1)

    return grad
```

#### 3-2-2.　学習開始

初期値設定や順伝播、逆伝播のプログラムを見てわかるように、このネットワークは、以下のようなノード数、層数となっている。

* 入力層：2ノード1層
* 中間層：10ノード1層
* 出力層：1ノード1層

出力層のノード数は2、誤差は__平均二乗誤差__で示すように回帰の学習である。そのため、出力層での活性化関数は__恒等写像__を使っている。

また、確率的勾配降下法の学習のため、サンプル総数からサンプルをランダムに抽出し学習する。

* サンプル総数`data_sets_size`：100000
* 学習サンプル`epoch`：1000

```python

# サンプルデータを作成
data_sets_size = 100000
data_sets = [0 for i in range(data_sets_size)]

for i in range(data_sets_size):
    data_sets[i] = {}
    # ランダムな値を設定
    data_sets[i]['x'] = np.random.rand(2)
    
    # 目標出力を設定
    data_sets[i]['d'] = f(data_sets[i]['x'])
    
losses = []
# 学習率
learning_rate = 0.07

# 抽出数
epoch = 1000

# パラメータの初期化
network = init_network()

# データのランダム抽出
random_datasets = np.random.choice(data_sets, epoch)
```

ランダムに抽出したサンプルを順番に`for`文で繰り返し学習。各サンプルにおける順伝播、逆伝播学習後の平均二乗誤差を`losses`に保管し、グラフ表示確認できるようにしている。

```python

# 勾配降下の繰り返し
for dataset in random_datasets:
    x, d = dataset['x'], dataset['d']
    z1, y = forward(network, x)
    grad = backward(x, d, z1, y)
    # パラメータに勾配適用
    for key in ('W1', 'W2', 'b1', 'b2'):
        network[key]  -= learning_rate * grad[key]

    # 誤差
    loss = functions.mean_squared_error(d, y)
    losses.append(loss)

print("##### 結果表示 #####")    
lists = range(epoch)

plt.plot(lists, losses, '.')
# グラフの表示
plt.show()

print("##### 更新後パラメータ #####") 
print_vec("重み1", network['W1'])
print_vec("重み2", network['W2'])
print_vec("バイアス1", network['b1'])
print_vec("バイアス2", network['b2'])
print("*** 平均二乗誤差 ***\n",loss)
```

ランダム抽出したサンプル数1000による`losses`の推移は以下のグラフのようになった。そして、更新後のパラメータも合わせて記す。
![deep1-13.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/20834793-9380-5373-5a75-6444819f5fdd.jpeg)

グラフから推察すると、サンプル数100程度も`losses`はほぼ0となっている。今回は__学習率__`
learning_rate`を__0.07__で学習している。試しに学習率やサンプル抽出数を変えて見てみよう。

#### 3-2-3.　サンプル抽出数・学習率変更による変化

パラメータを変える度にデータが変わるため、正確な変化は見えない点は容赦頂きたい。

まずは、サンプル抽出数変化で見てみる。抽出数`epoch`を30,100,500を追加。

* epoch = 30
![deep1-14.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/af173e94-8470-6728-6fe4-93ac12d7a758.jpeg)

* epoch = 100
![deep1-15.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/9e0ebd3a-da4f-9e00-c2dd-f50e0638513d.jpeg)

* epoch = 500
![deep1-16.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/40c1afdf-e7f7-5b2b-d046-d52cf65795f4.jpeg)

サンプル抽出数を増やす度、学習の繰り返し数が変わるため、抽出数を増やすと最終的な平均二乗誤差が小さくなっていくことが分かった。それ以外の変化は無いことは学習結果で見える形となった。

次に学習率を変えてみる。以下の式より学習率を大きくすると次の重み、バイアスの値が大きく変化するため、学習が早くなりそう。そして小さくするとその逆となる。

```python

# 学習率
learning_rate = 0.07

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

今回サンプルとする関数は、累乗の関数ではないため、途中にある少し小さな値（極小値）に誤収束することは無いと考える。それでは学習率を変化させ学習してみよう。学習率`learning_rate`を0.01,0.3,0.5を追加。抽出数`epoch`はより変化を見るため200で実施。


```python

# サンプルとする関数
#yの値を予想するAI

def f(x):
    y = 3 * x[0] + 2 * x[1]
    return y
```

* learning_rate = 0.01
![deep1-18.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/52cd484e-850a-53ce-1444-17f41b9a1d9d.jpeg)

* learning_rate = 0.07
![deep1-21.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/8da58829-0c42-7784-9bac-c7e2e82e47c3.jpeg)

* learning_rate = 0.3
![deep1-19.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/92a78eb6-4e93-3f49-c57f-274bc9a18361.jpeg)

* learning_rate = 0.5
![deep1-20.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691536/06de00cc-c801-01c4-9160-3b13ba891e6d.jpeg)

200回学習後の平均二乗誤差が最小だった学習率は__0.07__。前述したように、毎回違うデータでの学習のため、妥当性までは判断し難いが、学習率を大きくしすぎると__過学習__になり、誤差が大きくなってしまっているかもしれないと考察する。


###関連ページ

* [ディープラーニング講座「応用数学」要点まとめ](https://qiita.com/Shinji333/items/3aec7b74d5bbd6fdd383)
* [ディープラーニング講座「機械学習」要点まとめ](https://qiita.com/Shinji333/items/0ecd7270e1adee2dbe94)
* [ディープラーニング講座「機械学習」実装演習](https://qiita.com/Shinji333/items/42e820362fb2169c9f8a)
* ディープラーニング講座「深層学習：Day1」要点まとめ＆実装演習＜本記事＞
* [ディープラーニング講座「深層学習：Day2」要点まとめ＆実装演習](https://qiita.com/Shinji333/items/e55f0183e1214cde16dc)
* [ディープラーニング講座「深層学習：Day3」要点まとめ＆実装演習](https://qiita.com/Shinji333/items/b8d9c10ce66733bd78f3)
* [ディープラーニング講座「深層学習：Day4」要点まとめ＆実装演習](https://qiita.com/Shinji333/items/56a289a0e198b735fd47)