旅行好きなソフトエンジニアの備忘録

プログラミングや技術関連のメモを始めました

【Python】 LSTMによる時系列データの予測

前回SimpleRNNによる時系列データの予測を行いましたが、今回はLSTMを用いて時系列データの予測を行ってみます。

ni4muraano.hatenablog.com

LSTMはSimpleRNNと比較すると長期依存性の高いデータに有効とのことなので、50回に一回パルスが発生する信号に対する予測をSimpleRNNとLSTMで行ってみました。

import numpy as np
import matplotlib.pyplot as plt
from keras.models import Sequential
from keras.layers.core import Dense, Activation
from keras.layers.recurrent import SimpleRNN, LSTM
from keras.optimizers import Adam
from keras.callbacks import EarlyStopping

np.random.seed(0)

def generate_periodic_data():
    pulse = []
    for i in range(1000):
        if i%25 == 0:
            pulse.append(1.0)
        else:
            pulse.append(0.0)

    return np.array(pulse)


def generate_data(pulse, length_per_unit, dimension):
    sequences = []
    target = []
    for i in range(0, pulse.size - length_per_unit):
        sequences.append(pulse[i:i + length_per_unit])
        target.append(pulse[i + length_per_unit])

    X = np.array(sequences).reshape(len(sequences), length_per_unit, dimension)
    Y = np.array(target).reshape(len(sequences), dimension)

    N_train = int(len(sequences)*0.8)
    X_train = X[:N_train]
    X_validation = X[N_train:]
    Y_train = Y[:N_train]
    Y_validation = Y[N_train:]

    return (X_train, X_validation, Y_train, Y_validation)


def build_model(input_shape, hidden_layer_count):
    model = Sequential()
    model.add(LSTM(hidden_layer_count, input_shape=input_shape))
    model.add(Dense(input_shape[1]))
    model.add(Activation('linear'))
    model.compile(loss='mse', optimizer=Adam())
    return model


# 一つの時系列データの長さ
LENGTH_PER_UNIT = 201
# 一次元データを扱う
DIMENSION = 1
# パルスの生成
pulse = generate_periodic_data()
# トレーニング、バリデーション用データの生成
X_train, X_validation, Y_train, Y_validation = generate_data(pulse, LENGTH_PER_UNIT, DIMENSION)

# LSTM隠れ層の数
HIDDEN_LAYER_COUNT = 25
# 入力の形状
input_shape=(LENGTH_PER_UNIT, DIMENSION)
# モデルの生成
model = build_model(input_shape, HIDDEN_LAYER_COUNT)

# モデルのトレーニング
epochs = 500
batch_size = 10
early_stopping = EarlyStopping(monitor='val_loss', patience=10, verbose=1)
model.fit(X_train, Y_train,
          batch_size=batch_size,
          epochs=epochs,
          validation_data=(X_validation, Y_validation),
          callbacks=[early_stopping])

# 予測を行う
part_of_sequence = np.array([pulse[i] for i in range(LENGTH_PER_UNIT)])
predicted = [None for i in range(LENGTH_PER_UNIT)]
Z = X_train[:1, :, :]
for i in range(pulse.size - LENGTH_PER_UNIT + 1):
    y_ = model.predict(Z)
    # 予測結果を入力として利用するため、第0項を削除し予測結果をひっつける
    Z = np.concatenate(
            (Z.reshape(LENGTH_PER_UNIT, DIMENSION)[1:], y_),
            axis=0).reshape(1, LENGTH_PER_UNIT, DIMENSION)
    predicted.append(y_.reshape(-1))
predicted = np.array(predicted)

# 予測結果の描画
plt.rc('font', family='serif')
plt.figure()
plt.ylim([0.0, 1.1])
plt.plot(pulse, linestyle='dotted', color='#aaaaaa')
plt.plot(part_of_sequence, linestyle='dashed', color='black')
plt.plot(predicted, color='black')
plt.show()

SimpleRNNの予測結果がこちらでf:id:ni4muraano:20170617162126p:plain

LSTMの予測結果がこうなりました。f:id:ni4muraano:20170617162147p:plain

LSTMはもう少し綺麗に予測できることを期待していたのですが、パラメータチューニングを行っていないので上図のような結果になったのかなと思います。とはいえ閾値決めてあげることで後処理でどうにでもなりそうなのでSimpleRNNよりは良い結果なのではと思います。

詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~

詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~