LSTMを用いた株価予測の時系列解析モデルの構築

# はじめに

本記事では、LSTM（長短期記憶）[^1]モデルを用いた株価予測の時系列解析モデルの構築方法について解説します。具体的には、Pythonを用いたデータの取得から、前処理、モデルの構築、評価までの一連の流れを紹介します。

# 実行環境
本記事のコードはGoogle Colabで実行されています。

- プラットフォーム: Google Colab
- Pythonバージョン: Google Colabのデフォルト（執筆時点では3.10）

# 使用するライブラリ

本記事では、以下のPythonライブラリを使用します。

```python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import yfinance as yf
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_squared_error, r2_score
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, LSTM
```

# データの取得

まず、yfinanceライブラリを使って株価データを取得します。yfinanceは、Yahoo Financeから株価データを取得するためのPythonライブラリです。
ここでは、例としてApple社（AAPL）の株価データを使用します。

```python
# データの取得
ticker = 'AAPL'
data = yf.download(ticker, start='2020-01-01', end='2023-01-01')
```

このデータセットには様々な株価情報が含まれます。

```python
# カラムを確認してみる
data.columns

# Output: Index(['Open', 'High', 'Low', 'Close', 'Adj Close', 'Volume'], dtype='object')
```

各カラムの意味
- `Open`: その日の取引開始時の株価
- `High`: その日の取引での最高株価
- `Low`: その日の取引での最安株価
- `Close`: その日の取引終了時の株価
- `Adj Close`: 配当や分割を考慮した調整後の株価
- `Volume`: その日の取引量（株式数）

ここでは株価予測でよく用いられる終値 `Close` を使用します。

```python
data = data["Close"]
```


# データの前処理

次に、データの前処理を行います。LSTMモデルはスケーリングされたデータを必要とするため、データを正規化します。

```python
# データの前処理
scaler = MinMaxScaler(feature_range=(0, 1))
data_scaled = scaler.fit_transform(np.array(data).reshape(-1, 1))
```

正規化前後のデータを比較してみましょう。

```python
# 図で比較
plt.figure(figsize=(14, 7))

# 正規化前のデータをプロット
plt.subplot(2, 1, 1)
plt.plot(data.index, data, label='Original Close')
plt.title('Original Close Prices')
plt.xlabel('Date')
plt.ylabel('Price')
plt.legend()

# 正規化後のデータをプロット
plt.subplot(2, 1, 2)
plt.plot(data.index, data_scaled, label='Scaled Close', color='red')
plt.title('Scaled Close Prices')
plt.xlabel('Date')
plt.ylabel('Scaled Price')
plt.legend()

# レイアウト調整
plt.tight_layout()
plt.show()
```

![scaling.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381702/5d997dba-b40b-db9c-1138-e98aeec71838.png)

データの形状はそのままですが、縦軸を見るとスケールが0から1の範囲に正規化されていることがわかります。ここでは詳細については触れませんが、このように正規化することで安定した学習や、予測精度の向上につながります。

次に、訓練データとテストデータに分割し、LSTM用のデータセットを作成します。
`create_dataset` 関数では、時系列データを一定期間 (`time_step`) で区切り、その区切り毎に入力データ `X` とラベルデータ `Y` を作成しています。

`time_step` は短すぎるとモデルが十分な履歴を学習できず、長すぎるとモデルが過去のデータに引きずられ過ぎる可能性があります。ここでは、株式市場に月次や四半期のサイクルがあることから株価予測モデルで使用されることが多い60日（約2か月）を選択しています。

```python
# 訓練データとテストデータの分割
training_size = int(len(data_scaled) * 0.8)
train_data, test_data = data_scaled[:training_size], data_scaled[training_size:]

# データをLSTM用に変換
def create_dataset(dataset, time_step=1):
    X, Y = [], []
    for i in range(len(dataset) - time_step - 1):
        a = dataset[i:(i + time_step), 0]
        X.append(a)
        Y.append(dataset[i + time_step, 0])
    return np.array(X), np.array(Y)

time_step = 60
X_train, y_train = create_dataset(train_data, time_step)
X_test, y_test = create_dataset(test_data, time_step)

# LSTMの入力の形状を (samples, time_steps, features) に変換
X_train = X_train.reshape(X_train.shape[0], X_train.shape[1], 1)
X_test = X_test.reshape(X_test.shape[0], X_test.shape[1], 1)
```

# モデルの構築

LSTMモデルを構築し、訓練データでモデルを学習させます。

```python
# LSTMモデルの構築
model = Sequential()
model.add(LSTM(50, return_sequences=True, input_shape=(time_step, 1)))
model.add(LSTM(50, return_sequences=False))
model.add(Dense(25))
model.add(Dense(1))

# モデルのコンパイルと訓練
model.compile(optimizer='adam', loss='mean_squared_error')
model.fit(X_train, y_train, batch_size=1, epochs=1)
```

# 予測

モデルを使って訓練データとテストデータの予測を行い、結果を逆変換します（正規化を元に戻す）。

```python
# 予測
train_predict = model.predict(X_train)
test_predict = model.predict(X_test)

# 予測結果を逆変換
train_predict = scaler.inverse_transform(train_predict)
test_predict = scaler.inverse_transform(test_predict)
y_test = scaler.inverse_transform([y_test])

# モデルの評価
mse = mean_squared_error(y_test[0], test_predict[:, 0])
r2 = r2_score(y_test[0], test_predict[:, 0])

print('MSE: {:.2f}'.format(mse))
print('R^2: {:.2f}'.format(r2))

# Output
# MSE: 87.30
# R^2: -0.08
```
`MSE` は平均二乗誤差 (Mean Squared Error)、`R^2` は決定係数です。
`MSE` はその名の通り、実際の値と予測値の差の二乗の平均です。一方、`R^2` はモデルがどれだけデータの分散を説明できるかを示す指標です。
今回は `R^2` の値が負になってしまいました。これは常にデータの平均値を予測値とする（分散が説明できない）単純なモデルよりも精度が悪いことを示しています。

# プロット

予測結果をプロットし、視覚的に確認します。

```python
# プロット
plt.figure(figsize=(10, 6))
plt.plot(data.index, data, label='Historical')
train_predict_plot = np.empty_like(data_scaled)
train_predict_plot[:, :] = np.nan
train_predict_plot[time_step:len(train_predict) + time_step, :] = train_predict
plt.plot(data.index, train_predict_plot, label='Train Predict')

test_predict_plot = np.empty_like(data_scaled)
test_predict_plot[:, :] = np.nan
test_predict_plot[len(train_predict) + (time_step * 2) + 1:len(data_scaled) - 1, :] = test_predict
plt.plot(data.index, test_predict_plot, label='Test Predict')

plt.legend()
plt.show()
```
![result1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381702/96546f62-c8ef-ea55-c6ea-7b78c2c9d57c.png)

青色の実測値と緑色の予測値を比較すると、大まかな変動は捉えられているように見えます。
しかし、よく見ると緑色の予測値が青色の予測値の変動から遅れて変動しているようです。これでは「予測」できているとは言えそうにありません。

この原因としては、株価データでは上昇トレンドはゆっくりとした安定した上昇を示すことが多いのに対し、下降トレンドは急激な下落を伴うことが多い過去のデータから学習するLSTMモデルにおいて、過去のデータに引っ張られ過ぎていることが挙げられます。これは時系列の長さ (`time_step`) 等を調整することで改善が期待できます。例えば、`time_step` を短くすることで、モデルがより最新のデータに敏感になります。

次に、Test Predictの部分を切り取ってみましょう。

```python
# プロット
plt.figure(figsize=(14, 7))

# 元のデータと訓練データの予測をプロット
plt.plot(data_close, label='Original Close')
plt.plot(pd.Series(train_predict.flatten(), index=data_close.index[time_step:len(train_predict) + time_step]), label='Train Predict')

# テストデータの予測をプロット（ズームアップ）
plt.plot(pd.Series(test_predict.flatten(), index=data_close.index[len(train_predict) + (time_step * 2) + 1:len(data_close) - 1]), label='Test Predict', color='green')

# テストデータの予測部分をズームアップ
plt.xlim([data_close.index[len(train_predict) + (time_step * 2) + 1], data_close.index[-1]])

plt.legend()
plt.title('Zoomed in Test Predict')
plt.xlabel('Date')
plt.ylabel('Price')
plt.show()
```

![result2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381702/0e11583d-0ea0-e840-615a-46551e4907dc.png)


この図を見ると、`R^2`の低さに表れている通り、株価の細かな変動は追い切れていないことがわかります。
これについてはモデルを少し複雑にしてみることで改善が期待できます。例えば、LSTM層の数やユニット数を増やすことで、モデルの表現力を高めることができます。その他にも、入力特徴量の追加なども有効です。

# おわりに

本記事では、LSTMを用いた株価予測のための時系列解析モデルの構築方法を紹介しました。データの前処理からモデルの評価までを一通り行い、改善点は色々あるものの、LSTMモデルで一定の予測は行えることを示しました。

LSTMモデルは、時系列データのパターンを学習し、将来の値を予測するのに適したモデルです。最近では、今回用いたLSTMを発展させたBiLSTMを用いた予測 [^2] やネット上のニュースやコメントデータを取り入れた予測 [^3]、株価時系列を周波数に応じて分解してから行う予測 [^4] なども研究されています。
このような最近の研究の論文紹介も追々発信していく予定です。

[^1]:Hochreiter, S., & Schmidhuber, J. (1997). Long Short-Term Memory. Neural Computation, 9(8), 1735–1780. https://doi.org/10.1162/neco.1997.9.8.1735
[^2]:Lu, W., Li, J., Wang, J. et al. A CNN-BiLSTM-AM method for stock price prediction. Neural Comput & Applic 33, 4741–4753 (2021). https://doi.org/10.1007/s00521-020-05532-z
[^3]:X. Ji, J. Wang and Z. Yan, "A stock price prediction method based on deep learning technology," in International Journal of Crowd Science, vol. 5, no. 1, pp. 55-72, April 2021, doi: 10.1108/IJCS-05-2020-0012.
[^4]:Hadi Rezaei, Hamidreza Faaljou, Gholamreza Mansourfar, Stock price prediction using deep learning and frequency decomposition, Expert Systems with Applications, Volume 169, 2021, 114332, ISSN 0957-4174, https://doi.org/10.1016/j.eswa.2020.114332.