4. 基于Transformer的股票价格预测

基于Transformer的股票价格预测 学习卡片

任务说明

本案例使用某只股票的历史日线数据(开盘价、最高价、最低价、收盘价、成交量),构建一个Transformer模型,以过去60个交易日的序列为输入,预测下一个交易日的收盘价。最终通过平均误差(MAE)和均方根误差(RMSE)评估预测精度。

数据准备与预处理

使用yfinance获取股票数据(以AAPL为例),若网络不可用则使用内置的随机生成数据作为替代,保证案例可独立运行。对全部特征进行MinMax归一化,按时间顺序划分训练集(80%)与测试集(20%),并构造滑动窗口样本。

Transformer模型定义

模型采用编码器结构:输入经过线性嵌入后加入位置编码,通过两层TransformerEncoderLayer提取时序依赖,最后经全连接层输出预测值。模型结构如下:

输入序列 (60, 5) 线性嵌入 d_model=64 位置编码 TransformerEncoder 2层, 4头注意力 d_model=64, dim_feedforward=128 全连接层 → 1 预测收盘价

完整代码实现


import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_absolute_error, mean_squared_error

# ---------- 1. 数据获取(网络不可用时自动使用模拟数据) ----------
def load_data(ticker="AAPL", seq_len=60):
    try:
        import yfinance as yf
        df = yf.download(ticker, start="2020-01-01", end="2024-01-01", progress=False)
        if len(df) < 200:
            raise ValueError("数据量不足")
        data = df[["Open", "High", "Low", "Close", "Volume"]].values
    except Exception:
        # 模拟数据:生成2000条趋势+噪声序列
        np.random.seed(42)
        n = 2000
        t = np.linspace(0, 20*np.pi, n)
        base = 100 + 10*np.sin(t) + t*0.5
        noise = np.random.normal(0, 2, (n, 5))
        data = np.column_stack([base, base*1.02, base*0.98, base, 
                                np.random.randint(1e6, 5e6, n)]) + noise
    return data.astype(np.float32)

# ---------- 2. 数据预处理 ----------
def create_sequences(data, seq_len=60):
    X, y = [], []
    for i in range(len(data) - seq_len):
        X.append(data[i:i+seq_len])
        y.append(data[i+seq_len, 3])  # 第3列为收盘价
    return np.array(X), np.array(y)

data = load_data()
scaler = MinMaxScaler()
data_scaled = scaler.fit_transform(data)

X, y = create_sequences(data_scaled)
# 划分训练/测试集(按时间顺序)
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.tensor(X_train, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.float32).unsqueeze(1)
X_test = torch.tensor(X_test, dtype=torch.float32)
y_test = torch.tensor(y_test, dtype=torch.float32).unsqueeze(1)

train_loader = DataLoader(TensorDataset(X_train, y_train), batch_size=64, shuffle=True)

# ---------- 3. Transformer模型 ----------
class StockTransformer(nn.Module):
    def __init__(self, input_dim=5, d_model=64, nhead=4, num_layers=2, dropout=0.1):
        super().__init__()
        self.embed = nn.Linear(input_dim, d_model)
        self.pos_enc = nn.Parameter(torch.randn(1, 60, d_model) * 0.1)
        encoder_layer = nn.TransformerEncoderLayer(
            d_model=d_model, nhead=nhead, dim_feedforward=128, 
            dropout=dropout, batch_first=True
        )
        self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
        self.fc_out = nn.Linear(d_model, 1)
        
    def forward(self, x):
        # x: (batch, seq_len, input_dim)
        x = self.embed(x) + self.pos_enc[:, :x.size(1), :]
        x = self.transformer(x)
        # 取最后一个时间步的输出
        x = x[:, -1, :]
        return self.fc_out(x)

model = StockTransformer()
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)

# ---------- 4. 训练 ----------
epochs = 30
for epoch in range(epochs):
    model.train()
    total_loss = 0
    for batch_X, batch_y in train_loader:
        optimizer.zero_grad()
        pred = model(batch_X)
        loss = criterion(pred, batch_y)
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
    if (epoch+1) % 10 == 0:
        print(f"Epoch {epoch+1}/{epochs}, Loss: {total_loss/len(train_loader):.6f}")

# ---------- 5. 预测与评估 ----------
model.eval()
with torch.no_grad():
    y_pred_scaled = model(X_test).numpy()
    y_test_scaled = y_test.numpy()

# 反归一化(仅收盘价列)
close_scaler = MinMaxScaler()
close_scaler.min_, close_scaler.scale_ = scaler.min_[3], scaler.scale_[3]
y_pred = close_scaler.inverse_transform(y_pred_scaled)
y_true = close_scaler.inverse_transform(y_test_scaled)

mae = mean_absolute_error(y_true, y_pred)
rmse = np.sqrt(mean_squared_error(y_true, y_pred))
print(f"测试集 MAE: {mae:.4f}")
print(f"测试集 RMSE: {rmse:.4f}")

# 输出前5个预测样本对比
print("\n前5个样本预测对比(真实值 vs 预测值):")
for i in range(5):
    print(f"真实: {y_true[i][0]:.2f}  预测: {y_pred[i][0]:.2f}")

运行与验证

依赖安装:


pip install torch numpy scikit-learn yfinance

运行方式:将上述代码保存为stock_transformer.py,在终端执行python stock_transformer.py。

验证方法:观察训练过程中损失是否逐步下降;训练结束后查看测试集MAE与RMSE数值。若使用模拟数据(无网络时),MAE通常小于5.0;若使用真实AAPL数据,MAE通常在1~5之间(取决于数据时间段)。程序输出的前5个样本对比中,预测值应与真实值处于同一数量级且差异合理。

判定标准:代码无报错完整运行;输出包含训练损失与最终评估指标;预测值与真实值趋势一致,MAE/RMSE为有限正数且明显小于收盘价标准差(说明模型学到了有效模式而非输出常数)。