深層学習（前編・day 1）レポート

# 目次
- 深層学習（前編・day 1）
  - Section1　入力層～中間層 
  - Section2　活性化関数
  - Section3　出力層
  - Section4　勾配降下法
  - Section5　誤差逆伝播法

# 導入

なぜ、ほかでもできるのにニューラルネットワークでやるのか？
次元数が増えた時に対応できる

###【確認テスト】
Q.ディープラーニングは、結局何をやろうとしているのか2行以内で述べよ。
A.誤差を最小化するパラメータを発見すること。

Q.また、次の中のどの値の最適化が最終目的か。全て選べ。
A.重み[W]、バイアス[b]

Q.次のネットワークを紙にかけ。
A.
入力層：2ノード1層
中間層：3ノード2層
出力層：1ノード1層
![nn.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60541/da78555a-83e1-cebd-1194-ee75a42b82aa.png)

# Section1　入力層～中間層
###【考察】
###【実装演習結果】



#### 順伝播（単層・単ユニット）

```python:
# 重み
W = np.array([[0.1], [0.2]])
## 試してみよう_配列の初期化
#W = np.zeros(2)
#W = np.ones(2)
#W = np.random.rand(2)
#W = np.random.randint(5, size=(2))
print_vec("重み", W)
# バイアス
b = 0.5
## 試してみよう_数値の初期化
#b = np.random.rand() # 0~1のランダム数値
#b = np.random.rand() * 10 -5  # -5~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)
```
```python:
*** 重み ***
[[0.1]
 [0.2]]

*** バイアス ***
0.5

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

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

*** 中間層出力 ***
[1.3]
```
###【確認テスト】
Q.この図式に動物の分類の実例を入れてみよう。
A.動物種類分類ネットワーク

Q.この数式をPythonで書け。（Numpyを用いること）
![math-20200508.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60541/b259c98e-f111-e964-b204-cdf84d6972c7.png)
A.
![math-20200508_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60541/90326ad6-afea-503b-7307-abe4fb4eb448.png)

Q.全結合NN - 単層・複数ノードにおいて、1_1_forward_propagation.ipynb のファイルから中間層出力を定義しているソースを抜き出せ。

A.

```python:
# 中間層出力
z = functions.relu(u)
```

###【演習問題】
なし

# Section2　活性化関数
 - 活性化関数
  - ステップ関数
  - シグモイド関数
  - ReLU 関数

 - ステップ関数
  - 人間の脳を模したのに機械的
  - 線形分離可能なものしか学習できない

 - シグモイド関数
  - 勾配消失問題がある
  - RNN では一番いい

 - ReLU 関数
  - 勾配消失問題の回避可
  - スパース化に貢献
  - 必ずしも使うものではない、固執しすぎない

###【考察】

###【実装演習結果】

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

```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]
])
## 試してみよう_配列の初期化
#W = np.zeros((4,3))
#W = np.ones((4,3))
#W = np.random.rand(4,3)
#W = np.random.randint(5, size=(4,3))
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)
```

```python:
*** 重み ***
[[0.1 0.2 0.3]
 [0.2 0.3 0.4]
 [0.3 0.4 0.5]
 [0.4 0.5 0.6]]

*** バイアス ***
[0.1 0.2 0.3]

*** 入力 ***
[ 1.  5.  2. -1.]

*** 総入力 ***
[1.4 2.2 3. ]

*** 中間層出力 ***
[0.80218389 0.90024951 0.95257413]
```
###【確認テスト】
Q.線形と非線形の違いを図にかいて簡易に説明せよ。
A.

 - 線形

![logisticregression_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60541/7d1df2a9-4ead-09e1-3b10-088a99a2f41e.png)

- 非線形

![nonlinear regression_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60541/57a7448a-e9a7-123c-6cef-33f4c3f05d45.png)

Q.全結合NN - 単層・複数ノードで以下の数式を1_1_forward_propagation.ipynb のファイルから該当する箇所を抜き出せ。

A.

```python:
# 中間層出力
z = functions.sigmoid(u)
```

###【演習問題】
なし

# Section3　出力層
## 3-1 誤差関数
分類問題では平均二乗誤差は使わない

## 3-2 出力層の活性化関数

![output.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60541/2d43f3cf-9c90-ce53-d39c-5a08a2d3bafb.png)

 1 シグモイド関数
![math-20200511.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60541/e985e24a-9858-fc12-f304-87679504d1ee.png)

 2 ソフトマックス関数
![math-20200511_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60541/4bbcc656-3d9a-ebc3-dcc3-6cfb9b3b7642.png)

###【考察】
###【実装演習結果】

#### 順伝播（3層・複数ユニット）
```python:
# ウェイトとバイアスを設定
# ネートワークを作成
def init_network():
    print("##### ネットワークの初期化 #####")
    network = {}
    #試してみよう
    #_各パラメータのshapeを表示
    #_ネットワークの初期値ランダム生成
    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['W3'] = np.array([
        [0.1, 0.3],
        [0.2, 0.4]
    ])
    network['b1'] = np.array([0.1, 0.2, 0.3])
    network['b2'] = np.array([0.1, 0.2])
    network['b3'] = np.array([1, 2])
    print_vec("重み1", network['W1'] )
    print_vec("重み2", network['W2'] )
    print_vec("重み3", network['W3'] )
    print_vec("バイアス1", network['b1'] )
    print_vec("バイアス2", network['b2'] )
    print_vec("バイアス3", network['b3'] )
    return network

# プロセスを作成
# x：入力値
def forward(network, x):
    print("##### 順伝播開始 #####")
    W1, W2, W3 = network['W1'], network['W2'], network['W3']
    b1, b2, b3 = network['b1'], network['b2'], network['b3']
    # 1層の総入力
    u1 = np.dot(x, W1) + b1
    # 1層の総出力
    z1 = functions.relu(u1)
    # 2層の総入力
    u2 = np.dot(z1, W2) + b2
    # 2層の総出力
    z2 = functions.relu(u2)
    # 出力層の総入力
    u3 = np.dot(z2, W3) + b3
    # 出力層の総出力
    y = u3
    print_vec("総入力1", u1)
    print_vec("中間層出力1", z1)
    print_vec("総入力2", u2)
    print_vec("出力1", z1)
    print("出力合計: " + str(np.sum(z1)))
    return y, z1, z2

# 入力値
x = np.array([1., 2.])
print_vec("入力", x)
# ネットワークの初期化
network =  init_network()
y, z1, z2 = forward(network, x)
```
```python:
*** 入力 ***
[1. 2.]

##### ネットワークの初期化 #####
*** 重み1 ***
[[0.1 0.3 0.5]
 [0.2 0.4 0.6]]

*** 重み2 ***
[[0.1 0.4]
 [0.2 0.5]
 [0.3 0.6]]

*** 重み3 ***
[[0.1 0.3]
 [0.2 0.4]]

*** バイアス1 ***
[0.1 0.2 0.3]

*** バイアス2 ***
[0.1 0.2]

*** バイアス3 ***
[1 2]

##### 順伝播開始 #####
*** 総入力1 ***
[0.6 1.3 2. ]

*** 中間層出力1 ***
[0.6 1.3 2. ]

*** 総入力2 ***
[1.02 2.29]

*** 出力1 ***
[0.6 1.3 2. ]

出力合計: 3.9
```

#### 多クラス分類（2-3-4ネットワーク）
```python:
# 2-3-4ネットワーク
# ！試してみよう_ノードの構成を 3-5-4 に変更してみよう
# ウェイトとバイアスを設定
# ネートワークを作成
def init_network():
    print("##### ネットワークの初期化 #####")
    #試してみよう
    #_各パラメータのshapeを表示
    #_ネットワークの初期値ランダム生成
    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.7, 1.0],
        [0.2, 0.5, 0.8, 1.1],
        [0.3, 0.6, 0.9, 1.2]
    ])
    network['b1'] = np.array([0.1, 0.2, 0.3])
    network['b2'] = np.array([0.1, 0.2, 0.3, 0.4])
    print_vec("重み1", network['W1'] )
    print_vec("重み2", network['W2'] )
    print_vec("バイアス1", network['b1'] )
    print_vec("バイアス2", network['b2'] )
    return network

# プロセスを作成
# x：入力値
def forward(network, x):
    print("##### 順伝播開始 #####")
    W1, W2 = network['W1'], network['W2']
    b1, b2 = network['b1'], network['b2']
    # 1層の総入力
    u1 = np.dot(x, W1) + b1
    # 1層の総出力
    z1 = functions.relu(u1)
    # 2層の総入力
    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

## 事前データ
# 入力値
x = np.array([1., 2.])
# 目標出力
d = np.array([0, 0, 0, 1])
# ネットワークの初期化
network =  init_network()
# 出力
y, z1 = forward(network, x)
# 誤差
loss = functions.cross_entropy_error(d, y)
## 表示
print("\n##### 結果表示 #####")
print_vec("出力", y)
print_vec("訓練データ", d)
print_vec("誤差",  loss)
```
```python:
##### ネットワークの初期化 #####
*** 重み1 ***
[[0.1 0.3 0.5]
 [0.2 0.4 0.6]]

*** 重み2 ***
[[0.1 0.4 0.7 1. ]
 [0.2 0.5 0.8 1.1]
 [0.3 0.6 0.9 1.2]]

*** バイアス1 ***
[0.1 0.2 0.3]

*** バイアス2 ***
[0.1 0.2 0.3 0.4]

##### 順伝播開始 #####
*** 総入力1 ***
[0.6 1.3 2. ]

*** 中間層出力1 ***
[0.6 1.3 2. ]

*** 総入力2 ***
[1.02 2.29 3.56 4.83]

*** 出力1 ***
[0.01602796 0.05707321 0.20322929 0.72366954]

出力合計: 1.0

##### 結果表示 #####
*** 出力 ***
[0.01602796 0.05707321 0.20322929 0.72366954]

*** 訓練データ ***
[0 0 0 1]

*** 誤差 ***
0.3234202933601941
```
#### 回帰（2-3-2ネットワーク）
```python:
# ！試してみよう_ノードの構成を 3-5-4 に変更してみよう
# ウェイトとバイアスを設定
# ネートワークを作成
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

# プロセスを作成
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
    print_vec("総入力1", u1)
    print_vec("中間層出力1", z1)
    print_vec("総入力2", u2)
    print_vec("出力1", y)
    print("出力合計: " + str(np.sum(z1)))
    return y, z1

# 入力値
x = np.array([1., 2.])
network =  init_network()
y, z1 = forward(network, x)
# 目標出力
d = np.array([2., 4.])
# 誤差
loss = functions.mean_squared_error(d, y)
## 表示
print("\n##### 結果表示 #####")
print_vec("中間層出力", z1)
print_vec("出力", y)
print_vec("訓練データ", d)
print_vec("誤差",  loss)
```
```python:
##### ネットワークの初期化 #####
*** 重み1 ***
[[0.1 0.3 0.5]
 [0.2 0.4 0.6]]

*** 重み2 ***
[[0.1 0.4]
 [0.2 0.5]
 [0.3 0.6]]

*** バイアス1 ***
[0.1 0.2 0.3]

*** バイアス2 ***
[0.1 0.2]

##### 順伝播開始 #####
*** 総入力1 ***
[0.6 1.3 2. ]

*** 中間層出力1 ***
[0.6 1.3 2. ]

*** 総入力2 ***
[1.02 2.29]

*** 出力1 ***
[1.02 2.29]

出力合計: 3.9

##### 結果表示 #####
*** 中間層出力 ***
[0.6 1.3 2. ]

*** 出力 ***
[1.02 2.29]

*** 訓練データ ***
[2. 4.]

*** 誤差 ***
0.9711249999999999
```

#### 2値分類（2-3-1ネットワーク）
```python:
# ！試してみよう_ノードの構成を 5-10-1 に変更してみよう
# ウェイトとバイアスを設定
# ネートワークを作成
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.2],
        [0.4],
        [0.6]
    ])
    network['b1'] = np.array([0.1, 0.2, 0.3])
    network['b2'] = np.array([0.1])
    return network

# プロセスを作成
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 = functions.sigmoid(u2)      
    print_vec("総入力1", u1)
    print_vec("中間層出力1", z1)
    print_vec("総入力2", u2)
    print_vec("出力1", y)
    print("出力合計: " + str(np.sum(z1)))
    return y, z1

# 入力値
x = np.array([1., 2.])
# 目標出力
d = np.array([1])
network =  init_network()
y, z1 = forward(network, x)
# 誤差
loss = functions.cross_entropy_error(d, y)
## 表示
print("\n##### 結果表示 #####")
print_vec("中間層出力", z1)
print_vec("出力", y)
print_vec("訓練データ", d)
print_vec("誤差",  loss)
```
```python:
##### ネットワークの初期化 #####
##### 順伝播開始 #####
*** 総入力1 ***
[0.6 1.3 2. ]

*** 中間層出力1 ***
[0.6 1.3 2. ]

*** 総入力2 ***
[1.94]

*** 出力1 ***
[0.87435214]

出力合計: 3.9

##### 結果表示 #####
*** 中間層出力 ***
[0.6 1.3 2. ]

*** 出力 ***
[0.87435214]

*** 訓練データ ***
[1]

*** 誤差 ***
0.13427195993720972
```

###【確認テスト】
Q.なぜ、引き算ではなく二乗するか述べよ
A.値を正にするため

Q.下式の1/2はどういう意味を持つか述べよ
![math-20200509.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60541/5a18fa5c-4b00-be1e-a2a2-57494dfb6618.png)

A.微分を簡単にするため

Q.①~③の数式に該当するソースコードを示し、一行ずつ処理を説明せよ。
![softmax.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60541/03691559-774f-28ef-b3f5-049a98fe6bd4.png)
ベクトル前提
①：def softmax(x):
配列の全要素について指数関数の値を計算している。
②：np.exp(x)
配列の全要素について指数関数の値を計算している。
③：np.sum(np.exp(x), axis=0)
配列の全要素について指数関数の値を総和で割っている。

![cross_entropy_error.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60541/df924274-9f52-0d1e-3ac5-f6e56975c529.png)

①：def cross_entropy_error(d, y):
yはNNの出力となる。
②：-np.sum(np.log(y[np.arrange(batch_size), d] + 1e-7)) / batch_size
0にならないように微小な値を加算している。

###【演習問題】
なし

# Section4　勾配降下法

###深層学習の目的
  - 学習を通して誤差を最小にするネットワークを作成すること
   - 誤差E(w)を最小化するパラメータwを発見すること
      - 勾配降下法を利用してパラメータを最適化 

###勾配降下法
- 勾配降下法
  - バッチ勾配降下法（最急降下法）
  - 確率的勾配降下法(SGD)
  - ミニバッチ勾配降下法 

###【考察】
###【実装演習結果】

```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("")
```
```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

# 順伝播
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)
    ## 試してみよう
    #z1 = functions.sigmoid(u1)
    u2 = np.dot(z1, W2) + b2
    y = u2
    # print_vec("総入力1", u1)
    # print_vec("中間層出力1", z1)
    # print_vec("総入力2", u2)
    # print_vec("出力1", y)
    # print("出力合計: " + str(np.sum(y)))    
    return z1, y

# 誤差逆伝播
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 = np.dot(delta2, W2.T) * functions.d_sigmoid(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)
    # print_vec("偏微分_重み1", grad["W1"])
    # print_vec("偏微分_重み2", grad["W2"])
    # print_vec("偏微分_バイアス1", grad["b1"])
    # print_vec("偏微分_バイアス2", grad["b2"])
    return grad

# サンプルデータを作成
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]['x'] = np.random.rand(2) * 10 -5 # -5〜5のランダム数値
    # 目標出力を設定
    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 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()
```

###【確認テスト】

![gradient_descent.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60541/45ff9b10-3ac9-de81-7d86-3a5e504abb45.png)
1_2_back_propagation.ipynb フォイルより

```python:
network[key]  -= learning_rate * grad[key]
```
```python:
grad = backward(x, d, z1, y)
```

Q.オンライン学習とは何か2行でまとめよ
A.新しく入ってきたユーザーのデータのみで学習をさせていく。既存のモデルを更に学習させていく。

Q.この数式の意味を図に書いて説明せよ。
![math-20200513.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60541/58129417-b95f-17b5-f1b5-88c71db84438.png)
A.

###【演習問題】
なし

# Section5　誤差逆伝播法(Back Propagation)

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

 - 計算リソースをくわれないようにする。

###【考察】
###【実装演習結果】

```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("")
```

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

# 順伝播
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 = 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

# 誤差逆伝播
def backward(x, d, z1, y):
    print("\n##### 誤差逆伝播開始 #####")
    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)
    print_vec("偏微分_dE/du2", delta2)
    print_vec("偏微分_dE/du2", delta1)
    print_vec("偏微分_重み1", grad["W1"])
    print_vec("偏微分_重み2", grad["W2"])
    print_vec("偏微分_バイアス1", grad["b1"])
    print_vec("偏微分_バイアス2", grad["b2"])
    return grad
    
# 訓練データ
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("##### 更新後パラメータ #####") 
print_vec("重み1", network['W1'])
print_vec("重み2", network['W2'])
print_vec("バイアス1", network['b1'])
print_vec("バイアス2", network['b2'])
```

```python:
##### ネットワークの初期化 #####
*** 重み1 ***
[[0.1 0.3 0.5]
 [0.2 0.4 0.6]]

*** 重み2 ***
[[0.1 0.4]
 [0.2 0.5]
 [0.3 0.6]]

*** バイアス1 ***
[0.1 0.2 0.3]

*** バイアス2 ***
[0.1 0.2]

##### 順伝播開始 #####
*** 総入力1 ***
[[1.2 2.5 3.8]]

*** 中間層出力1 ***
[[1.2 2.5 3.8]]

*** 総入力2 ***
[[1.86 4.21]]

*** 出力1 ***
[[0.08706577 0.91293423]]

出力合計: 0.9999999999999999

##### 誤差逆伝播開始 #####
*** 偏微分_dE/du2 ***
[[ 0.08706577 -0.08706577]]

*** 偏微分_dE/du2 ***
[[-0.02611973 -0.02611973 -0.02611973]]

*** 偏微分_重み1 ***
[[-0.02611973 -0.02611973 -0.02611973]
 [-0.13059866 -0.13059866 -0.13059866]]

*** 偏微分_重み2 ***
[[ 0.10447893 -0.10447893]
 [ 0.21766443 -0.21766443]
 [ 0.33084994 -0.33084994]]

*** 偏微分_バイアス1 ***
[-0.02611973 -0.02611973 -0.02611973]

*** 偏微分_バイアス2 ***
[ 0.08706577 -0.08706577]

##### 結果表示 #####
##### 更新後パラメータ #####
*** 重み1 ***
[[0.1002612  0.3002612  0.5002612 ]
 [0.20130599 0.40130599 0.60130599]]

*** 重み2 ***
[[0.09895521 0.40104479]
 [0.19782336 0.50217664]
 [0.2966915  0.6033085 ]]

*** バイアス1 ***
[0.1002612 0.2002612 0.3002612]

*** バイアス2 ***
[0.09912934 0.20087066]
```

###【確認テスト】
Q.誤差逆伝播法では不要な再帰的処理を避ける事が出来る。既に行った計算結果を保持しているソースコードを抽出せよ。
A.以下の 1_2_back_propagation.ipynb のファイルの delta2

```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_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)
    print_vec("偏微分_dE/du2", delta2)
    print_vec("偏微分_dE/du2", delta1)
    print_vec("偏微分_重み1", grad["W1"])
    print_vec("偏微分_重み2", grad["W2"])
    print_vec("偏微分_バイアス1", grad["b1"])
    print_vec("偏微分_バイアス2", grad["b2"])
    return grad
```

Q.2つの空欄に該当するソースコードを探せ。
A.
![math-20200513_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60541/c9e28254-1ed2-b832-e5b2-bb3eb505e90f.png)

```python:
# 出力層でのデルタ
delta2 = functions.d_mean_squared_error(d, y)
```
![math-20200513_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60541/e2389def-9e13-89ad-0b5d-ddc3d8133b64.png)

```python:
delta2 = functions.d_mean_squared_error(d, y)
```
![math-20200513_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/60541/32ca0efa-73f7-2491-803c-7b48d1aec3f2.png)

```python:
# W2の勾配
grad['W2'] = np.dot(z1.T, delta2)
```
###【演習問題】