ラビットチャレンジレポート 深層学習Day3 その1
Section1 RNN
1-1 RNN全体像
1-1-1 RNNとは
RNNとは、時系列データに対応可能なニューラルネットワークである。
1-1-2 時系列データ
時系列データとは、時間的順序を追って一定間隔ごとに観察され、
しかも相互に統計的依存関係が認められるようなデータの系列
- 例
- 音声データ
- テキストデータ etc
時系列データは物理時間に従って現れるものだけでなく、テキストデータのようにある順序で系列として処理するデータも含まれる。
CNNでは、このような系列処理は難しいため、RNNの技術が登場するようになった。これにより、自然言語処理技術が大きく飛躍することになった。
1-1-3 RNNとは
RNNは、以前の中間層の状態も利用して学習する構造になっている。
下図に、RNNの概念図を示す。
上図の左部が、RNNのネットワークの模式図で、入力層x、中間層z、出力層で構成されている。
RNNの特徴は、中間層zが自己回帰していることである。
分かりやすく展開したものが右部になる。
中間層の初期状態がz0とし、
時系列で[x1,x2,x3,x4....]というデータが入力されるとすると、
y1の処理には、中間層状態z1と前の中間状態に重みをかけたW×z0の線形結合を使用し、
y2の処理には、中間層状態z2と前の中間状態に重みをかけたW×z1の線形結合を使用し、
y3の処理には、中間層状態z3と前の中間状態に重みをかけたW×z2の線形結合を使用し、
.....
という形になる。
数式としては、下記のように書ける。
f(x):活性化関数
g(x):活性化関数
コードとしては、下記のように表せる。
u[:,t+1]=np.dot(X,W_in)+np.dot(z[:,t].reshape(1,-1),W) z[:,t+1]=functions.sigmoid(u[:,t+1]) np.dot(z[:,t+1].reshape(1,-1),W_out) y[:,t]=functions.sigmoid(np.dot(z[:,t+1].reshape(1,-1), W_out))
- 確認テスト
RNNのネットワークには大きく分けて3つの重みがある。一つは入力から現在の中間層を定義する際にかけられる重み、一つは中間層から出力を定義する際にかけられる重みである。
残り1つの重みについて説明せよ。
[回答]
中間層から中間層に回帰する際の重み。
- 演習チャレンジ
以下は再帰型ニューラルネットワークにおいて、構文木を入力として、再帰的に文全体の表現ベクトルを得るプログラムである。ただし、ニューラルネットワークの重みパラメータはグローバル変数として定義してあるものとし、activation関数は何らかの活性化関数であるとする。木構造は再帰的な辞書で定義してあり、rootが最も外側の辞書であると仮定する。
(く)にあてはまるコードは何か
[回答]
(2)
隣接単語から表現ベクトルを作るという処理は、隣接している表現leftとrightを合わせたものを特徴量としてそこに重みをかけることで実現する。
1-2 BPTT
1-2-2 BPTTの数学的記述
RNNのモデル式を再掲する。
u[:,t+1]=np.dot(X,W_in)+np.dot(z[:,t].reshape(1,-1),W) z[:,t+1]=functions.sigmoid(u[:,t+1]) np.dot(z[:,t+1].reshape(1,-1),W_out) y[:,t]=functions.sigmoid(np.dot(z[:,t+1].reshape(1,-1), W_out))
BPTTの微分の導出と、対応するコードを示す。
[]は、すべてのtに渡る計算をすることを示す。
まず、損失関数Eの入力重みWinによる微分
np.dot(X.T,delta[:t].reshape(1,-1))
Eの出力重みWoutによる微分
np.dot(z[:,t+1].reshape(-1,1),delta_out[:,t].reshape(-1,1))
Eの中間層重みによる微分
np.dot(z[:,t].reshape(-1,1),delta[:,t].reshape(1,-1))
- 確認テスト
下図のy1をx,s0,s1,w,woutを用いて数式で表せ。
*バイアスは任意の文字で定義せよ。
*また中間層の出力にシグモイド関数g(x)を作用させよ。
[回答]
ここで、時間的に遡っての微分δt-z-1について計算してみる。
第一式のコードは下記のように書ける
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])
最終的に各パラメータの更新式は以下のようになる。
上部3つの式に関するコードは下記のように書ける
W_in-=learning_rate*W_in_grad W_out=learnign_rate*W_out_grad W-=learning_rate*W_grad
BPTTの全体像
最後に、損失関数の中で、時間にわたる計算がどのようにあらわされるかを示す。
損失関数をE=loss(x1,x2)、教師データをdとすると、RNNの損失関数は下記のように表せる。
上記のように、yを展開すると、の項が出てくる。さらに、を展開すると、の項が出てくる。このように、時間をさかのぼって計算することが出来る。
RNN,BPTTの実装演習
バイナリを加算して桁上げする操作を、RNNで実行する。
import numpy as np from common import functions import matplotlib.pyplot as plt def d_tanh(x): return 1/(np.cosh(x) ** 2) # データを用意 # 2進数の桁数 binary_dim = 8 # 最大値 + 1 largest_number = pow(2, binary_dim) # largest_numberまで2進数を用意 binary = np.unpackbits(np.array([range(largest_number)],dtype=np.uint8).T,axis=1) input_layer_size = 2 hidden_layer_size = 16 output_layer_size = 1 weight_init_std = 1 learning_rate = 0.1 iters_num = 10000 plot_interval = 100 # ウェイト初期化 (バイアスは簡単のため省略) W_in = weight_init_std * np.random.randn(input_layer_size, hidden_layer_size) W_out = weight_init_std * np.random.randn(hidden_layer_size, output_layer_size) W = weight_init_std * np.random.randn(hidden_layer_size, hidden_layer_size) # Xavier # W_in = np.random.randn(input_layer_size, hidden_layer_size) / (np.sqrt(input_layer_size)) # W_out = np.random.randn(hidden_layer_size, output_layer_size) / (np.sqrt(hidden_layer_size)) # W = np.random.randn(hidden_layer_size, hidden_layer_size) / (np.sqrt(hidden_layer_size)) # He # W_in = np.random.randn(input_layer_size, hidden_layer_size) / (np.sqrt(input_layer_size)) * np.sqrt(2) # W_out = np.random.randn(hidden_layer_size, output_layer_size) / (np.sqrt(hidden_layer_size)) * np.sqrt(2) # W = np.random.randn(hidden_layer_size, hidden_layer_size) / (np.sqrt(hidden_layer_size)) * np.sqrt(2) # 勾配 W_in_grad = np.zeros_like(W_in) W_out_grad = np.zeros_like(W_out) W_grad = np.zeros_like(W) u = np.zeros((hidden_layer_size, binary_dim + 1)) z = np.zeros((hidden_layer_size, binary_dim + 1)) y = np.zeros((output_layer_size, binary_dim)) delta_out = np.zeros((output_layer_size, binary_dim)) delta = np.zeros((hidden_layer_size, binary_dim + 1)) all_losses = [] for i in range(iters_num): # A, B初期化 (a + b = d) a_int = np.random.randint(largest_number/2) a_bin = binary[a_int] # binary encoding b_int = np.random.randint(largest_number/2) b_bin = binary[b_int] # binary encoding # 正解データ d_int = a_int + b_int d_bin = binary[d_int] # 出力バイナリ out_bin = np.zeros_like(d_bin) # 時系列全体の誤差 all_loss = 0 # 時系列ループ for t in range(binary_dim): # 入力値 X = np.array([a_bin[ - t - 1], b_bin[ - t - 1]]).reshape(1, -1) # 時刻tにおける正解データ dd = np.array([d_bin[binary_dim - t - 1]]) u[:,t+1] = np.dot(X, W_in) + np.dot(z[:,t].reshape(1, -1), W) z[:,t+1] = functions.sigmoid(u[:,t+1]) # z[:,t+1] = functions.relu(u[:,t+1]) # z[:,t+1] = np.tanh(u[:,t+1]) y[:,t] = functions.sigmoid(np.dot(z[:,t+1].reshape(1, -1), W_out)) #誤差 loss = functions.mean_squared_error(dd, y[:,t]) delta_out[:,t] = functions.d_mean_squared_error(dd, y[:,t]) * functions.d_sigmoid(y[:,t]) all_loss += loss out_bin[binary_dim - t - 1] = np.round(y[:,t]) for t in range(binary_dim)[::-1]: X = np.array([a_bin[-t-1],b_bin[-t-1]]).reshape(1, -1) 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]) # delta[:,t] = (np.dot(delta[:,t+1].T, W.T) + np.dot(delta_out[:,t].T, W_out.T)) * functions.d_relu(u[:,t+1]) # delta[:,t] = (np.dot(delta[:,t+1].T, W.T) + np.dot(delta_out[:,t].T, W_out.T)) * d_tanh(u[:,t+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 W_in_grad *= 0 W_out_grad *= 0 W_grad *= 0 if(i % plot_interval == 0): all_losses.append(all_loss) print("iters:" + str(i)) print("Loss:" + str(all_loss)) print("Pred:" + str(out_bin)) print("True:" + str(d_bin)) out_int = 0 for index,x in enumerate(reversed(out_bin)): out_int += x * pow(2, index) print(str(a_int) + " + " + str(b_int) + " = " + str(out_int)) print("------------") lists = range(0, iters_num, plot_interval) plt.plot(lists, all_losses, label="loss") plt.show()
結果の一部表示
iters:0 Loss:1.245858363786796 Pred:[0 0 0 0 0 0 0 0] True:[1 0 1 0 0 0 0 1] 82 + 79 = 0 ------------ iters:100 Loss:0.9697292981990132 Pred:[0 1 1 1 1 0 1 1] True:[1 0 0 1 1 0 1 1] 55 + 100 = 123 ------------ .....(途中一部省略) .------------ iters:9800 Loss:0.0009243998471599557 Pred:[0 1 1 0 0 1 0 1] True:[0 1 1 0 0 1 0 1] 68 + 33 = 101 ------------ iters:9900 Loss:0.0025480327477791424 Pred:[0 0 1 0 0 0 0 1] True:[0 0 1 0 0 0 0 1] 19 + 14 = 33 ------------
学習回数の増加に従って、PredとTrueの値が一致してきていることも分かる。
また、繰り返して学習することで、損失関数が低減していることがわかる。
- コード演習問題
下図は、BPTTを行うプログラムである。なお簡単化のため活性化関数は恒等関数であるとする。
また、calculate_dout関数は損失関数を出力に関して偏微分した値を返す関数であるとする。
(お)にあてはまる値はどれか
[解答]
中間出力は過去の中間層出力に依存する。
であることに注意すると、過去にさかのぼるたびにUがかけられる。つまり、(お)は、delta_t=delta_t.dot(U)となる。