🐈

Act 34. PythonでLSTMを試してみる

2025/01/02に公開

はじめに

今回は、PythonでLSTMモデルを使って為替データの予測を行ってみようと思う。

1つどうしても分からない箇所があるため、有識者の方がいらっしゃったらコチラの内容についてご回答いただきたい…。お願いします。

2024年6月のデータを1分間隔で取得しているため、そのデータを使って予測を行う。
なお、使用するのはclose(終値)のみ。

結論

先に結論から

青の線が実際の為替相場データで、オレンジの線が予測したデータ。
全然だめです!!!!(笑)

パッと思いついた改善点は以下。

  • 土日のデータが存在しないため、そこら辺を上手くやる必要があるかも
  • 実際に取引するのは14時~23時くらいを想定しているため、それ以外のデータは不要かも
  • 説明変数がcloseのみなので、それ以外のデータも追加した方が良いかも
  • 5分や1時間足でも予測した方が良いかも
  • 次の終値の値を予測するのではなく、95%信頼区間や上がるor下がるなどを予測した方が良い?

有識者に教えてほしい点

モデルの学習中にEpoch 3/100の 78 ステップ目で処理が止まってしまい、それ以降何分待っても処理が再開されなかった。なぜかが良く分からない…。
※PCを再起動して何回も試してみたが、どこかで処理が止まってしまう。(毎回停止するタイミングはバラバラ)

以下は失敗するパターン。
LSTMとDropoutを4つずつ実装しているが、この状態だと画像の通りエラーが発生する。

# モデルの定義
model = Sequential()
model.add(Input(shape=(X_train.shape[1], X_train.shape[2])))
model.add(LSTM(units=30, return_sequences=True))
model.add(Dropout(0.2))
model.add(LSTM(units=30, return_sequences=True))
model.add(Dropout(0.2))
model.add(LSTM(units=30, return_sequences=True))
model.add(Dropout(0.2))
model.add(LSTM(units=30))
model.add(Dropout(0.2))
model.add(Dense(units=1))

以下は成功するパターン。
LSTMとDropoutを3つずつ実装するとエラーが発生せずに学習が終了した。

# モデルの定義
model = Sequential()
model.add(Input(shape=(X_train.shape[1], X_train.shape[2])))
model.add(LSTM(units=30, return_sequences=True))
model.add(Dropout(0.2))
model.add(LSTM(units=30, return_sequences=True))
model.add(Dropout(0.2))
model.add(LSTM(units=30))
model.add(Dropout(0.2))
model.add(Dense(units=1))

最初はGPU使用率かな?と思ったが、どちらの場合も100%付近をうろうろしているので恐らく違う…。

他にはGPU使用率を下げるために、Callbackクラス的なものを使って、各ステップの最後に0.05秒間待機する処理を追加した。
その結果、GPU使用率は30%程度になったが、結局処理が止まってしまう。

全く分からないのでどなたか教えてください…。

実装

jupyter notebookでコードを書いていたため、セルごとに内容を記載していこうと思う。
自分が悩まなかったところの説明は割愛する。

まずはライブラリのインポートから。

Act33.ipynb
# ライブラリのインポート
import numpy as np
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import r2_score
from keras.models import Sequential
from keras.layers import LSTM, Dropout, Input, Dense
from keras.optimizers import Adam

import matplotlib.pyplot as plt
import japanize_matplotlib

次にデータの読み込み。

Act33.ipynb
# データの読み込み
df = pd.read_csv("USD_JPY-1min-202406.csv", index_col="openTime", parse_dates=True)
df.index.name = "datetime"
print(df.head(3))

データの出力は以下の通り。

                        open     high      low    close
datetime                                               
2024-06-03 07:00:00  157.296  157.317  157.269  157.288
2024-06-03 07:01:00  157.288  157.288  157.259  157.275
2024-06-03 07:02:00  157.275  157.317  157.274  157.317

なお、読み込んでいるCSVファイルは以下のような形式になっている。
2024-06-03 から 2024-06-29 までのデータが1分間隔で存在する。

openTime,open,high,low,close
2024-06-03 07:00:00,157.296,157.317,157.269,157.288
2024-06-03 07:01:00,157.288,157.288,157.259,157.275
2024-06-03 07:02:00,157.275,157.317,157.274,157.317

正規化を行う。
今回使用するのはclose(終値)のみなので、該当する列を取得してから正規化を行う。
なお、正規化の範囲は 0 ~ 1としている。

Act33.ipynb
# 正規化
data = np.reshape(df["close"], (len(df["close"]), 1))
scaler = MinMaxScaler(feature_range=(0, 1))
scaled_data = scaler.fit_transform(data)

訓練データの準備を行う。
今回は80%を訓練データとしたいので、データの長さに0.8を掛けている。

Act33.ipynb
# 訓練データ
training_data_len = int(scaled_data.shape[0] * 0.8)
train_data = scaled_data[:training_data_len]

予測期間を60として、訓練データを作っていく。
現在時刻から60分前までのデータを使って1分後のデータを予測する。

Act33.ipynb
# 予測期間
window_size = 60

# 訓練データを説明変数と目的変数に分ける
X_train, y_train = [], []
for i in range(window_size, len(train_data)):
  X_train.append(train_data[i-window_size:i])
  y_train.append(train_data[i])

データをリスト型からnumpy.array型に変換する。

Act33.ipynb
# numpy arrayに変換
X_train =  np.array(X_train)
y_train = np.array(y_train)
print(X_train.shape)
print(y_train.shape)

出力は以下の通り。

# (サンプル数, 予測期間, 特徴量)
(22788, 60, 1)
(22788, 1)

全体で8層のモデルを定義する。

Act33.ipynb
# モデルの定義
model = Sequential()
model.add(Input(shape=(X_train.shape[1], X_train.shape[2])))
model.add(LSTM(units=30, return_sequences=True))
model.add(Dropout(0.2))
model.add(LSTM(units=30, return_sequences=True))
model.add(Dropout(0.2))
model.add(LSTM(units=30))
model.add(Dropout(0.2))
model.add(Dense(units=1))
  1. Inputレイヤー:

    • 入力データの形状を指定するレイヤー(shape=(X_train.shape[1], X_train.shape[2]))。
    • 入力データは3次元(サンプル数、時間ステップ数、特徴量数)を想定。
  2. LSTMレイヤー (1層目):

    • ユニット数: 30
    • return_sequences=True: 次のLSTMレイヤーに全ての時間ステップの出力を渡す。
  3. Dropoutレイヤー (1層目):

    • 20%のニューロンをランダムに無効化することで過学習を防ぐ。
  4. LSTMレイヤー (2層目):

    • ユニット数: 30
    • return_sequences=True: 再び次のLSTMレイヤーに全ての時間ステップの出力を渡す。
  5. Dropoutレイヤー (2層目):

    • 同じく20%のニューロンを無効化。
  6. LSTMレイヤー (3層目):

    • ユニット数: 30
    • return_sequences=False(デフォルト): 最後の時間ステップのみを出力。
  7. Dropoutレイヤー (3層目):

    • 同様に20%のニューロンを無効化。
  8. Denseレイヤー:

    • ユニット数: 1(出力層)。
    • LSTMの出力を1つのスカラー値(例: 次の値を予測)に変換。

人間側で設定するパラメータを定義する。
本当はこれだけじゃないけど、何となく定義してみた。

Act33.ipynb
# ハイパーパラメータ
learning_rate = 0.01          # 学習率を小さく設定
loss = "mean_squared_error"   # 損失関数
batch_size = 32               # バッチサイズ
epochs = 30                   # 学習回数

最適化アルゴリズムの設定。
今回はAdamという最適化アルゴリズムを使ってみる。
learning_rateは学習率、clipvalueは勾配の値を1.0でクリップ(制限)する設定。

Act33.ipynb
# モデルのコンパイル
optimizer = Adam(learning_rate=learning_rate, clipvalue=1.0)
model.compile(optimizer=optimizer, loss=loss)  # optimizerをモデルに適用

訓練データを使ってモデルの学習を行う。

Act33.ipynb
# モデルの学習
model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1)

以下のような感じで学習が完了する。

Epoch 1/30
713/713 ━━━━━━━━━━━━━━━━━━━━ 10s 13ms/step - loss: 0.0057
Epoch 2/30
713/713 ━━━━━━━━━━━━━━━━━━━━ 9s 13ms/step - loss: 6.0059e-04
Epoch 3/30
713/713 ━━━━━━━━━━━━━━━━━━━━ 9s 12ms/step - loss: 5.9561e-04
Epoch 4/30
713/713 ━━━━━━━━━━━━━━━━━━━━ 10s 13ms/step - loss: 5.9227e-04
Epoch 5/30
713/713 ━━━━━━━━━━━━━━━━━━━━ 9s 13ms/step - loss: 5.8055e-04
Epoch 6/30
713/713 ━━━━━━━━━━━━━━━━━━━━ 9s 13ms/step - loss: 6.0025e-04
Epoch 7/30
713/713 ━━━━━━━━━━━━━━━━━━━━ 9s 13ms/step - loss: 5.3815e-04
Epoch 8/30
713/713 ━━━━━━━━━━━━━━━━━━━━ 9s 13ms/step - loss: 5.2771e-04
Epoch 9/30
713/713 ━━━━━━━━━━━━━━━━━━━━ 9s 12ms/step - loss: 4.5087e-04
Epoch 10/30
713/713 ━━━━━━━━━━━━━━━━━━━━ 9s 13ms/step - loss: 4.4322e-04
Epoch 11/30
713/713 ━━━━━━━━━━━━━━━━━━━━ 9s 13ms/step - loss: 4.5963e-04
Epoch 12/30
713/713 ━━━━━━━━━━━━━━━━━━━━ 9s 13ms/step - loss: 4.8614e-04
Epoch 13/30
...
Epoch 29/30
713/713 ━━━━━━━━━━━━━━━━━━━━ 10s 14ms/step - loss: 3.9030e-04
Epoch 30/30
713/713 ━━━━━━━━━━━━━━━━━━━━ 10s 14ms/step - loss: 3.9318e-04
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...
<keras.src.callbacks.history.History at 0x7f51f4e996a0>

毎回学習を行うのも面倒なので、とりあえずモデルを保存しておく。

Act33.ipynb
model.save("20250102_model.keras")

訓練データ同様に、テストデータも作成する。

Act33.ipynb
# テストデータの作成
test_data = scaled_data[training_data_len:]

X_test = []
y_test = df["close"].iloc[training_data_len + window_size:]
for i in range(window_size, len(test_data)):
  X_test.append(test_data[i-window_size:i])

X_test = np.array(X_test)
y_test = np.array(y_test)
print(X_test.shape)
print(y_test.shape)

テストデータを使って予測を行う。

Act33.ipynb
# 予測
y_pred = model.predict(X_test)
y_pred = scaler.inverse_transform(y_pred)

様々な指標を確認してみる。

Act33.ipynb
# 二乗平均平方根誤差(RMSE): 0に近いほど良い
rmse = np.sqrt(np.mean(((y_pred - y_test) ** 2)))
print(f"RMSE: {rmse}")

# 決定係数(r2) : 1に近いほど良い
r2s = r2_score(y_true=y_test, y_pred=y_pred)
print(f"R2 Score: {r2s}")

出力は以下の通り。

RMSE: 0.7073978138642296
R2 Score: 0.05647584308762832

予測結果をプロットして確認するため、色々と処理を行う。

Act33.ipynb
# 予測結果のデータフレームを作成
df_pred = pd.DataFrame(y_pred, columns=["Pred"])
df_pred.index = df.index[training_data_len + window_size:]

fig, ax = plt.subplots(figsize=(16, 8))
ax.plot(df["close"], label="実際の値")
ax.plot(df_pred, label="予測した値")
ax.legend()

冒頭に載せたが、予測結果はこんな感じ。
全然ダメダメだね…。

土日の部分が平坦になってるし。

もっと分かりやすくするため、100件だけデータを表示してみた。

Act33.ipynb
fig, ax = plt.subplots(figsize=(16, 8))
ax.plot(df["close"].iloc[training_data_len + window_size:training_data_len + window_size + 100], label="実際の値")
ax.plot(df_pred[:100], label="予測した値")
ax.grid(True)
ax.legend()

結果は以下の通り。
全く予測出来ていない!1日だけのデータとか使ったら何となく形になるかも。

さいごに

難しいけどめちゃくちゃ楽しい!
こうしたらよいのでは?とか色々と案が出てきているので、順に試していきたい。

それと、モデルを実装するところをもう少し理解しておいた方が良いなと思ったので、次回はそこら辺も勉強することに決定。

有識者の方は分からなかったところ教えてください…(泣)

ではまた

Discussion