深層学習「RNN」

#1. RNNとは

時間的なつながりのある時系列データ（音声データ、テキストデータ、株価）を処理できるNNをRNN（再起型NN）という。

時系列にデータを見たときに、時間の前後でデータが相互に関係しているデータが時系列データ。

こういう場合、NNも時系列な関係を学習へ反映させないといけない。これが出来るのがRNN.

#2. RNN構造

RNNも、基本的な構造は普通のNNと同じである。中間層で再帰構造があるだけ。

![RNN構造.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1436941/0daa3508-f035-8aa1-03d4-1c0272d1e7e8.png)

また、下のように書かれる場合もある。
左側は、基本的な構造を示し、右側は、時系列に前の中間層データが入力されることを意味する。これは、例えば、文章の単語を順番に入力する場合、ひとつ前の単語情報も入力に使うようなイメージ。

![RNN全体構造.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1436941/398de837-b6df-0750-7928-03b552133d2a.png)

また、RNNの重みは、入力、中間、出力の3パターンある。

![RNNの重み.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1436941/0250c3c5-ad89-7c70-b4c1-43cdf480cd54.png)

活性化関数の関係を式にすると、

~~~math
中間層への入力：u^t = W_{in} x^t + W z^{t-1} + b \\

中間層の出力：z^t = f (u^t) = f( W_{in} x^t + W z^{t-1} + b ) \\

出力層への入力v^t = W_{out} z^t + c \\
出力層の結果：y^t = g(v^t) = g(W_{out} z^t + c )

\\
※f(),g()は、活性化関数である。
~~~

$z^{t-1}$は、前回の中間層の出力を意味する。

#3. RNN特徴

#3.1. RNNの実装構造

学習の際に時系列ループがある。

株価の10年分のデータあれば、10年分のデータを時系列に学習する順伝搬があり、
同じく逆伝搬がある。

文章の場合、単語を順番に特徴量の数字の羅列として計算をしていく。
前後の単語の特徴量の数字を連結（concatenate)する。これをすべての単語についてやって、構文木を作る。数字を連結ばっかりしていると、数字の個数がどんどん大きくなる、いい感じの重みWをかけて全結合してやることで、ひとつの特徴量の数字として出力させる。


#4. RNNの誤差逆伝搬（BPTT)

RNNの逆伝播にはBPTT（Backpropagation Through Time）という手法を活用する。

##4.1. 誤差逆伝搬の基本


誤差関数は、二乗誤差関数とする。

~~~math
E(y) = \frac{1}{2} \sum_{j=1}^N (y_j - d_j)^2　
=\frac{1}{2} ||y-d||^2 \\

~~~

出力層の活性化関数は恒等写像とする

~~~math
y= u
~~~

層入力の計算は

~~~math
u = w z + b
~~~

とすると、微分の連鎖律の式は、

~~~math
\frac{\partial E}{\partial w_{ji}^{2}} = 
\frac{\partial E}{\partial y}
\frac{\partial y}{\partial u^2}
\frac{\partial u^2}{\partial w_{ji}^2}

~~~

各項を微分

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

~~~math

\frac{\partial y}{\partial u} = 1
~~~



~~~math
\frac{\partial u_j^2}{\partial w_{ji}^2} =
\frac{\partial }{\partial w_{ji}^2} ((w^2 z1 + b)) = 
\frac{\partial }{\partial w_{ji}^2} 
\Biggl(
\begin{vmatrix}
w_{11} & w_{12} &  \cdots & w_{1i}\\
\cdots \\
w_{j1} & w_{j2} & \cdots & w_{ji} & \cdots\\
\cdots \\
\end{vmatrix}

\begin{vmatrix}
z_1\\
z_2\\
\cdots \\
z_i \\
\cdots\\

\end{vmatrix}
+
\begin{vmatrix}
b_1\\
b_2\\
\cdots \\
b_i \\
\cdots\\
\end{vmatrix}
\Biggr)

=
\begin{vmatrix}
0\\
0\\
\cdots \\
z_i \\
\cdots\\
\end{vmatrix}
~~~

結果は、


~~~math
\frac{\partial E}{\partial w_{ji}^{2}} = 
\frac{\partial E}{\partial y}
\frac{\partial y}{\partial u^2}
\frac{\partial u^2}{\partial w_{ji}^2}

= (y-d) * 1 * 

\begin{vmatrix}
0\\
0\\
\cdots \\
z_i \\
\cdots\\
\end{vmatrix}

= (y_j - d_j) z_i

~~~


#4.2. BPTTの基本

基本を押さえたうえで、BPTTに関して。
RNNでは、重みが三種類ある。$W_{in} ,W, W_{out}$　である。

![RNNの重み.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1436941/0250c3c5-ad89-7c70-b4c1-43cdf480cd54.png)

これらを、それぞれに対してBack Propagationの微分で求める。
ここで必要なのは、次の4式。というかこれらを微分してもとめる。

~~~math

中間層への入力：u^t = W_{in} x^t + W z^{t-1} + b \\

中間層の出力：z^t = f (u^t) = f( W_{in} x^t + W z^{t-1} + b ) \\

出力層への入力v^t = W_{out} z^t + c \\
出力層の結果：y^t = g(v^t) = g(W_{out} z^t + c )\\
\\
※f(),g()は、活性化関数である。
~~~

具体的には次のように、誤差関数Eを、$W_{in} ,W, W_{out}$のそれぞれで微分する際に使う。

~~~math
①　\frac{\partial E}{\partial W_{in}} =
\frac{\partial E}{\partial u^t}  \left[ \frac{\partial u^t}{\partial W_{in}} \right]^T = \delta^t \left[ x^t \right]^T
\\

②　\frac{\partial E}{\partial W_{out}} =
\frac{\partial E}{\partial v^t}  \left[ \frac{\partial v^t}{\partial W_{out}} \right]^T = \delta^{out,t} \left[ z^t \right]^T 
\\

③　\frac{\partial E}{\partial W} =
\frac{\partial E}{\partial u^t}  \left[ \frac{\partial u^t}{\partial W} \right]^T = \delta^t \left[ z^{t-1} \right]^T
\\
~~~

①は、$W_{in}$は、uの式なので、uを微分の連鎖律で式展開している。しかし、Eは別にuの式ではない。直接関係するのは、yなので、微分の連鎖律を満たしてないように見える。誤差逆伝搬だと順番にやるのに。しかし、式展開で順番に書いていくと面倒なので、それを全てやった前提で省略して式として表しているのが、右辺の$\delta^t$の部分である。
また、①の式の中で、$\left[  \right]^T $となっている部分は転置ではない。これは、時間的に過去へさかのぼって微分するという意味である。
そして、$\left[ \frac{\partial u^t}{\partial W_{in}} \right]^T$では、式$u^t$を$W_{in}$で微分しているが、$x^t$以外は定数扱いなので、他がすべて消えている。

②、③も同様。

なお、$\delta^t$を確認

~~~math
\frac{\partial E}{\partial u^t} = \frac{\partial E}{\partial v^t} \frac{\partial v^t}{\partial u^t} = \delta^t
\\
\delta^{t-1} = \frac{\partial E}{\partial u^{t-1}} = \delta^t \left( W f'( u^{t-1}) \right) 
\\
\delta^{t-z-1} = \delta^{t-z} \left( W f'( u^{t-z-1}) \right) 
\\

~~~

##4.3. BPTTでのパラメータ更新

RNNで誤差逆伝搬で更新が必要なパラメータは、５つある。$W_{in}, W, W_{out}, b,c$である。

~~~math
W_{in}^{t+1} = W_{in}^t - \epsilon \frac{\partial E}{\partial W_{in}} = W_{in}^t - \epsilon \sum_{z=0}^{Tt} \delta^{t-z} \left[ x^{t-z} \right]^T

\\
W_{out}^{t+1} = W_{out}^t - \epsilon \frac{\partial E}{\partial W_{out}} = W_{out}^t - \epsilon \delta^{out,t} \left[ z^{t} \right]^T

\\
W^{t+1} = W^t - \epsilon \frac{\partial E}{\partial W} = W_{in}^t - \epsilon \sum_{z=0}^{Tt} \delta^{t-z} \left[ z^{t-z-1} \right]^T

\\
b^{t+1} = b^t -- \epsilon \frac{\partial E}{\partial b} = b^t - \epsilon \sum_{z=0}^{Tt} \delta^{t-z}
\\
c^{t+1} = c^t -- \epsilon \frac{\partial E}{\partial c} = c^t - \epsilon \delta^{out,t}
\\
\\
※ \epsilon　は、学習率である。

~~~

上記で、$\sum$を使っている式の理由は、中間層での処理に、前の時間の結果を全て反映する必要があるからである。重みもバイアスも。ここがRNNの特徴。


#5. RNN実装

#5.1. 2進数足し算AIの例題

コード実装してRNNをおさらい。

テーマは、「2進数足し算結果を予測するAI」

~~~math
a + b = c
a=145 = [0110 0101]
b=123 = [0101 0011]
c=270 = [1011 1000]
~~~

上記のa、bを入力してcを予測出力出来るモデルをRNNで構築

##5.2. 2進数足し算AIのmodel

入力はa,bの8bitを順番に入力出来れば良いので2層
中間層は、入力の倍の16層
出力は、計算の予測結果なので1層

入力の8bit配列を順番に後ろから入力するたびに、時刻tを経過させるイメージのRNNモデル。

![RNN二進数加算モデル.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1436941/abf15658-8eeb-f65d-889d-2c2351d1923f.png)

なお、上記のモデルで、u、zの配列を1bit分足して9bitにしている
bit演算なので、配列の後ろから使っていくが、t=0時刻のときに、1bit目から使って、t=-1時刻の時のzの0bit目を参照できるようにするための実装上の手当てである。再帰構造の処理なので、1時刻前のデータを参照する必要があるが、0秒の時には、その前がないので。

##5.3. model構造

~~~py
for  #10000回のイテレーション
 
 #ウェイトの初期化
 W_in_grad = np.zeros_like(W_in)
 W_out_grad = np.zeros_like(W_out)
 W_grad = np.zeros_like(W)

 #8bitを順に入力して予測値と正解の誤差計算
 for t in range(binary_dim):
        u[:,t+1] = np.dot(X, W_in) + np.dot(z[:,t].reshape(1, -1), W)
        z[:,t+1] = functions.sigmoid(u[:,t+1])
        y[:,t] = functions.sigmoid(np.dot(z[:,t+1].reshape(1, -1), W_out))

 #8bit分のウェイト更新用の勾配を逆伝搬で計算
 for t in range(binary_dim)[::-1]:
        W_out_grad += np.dot(z[:,t+1].reshape(-1,1), delta_out[:,t].reshape(-1,1))  
        W_grad += np.dot(z[:,t].reshape(-1,1), delta[:,t].reshape(1,-1))          
        W_in_grad += np.dot(X.T, delta[:,t].reshape(1,-1))                     
    
 #ウェイトの更新
 W_in -= learning_rate * W_in_grad
 W_out -= learning_rate * W_out_grad
 W -= learning_rate * W_grad

~~~