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$ を出力する。

構成図(一部)

数式

\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$:バイアス

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

特徴

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

RNNについて

BPTT

BPTTとは

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

BPTTの数学的記述

3つの重み

\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}
\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}
\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つのバイアス

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

重みのサンプルコード

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))
\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}
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])

パラメータの更新式

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$ の間違いだと思う。

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

コード演習問題

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

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

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

weight_init_std=10

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

weight_init_std=0.1

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

learning_rate=1

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

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

hidden_layer_size=160

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

Xavierの初期値

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

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

Heの初期値

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

中間層の活性化関数 ReLU

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

中間層の活性化関数 tanh

自分の解答

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

模範解答

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

式は違うが同じ意味。

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

確認テスト

重みの説明

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

連鎖率の復習

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

\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)$ を作用させる。

\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)$ と書いた。
  • 出力層には活性化関数を書かなかった。

模範解答はこちら。

\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に関する問題はない。