LSTM/Transformerで株価予測モデルを作る【PyTorch実装】

Python実装・コード

深層学習を使った株価予測の2大アーキテクチャ「LSTM」と「Transformer」をPyTorchで実装します。時系列データの特性を活かした本格的なモデル構築を解説します。

インストール

pip install torch torchvision yfinance scikit-learn numpy pandas matplotlib

データ準備と前処理

import torch
import torch.nn as nn
import numpy as np
import pandas as pd
import yfinance as yf
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_squared_error, mean_absolute_error
import matplotlib.pyplot as plt

# データ取得
df = yf.download("7203.T", period="10y", progress=False)
close_prices = df["Close"].values.reshape(-1, 1)

# 正規化(0〜1の範囲にスケーリング)
scaler = MinMaxScaler(feature_range=(0, 1))
scaled = scaler.fit_transform(close_prices)

# シーケンスデータ作成
def create_sequences(data, seq_length=60):
    """過去seq_length日のデータから翌日を予測する系列を作成"""
    X, y = [], []
    for i in range(seq_length, len(data)):
        X.append(data[i-seq_length:i, 0])
        y.append(data[i, 0])
    return np.array(X), np.array(y)

SEQ_LENGTH = 60  # 過去60日を使って予測
X, y = create_sequences(scaled, SEQ_LENGTH)

# 学習・テスト分割(80:20)
split = int(len(X) * 0.8)
X_train, X_test = X[:split], X[split:]
y_train, y_test = y[:split], y[split:]

# PyTorchテンソルに変換
X_train = torch.FloatTensor(X_train).unsqueeze(-1)  # (N, 60, 1)
X_test = torch.FloatTensor(X_test).unsqueeze(-1)
y_train = torch.FloatTensor(y_train).unsqueeze(-1)
y_test = torch.FloatTensor(y_test).unsqueeze(-1)

print(f"X_train shape: {X_train.shape}")
print(f"X_test shape: {X_test.shape}")

LSTMモデルの実装

class LSTMModel(nn.Module):
    def __init__(self, input_size=1, hidden_size=64, num_layers=2,
                 output_size=1, dropout=0.2):
        super(LSTMModel, self).__init__()
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        
        self.lstm = nn.LSTM(
            input_size=input_size,
            hidden_size=hidden_size,
            num_layers=num_layers,
            batch_first=True,
            dropout=dropout
        )
        self.fc = nn.Linear(hidden_size, output_size)
    
    def forward(self, x):
        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size)
        c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size)
        out, _ = self.lstm(x, (h0, c0))
        out = self.fc(out[:, -1, :])  # 最後のタイムステップのみ使用
        return out

def train_model(model, X_train, y_train, epochs=100, lr=0.001):
    criterion = nn.MSELoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=lr)
    
    losses = []
    for epoch in range(epochs):
        model.train()
        optimizer.zero_grad()
        outputs = model(X_train)
        loss = criterion(outputs, y_train)
        loss.backward()
        optimizer.step()
        losses.append(loss.item())
        
        if (epoch + 1) % 20 == 0:
            print(f"Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.6f}")
    
    return losses

lstm_model = LSTMModel(hidden_size=64, num_layers=2, dropout=0.2)
lstm_losses = train_model(lstm_model, X_train, y_train, epochs=100)

Transformerモデルの実装

class TransformerModel(nn.Module):
    def __init__(self, input_size=1, d_model=64, nhead=4,
                 num_encoder_layers=2, dropout=0.1):
        super(TransformerModel, self).__init__()
        self.input_proj = nn.Linear(input_size, d_model)
        encoder_layer = nn.TransformerEncoderLayer(
            d_model=d_model, nhead=nhead,
            dropout=dropout, batch_first=True
        )
        self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=num_encoder_layers)
        self.fc = nn.Linear(d_model, 1)
    
    def forward(self, x):
        x = self.input_proj(x)       # (N, seq, d_model)
        x = self.transformer(x)      # (N, seq, d_model)
        x = self.fc(x[:, -1, :])     # 最後のトークンを使用
        return x

transformer_model = TransformerModel(d_model=64, nhead=4, num_encoder_layers=2)
transformer_losses = train_model(transformer_model, X_train, y_train, epochs=100, lr=0.0005)

予測結果の評価と可視化

def evaluate_model(model, X_test, y_test, scaler, model_name):
    """モデルの予測精度を評価"""
    model.eval()
    with torch.no_grad():
        y_pred = model(X_test)
    
    # 逆正規化
    y_pred_inv = scaler.inverse_transform(y_pred.numpy())
    y_test_inv = scaler.inverse_transform(y_test.numpy())
    
    rmse = np.sqrt(mean_squared_error(y_test_inv, y_pred_inv))
    mae = mean_absolute_error(y_test_inv, y_pred_inv)
    
    print(f"\n=== {model_name} ===")
    print(f"RMSE: {rmse:.2f}円")
    print(f"MAE: {mae:.2f}円")
    
    # 方向的中率
    actual_dir = np.diff(y_test_inv.flatten()) > 0
    pred_dir = np.diff(y_pred_inv.flatten()) > 0
    direction_acc = (actual_dir == pred_dir).mean()
    print(f"方向的中率: {direction_acc:.1%}")
    
    return y_pred_inv, y_test_inv

lstm_pred, actual = evaluate_model(lstm_model, X_test, y_test, scaler, "LSTM")
transformer_pred, _ = evaluate_model(transformer_model, X_test, y_test, scaler, "Transformer")

# 可視化
plt.figure(figsize=(14, 6))
plt.plot(actual, label="実際の株価", color="blue")
plt.plot(lstm_pred, label="LSTM予測", color="red", linestyle="--")
plt.plot(transformer_pred, label="Transformer予測", color="green", linestyle=":")
plt.title("株価予測比較(LSTM vs Transformer)")
plt.xlabel("時間")
plt.ylabel("株価(円)")
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

注意点:深層学習と株価予測の現実

深層学習モデルは過去のパターンを学習しますが、以下の点に注意が必要です:

  • RMSEやMAEが低くても、方向的中率が50%前後になることが多い
  • 価格予測より方向予測(上がる/下がる)に活用するのが現実的
  • 過学習を防ぐためにDropout・早期終了を活用すること
  • LightGBMの方が株価予測タスクでは精度が高い場合も多い

まとめ

LSTMとTransformerの両アーキテクチャをPyTorchで実装しました。Transformerは並列処理が得意で長期依存関係の捉えに優れます。LSTMはシンプルで扱いやすく、入門には最適です。実際の運用では必ずバックテストで有効性を検証してください。

タイトルとURLをコピーしました