ディープラーニングのお勉強体験記”19:LSTMのSIN波学習”(リカレントニューラルネットワーク”RNN”を中心にバックプロパゲーション”BPTT”を数式を使って理解したい!)
- リンクを取得
- ×
- メール
- 他のアプリ
「ゼロから作るDeep Learning 2 ―自然言語処理編」のRNNコード、P216、第5章「5.5.3 RNNLMの学習コード」を使って作ったSIN波の学習コードをLSTMにする。
RNNのところで、P216、第5章「5.5.3 RNNLMの学習コード」に手を加えて、それを「SIN波の学習コード」にしましたが、それをLSTMコードに作り替えました。
それがこれ
P216、第5章「5.5.3 RNNLMの学習コード」を使ったSIN波の学習コードのLSTMに作り変えたコード
# ゼロから作る 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=773) # 発生する乱数を固定する(773 326 512 138) # ハイパーパラメータの設定 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.008 max_epoch = 301 # 学習データの読み込み ## -------------------学習用データ------------------------------------ # -- 訓練データの作成 -- 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 LSTM: def __init__(self, Wx, Wh, b): ''' Parameters ---------- Wx: 入力`x`用の重みパラーメタ(4つ分の重みをまとめる) Wh: 隠れ状態`h`用の重みパラメータ(4つ分の重みをまとめる) b: バイアス(4つ分のバイアスをまとめる) ''' 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, c_prev): Wx, Wh, b = self.params N, H = h_prev.shape A = np.dot(x, Wx) + np.dot(h_prev, Wh) + b f = A[:, :H] g = A[:, H:2*H] i = A[:, 2*H:3*H] o = A[:, 3*H:] f = sigmoid(f) g = np.tanh(g) i = sigmoid(i) o = sigmoid(o) c_next = f * c_prev + g * i h_next = o * np.tanh(c_next) self.cache = (x, h_prev, c_prev, i, f, g, o, c_next) return h_next, c_next def backward(self, dh_next, dc_next): Wx, Wh, b = self.params x, h_prev, c_prev, i, f, g, o, c_next = self.cache tanh_c_next = np.tanh(c_next) ds = dc_next + (dh_next * o) * (1 - tanh_c_next ** 2) dc_prev = ds * f di = ds * g df = ds * c_prev do = dh_next * tanh_c_next dg = ds * i di *= i * (1 - i) df *= f * (1 - f) do *= o * (1 - o) dg *= (1 - g ** 2) dA = np.hstack((df, dg, di, do)) dWh = np.dot(h_prev.T, dA) dWx = np.dot(x.T, dA) db = dA.sum(axis=0) self.grads[0][...] = dWx self.grads[1][...] = dWh self.grads[2][...] = db dx = np.dot(dA, Wx.T) dh_prev = np.dot(dA, Wh.T) return dx, dh_prev, dc_prev class TimeLSTM: 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.c = None, None self.dh = 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') if not self.stateful or self.c is None: self.c = np.zeros((N, H), dtype='f') for t in range(T): layer = LSTM(*self.params) self.h, self.c = layer.forward(xs[:, t, :], self.h, self.c) 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, dc = 0, 0 grads = [0, 0, 0] for t in reversed(range(T)): layer = self.layers[t] dx, dh, dc = layer.backward(dhs[:, t, :] + dh, dc) 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, c=None): self.h, self.c = h, c def reset_state(self): self.h, self.c = None, 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レイヤは無効にする lstm_Wx = (rn(D, 4 * H) / np.sqrt(D)).astype('f') lstm_Wh = (rn(H, 4 * H) / np.sqrt(H)).astype('f') lstm_b = np.zeros(4 * H).astype('f') affine_W = (rn(H, V) / np.sqrt(H)).astype('f') affine_b = np.zeros(V).astype('f') # レイヤの生成 self.layers = [ # TimeEmbedding(embed_W), # embedingレイヤは無効にする TimeLSTM(lstm_Wx, lstm_Wh, lstm_b, stateful=True), TimeAffine(affine_W, affine_b) ] # self.loss_layer = TimeSoftmaxWithLoss() # TimeSoftmaxWithLossレイヤは無効にする self.loss_layer = TimeOutputWithLoss() self.lstm_layer = self.layers[0] # 「TimeEmbedding」を外したので「TimeLSTM」を「self.lstm_layer」にするため「self.layers[1]」を「self.layers[0]」にした # 「rnn」を「lstm」に書き換え # すべての重みと勾配をリストにまとめる 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.lstm_layer.reset_state() # 「rnn」を「lstm」に書き換え # --------------------------------------------------------------------------------------------------------------------------- # 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()
実行結果がこれ。
特に問題は無いと思います。
ただ、SIN波の学習は、初期条件がシビアなんですかね?、初期値に使う乱数がすごく効くようなので、RNN時と同じで、乱数を固定して発生させています。
変更箇所は基本的に ”「ゼロから作るDeep Learning 2 ―自然言語処理編」のRNNコード、P216、第5章「5.5.3 RNNLMの学習コード」をLSTMコードに変更する。” の時とほぼ一緒です
・「functions.py」から「sigmoid()」関数を追加しました
・「common」の「time_layers.py」から「class LSTM」と「class TimeLSTM」をコピーし、「class RNN」と「class TimeRNN」と入れ替えました
少し異なるのは下記の「class SimpleRnnlm」の部分です。(例えば、 「embedingレイヤは無効にする」 とかですね)
"「ゼロから作るDeep Learning 2 ―自然言語処理編」のRNNコード、P216、第5章「5.5.3 RNNLMの学習コード」をLSTMコードに変更する。" から「class SimpleRnnlm」コピペして、コメントを追加したところが変更部分になります。
変更方法は ”「ゼロから作るDeep Learning 2 ―自然言語処理編」のRNNコード、P216、第5章「5.5.3 RNNLMの学習コード」を使ってSIN波の学習コードを作る。” とも共通する部分が多いので、参照して頂くと分かりやすいのではないでしょうか、、、、、
上級者の人が作ったコードはきれいにまとまっているので、変更するとき助かりますよねー(ただ、難しいテクニックを使われると意味わかんなくてお手上げになることが多いですが、、、、、、)
以上です。
- リンクを取得
- ×
- メール
- 他のアプリ
コメント
コメントを投稿