深層学習 Day 3 - Section 1 再帰型ニューラルネットワークの概念 のまとめ

この記事は個人的なお勉強用のメモです。

# このお勉強の位置

深層学習 Day 3

**Section 1 再帰型ニューラルネットワークの概念**　←　これ
Section 2 LSTM
Section 3 GRU
Section 4 双方向RNN
Section 5 Seq2Seq
Section 6 Word2vec
Section 7 Attention Mechanism

# 講義

## RNN

再帰型ニューラルネットワーク（Recurrent Neural Network）
時系列データに対応可能なNN

後で登場するLSTMとかを含めてRNNと呼ぶ場合、
今回のRNNをシンプルなRNNと呼ぶことも。

### 時系列データ

時間的順序を追って一定間隔ごとに観察され、
相互に統計的依存関係が認められるようなデータの系列

- 音声データ
- テキストデータ
- 株価推移のデータ（タイムスタンプがあるデータ）
- 月ごとの訪問数

### 構成

1. 入力データ $x$ に重みを掛けて $s$ が出力される。  
$s$ は次の概念 $s_0$ に用いられる。
2. 以下の2つを加算して $s_1$ を計算する。  
・$s_0$ に重みを掛けた値  
・次の入力値 $x_1$
3. $s_1$ に活性化関数を実行して重みを掛けて、$y_1$ として出力する。
4. 1から3まで繰り返し

※講義の図では $y$ から $s$ に矢印が伸びているが、矢印の向きが逆だと思う。


重みは3種類
$W_{(in)}$：$x$ に掛けて $s$ を出力する。
$W$：$s_{i-1}$ に掛けて $s_i$ を出力する。
$W_{(out)}$：$s$ に掛けて $y$ を出力する。


#### 構成図（一部）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810819/1224c4e2-df1a-f07c-fe37-5b6f231d3e9f.png)


数式

```math
\begin{align}
u^t&=W_{(in)}x^t+Wz^{t-1}+b\\
z^t
&=f(u^t)\\
v^t&=W_{(out)}z^t+c\\
y^t
&=g(v^t)\end{align}
```
$b$：バイアス
$c$：バイアス


```python
u[:, t+1] = np.dot(X, W_in) + np.dot(z[:, t].reshape(1, -1), W)
z[:, t+1] = functions.sigmoid(u[:, t+1])
```

```python
y[:, t] = functions.sigmoid(np.dot(z[:, t+1].reshape(1, -1), W_out))
```

#### 特徴

時系列モデルを扱うには、初期の状態と過去の状態 t-1 の状態を保持し、
次の時間での t を再帰的に求める再帰構造が必要。

#### RNNについて

### BPTT

#### BPTTとは

Back Propagation Through Time
誤差逆伝播法の一種。


#### BPTTの数学的記述

3つの重み

```math
\begin{align}
\frac{\partial E}{\partial W_{(in)}}
&=\frac{\partial E}{\partial u^t}\Biggl[\frac{\partial u^t}{\partial W_{(in)}}\Biggr]^T\\
&=\delta^t \bigl[x^t\big]^T
\end{align}
```
```math
\begin{align}
\frac{\partial E}{\partial W_{(out)}}
&=\frac{\partial E}{\partial v^t}\Biggl[\frac{\partial v^t}{\partial W_{(out)}}\Biggr]^T\\
&=\delta^{out,t} \bigl[z^t\big]^T
\end{align}
```
```math
\begin{align}
\frac{\partial E}{\partial W}
&=\frac{\partial E}{\partial u^t}\Biggl[\frac{\partial u^t}{\partial W}\Biggr]^T\\
&=\delta^t \bigl[z^{t-1}\big]^T
\end{align}
```

2つのバイアス

```math
\begin{align}
\frac{\partial E}{\partial b}
&=\frac{\partial E}{\partial u^t}\frac{\partial u^t}{\partial b}\\
&=\delta^t
\end{align}
```
```math
\begin{align}
\frac{\partial E}{\partial c}
&=\frac{\partial E}{\partial v^t}\frac{\partial v^t}{\partial c}\\
&=\delta^{out,t}
\end{align}
```

重みのサンプルコード

```python
np.dot(X.T, delta[:,t].reshape(1,-1))
np.dot(z[:,t+1].reshape(-1,1), delta_out[:,t].reshape(-1,1))
np.dot(z[:,t].reshape(-1,1), delta[:,t].reshape(1,-1))
```


```math
\begin{align}
\frac{\partial E}{\partial u^t}
&=
\frac{\partial E}{\partial v^t}
\frac{\partial v^t}{\partial u^t}
\\
&=
\frac{\partial E}{\partial v^t}
\frac{\partial \Bigl(W_{(out)}f(u^t)+c\Bigr)}{\partial u^t}
\\
&=f'(u^t)W_{(out)}^T \delta^{out,t}
\\
&=
\delta^t
\\
\delta ^{t-1}
&=
\frac{\partial E}{\partial u^{t-1}}
\\
&=
\frac{\partial E}{\partial u^{t}}
\frac{\partial u^t}{\partial u^{t-1}}
\\
&=
\delta^t\Biggl(
\frac{\partial u^t}{\partial z^{t-1}}
\frac{\partial z^{t-1}}{\partial u^{t-1}}
\Biggr)
\\
&=
\delta^t
W\biggl(f'(u^{t-1})\biggr)
\\
\delta^{t-z-1}&=\delta^{t-z}\Bigl(Wf'(u^{t-z-1})\Bigr)
\end{align}
```

```python
delta[:,t] = (np.dot(delta[:,t+1].T, W.T) + np.dot(delta_out[:,t].T, W_out.T)) *
  functions.d_sigmoid(u[:,t+1])
```

パラメータの更新式

```math
W_{(in)}^{t+1}=W_{(in)}^t-\epsilon \frac{\partial E}{\partial W_{(in)}}
=W_{(in)}^t -\epsilon \sum_{z=0}^{T_t} \delta^{t-z}\Bigl[x^{t-z}\Bigr]^T\\
W_{(out)}^{t+1}=W_{(out)}^{t}-\epsilon \frac{\partial E}{\partial W_{(out)}}
=W_{(in)}^t - \epsilon \delta^{out,t}\Bigl[z^{t}\Bigr]^T\\
W^{t+1}=W^{t}-\epsilon \frac{\partial E}{\partial W}
=W^t - \epsilon \sum_{z=0}^{T_t} \delta^{t-z}\Bigl[x^{t-z-1}\Bigr]^T\\
b^{t+1}=b^t-\epsilon  \frac{\partial E}{\partial b}=b^t-\epsilon \sum_{z=0}^{T_t} \delta^{t-z}\\
c^{t+1}=c^t-\epsilon  \frac{\partial E}{\partial c}=c^t-\epsilon \delta^{out,t}\\
```

講義では $W^{t+1}$ の更新式に $W_{(in)}^t$ が書かれているが、
$W^t$ の間違いだと思う。

```python
W_in -= learning_rate * W_in_grad
W_out -= learning_rate * W_out_grad
W -= learning_rate * W_grad
```

### コード演習問題

```python
for t in reversed(range(n_seq)):
  dv += np.dot(do[:, t].T, hiddens[:, t]) / batch_size
  delta_t = do[:, t].dot(V)
  for bptt_step in reversed(range(t+1)):
    dW += np.dot(delta_t.T, xs[:, bptt_step]) / batch_size
    dU += np.dot(delta_t.T, hiddens[:, bptt_step-1]) / batch_size
    delta_t = delta_t.dot(U)
  return dW, dU, dV
```

$\frac{\partial h_t}{\partial h_{t-1}}=U$ であるため、
過去に遡るたびに $U$ が掛けられる。
後で復習。

# 実装演習

2つの数値の足し算を学習するプログラム
2つの数値と正解値をそれぞれ2進数のビット列で表現する。
2つの数値のビット列の下位ビットをペアにして、順番に入力データとして与える。
このとき、前のビット計算の途中の値を保存し、次のビット計算で参照する（この部分がRNN）。

### そのまま実行

weight_init_std = 1
learning_rate = 0.1
hidden_layer_size = 16

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810819/9e53a031-25a9-6444-68a5-ca566cd42492.png)

横軸：学習回数
縦軸：誤差

学習回数が増えるにつれて、誤差が小さくなっている。
これはRNNが足し算を学習したということか。

### weight_init_std=10

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810819/0c464364-cd21-ead3-1352-79f10bb29661.png)

weight_init_std=1のときに比べて、誤差のばらつきが大きい。
また、収束したとは言えない。

### weight_init_std=0.1

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810819/57b30f95-7eb7-1d49-d9ba-0a4d8824320a.png)

学習が進んでいない。
重みが小さいため、最適値まで到達していないということか。

### learning_rate=1

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810819/db4617ea-125b-575f-1fa1-1f6f0fd323e6.png)

収束が速くなった。
加えて誤差も見た目ではゼロになっている。

誤差がゼロというのは過学習の可能性を疑うが
この場合は毎回新規にデータを生成して誤差を測定しているため
過学習にはあたらないはず。

### hidden_layer_size=160

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810819/0030e11a-50a0-0540-d653-dcc2a867c2b2.png)

中間層のノードの数を10倍の160個にしたら、
誤差のばらつきが非常に大きくなった。
これは未知のデータに対する過学習の可能性がある。
（やっていることは毎回未知のデータに対する測定なので。）


### Xavierの初期値

実装メモ
Xavierの初期値やHeの初期値の場合は、weight_init_stdを掛ける必要はない。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810819/fbdeb325-c000-7459-ee07-14af8f41da24.png)

Xavierの初期値を指定すると、学習が悪化した。

### Heの初期値

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810819/644e9c24-2b1a-b18a-e621-cab74133e5d1.png)

Xavierの初期値の場合よりはまともだが、何もしない方が学習の進み具合が良い。

### 中間層の活性化関数 ReLU

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810819/4fc42584-480a-9b72-d4ff-8508d610094f.png)

変更したソースは模範解答と全く同じだが、
なぜか全く学習が進まない。

### 中間層の活性化関数 tanh

自分の解答

```python
def d_tanh(x):
    return 4 / (np.exp(x) + np.exp(-x)) ** 2
```

模範解答

```python
def d_tann(x):
    return 1/(np.cosh(x) ** 2)
```

式は違うが同じ意味。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/810819/b2aff525-49ae-d7bc-1376-5b32e6ff8503.png)

学習回数が2000回を超えるくらいから急激に誤差が少なくなった。
不思議に思って何回か実行してみたが、傾向は同じ。
4000回に到達するころには誤差はゼロに近い。

# 確認テスト

### 重みの説明

3つ目の重みは、中間層から次の中間層を定義する際にかけられる重み。


### 連鎖率の復習

$z=t^2$ ⇒ $\frac{dz}{dt}=2t$
$t = x+y$ ⇒ $\frac{dt}{dx}=1$

```math
\begin{align}
\frac{dz}{dx}
&=\frac{dz}{dt}\frac{dt}{dx}\\
&=2t\times 1\\
&=2(x+y)
\end{align}
```


### y1を数式で表す

使用する変数：$x, s_0, s_1, W_{(in)}, W, W_{(out)}$
中間層の出力にシグモイド関数 $g(x)$ を作用させる。

```math
\begin{align}
s_1
&=g(W_{(in)}x_1+Ws_0+b)\\
y_1
&=W_{(out)}s_1+c\\
\end{align}
```

- $s_0$ や $s_1$ というのが、活性化関数を作用させる前の値なのか後なのかわからなかった。  
活性化関数を作用させた後という前提で解答を書いた。
- 問題文に $g(x)$ と書いてあるのでそのまま $g(x)$ と書いた。
- 出力層には活性化関数を書かなかった。

模範解答はこちら。

```math
\begin{align}
z_1&=sigmoid(s_0W+x_1W_{(in)}+b)\\
y_1&=sigmoid(z_1W_{(out)}+c)
\end{align}
```

模範解答は $s$ と $z$ を厳密に使い分けているのだろうか。
（$s_0W$ とあることから、$s_0$ は前の中間層で活性化関数を作用させた後である。
一方で、$z_1=sigmoid(...)$ とあることから、$z_1$ も中間層で活性化関数を
作用させた後である。しかし、上記の2つの変数名が $s$ と $z$ とで一致しない。）


# 修了テスト～練習問題～

BPTTに関する問題はない。