2016年7月24日日曜日

LSTMによる正弦波の予測 〜 Chainerによる実装 〜


はじめに


 「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_unitsout_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とすると予測手順は以下のようになる。
  1. 4つの実数値の並びから最後の値を予測する(これを4とする)。
  2. 最初の値を捨てて4を最後尾に追加し、これを使って最後の値を予測する(これを5とする)。
  3. 最初の値を捨てて5を最後尾に追加し、これを使って最後の値を予測する(これを6とする)。
  4. (4,5,6)が求める予測値である。
input_seqの長さを50として、pre_lengthを50とした時の結果を以下に示す。
input_seqの長さを25として、pre_lengthを75とした時の結果を以下に示す。
隠れ層のユニットの数が多いほど精度は良い。しかし、いずれのユニット数の場合も、最初に与える波形から遠ざかるにつれて精度は悪くなる。

2 件のコメント:

  1. 参考にさせてもらっているのですが、動作が期待通りでない個所と、ソースの不明点があり、可能でしたらご教示いただけないでしょうか。

    ①ほぼ 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)

    よろしくお願いいたします。

    返信削除
    返信

    1. ブログに示したように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自体も全て予測値に占められることになります。

      削除