ディープラーニングのお勉強体験記”26:GRUのSIN波学習”(リカレントニューラルネットワーク”RNN”を中心にバックプロパゲーション”BPTT”を数式を使って理解したい!)
- リンクを取得
- ×
- メール
- 他のアプリ
「ゼロから作るDeep Learning 2 ―自然言語処理編」のRNNコード、P216、第5章「5.5.3 RNNLMの学習コード」を使って作ったSIN波の学習コードをGRUにする。
RNNのところで、P216、第5章「5.5.3 RNNLMの学習コード」に手を加えて、それを「SIN波の学習コード」にしましたが、それをGRUコードに作り替えました。
それがこれ。
P216、第5章「5.5.3 RNNLMの学習コード」を使ったSIN波の学習コードのGRUに作り変えたコード
# ゼロから作る Deep Learning2のP216、第5章「5.5.3 RNNLMの学習コード」でコード全体が見えるようにできるだけ「import」を外した # SINカーブを学習させるため変更したコード # coding: utf-8 # import sys # sys.path.append('C:\\kojin\\資料\\AI関連\\ゼロから作る Deep Learning\\ゼロから作る Deep Learning2\\deep-learning-from-scratch-2-master\\') import matplotlib.pyplot as plt import numpy as np # from common.optimizer import SGD # from dataset import ptb # このimportを有効にするには上記パス設定「sys.path.append('C:\\kojin\\AI関連\\・・・」が必要! # from simple_rnnlm import SimpleRnnlm np.random.seed(seed=7724) # 発生する乱数を固定する(7724) # ハイパーパラメータの設定 batch_size = 10 time_size = 10 # Truncated BPTTの展開する時間サイズ n_in = 1 # 入力層のニューロン数 n_mid = 45 # 中間層のニューロン数 n_out = 1 # 出力層のニューロン数 # できるだけオリジナルのコードに変更を加えないため、既存の変数に代入する vocab_size = n_out wordvec_size = n_in hidden_size = n_mid lr = 0.005 max_epoch = 501 # 学習データの読み込み ## -------------------学習用データ------------------------------------ # -- 訓練データの作成 -- sin_x = np.linspace(-2*np.pi, 2*np.pi, 101) # -2πから2πまで sin_y = np.sin(sin_x) # sin関数 predict用で使用する(なんかバグがある、predict部分で再宣言必要) sin_y_noise = np.sin(sin_x) + 0.05*np.random.randn(len(sin_x)) # sin関数に乱数でノイズを加える x_data = sin_y_noise.reshape(-1, 1) t_data = sin_y_noise.reshape(-1, 1) #------------------------------------------------------------------------------------------------ xs = x_data[:-1] # 入力 ts = t_data[1:] # 出力(教師ラベル) data_size = len(xs) #///////////////////RNNに対応した「time_size」行列の学習用データを作成 開始部分///////////////////////// x_time = np.empty((data_size - time_size, time_size, wordvec_size), dtype='f') t_time = np.empty((data_size - time_size, time_size, vocab_size), dtype='f') for i in range(data_size - time_size): for j in range(time_size): x_time[i, j, :] = xs[i + j] t_time[i, j, :] = ts[i + j] #///////////////////RNNに対応した「time_size」行列の学習用データを作成 終了部分///////////////////////// # 学習時に使用する変数 # max_iters = data_size // (batch_size * time_size) max_iters = data_size // batch_size # SIN波学習のデータ量として、「time_size」で割ってしまうと足りなくなる time_idx = 0 total_loss = 0 loss_count = 0 ppl_list = [] # /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// # /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// # できるだけimportを外すため、classをコピペした箇所の「開始」部分 # /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// # GPUを定義しておく(コードのどこかでこの定義を参照しているらしいけど、PCにNVIDIA無いので、下記定義をするだけ) GPU = False # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # optimizer.py の抜粋「開始」部分 # --------------------------------------------------------------------------------------------------------------------------- class SGD: ''' 確率的勾配降下法(Stochastic Gradient Descent) ''' def __init__(self, lr=0.01): self.lr = lr def update(self, params, grads): for i in range(len(params)): params[i] -= self.lr * grads[i] # --------------------------------------------------------------------------------------------------------------------------- # optimizer.py の抜粋「終了」部分 # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # functions.py の全部抜粋「開始」部分 # --------------------------------------------------------------------------------------------------------------------------- def sigmoid(x): return 1 / (1 + np.exp(-x)) # --------------------------------------------------------------------------------------------------------------------------- # functions.py の全部抜粋「終了」部分 # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # time_layers.py の抜粋「開始」部分 # --------------------------------------------------------------------------------------------------------------------------- class TimeAffine: def __init__(self, W, b): self.params = [W, b] self.grads = [np.zeros_like(W), np.zeros_like(b)] self.x = None def forward(self, x): N, T, D = x.shape W, b = self.params rx = x.reshape(N*T, -1) out = np.dot(rx, W) + b self.x = x return out.reshape(N, T, -1) def backward(self, dout): x = self.x N, T, D = x.shape W, b = self.params dout = dout.reshape(N*T, -1) rx = x.reshape(N*T, -1) db = np.sum(dout, axis=0) dW = np.dot(rx.T, dout) dx = np.dot(dout, W.T) dx = dx.reshape(*x.shape) self.grads[0][...] = dW self.grads[1][...] = db return dx class GRU: def __init__(self, Wx, Wh, b): ''' Parameters ---------- Wx: 入力`x`用の重みパラーメタ(3つ分の重みをまとめる) Wh: 隠れ状態`h`用の重みパラメータ(3つ分の重みをまとめる) b: バイアス(3つ分のバイアスをまとめる) ''' self.params = [Wx, Wh, b] self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)] self.cache = None def forward(self, x, h_prev): Wx, Wh, b = self.params H = Wh.shape[0] Wxz, Wxr, Wxh = Wx[:, :H], Wx[:, H:2 * H], Wx[:, 2 * H:] Whz, Whr, Whh = Wh[:, :H], Wh[:, H:2 * H], Wh[:, 2 * H:] bz, br, bh = b[:H], b[H:2 * H], b[2 * H:] z = sigmoid(np.dot(x, Wxz) + np.dot(h_prev, Whz) + bz) r = sigmoid(np.dot(x, Wxr) + np.dot(h_prev, Whr) + br) h_hat = np.tanh(np.dot(x, Wxh) + np.dot(r*h_prev, Whh) + bh) h_next = (1-z) * h_prev + z * h_hat self.cache = (x, h_prev, z, r, h_hat) return h_next def backward(self, dh_next): Wx, Wh, b = self.params H = Wh.shape[0] Wxz, Wxr, Wxh = Wx[:, :H], Wx[:, H:2 * H], Wx[:, 2 * H:] Whz, Whr, Whh = Wh[:, :H], Wh[:, H:2 * H], Wh[:, 2 * H:] x, h_prev, z, r, h_hat = self.cache dh_hat =dh_next * z dh_prev = dh_next * (1-z) # tanh dt = dh_hat * (1 - h_hat ** 2) dbh = np.sum(dt, axis=0) dWhh = np.dot((r * h_prev).T, dt) dhr = np.dot(dt, Whh.T) dWxh = np.dot(x.T, dt) dx = np.dot(dt, Wxh.T) dh_prev += r * dhr # update gate(z) dz = dh_next * h_hat - dh_next * h_prev dt = dz * z * (1-z) dbz = np.sum(dt, axis=0) dWhz = np.dot(h_prev.T, dt) dh_prev += np.dot(dt, Whz.T) dWxz = np.dot(x.T, dt) dx += np.dot(dt, Wxz.T) # rest gate(r) dr = dhr * h_prev dt = dr * r * (1-r) dbr = np.sum(dt, axis=0) dWhr = np.dot(h_prev.T, dt) dh_prev += np.dot(dt, Whr.T) dWxr = np.dot(x.T, dt) dx += np.dot(dt, Wxr.T) self.dWx = np.hstack((dWxz, dWxr, dWxh)) self.dWh = np.hstack((dWhz, dWhr, dWhh)) self.db = np.hstack((dbz, dbr, dbh)) self.grads[0][...] = self.dWx self.grads[1][...] = self.dWh self.grads[2][...] = self.db return dx, dh_prev class TimeGRU: def __init__(self, Wx, Wh, b, stateful=False): self.params = [Wx, Wh, b] self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)] self.layers = None self.h, self.dh = None, None self.stateful = stateful def forward(self, xs): Wx, Wh, b = self.params N, T, D = xs.shape H = Wh.shape[0] self.layers = [] hs = np.empty((N, T, H), dtype='f') if not self.stateful or self.h is None: self.h = np.zeros((N, H), dtype='f') for t in range(T): layer = GRU(*self.params) self.h = layer.forward(xs[:, t, :], self.h) hs[:, t, :] = self.h self.layers.append(layer) return hs def backward(self, dhs): Wx, Wh, b = self.params N, T, H = dhs.shape D = Wx.shape[0] dxs = np.empty((N, T, D), dtype='f') dh = 0 grads = [0, 0, 0] for t in reversed(range(T)): layer = self.layers[t] dx, dh = layer.backward(dhs[:, t, :] + dh) dxs[:, t, :] = dx for i, grad in enumerate(layer.grads): grads[i] += grad for i, grad in enumerate(grads): self.grads[i][...] = grad self.dh = dh return dxs def set_state(self, h): self.h = h def reset_state(self): self.h = None class TimeOutputWithLoss: # このコードは「class TimeSoftmaxWithLoss」の代わりに実装する def __init__(self): self.cache = None def forward(self, xs, ts): N, T, D = xs.shape # ここでDは1 # RNNのタイプに合わせて「#1」か「#2」を選択する # # ------------------RNN many to many start---------------#1 # loss = 0.5 * np.sum((xs - ts)**2) #1 # loss /= N # 1データ分での誤差 #1 # # -------------------RNN many to many end----------------#1 # ------------------RNN many to one start----------------#2 loss = 0.5 * np.sum((xs[:, T-1, :] - ts[:, T-1, :])**2) #2 loss /= N # 1データ分での誤差 #2 # -------------------RNN many to one end-----------------#2 self.cache = (ts, xs, (N, T, D)) return loss def backward(self, dout=1): ts, xs, (N, T, D) = self.cache # RNNのタイプに合わせて「#1」か「#2」を選択する # # ------------------RNN many to many start---------------#1 # dout = xs - ts #1 # dout /= N #1 # # -------------------RNN many to many end----------------#1 # ------------------RNN many to one start----------------#2 dout = np.zeros([N, T, D], dtype='float') #2 dout[:, T-1, :] = xs[:, T-1, :] - ts[:, T-1, :] #2 # -------------------RNN many to one end-----------------#2 return dout # --------------------------------------------------------------------------------------------------------------------------- # time_layers.py の抜粋「終了」部分 # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # simple_rnnlm.py の抜粋「開始」部分 # --------------------------------------------------------------------------------------------------------------------------- class SimpleRnnlm: def __init__(self, vocab_size=10000, wordvec_size=100, hidden_size=100): V, D, H = vocab_size, wordvec_size, hidden_size rn = np.random.randn # 重みの初期化 # embed_W = (rn(V, D) / 100).astype('f') # embedingレイヤは無効にする gru_Wx = (rn(D, 3 * H) / np.sqrt(D)).astype('f') # 「lstm」を「gru」に書き換えつつ、「4」を「3」にした gru_Wh = (rn(H, 3 * H) / np.sqrt(H)).astype('f') # 「lstm」を「gru」に書き換えつつ、「4」を「3」にした gru_b = np.zeros(3 * H).astype('f') # 「lstm」を「gru」に書き換えつつ、「4」を「3」にした affine_W = (rn(H, V) / np.sqrt(H)).astype('f') affine_b = np.zeros(V).astype('f') # レイヤの生成 # 「TimeLSTM」を「TimeGRU」に書き換え self.layers = [ # TimeEmbedding(embed_W), # embedingレイヤは無効にする TimeGRU(gru_Wx, gru_Wh,gru_b, stateful=True), TimeAffine(affine_W, affine_b) ] # self.loss_layer = TimeSoftmaxWithLoss() # TimeSoftmaxWithLossレイヤは無効にする self.loss_layer = TimeOutputWithLoss() self.gru_layer = self.layers[0] # 「TimeEmbedding」を外したので「TimeGRU」を「self.gru_layer」にするため「self.layers[1]」を「self.layers[0]」にした # 「rnn」を「gru」に書き換え # すべての重みと勾配をリストにまとめる self.params, self.grads = [], [] for layer in self.layers: self.params += layer.params self.grads += layer.grads #------オリジナルコードに予測(predict)が無いので、LSTMのコードから持ってくる------ def predict(self, xs): for layer in self.layers: xs = layer.forward(xs) return xs def forward(self, xs, ts): for layer in self.layers: xs = layer.forward(xs) loss = self.loss_layer.forward(xs, ts) return loss def backward(self, dout=1): dout = self.loss_layer.backward(dout) for layer in reversed(self.layers): dout = layer.backward(dout) return dout def reset_state(self): self.gru_layer.reset_state() # 「rnn」を「gru」に書き換え # --------------------------------------------------------------------------------------------------------------------------- # simple_rnnlm.py の抜粋「終了」部分 # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// # できるだけimportを外すため、classをコピペした箇所の「終了」部分 # /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// # /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// # モデルの生成 model = SimpleRnnlm(vocab_size, wordvec_size, hidden_size) optimizer = SGD(lr) # ミニバッチの各サンプルの読み込み開始位置を計算 # jump = (corpus_size - 1) // batch_size # offset = [i * jump for i in range(batch_size)] for epoch in range(max_epoch): #---「common」の「trainer.py」から「class Trainer」よりデータをシャッフルする部分を抜粋--- time_idx = 0 # インデックス追加部分 # シャッフル idx = np.random.permutation(np.arange(data_size - time_size)) x_shuffle = x_time[idx, ] t_shuffle = t_time[idx, ] #----------------------------------------------------------------------------------- # ///////////////////////////////////////////////////////////////////////////////////////////////////////// for iter in range(max_iters - 1): # ミニバッチの取得 batch_x = np.empty((batch_size, time_size, wordvec_size), dtype='f') batch_t = np.empty((batch_size, time_size, vocab_size), dtype='f') for i in range(batch_size): batch_x[i, :, :] = x_shuffle[time_idx + i, ] batch_t[i, :, :] = t_shuffle[time_idx + i, ] time_idx += batch_size # ///////////////////////////////////////////////////////////////////////////////////////////////////////// # 勾配を求め、パラメータを更新 loss = model.forward(batch_x, batch_t) model.backward() optimizer.update(model.params, model.grads) total_loss += loss loss_count += 1 # エポックごとにパープレキシティの評価 「パープレキシティ」はここでは「ロス」として扱う # ppl = np.exp(total_loss / loss_count) ppl = total_loss # print('| epoch %d | perplexity %.2f' # % (epoch+1, ppl)) ppl_list.append(float(ppl)) total_loss, loss_count = 0, 0 #----------------予測部分開始------------------------------------- # -- 予測 -- # 順伝播 RNN層 # x_predict = xs[:] x_predict = sin_y.reshape(-1, 1) # x_predictのデータ構造をsin_yと同じにしている x_predict.fill(0.0) sin_y = np.sin(sin_x) # sin関数 なぜかここで再定義しないとsinデータを読み込まない for i in range(time_size): x_predict[i, ] = sin_y[i, ] # 「x_predict」の最初の「time_size」分SIN波データを組み込む y_predict = np.empty((batch_size, time_size, vocab_size), dtype='f') pred_batch_x = np.empty((batch_size, time_size, wordvec_size), dtype='f') # batch_x = np.empty((batch_size, time_size, wordvec_size), dtype='f') # 「batch_size」 ありきでコードが組まれているので、入力データを「batch_size」分、重複させて作る for j in range(data_size - time_size + 1): for t in range(time_size): for i in range(batch_size): pred_batch_x[i, t, :] = x_predict[j + t, ] # データは時系列に作成、「batch_size」分、重複させている y_predict = model.predict(pred_batch_x) x_predict[j + time_size, :] = y_predict[0, time_size-1, 0] #-----------------予測部分終了------------------------------------ # グラフの描画 誤差の表示部分 x = np.arange(len(ppl_list)) plt.plot(x, ppl_list, label='train') plt.xlabel('epochs') plt.ylabel('loss') plt.show() # グラフの描画 学習結果の波形パターンの一致度合いを表示 td = ts.reshape(-1) x = np.arange(len(td)) plt.plot(x, td, x_predict, label='train') plt.xlabel('epochs') plt.ylabel('sin(x)') plt.show()
実行結果がこれ。
特に問題は無いと思います。
RNNからGRUへ変更するために書き換えたところはLSTMで書き換えたところとほぼ同じです。
ただ、LSTMの時と同じで、SIN波の学習は、初期条件がシビアなんでしょうね、やっぱり初期値の乱数はすごく効くようで、RNN、LSTMの時と同じで、乱数を固定して発生させています。
変更箇所は基本的に ”「ゼロから作るDeep Learning 2 ―自然言語処理編」のRNNコード、P216、第5章「5.5.3 RNNLMの学習コード」をLSTMコードに変更する。” の時とほぼ一緒です
・「functions.py」から「sigmoid()」関数を追加しました
・「common」の「time_layers.py」から「class GRU」と「class TimeGRU」をコピーし、「class RNN」と「class TimeRNN」と入れ替えました
少し異なるのは下記の「class SimpleRnnlm」の部分です。(例えば、 「embedingレイヤは無効にする」 とかですね)
”「ゼロから作るDeep Learning 2 ―自然言語処理編」のRNNコード、P216、第5章「5.5.3 RNNLMの学習コード」をGRUコードに変更する。” から「class Simplegrulm」をコピペして、コメントを追加したところが変更部分になります。(コードが増えてきて、ちょっと、いろいろとっちらかっていて、クラス名が「class SimpleRnnlm」と混在してますけど、、、、LSTMとも合わせていつか整理しないと、、、、)
変更方法は ”「ゼロから作るDeep Learning 2 ―自然言語処理編」のRNNコード、P216、第5章「5.5.3 RNNLMの学習コード」を使ってSIN波の学習コードを作る。” とも共通する部分が多いので、参照して頂くと分かりやすいのではないでしょうか、、、、、
以上です。
- リンクを取得
- ×
- メール
- 他のアプリ
コメント
コメントを投稿