お前、最後にLSTMで時系列予測したのいつ？【Python】

# はじめに
AIの手法の一つとして、**LSTM**というのがある。

これは、端的に説明すると、**"時系列データの先を予測出来るやつ** 。
（株価とかの予測にも使うことが出来るようなやつ）

**てことで今回は、LSTMを使った簡単なサンプルプログラムを書いてみたので、紹介する。**

具体的には、sin波形のパターンを学習し、未来のデータ点を予測するようなプログラムを書いてみた。

# サンプルプログラム
コメントアウトで解説多めで書きました。

```python
# 必要なライブラリをインポートする
import numpy as np  # 数値計算を効率的に行うためのライブラリ
import matplotlib.pyplot as plt  # グラフ描画のためのライブラリ
from keras.models import Sequential  # KerasのSequentialモデルを使用するためのインポート
from keras.layers import LSTM, Dense  # LSTMとDense層を使用するためのインポート

# データ生成
# 0から100までの等間隔な数列を生成し、サイン関数でデータを生成する
data = np.sin(np.linspace(0, 100, 1000))  # np.linspaceは0から100まで1000分割した数列を生成し、np.sinでその数列のサイン値を計算する
sequence_length = 10  # 一つの入力シーケンスの長さを定義する

# データ準備関数
# 与えられたデータから、LSTMモデルの入力となるシーケンスを準備する
def prepare_sequences(data, sequence_length):
    x, y = [], []  # 入力シーケンスと出力値を格納するためのリストを初期化する
    # シーケンスの長さだけデータをスライスし、入力と出力を準備する
    for i in range(len(data) - sequence_length):
        x.append(data[i:i + sequence_length])  # i番目からsequence_length分のデータを取り出し、入力シーケンスとしてxに追加する
        y.append(data[i + sequence_length])  # i番目からsequence_lengthを足した位置のデータを取り出し、出力値としてyに追加する
    return np.array(x), np.array(y)  # xとyをNumPy配列に変換して返す

# 関数を使ってデータを準備する
x, y = prepare_sequences(data, sequence_length)  # 上で定義した関数を使って、入力シーケンスと出力値を準備する

# データ分割
# データの80%を訓練用、残りの20%をテスト用に分割する
split_fraction = 0.8  # 分割する割合を定義する
split = int(split_fraction * len(x))  # 実際に分割する位置を計算する

# 訓練データとテストデータに分割する
x_train, x_test = x[:split], x[split:]  # 入力シーケンスを訓練用とテスト用に分割する
y_train, y_test = y[:split], y[split:]  # 出力値を訓練用とテスト用に分割する

# モデル構築
# Sequentialモデルを使用し、LSTM層とDense層を追加してモデルを構築する
model = Sequential()  # Sequentialモデルのインスタンスを作成する
model.add(LSTM(50, activation='relu', input_shape=(sequence_length, 1)))  # LSTM層を追加する。50はユニット数、activationは活性化関数、input_shapeは入力の形状を指定する
model.add(Dense(1))  # 出力層としてDense層を追加する。1は出力の次元数を指定する
model.compile(optimizer='adam', loss='mse')  # モデルをコンパイルする。optimizerは最適化アルゴリズム、lossは損失関数を指定する

# モデル訓練
# 訓練データを使ってモデルを100エポックで訓練する
model.fit(x_train, y_train, epochs=100, validation_data=(x_test, y_test))  # モデルを訓練する。epochsは訓練の回数、validation_dataは検証用データを指定する

# 予測
# テストデータを使って予測を行う
predicted = model.predict(x_test)  # モデルを使ってテストデータに対する予測を行う

# サブプロットの作成
# 4つのサブプロットを作成して、データと予測結果を可視化する
fig, axs = plt.subplots(4, 1, figsize=(10, 18))  # 4つのサブプロットを持つ図を作成する

# 全てのサブプロットで横軸の範囲を揃える
x_min = 0  # 横軸の最小値を定義する
x_max = len(data)  # 横軸の最大値を定義する

# 元のデータをプロット
axs[0].plot(data, label='Original Data')  # 元のデータをプロットする
axs[0].set_title('Original Data')  # サブプロットのタイトルを設定する
axs[0].legend()  # 凡例を表示する
axs[0].set_xlim(x_min, x_max)  # 横軸の範囲を設定する

# 訓練データをプロット
axs[1].plot(np.arange(sequence_length, sequence_length + len(y_train)), y_train, label='Training Data')  # 訓練データをプロットする
axs[1].set_title('Training Data')  # サブプロットのタイトルを設定する
axs[1].legend()  # 凡例を表示する
axs[1].set_xlim(x_min, x_max)  # 横軸の範囲を設定する

# テストデータをプロット
axs[2].plot(np.arange(split + sequence_length, split + sequence_length + len(y_test)), y_test, label='Testing Data')  # テストデータをプロットする
axs[2].set_title('Testing Data')  # サブプロットのタイトルを設定する
axs[2].legend()  # 凡例を表示する
axs[2].set_xlim(x_min, x_max)  # 横軸の範囲を設定する

# 予測データをプロット
axs[3].plot(np.arange(split + sequence_length, split + sequence_length + len(predicted)), predicted, label='Predicted Data')  # 予測データをプロットする
axs[3].set_title('Predicted Data')  # サブプロットのタイトルを設定する
axs[3].legend()  # 凡例を表示する
axs[3].set_xlim(x_min, x_max)  # 横軸の範囲を設定する

# グラフの表示
plt.tight_layout()  # グラフのレイアウトを整える
plt.show()  # グラフを表示する


```


# プログラムの解説
細かい解説はプログラム内に書いてあると思うので、ここではざっくりとした流れを書く。

#### データの生成と準備
まず、等間隔の数列を生成し、サイン関数を適用してデータセットを作成する。次に、このデータからLSTMモデルが処理できる形式のシーケンスを準備する。このステップは、モデルが時系列のパターンを学習する基盤を作る。

#### データの分割
準備したデータを訓練用とテスト用に分割する。大半を訓練に使い、残りでモデルの性能を評価する。

#### モデルの構築と訓練
Sequentialモデルを用いて、LSTM層とDense層を持つネットワークを構築する。訓練データを使い、モデルがデータのパターンを学習するよう訓練する。

#### 予測と可視化
訓練されたモデルを使い、テストデータに基づいて未来のデータポイントを予測する。最後に、元のデータ、訓練データ、テストデータ、予測結果をグラフにプロットし、可視化する。


# 実行結果
↑のサンプルプログラムを実行すると、以下のような結果が得られた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3700895/8a95a005-236c-2ed8-576a-702eb972264b.png)



# まとめ
・LSTMを使って、sin波形のパターンを学習させて、sin波形の未来のデータ点を予測させた。
・実行結果としては、うまくいった。
・今回のLSTMのモデルでは、「層のユニット数」を50個で設定しているが、こういうパラメータの決め方のコツがつかめていないので、そこらへんについてはもうちょっと調査していきたい。


# Q&A

#### "シーケンスの長さだけデータをスライスし、入力と出力を準備する"ってどういう意味？
"シーケンスの長さだけデータをスライスし、入力と出力を準備する"というのは、与えられたデータセットから連続するデータの塊（シーケンス）を取り出し、それを機械学習モデルの入力として使用することを意味します。この場合、スライスとは、配列やリストから特定の範囲の要素を選択的に取り出す操作のことです。

例えば、あなたが持っているデータが [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] で、シーケンスの長さが 3 だとしましょう。このとき、最初のスライスは [0, 1, 2]、次のスライスは [1, 2, 3] となり、このようにデータセット全体を通してスライスを繰り返していきます。各スライスはモデルの入力（x）となり、その直後の単一のデータポイント（この例では [3]）は対応する出力（y）となります。

#### つまるところ、このプログラムは、"10サンプリングのシーケンス & その次の出力値"の組み合わせを大量に用意して、それをモデルに学習させまくってるということか？

その通りです。このプログラムは、10個の連続したデータポイント（サンプリングされたシーケンス）を取り出し、それぞれのシーケンスの直後に来るデータポイントを出力値として用意します。そして、これらの入力シーケンスと出力値の組み合わせ（( x, y )ペア）を大量に生成して、LSTMモデルを訓練しています。

具体的には、各入力シーケンス ( x ) は10個のデータポイントで構成され、対応する出力値 ( y ) はそのシーケンスの直後に来る単一のデータポイントです。モデルはこれらの ( x, y ) ペアからパターンを学習し、未来のデータポイントを予測する方法を学びます。

この学習プロセスを通じて、モデルは与えられたシーケンスに基づいて次の値を予測する能力を獲得し、最終的には新しいデータに対する予測を行うことができるようになります。この手法は時系列データやシーケンスデータの予測に非常に有効で、金融市場の分析、気象予測、言語処理など、多くの分野で応用されています。