はじめに
「RNNにsin波を学習させて予測してみた」ではTensorflowを使って、「深層学習ライブラリKerasでRNNを使ってsin波予測」ではKerasを使って、RNNによる正弦波の学習・予測が行われている。ここでは同じことをChainerを使って実装する。
ネットワークの構造
実装は以下の通りである。
-- lstm.py -- コンストラクタの引数の意味は以下の通り。
引数名 | 意味 | デフォルト値 |
---|---|---|
in_units | 入力層のユニット数 | 1 |
hidden_units | 隠れ層のユニット数 | 2 |
out_units | 出力層のユニット数 | 1 |
隠れ層の各ユニットはLSTM(Long Short Term Memory)、損失関数は2乗平均誤差である。
in_units
とout_units
は1に固定し(実数値を1つ受け取り実数値を1つ返す)、hidden_units
の値を変えた時の精度の変化を見る(後述)。
訓練データの作成
実装は以下の通りである。
-- make_data.py -- コンストラクタの引数の意味は以下の通り。
引数名 | 意味 |
---|---|
steps_per_cycle | 正弦波1周期の分割数 |
number_of_cycles | 生成する周期の数 |
steps_per_cycle
を50に、number_of_cycles
を100としてデータを作成した。関数make
は全データを1次元の配列(np.ndarray)に入れて返す。関数make_mini_batch
は、長さlength_of_sequence
のデータをバッチサイズ分(mini_batch_size
)だけ切り出す。
訓練
実装は以下の通りである。
-- train.py -- プログラム冒頭の定数の意味は以下の通り。
引数名 | 意味 |
---|---|
IN_UNITS | 入力層のユニット数 |
HIDDEN_UNITS | 隠れ層のユニット数 |
OUT_UNITS | 出力層のユニット数 |
TRAINING_EPOCHS | 訓練時のエポック数 |
DISPLAY_EPOCH | ログを出力するタイミング |
MINI_BATCH_SIZE | ミニバッチの数 |
LENGTH_OF_SEQUENCE | 学習する実数値のシーケンスの長さ |
STEPS_PER_CYCLE | 正弦波1周期の分割数 |
NUMBER_OF_CYCLES | 周期の数 |
関数
compute_loss
では、長さLENGTH_OF_SEQUENCE
の配列をMINI_BATCH_SIZE
個一括して処理を行っている(下図参照)。
また、LENGTH_OF_SEQUENCE
$\times$MINI_BATCH_SIZE
個のデータの塊を1epochと定義した。以下を実行する。
DISPLAY_EPOCH
(上の場合は10)epoch ごとに訓練時の損失値が表示される。上記のパラメータのとき計算時間は27分ほどであった。
学習曲線
下図は
HIDDEN_UNITS
の値を2,3,4,5に変えた時の学習曲線である。縦軸は損失(2乗平均誤差)の対数($\log_{10}$)を取った値である。10epoch毎に1回ずつ表示していので横軸の値を10倍したものが実際のepoch数である。
隠れ層のユニット数を増やすと損失は小さくなるが、3以上になるとそれほど差はないことが分る。
予測
予測時のスクリプトは以下の通りである。
-- predict.py -- 上記のスクリプトでしていることを以下に図示する。 ここで、
input_seq
は最初に与える波形、pre_length
はその波形をもとに予測する波形の長さを表す。例えば、input_seq
の長さを4、pre_length
を3とすると予測手順は以下のようになる。
- 4つの実数値の並びから最後の値を予測する(これを4とする)。
- 最初の値を捨てて4を最後尾に追加し、これを使って最後の値を予測する(これを5とする)。
- 最初の値を捨てて5を最後尾に追加し、これを使って最後の値を予測する(これを6とする)。
- (4,5,6)が求める予測値である。
input_seq
の長さを50として、pre_length
を50とした時の結果を以下に示す。
input_seq
の長さを25として、pre_length
を75とした時の結果を以下に示す。
隠れ層のユニットの数が多いほど精度は良い。しかし、いずれのユニット数の場合も、最初に与える波形から遠ざかるにつれて精度は悪くなる。
参考にさせてもらっているのですが、動作が期待通りでない個所と、ソースの不明点があり、可能でしたらご教示いただけないでしょうか。
返信削除①ほぼ As isでPredictを実行しましたところ、Prediction_5.txtの結果が
次のようになり、Sin形状と全く異なる結果でした。Prediction_5.txtの結果は予測されたSin波の値そのものが記録されるものという理解でいたのですが、正しいでしょうか。
24 -0.951056540012
25 -0.927622199059
26 -0.909254074097
27 -0.894990622997
28 -0.883809745312
29 -0.874989032745
30 -0.867993831635
31 -0.86242300272
・
・
96 -0.839547872543
97 -0.839547872543
98 -0.839547872543
99 -0.839547872543
②def predict_sequence(model, input_seq, output_seq, dummy):
の、output_seqは、どのような意味を持つ引数になるでしょうか。
当該関数内では直接は利用されていないように見えました。
③def predict_sequence内の、下記for 文で、future=model(x, dummy)
を実行していますが、これはどういった意味になるでしょうか。
for i in range(sequences_col):
x = chainer.Variable(xp.asarray(input_seq[i:i+1], dtype=np.float32)[:, np.newaxis])
future = model(x, dummy)
よろしくお願いいたします。
①
削除ブログに示したようにsin波になります。
今回もう一度実行しましたが、上の図と同じ結果が出ます。
②
これpredict_sequence内では必要ないですね。私のミスです。
③
中程にある図に
(0,1,2,3) -> 4
(1,2,3,4) -> 5
(2,3,4,5) -> 6
が示されています。
45行目から始まるforループ内では、
input_seq=(0,1,2,3)としてfuture=4を求めます。
次のステップでは
input_seq=(1,2,3,4)としてfuture=5を求めます。
次のステップでは
input_seq=(2,3,4, 5)としてfuture=6を求めます。
以下同様です。
25行目から始まるforループでは、
input_seq=(0,1,2,3)のとき(全て実測値)
最初の実測値0から次の値1(予測値)を求めて捨てます。
次のステップでは1(実測値)から2(予測値)を求めて捨てます。
次のステップでは2(実測値)から3(予測値)を求めて捨てます。
次のステップでは3(実測値)から4(予測値)を求めます。これが返り値になります。
結局、実測値 input_seq=(0,1,2,3)から予測値 4を求めたことになります。最初、input_seqは全て実測値ですが、1つずつ予測値が増えていきます。数回のあとinput_seq自体も全て予測値に占められることになります。