強まっていこう

あっちゃこっちゃへ強まっていくためのブログです。

LSTM が良くわからない!グダグダ言わずに使い方教えろ!!と言う短気な方、どうぞ - その1

LSTM についていろいろ調べたもののうだうだ理屈ばっかこねくり回されてよく解らなくなってしまった方々、ようこそ。

パパッとコイツの使い方、教えます。

まずはコイツって何か?と言うと、あるデータの先を予想する奴です。株価予想したりします。文章相手に使うと自然言語処理が出来たり。

どうやるか?

[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

的なデータがあるとします。こやつを

[ 0, 1, 2 ] [ 3 ]
[ 1, 2, 3 ] [ 4 ]
[ 2, 3, 4 ] [ 5 ]
[ 3, 4, 5 ] [ 6 ]

ってな感じで分けて行くんですな。
前の要素 3 な配列が学習用データで、後ろの要素 1 のやつが正解データです。
1つずつ値をずらしていって、正解データをその先のデータにすりゃ良いだけです。

前の配列の要素数 3 ってのは別になんでも良くて、モノによって調整する値です。

このデータを食わせて学習するわけです。

早速実践しましょうか。

まずはこの画像を見てください。

f:id:wolfbash:20181207232321j:plain

この画像は、2波長分のノイズ入り sin の値を学習させている途中結果を表示したものです。

青の破線。これは、sin の値にランダムなノイズを加えたものになります。
オレンジは、その段階で学習した結果の値をプロットしたものです。

徐々にオレンジが青の破線に近づいていっていることが一目瞭然かと思います。これは学習がうまく行って素敵なモデルが仕上がっている証拠です。

「'loss': ふにゃふにゃ」的な値が出ていますが、これは、間違い率です。学習がうまく行っていれば減って行くもんだと思ってください。

さて、お次はこの画像。

f:id:wolfbash:20181207232324j:plain

これは、学習した結果出来上がったモデルに対して、8波長分に伸ばしたランダムノイズデータを与えてみたものです。

2波長しか与えずに勉強していたのに、それより長い波長でもバッチリマッチしていますよね。
これは、与えられたデータよりも先のデータが予測出来てるってことになります。

さ、なんとなくわかったらお次はコード。

import numpy as np
import keras
import matplotlib.pyplot as plt
from keras.models import Sequential
from keras.layers import Dense
from keras.layers.recurrent import LSTM

class DumperCallback(keras.callbacks.Callback):

  def __init__(self, conf, X_train, y_train, X_train_LSTM, y_train_LSTM, **args):
    self.conf = conf
    self.X_train = X_train
    self.y_train = y_train
    self.X_train_LSTM = X_train_LSTM
    self.y_train_LSTM = y_train_LSTM    
    super().__init__(**args)

  def on_epoch_end(self, epoch, logs):
    print(logs)
    if epoch % self.conf['plot'] == 0:
      predicted = self.model.predict(self.X_train_LSTM)
      plt.plot(self.X_train, self.y_train, linestyle='dashed')
      plt.plot(self.X_train[self.conf['LSTM_len']:], predicted.reshape(-1))
      plt.show()

def gen_data(start = 0, wave_len = 2):
  X_train = np.arange(start, start + np.pi * wave_len * 2, 0.1)  
  y_train = np.sin(X_train) + np.random.uniform(-0.1, 0.1, len(X_train))
  return X_train, y_train

def conv_data_for_LSTM(conf, X_train, y_train):
  X_train_LSTM, y_train_LSTM = [], []
  LSTM_len = conf['LSTM_len']
  for i in range(len(y_train) - LSTM_len):
    X_train_LSTM.append(y_train[i: i + LSTM_len])
    y_train_LSTM.append(y_train[i + LSTM_len])
  X_train_LSTM = np.array(X_train_LSTM).reshape(len(X_train_LSTM), LSTM_len, 1)
  y_train_LSTM = np.array(y_train_LSTM).reshape(len(y_train_LSTM), 1)
  return X_train_LSTM, y_train_LSTM

def gen_model(conf):
  model = Sequential()
  model.add(LSTM(conf['n_hid'], batch_input_shape=(None, conf['LSTM_len'], conf['n_out']), return_sequences=False))
  model.add(Dense(conf['n_out'], activation='linear'))
  model.compile(loss="mse", optimizer='adam')
  return model

def learn(conf, model, X_train_LSTM, y_train_LSTM, callbacks):
  model.fit(X_train_LSTM, y_train_LSTM, epochs=conf['epoch'], validation_split=conf['validation_split'], verbose=0, callbacks=callbacks)

def predict(conf, model, X_train, y_train, X_train_LSTM, y_train_LSTM):  
  predicted = model.predict(X_train_LSTM)
  plt.plot(X_train, y_train, linestyle='dashed')
  plt.plot(X_train[conf['LSTM_len']:], predicted.reshape(-1))
  plt.show()

if __name__ == '__main__':
  conf = {
    'epoch': 20,
    'plot': 4,
    'n_hid': 300,
    'n_out': 1,
    'validation_split': 0.1,
    'LSTM_len': 10,
  }

  X_train, y_train = gen_data()
  X_train_LSTM, y_train_LSTM = conv_data_for_LSTM(conf, X_train, y_train)
  model = gen_model(conf)
  callbacks = [ DumperCallback(conf, X_train, y_train, X_train_LSTM, y_train_LSTM) ]
  learn(conf, model, X_train_LSTM, y_train_LSTM, callbacks)
  
  print('[ PREDICT ]')
  X_train2, y_train2 = gen_data(np.pi * 4 + 0.1, 8)
  X_train_LSTM2, y_train_LSTM2 = conv_data_for_LSTM(conf, X_train2, y_train2)
  predict(conf, model, X_train2, y_train2, X_train_LSTM2, y_train_LSTM2)

keras、matplotlib などの伝家の宝刀は各自ググってください。たらふくドキュメントあるので。Jupyter を使うとかなり楽です。豪華 NVIDIAGPU が載っているのは Windows マシンでしょ?どうせ。何も考えずに Anaconda 入れて、keras-gpu 突っ込んでください。

まず main の中からご説明。

gen_data で学習データを作成。X_train は横軸データ。y_train は縦軸データで、コイツはランダムなノイズ入り sin データ。プロットするとギザギザの青破線になるデータ。

conv_data_for_LSTM で、X_train と y_train を LSTM 用のデータにコンバート。最初の方に説明した、配列をちょいとずつ進めたデータを作る。

gen_model でモデルを作成。

callbacks で仕込んでいる DumperCallback は、途中経過をプロットするためのやつ。

learn で学習。

X_train2, y_train2 = gen_data(np.pi * 4 + 0.1, 8)

は、学習が終わったモデルに突っ込む未来予想用データを作成。横軸のスタート位置を2波長分先送りし、さらに波長を8にすることで、モデルが学習時には全く知らないデータを作成。ランダムなノイズも入っているのでなおさらカオス。

conv_data_for_LSTM で予想データをコンバートし、predicted で予測データを作成しプロット。

お次は conf。

conf = {
  'epoch': 20, # 学習回数
  'plot': 4, # 学習状況をプロットするタイミング(4 なら学習4回で1回出力)
  'n_hid': 300, # 隠れ層(なんとなく増やしたり減らしたりするやつ)
  'n_out': 1, # 出力の配列要素数
  'validation_split': 0.1, # 学習中に入力データからテストデータに回すデータの割合(0.1 なら 10%)
  'LSTM_len': 10, # LSTM 用データの長さ。この記事の最初に書いた例なら 3
}

この conf の値は、n_out 以外は適当に弄って良い感じのとこで止めたり止めなかったりするいい加減な数字。グラフを見ながら弄り倒すやつ。

ってな感じで、パパパパッと説明してみましたが、どうでしょう?

大まかな流れはなんとなく掴むことが出来ましたかね。お次は、各関数を詳しく説明したいんですが、疲れたんでまた後日!!