クイックノート

ちょっとした発見・アイデアから知識の発掘を

【R】LSTMで時系列を予測してみる

ディープラーニングで時系列を扱うとなると、
定番なのはLSTM(Long Short Term Memory)と呼ばれるモデルです。

LSTMでは、時間的な関係をニューロンの構造に組み込んだもので、
データに時間の流れなどが含まれる場合に、適したモデルとなります。

今回は、このLSTMを使って、時系列の予測をしてみます。

LSTM とは

LSTM とは、ニューラルネットワークの中間層の構造の一つで、
自身の出力を、再帰的に入力するような構造を持ったものです。

f:id:u874072e:20180827161123p:plain

図のように、自分の層の中で、
それぞれのニューロンの出力が、次のニューロンの入力として使われます。

このようにすることで、「ニューロンtさん」の次は「ニューロンt+1さん」という形で、
LSTM層の中のニューロンに順番が生まれることになります。

出力が別のニューロンの入力に「戻ってくる」ニューラルネットワークは、 RNNと呼ばれ、LSTMもこのRNNの一種と言えます。

一般に、ニューロンの出力を戻してくると、学習が不安定になるのですが、
LSTMでは、この学習の問題を避ける仕組みが導入されており、
良い性能を出すように改良されてきたものになります。

準備

ここでは、kerasパッケージを用いてLSTMを実行するので、
kerasをインストールしておく必要があります。

インストール方法は、下記の記事を参照してください。

clean-copy-of-onenote.hatenablog.com

LSTM による時系列の再現

それでは、さっそくLSTMで時系列を学習して、
時系列を再現できるか確かめてみましょう。

データの準備

今回は、Rで標準に用意されているデータセット
UKgasを使って時系列の学習を行います。

最終的に、 ある一定の時間分のデータを使って、次の時間のデータを予測
するようにデータを学習させるので、
ひとつながりの時系列データを一定期間ごとに区切って、
入力と出力の訓練データを作る必要があります。

f:id:u874072e:20180827163525p:plain

library(keras)
make_data_for_lstm = function(ts_df,window,rm.na=F){
  data.x = NULL
  data.y = NULL
  n = dim(ts_df)[2]
  for(i in 1:n){
    ts_x = ts_df[,i]
    for(j in 1:(length(ts_x)-window)){
      if(rm.na){
        tmp.x = ts_x[1:window + j -1]
        tmp.y = ts_x[1:window + j -1]
        if(sum(c(is.na(tmp.x),is.na(tmp.y))) == 0){
          data.x = rbind(data.x,ts_x[1:window + j -1])
          data.y = rbind(data.y,ts_x[window + j])          
        }
      }else{
        data.x = rbind(data.x,ts_x[1:window + j -1])
        data.y = rbind(data.y,ts_x[window + j])
      }
    }
  }
  return(list(x=array_reshape(data.x,c(dim(data.x),1)),
              y=data.y))
}

上の関数make_data_for_lstmでは、時系列データts_dfを、
windowで指定した長さ毎に区切って訓練用のデータを生成します。

上の関数を使って、UKgasをLSTMへの入力用に加工します。

  window=10
  ts_df = as.matrix(UKgas)
  
  data = make_data_for_lstm(ts_df,window,rm.na=T)
  
  scale = max(ts_df,na.rm = T)
  x = data$x / scale
  y = data$y / scale

最後の三行では、データを最大値で割ってスケーリングしています。

モデルの準備

続いて、モデルを準備していきます。
ここでは、最もシンプルなもので、
LSTMを1層だけ中間層に挟んだモデルを使うことにします。

  model = keras_model_sequential()
  model %>%
    layer_lstm(units = 64,input_shape = c(dim(x)[2],1)) %>%
    layer_dropout(rate=0.4) %>%
    layer_dense(units=1)

LSTM の入力は、三次元になっていて、
訓練データの数×1つの訓練データの入力の長さ×変数の数
となっています。

今の場合、単一の変数の時系列を扱っているので、変数の数は1を設定しています。
make_data_for_lstmでもそれに合わせて、データが生成されます。

モデルを記述したらコンパイルしましょう。

  model %>% compile(loss="mean_squared_error",
                    optimizer = optimizer_adam(),
                    metrics = "accuracy")

出力の数値自体を合わせるように学習してほしいので、
ロス関数は、mean_squared_errorを指定して、
最小二乗誤差となるように学習を進めます。

モデルのフィッティング

初めに生成した訓練用のデータでモデルの重みを学習していきます。

  model %>% fit(x,y,
                epochs=1000,batch_size = 10,validation_split = 0.2)

学習の進み方を見ているとかなり上手く学習できてそうですね。

f:id:u874072e:20180827155717p:plain

時系列が再現できるかを確認

学習が完了したので、ニューラルネットワークに時系列を出力させてみましょう。

test_LSTM = function(model,ts_df,i=1){
  test_x = ts_df[,i]
  test_x = make_data_for_lstm(as.matrix(test_x),window)$x
  scale=max(ts_df,na.rm = T)
  test_x = test_x/scale
  
  pred_x = model %>% predict(test_x)  
  ts.plot(ts_df[,i]/scale->a,ylim=c(min(c(a,pred_x)),max(c(a,pred_x))),ylab="y")
  lines(c(rep(NA,window),pred_x),col=2)
}
test_LSTM(model,ts_df,1)

下の図は、学習に使った時系列をそのまま入力として与えて、
直前までの入力で、次のデータを予測した結果と、
元のデータを比べたものです。

f:id:u874072e:20180827155921p:plain

赤の線が予測の結果ですが、
黒の線とよく一致していることが分かります。

過去のデータから未来のデータを予測

上では、予測とはいっても、
既にモデルの学習に使ったデータを予測しているので、
正確には、時系列の再現をしているだけになります。

本当に予測までできるのかを調べるために、
前半80個のデータだけを使ってモデルを学習して、
学習に使っていないデータを予測してみましょう。

  ts_df = as.matrix(UKgas)[1:80,]
  
  data = make_data_for_lstm(ts_df,window,rm.na=T)
  
  scale = max(ts_df,na.rm = T)
  x = data$x / scale
  y = data$y / scale
  
  model = keras_model_sequential()
  model %>%
    layer_lstm(units = 64,input_shape = c(dim(x)[2],1)) %>%
    layer_dropout(rate=0.4) %>%
    layer_dense(units=1)
  
  model %>% compile(loss="mean_squared_error",
                    optimizer = optimizer_adam(),
                    metrics = "accuracy")
  
  model %>% fit(x,y,
                epochs=1000,batch_size = 10,validation_split = 0.2)
  
  #######################
  ts_df = as.matrix(UKgas[-1:(-80+window)])
  test_LSTM(model,ts_df,1)

下の図は、1つ先の予測を行った結果を赤線で、
正解を黒線で示していますが、
かなり上手く行ってそうに見えます。

f:id:u874072e:20180827170755p:plain

プライバシーポリシー