ラビットチャレンジ - 深層学習 Day1 Section5 誤差逆伝播法

# 0．概要
本記事は日本ディープラーニング協会認定の講座プログラムである「ラビット・チャレンジ」が提供している科目の1つである深層学習のレポートである。
記事タイトルに記載のとおり、Day1 Section5 誤差逆伝播法について以下にまとめる。

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

つまり、順伝播によって算出された誤差(どれだけ目的とズレているか)を微分して前に戻していくことで重みとバイアスを修正していく。

## 1.1．計算の流れ

入力層、中間層1つ、出力層の3層ニューラルネットワークの場合、中間層までの計算は以下のようになる(中間層から入力層へも同じ流れ)。

1．誤差関数(二乗誤差)$E$を$y$について微分。
目的変数とのズレを見る誤差の計算から活性化関数へ戻していく部分。

```math
\frac{\partial E}{\partial y}=\frac{\partial}{\partial y}\frac{1}{2}||y-d||^2=y-d
```

2．今回は恒等関数を活性化関数に仕様している(そのまま出力)。$y(u)$を$u$について微分。
活性化関数から出力層への部分。

```math
\frac{\partial y(u)}{\partial u}=\frac{\partial u}{\partial u}=1
```

3．中間層の出力から得られた入力$z$を元に$w^{(I)}z^{(I-1)}+b^{(I)}$で算出した$u$を$w$について微分。
出力層から入力への部分。

```math
\frac{\partial u}{\partial w_{ji}}=\frac{\partial}{\partial w_{ji}}(w^{(I)}z^{(I-1)}+b^{(I)})=\frac{\partial}{\partial w_{ji}}
\left(
\left[
\begin{array}{rrrrr}
w_{11}z_{i} & +...+ & w_{1i}z_{i} & +...+ & w_{1I}z_{I} \\
& & \vdots & & \\
w_{j1}z_{i} & +...+ & w_{ji}z_{i} & +...+ & w_{jI}z_{I} \\
& & \vdots & & \\
w_{J1}z_{i} & +...+ & w_{Ji}z_{i} & +...+ & w_{JI}z_{I} \\
\end{array}
\right]
+
\left[
\begin{array}{r}
b_{1} \\
\vdots \\
b_{j} \\
\vdots \\
b_{J} \\
\end{array}
\right]
\right)
=
\left[
\begin{array}{r}
0 \\
\vdots \\
z_{j} \\
\vdots \\
0 \\
\end{array}
\right]
```

4．微分の連鎖律

```math
\frac{\partial E)}{\partial w_{ji}^{(2)}}=\frac{\partial E}{\partial y}\frac{\partial y}{\partial u}\frac{\partial u}{\partial w_{ji}^{(2)}}=
(y-d)
\left[
\begin{array}{r}
0 \\
\vdots \\
z_{j} \\
\vdots \\
0 \\
\end{array}
\right]
=(y_{i}-d_{i})z_{i}
```

微分の連鎖律によって1～3までの計算を掛け合わせることによって誤差関数$E$を重み$w$で微分した結果を得られる。
$w$の(2)は2層目の中間層を示す。

# 2．確認テスト

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

回答：

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

## 2.2．確認テスト2

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

上記式に該当する「1_3_stochastic_gradient_descent.ipynb」のソースコードは以下である。

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

以下の2つの数式に該当するソースコードを探せ。

補足：z1

```Python:
z1, y = forward(network, x)
```

数式1：

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

回答1：

```Python:
delta1 = np.dot(delta2, W2.T) * functions.d_sigmoid(z1)
```

※このソースコードでは活性化関数にシグモイド関数が使用されている。

数式2：

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

回答2：

```Python:
grad['W1'] = np.dot(x.T, delta1)
```

# X．ラビットチャレンジとは
ラビットチャレンジとは、日本ディープラーニング協会認定の講座プログラムの1つ。
E資格を受験するためにはこのラビットチャレンジ等、いずれかの講座プログラムを修了しなければならない。

ラビットチャレンジの特徴は「現場で潰しが効くディープラーニング講座」の通学講座録画ビデオを編集した教材を使用した自習スタイルであるという点。
サポートは他の講座より少なく、受け身ではなく自主的に学んでいく姿勢でなければ進められないが、その分、他の講座に比べると安価であり、手が出しやすい。
ある程度知識がある人、自力で頑張るぞというガッツのある人向けではないかと感じる。

![bnr_jdla.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/389699/bce66671-a2f5-a7f9-de6f-3f87e15c253b.png)