ラビットチャレンジ #11 深層学習Day1

# ニューラルネットワークの全体像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/c344859d-8cdb-1ee7-64a5-128e2531f482.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/44e43b4a-e849-b007-d924-e63035be1cf4.png)

* 明示的なプログラムの代わりに多数の中間層を持つニューラルネットワークを用いて、入力値から目的とする出力値に変換するを数学モデルを構築すること
* 最終的に重みとバイアスを最適化したい

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/324c84c8-0c83-4317-903e-54256fe91ce5.png)
![S__135684098.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/af8c6dac-96e7-d482-2f19-6cd62c9fb4b4.jpeg)

## ニューラルネットワークでできること
### 回帰
* 連続する実数値を取る関数の近似
    * 回帰分析
        * 線形回帰
        * 回帰木
        * ランダムフォレスト
        * ニューラルネットワーク

### 分類
* 離散的な結果を予想するための分析
    * 分類分析
        * ベイズ分類
        * ロジスティック回帰
        * 決定木
        * ランダムフォレスト
        * ニューラルネットワーク

# Section1 入力層～中間層
* 入力層から中間層を表すと下記のようになる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/18511465-9513-4167-106b-f338344cadf9.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/2d4aea18-3493-6607-f6bd-7525683b3568.png)

* 入力が犬の画像だと仮定すると、下記のように考えられる
    * $x_1$が体の大きさ
    * $x_2$が髭の長さ
    * $X_3$が色
    * $x_4$が耳の大きさ

* 入力層とは言葉の通りニューラルネットワークへの入力データ（そのまま）
* 入力層には扱わないほうがいいデータもあり、下記のようなデータは入力層としてとるべきではない
    * 欠損値が多いデータ
    * 誤差の大きいデータ
    * 出力そのもの、出力を加工した情報
    * 連続性の無いデータ（背番号とか）
    * 無意味な数が割り当てられているデータ
* 中間層は入力層からデータを渡される層
    * そのまま入力層からデータを渡されるのではなく重みやバイアスを与える
* 入力$x_i(i=1,2,3,4)$に重み$w_i$をかけたものにバイアスを足したものが中間層に出力される($u$)
* つまり、下記のように考えられる
    * $W=[w_1,w_2,w_3,w_4]^T$
    * $X=[x_1,x_2,x_3,x_4]^T$
    * $u=WX+b$
* 中間層の活性化関数$f(x)$によって$u$から$z$に変換されて中間層から出力される
* ここで活性化関数を恒等関数とすると$u=z$となる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/cf3243e4-5d5b-73d0-ba24-c78c62c15bf3.png)

```python
u = np.dot(x,W) + b
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/a7077e7c-c868-682a-e1a1-1d36a80ccb3e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/85e8bde8-cd51-f700-c66d-59ae1fc28428.png)

## 実装演習

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

def print_vec(text, vec):
    print("*** " + text + " ***")
    print(vec)
    print("")

# 重み
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]
```

* 上のプログラムを図で表すと下のような感じ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/55d48f99-cbc2-68b7-519a-ff9aa14c2239.png)


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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/9dc7d47c-bdca-8830-44b0-08172670d47b.png)
![S__135692290.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/e7e70f16-5f78-0e0b-db17-52a4fb1b3f68.jpeg)

### 中間層でよく使われる活性化関数
* ReLU関数
* ステップ関数
* シグモイド関数

それぞれの関数の説明は下記

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/d7bc2a68-a76f-d21a-9b30-a51877169a66.png)

* 数式
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/24c46b90-2990-4a5e-8fb6-b80a5a74d0df.png)

* サンプルコード

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

#### ステップ関数
* しきい値を超えたら発火する関数
* 出力は常に１か０
* パーセプトロン（ニューラルネットワークの前身）で利用された関数
* 課題として0 -1間の間を表現できず、線形分離可能なものしか学習できなかった

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/279e24e4-6413-8db6-18c7-7cb97083f79e.png)

* 数式

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/a5412e23-54b4-3a14-a310-6cd7d4f7b0bd.png)

* サンプルコード

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


#### シグモイド関数
* 0 ~ 1の間を緩やかに変化する関数
* ステップ関数では0 or 1しかない状態に対し、信号の強弱を伝えられるようになり、予想ニューラルネットワーク普及のきっかけとなった

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/2881343d-b267-4288-e774-c55b455a591b.png)

* 数式

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/213cf19f-9d1e-79fb-4d56-5eb2576ad923.png)


* サンプルコード

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

### 全結合NN-単層・複数ノード

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/da3d2327-546e-085c-30ce-b79f845e5d83.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/5dca5600-ae46-24ec-8e23-0b91bf0abcef.png)

# Section3 出力層
* 出力層とは求めている結果を出力する層のこと（数字だったり、分類だったり、分類でも何割の確率の分類等）
* 出力層で結果を出力して終わりではない
* どれだけ精度の高い出力をだせているか評価する必要がある
* 出力層で出力された値がどれくらいあっているかを表すために **誤差関数**が使用される
* 出力された値を誤差関数を使って評価する
* 誤差の計算に使われる関数（誤差関数）には平均二乗誤差がよく使われる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/f90e0592-8ebe-590a-c174-82ebddb90340.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/d232e5c0-1b5c-7aaa-bb9f-1f4eda59b702.png)

* 2乗する理由
    * 引き算で負の値もあり得るため、2乗して誤差を正の値になるようにする
* 1/2する理由
    * 誤差逆伝搬の計算で、誤差関数の微分を用いるが、その際の計算式を簡単にするため

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

* 以上のことから中間層と出力層で使う活性化関数が異なる

* 活性化関数と誤差関数の使い分け

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/d3d3f80a-da31-3d19-3286-0a7d7aa1c300.png)


### 出力層でよく使われる活性化関数
    * ソフトマックス関数
    * 恒等写像
    * シグモイド関数

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/320ea0e5-ee73-457b-f735-ac4677fe012a.png)

* ① 出力

```python
softmax(x)
```

* ② $e$の入力乗

```python
np.exp(x)
```

* ③ $e$のすべての入力乗の合計

```python
np.sum(np.exp(x), axis=0)
# または
np.sum(np.exp(x))
```

### 誤差関数
#### 二乗誤差

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/0fc1d0f8-8e1e-cff0-34c1-fc7f70d3280c.png)

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

#### 交差エントロピー誤差

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/9f8d02ed-8386-b2c2-4394-e2da56d42a8d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/60ede13e-6167-f491-41a6-d08055eac09c.png)

* ①

```python
cross_entropy_error(d, y)
```

* ②

```python
-np.sum(np.log(y[np.arange(batch_size), d] + 1e-7)) / batch_size
```

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

## 実装演習
* Section2の内容も含みます

```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
    # 中間層の活性化関数
    # ReLU関数
    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'])

# *** 重み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]]

# 出力合計: 1.0

# ##### 誤差逆伝播開始 #####
# *** 偏微分_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]
```

# Section4 勾配降下法
### 勾配降下法の種類
* 勾配降下法
* 確率的勾配降下法
* ミニバッチ降下法

---

* 勾配降下法って何に使うのか？
* そもそも深層学習の目的は学習を通して誤差を最小にするネットワークを作成すること
    * それはつまり、誤差を最小化するパラメータを見つけること
    * どうやって誤差を最小化するパラメータを見るけるのか？
        * ここで使うのが勾配降下法！！

#### 勾配降下法

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/989146fe-e5c1-6e91-9075-9837b54c76c2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/1bd34232-03b3-1d06-4290-065f23c297a5.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/f818eee5-a882-b054-6346-186ad5091ca0.png)

* 学習率の値によって効率が大きく異なる
* 学習率で何が変わるのかというと、グレーの矢印の動き

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/ca035d8c-e561-6961-ae8e-5e526c4bc569.png)

* 学習率が大きいと最小値にたどりつかず発散してしまう
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/f3e5afff-2108-3cf2-df35-337757814801.png)

* 学習率が小さすぎるときは最小値にたどり着くことはできるが、たどり着くまでにとても時間がかかる

#### 確率的勾配降下法

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/f7409489-5331-804a-8c90-6eb9722e472f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/d56dab61-bb62-89b7-e519-e2462525e5e3.png)

* 確率的勾配降下法のメリット
    * データが冗長の場合の計算コストの軽減
    * 望まない局所極小解に収束するリスクの軽減
    * オンライン学習ができる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/6b2b0ec1-01a9-754d-bdfc-f5734bf1b929.png)

* 学習データが入ってくるたびに都度パラメータを更新し、学習を進めていく方法
* 一方、バッチ学習では一度にすべての学習データを使ってパラメータ更新を行う

#### ミニバッチ勾配降下法

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/b1d7eaaf-8394-ecca-8ce2-bc4e1beda478.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/9cc85650-5fc9-650a-c0fd-6542e66047d4.png)

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

## 実装演習
### 確率的勾配降下法

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

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/8048e4fa-4fbb-e482-75dc-490b6c8f6bcf.png)


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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/c0b0794a-02cb-322c-0f3d-78bed0551e41.png)

* 逆伝播により同じ式を繰り返し使うことが出来ること（微分の連鎖律を使っている）
* 計算結果（=誤差）から微分を逆算することで、不要な再帰的計算を避けて微分を算出できる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/d8f7e655-be12-3180-f5a5-91704ac0eb4c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/bcb929c9-9641-ffde-b637-2b285ff262ee.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/9a2ab9e7-9f78-7493-8ea6-592001769361.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2183979/a6f4a8e5-397f-5fe3-f735-df413da5420a.png)

## 実装演習

```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'])

# ##### ネットワークの初期化 #####
# *** 重み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]]

# 出力合計: 1.0

# ##### 誤差逆伝播開始 #####
# *** 偏微分_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]

```