基于LSTM-Transformer混合模型实现股票价格多变量时序预测(PyTorch版)

股票交易价格
前言

系列专栏:【深度学习:算法项目实战】✨︎
涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域,讨论了各种复杂的深度神经网络思想,如卷积神经网络、循环神经网络、生成对抗网络、门控循环单元、长短期记忆、自然语言处理、深度强化学习、大型语言模型和迁移学习。

在金融市场的分析中,股票价格预测一直是一个充满挑战且备受关注的领域。Transformer模型通过其独特的自注意力机制,能够有效地捕捉到时间序列数据中的长期依赖关系,这在股票价格预测等金融时序预测任务中显得尤为重要。然而,Transformer模型在处理局部依赖和时序信息方面可能不如LSTM等循环神经网络模型。因此,结合LSTM和Transformer的混合模型应运而生,旨在充分利用LSTM在处理时序信息和短期依赖方面的优势,以及Transformer在捕捉长期依赖关系方面的能力。本文将介绍一种基于LSTM-Transformer混合模型的股票价格多变量时序预测方法,该方法结合了LSTM和Transformer的优点,旨在提高股票价格预测的准确性。我们将使用PyTorch框架来实现该模型,并通过实验验证其在股票价格预测任务中的有效性。

LSTMTransformer

1. 数据集介绍

archive文档股票数据集,包含 AAPL.csvAMZN.csv等股票。数据集下载链接🔗

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import seaborn as sns

from sklearn.preprocessing import MinMaxScaler

import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader, TensorDataset, Subset
from torchinfo import summary
from torchmetrics.functional.regression import mean_absolute_error
from torchmetrics.functional.regression import mean_absolute_percentage_error
from torchmetrics.functional.regression import mean_squared_error
from torchmetrics.functional.regression import mean_squared_log_error
from torchmetrics.functional.regression import r2_score
np.random.seed(0) # 设置随机种子
torch.manual_seed(0) # 设置随机种子
df = pd.read_csv('../archive/AMZN.csv', index_col=0, parse_dates=True)
sliced_df = df.loc['2021-04-23 00:00:00':]
print(sliced_df.head(5))
                  Open        High         Low       Close   Adj Close  \
Date                                                                     
2021-04-23  165.955002  168.750000  165.425003  167.044006  167.044006   
2021-04-26  167.399994  171.422501  166.546997  170.449997  170.449997   
2021-04-27  172.173492  173.000000  169.900497  170.871506  170.871506   
2021-04-28  171.740005  174.494003  171.250000  172.925003  172.925003   
2021-04-29  175.255005  175.722504  171.750000  173.565506  173.565506   

               Volume  
Date                   
2021-04-23   63856000  
2021-04-26   97614000  
2021-04-27   76542000  
2021-04-28   92638000  
2021-04-29  153648000

2. 数据可视化

接下来,通过 matplotlib 绘制收盘价格,收盘价是股票在正常交易日的最后交易价格,是投资者跟踪其长期表现的标准基准。通过图形可以快速识别股价的趋势走向。

plt.style.use('seaborn-v0_8-whitegrid')
plt.rcParams['font.family'] = 'serif'
fig, ax = plt.subplots(figsize=(20, 5))
ax.plot(sliced_df['Close'], color='darkorange' ,label='AMZN')

locator = mdates.AutoDateLocator(minticks=8, maxticks=12)  # 自动定位刻度
formatter = mdates.DateFormatter('%Y-%m-%d')               # 自定义刻度标签格式
ax.xaxis.set_major_locator(locator)
ax.xaxis.set_major_formatter(formatter)

plt.title('Close Price History')  # 设置标题
plt.xticks(rotation=45)           # 旋转刻度标签以提高可读性
plt.ylabel('Close Price USD ($)')
plt.legend(loc="upper right")
plt.show()

在这里插入图片描述

3. 数据特征工程

3.1 特征缩放(归一化)

MinMaxScaler() 函数是 scikit-learn 库中预处理模块的一个非常实用的工具,用于特征缩放,特别是将特征值缩放到一个指定的范围内,通常是[0, 1]。这种缩放方法对于许多机器学习算法来说是非常有用的,因为它可以帮助改善算法的收敛速度和性能,特别是在特征尺度差异较大的情况下。

class TimeseriesData(object):
    def __init__(self, dataset):
        self.dataset = dataset
        self.scaler = MinMaxScaler()
        self._values_cache = None
        self._labels_cache = None

    def __len__(self):
        return len(self.dataset)

    def __getitem__(self, idx):
        return self.dataset[idx]

    def values(self):
        # 如果缓存中已经存储了值,则直接返回缓存的值
        if self._values_cache is None:
            data = self.dataset.drop(['Adj Close', 'Volume'], axis=1).values
            self._values_cache = self.scaler.fit_transform(data)
        return self._values_cache

    def labels(self):
        # 如果缓存中已经存储了标签,则直接返回缓存的标签
        if self._labels_cache is None:
            targets = self.dataset['Close'].values.reshape(-1, 1)
            self._labels_cache = self.scaler.fit_transform(targets)
        return self._labels_cache

提示TimeseriesData类主要用于处理时间序列数据,它围绕给定的数据集(通过dataset参数传入)进行了一系列操作,包括数据的标准化处理、数据缓存以及提供方便的数据获取方式等,旨在为后续基于时间序列数据的分析、建模等任务准备好合适的数据格式。

3.2 构建监督学习数据

在时间序列预测中,我们将时间序列数据转换为监督学习格式,以方便各种机器学习模型进行预测。

class SupervisedTimeseriesData(TimeseriesData):
    def __init__(self, dataset: pd.DataFrame, lag: int = 10):
        super(SupervisedTimeseriesData, self).__init__(dataset=dataset)
        self.lag = lag
        self._supervised_values_cache = None
        self._supervised_labels_cache = None

    @property
    def supervised_values(self):
        if self._supervised_values_cache is None:
            self._supervised_values_cache = self._compute_supervised_values()
        return self._supervised_values_cache

    @property
    def supervised_labels(self):
        if self._supervised_labels_cache is None:
            self._supervised_labels_cache = self._compute_supervised_labels()
        return self._supervised_labels_cache


    def _compute_supervised_values(self):
        x = [self.values()[i:i + self.lag] for i in range(self.__len__() - self.lag)]
        return torch.tensor(np.array(x), dtype=torch.float)

    def _compute_supervised_labels(self):
        return torch.tensor(self.labels()[self.lag:], dtype=torch.float)

SupervisedTimeseriesData 类继承 TimeseriesData 类中已经定义好的属性和方法,例如__len__方法用于获取数据集长度、values方法用于获取经过处理的数据值以及 labels 方法用于获取对应的标签数据等。进一步构建了适合有监督学习的时间序列数据格式,通过缓存机制和相应的计算方法,高效地提供了有监督学习所需的输入特征数据和目标标签数据,方便后续基于这些数据进行时间序列的预测、建模等相关任务。

SupervisedTimeseriesData类的__init__方法中,首先调用了父类TimeseriesData__init__方法(通过super(SupervisedTimeseriesData, self).__init__(dataset=dataset)),将传入的dataset(要求是pandasDataFrame类型)传递给父类进行初始化,这样就完成了数据相关的基础设置,比如数据的归一化准备以及相关属性的初始化等操作,这些都是从父类继承而来的功能。然后,初始化了一个新的属性self.lag,它表示时间序列数据中的滞后步数,默认值为10,这个参数在后续构建有监督学习的数据格式时会起到关键作用,用于确定输入特征和对应的目标标签之间在时间序列上的对应关系。此外,还初始化了两个缓存属性self._supervised_values_cacheself._supervised_labels_cache,初始值都设为None,它们的作用与父类中的缓存属性类似,是为了缓存经过特定计算得到的有监督学习的输入特征数据和对应的目标标签数据,避免重复计算,提高数据获取效率。

_compute_supervised_values方法用于计算有监督学习的输入特征数据。它通过列表推导式来构建一个列表x,其中对于每一个索引i(范围是从0self.__len__() - self.lag),会从self.values()(这个方法继承自父类,用于获取经过归一化处理的数据值)中提取一段长度为self.lag的数据切片(即self.values()[i:i + self.lag]),这样就构建出了一系列的特征数据序列,每个序列的长度为self.lag,代表了时间序列上连续的多个时间步的数据。最后,将这个列表x转换为numpy数组(通过np.array(x)),再进一步转换为torch张量(通过torch.tensor),并指定数据类型为torch.float,返回这个张量作为有监督学习的输入特征数据。

_compute_supervised_labels方法用于计算有监督学习的目标标签数据。它直接从self.labels()(同样继承自父类,获取经过归一化处理的标签数据)中提取从第self.lag个元素开始的所有数据(通过self.labels()[self.lag:]),意味着取的是对应输入特征数据之后的下一个时间步的数据作为目标标签(基于时间序列的滞后关系),然后将其转换为torch张量,指定数据类型为torch.float,返回这个张量作为有监督学习的目标标签数据。

3.3 数据集划分

class SupervisedTimeseriesDataset(Dataset):
    def __init__(self):
        super(SupervisedTimeseriesDataset, self).__init__()
        self.set = SupervisedTimeseriesData(dataset=sliced_df,lag=30)
        self.dataset = TensorDataset(self.set.supervised_values, self.set.supervised_labels)
        self.train_idx = list(range(self.__len__()*3//5))
        self.val_idx = list(range(self.__len__()*3//5, self.__len__()*4//5))
        self.test_idx = list(range(self.__len__()*4//5, self.__len__()))

    def __len__(self):
        return len(self.dataset)

    def __getitem__(self, index):
        return self.set.supervised_values[index], self.set.supervised_labels[index]

    @property
    def train_set(self):
        return Subset(self.dataset, indices=self.train_idx)

    @property
    def val_set(self):
        return Subset(self.dataset, indices=self.val_idx)

    @property
    def test_set(self):
        return Subset(self.dataset, indices=self.test_idx)

svd = SupervisedTimeseriesDataset()
print(svd.__len__())
print(len(svd.train_set), len(svd.val_set), len(svd.test_set))

__init__方法:首先调用父类(torch.utils.data.Dataset)的__init__方法来完成必要的初始化操作。创建了一个SupervisedTimeseriesData类的实例并赋值给self.set,传入了参数sliced_dfsliced_df是一个经过处理的、包含时间序列数据的pandas数据框)以及lag = 30,意味着它会基于这个数据框按照滞后步数为30来准备有监督学习所需的特征数据和标签数据,具体的实现过程由SupervisedTimeseriesData类内部的方法来实现。接着,通过TensorDataset将前面准备好的有监督学习的特征数据self.set.supervised_values和标签数据self.set.supervised_label包装起来,创建出一个新的数据集对象并赋值给self.dataset,这样后续可以方便地利用torch提供的数据加载等机制进行操作。然后,按照固定的比例(训练集占总数据集的3/5,验证集占1/5,测试集占1/5)划分数据集的索引,分别存储在self.train_idxself.val_idxself.test_idx这三个列表中,用于后续获取不同子集的数据。

__len__方法:返回self.dataset数据集的长度,也就是整个有监督时间序列数据集包含的样本数量。

__getitem__方法:返回的是对应索引位置的特征数据和标签数据。

属性方法(@property装饰的方法):通过Subset类(来自torch.utils.data模块),依据之前划分好的self.train_idx self.val_idx self.test_idx索引列表,从self.dataset中提取出不同的数据子集(训练集、验证集、测试集),并返回数据集对象。

最后创建实例svd = SupervisedTimeseriesDataset()

3.4 数据加载器

DataLoader 是PyTorch提供的一个数据加载器,用于对数据进行批量加载和处理。它实现了迭代器的接口,使用户可以通过迭代的方式逐批获取数据。

train_loader = DataLoader(dataset=svd.train_set, batch_size=64, shuffle=True)
valid_loader = DataLoader(dataset=svd.val_set,batch_size=64, shuffle=False)
test_loader = DataLoader(dataset=svd.test_set,batch_size=64, shuffle=False)

上述代码,基于svd实例来获取整个数据集、不同子集(训练集、验证集、测试集)的数据,并且将这些数据集提供给torch中的数据加载器DataLoader,创建不同子集的数据加载器对象。参数shuffle是否打乱数据。

4. 构建时序模型(TSF)

4.1 构建LSTM-Transformer模型

LSTMTransformer \text{LSTMTransformer} LSTMTransformer 是融合了长短期记忆网络 LSTM \text{LSTM} LSTM Transformer  \text{Transformer } Transformer 架构的神经网络模型,旨在结合 LSTM \text{LSTM} LSTM 对序列数据的长期依赖捕捉能力以及 Transformer \text{Transformer} Transformer 在处理序列时的并行计算优势和强大的特征提取能力,用于处理如时间序列预测等相关任务。

class LSTMTransformer(nn.Module):
    def __init__(self,
                 input_dim, # 输入数据的特征维度
                 hidden_dim, # 表示 LSTM 内部隐藏状态的维度
                 lstm_layers, # 指定了 LSTM 的层数
                 transformer_heads, # 设置了多头注意力机制 Multi-Head Attention中的头数
                 transformer_layers, # 编码器编码层堆叠的层数
                 output_dim, # 输出数据的特征维度
                 dropout=0.5 #设置 Dropout 概率默认值
                 ):
        super(LSTMTransformer, self).__init__()
        # 创建了一个 LSTM 层,多层 LSTM 可以逐步对输入序列进行更深入的特征提取,捕捉更复杂的长期依赖关系
        self.lstm = nn.LSTM(input_dim, hidden_dim, lstm_layers, dropout=dropout, batch_first=True)
        # 设置了 Dropout 概率,用于在训练过程中随机丢弃一部分神经元,防止过拟合,提高模型的泛化能力。
        # batch_first=True表示输入数据的格式中,批次维度在最前面,符合常见的[batch_size, seq_len, input_dim]这样的张量形状规范
        
        # 创建了一个 nn.TransformerEncoderLayer对象,它定义了单个 Transformer 编码器层的结构
        transformer_encoder_layer = nn.TransformerEncoderLayer(
            d_model=hidden_dim, # d_model=hidden_dim表示输入和输出的特征维度都与 LSTM 输出的隐藏维度hidden_dim保持一致,确保数据在从 LSTM 层传递到 Transformer 层时维度匹配。
            nhead=transformer_heads, # 设置了多头注意力机制 Multi-Head Attention中的头数,多头注意力可以从多个不同的角度去捕捉输入序列中的依赖关系,不同的头数会影响模型对特征的关注重点和提取能力。
            dim_feedforward=hidden_dim * 2, # 定义了 Transformer 层中前馈神经网络 Feed-Forward Network 的中间层维度,通常会设置为比d_model更大的值,以增强模型的非线性拟合能力。
            dropout=dropout, # 设置 Dropout 概率
            batch_first=True # batch_first=True表示输入数据的格式中,批次维度在最前面
        )
        # 创建了一个由多个nn.TransformerEncoderLayer堆叠而成的 Transformer 编码器,num_layers参数指定了堆叠的层数
        self.transformer_encoder = nn.TransformerEncoder(
            encoder_layer=transformer_encoder_layer, 
            num_layers=transformer_layers)
        # 全连接层,用于将经过 Transformer 编码器处理后的最后一个时间步的特征向量的维度从hidden_dim转换到output_dim
        self.fc = nn.Linear(hidden_dim, output_dim) 

    def forward(self, x): # x 的形状是[batch_size, seq_len, input_dim])
        lstm_out, _ = self.lstm(x) # 将输入数据 x 传入 LSTM 层进行前向传播
        transformer_input = lstm_out # 将 LSTM 的输出直接作为 Transformer 编码器的输入
        transformer_out = self.transformer_encoder(transformer_input) # Transformer 编码器输出
        output = self.fc(transformer_out[:, -1, :]) # 从 Transformer 编码器输出的特征向量中选取每个批次的最后一个时间步的特征向量,传入全连接层
        return output

Transformer 应用于时间序列预测是否需要解码层问题

当任务是预测下一个时间步的单个值时,Transformer 编码器输出的最后一个时间步的特征向量通常包含了足够的信息。例如,对于一个时间序列预测问题,将历史时间序列数据输入 Transformer 编码器,经过多头注意力机制和前馈神经网络等处理后,最后一个时间步的输出可以通过一个简单的全连接层直接映射到预测值。这是因为 Transformer 编码器能够有效地捕捉序列中的长短期依赖关系,最后一个时间步的特征已经对整个输入序列进行了综合编码,不需要额外的解码过程来生成预测结果。

当需要预测未来一段完整的时间序列时,就需要解码层。例如,要预测未来多个时间步的时间序列值,如股票价格在未来几天的走势或者气象数据在未来几小时的变化。此时,解码层可以利用 Transformer 编码器输出的上下文信息,以自回归的方式逐步生成未来的时间序列。具体来说,解码层在每个时间步会根据之前生成的时间步的预测结果和编码器提供的全局上下文信息,通过注意力机制等方式来生成下一个时间步的预测,从而构建出完整的未来时间序列预测。

4.2 实例化模型、定义损失函数和优化器

params = {
    'input_dim': 4,
    'hidden_dim': 64,
    'lstm_layers': 2,
    'transformer_heads': 8,
    'transformer_layers': 1,
    'output_dim': 1,
    'dropout': .5,
}
lstm_tf = LSTMTransformer(**params) # 实例化LSTMTransformer类,创建一个具体的模型对象lstm_tf
criterion = nn.MSELoss()            # 定义一个均方误差损失 Mean Squared Error Loss函数
optimizer = torch.optim.Adam(params=lstm_tf.parameters(), lr=0.0001) # 这行代码创建了一个基于 Adam 优化算法的优化器对象optimizer

4.3 模型概要

summary(model=lstm_tf, input_size=(64, 30, 4))
===============================================================================================
Layer (type:depth-idx)                        Output Shape              Param #
===============================================================================================
LSTMTransformer                               [64, 1]                   --
├─LSTM: 1-1                                   [64, 30, 64]              51,200
├─TransformerEncoder: 1-2                     [64, 30, 64]              --
│    └─ModuleList: 2-1                        --                        --
│    │    └─TransformerEncoderLayer: 3-1      [64, 30, 64]              33,472
├─Linear: 1-3                                 [64, 1]                   65
===============================================================================================
Total params: 84,737
Trainable params: 84,737
Non-trainable params: 0
Total mult-adds (Units.MEGABYTES): 99.39
===============================================================================================
Input size (MB): 0.03
Forward/backward pass size (MB): 5.90
Params size (MB): 0.27
Estimated Total Size (MB): 6.20
===============================================================================================

5. 模型训练

5.1 定义训练函数

在模型训练之前,我们需先定义 train 函数来执行模型训练过程

def train(model, iterator):
    model.train()
    epoch_loss = 0

    for batch_idx, (data, target) in enumerate(iterable=iterator):
        optimizer.zero_grad()
        output = model(data)
        loss = criterion(output, target)
        loss.backward()
        optimizer.step()
        epoch_loss += loss.item()
    avg_loss = epoch_loss / len(iterator)

    return avg_loss

上述代码定义了一个名为 train 的函数,用于训练给定的模型。它接收模型、数据迭代器作为参数,并返回训练过程中的平均损失。

5.2 定义评估函数

def evaluate(model, iterator): # Being used to validate and test
    model.eval()
    epoch_loss = 0

    with torch.no_grad():
        for batch_idx, (data, target) in enumerate(iterable=iterator):
            output = model(data)
            loss = criterion(output, target)
            epoch_loss += loss.item()

        avg_loss = epoch_loss / len(iterator)

        return avg_loss

上述代码定义了一个名为 evaluate 的函数,用于评估给定模型在给定数据迭代器上的性能。它接收模型、数据迭代器作为参数,并返回评估过程中的平均损失。这个函数通常在模型训练的过程中定期被调用,以监控模型在验证集或测试集上的性能。通过评估模型的性能,可以了解模型的泛化能力和训练的进展情况。

5.3 定义模型训练主程序

def main():
    train_losses = []
    val_losses = []

    for epoch in range(300):
        train_loss = train(model=lstm_tf, iterator=train_loader)
        val_loss = evaluate(model=lstm_tf, iterator=valid_loader)

        train_losses.append(train_loss)
        val_losses.append(val_loss)

        print(f'Epoch: {epoch + 1:02}, Train MSELoss: {train_loss:.5f}, Val. MSELoss: {val_loss:.5f}')

    plt.figure(figsize=(10, 5))
    plt.plot(train_losses, label='Training Loss')
    plt.plot(val_losses, label='Validation Loss')
    plt.xlabel('Epoch')
    plt.ylabel('MSELoss')
    plt.title('Training and Validation Loss over Epochs')
    plt.legend()
    plt.grid(True)
    plt.show()

5.4 执行模型训练过程

执行 main() 函数

main()
Epoch: 284, Train MSELoss: 0.00344, Val. MSELoss: 0.00173
Epoch: 285, Train MSELoss: 0.00344, Val. MSELoss: 0.00279
Epoch: 286, Train MSELoss: 0.00340, Val. MSELoss: 0.00179
Epoch: 287, Train MSELoss: 0.00311, Val. MSELoss: 0.00201
Epoch: 288, Train MSELoss: 0.00318, Val. MSELoss: 0.00254
Epoch: 289, Train MSELoss: 0.00306, Val. MSELoss: 0.00212
Epoch: 290, Train MSELoss: 0.00315, Val. MSELoss: 0.00195
Epoch: 291, Train MSELoss: 0.00297, Val. MSELoss: 0.00169
Epoch: 292, Train MSELoss: 0.00328, Val. MSELoss: 0.00269
Epoch: 293, Train MSELoss: 0.00355, Val. MSELoss: 0.00170
Epoch: 294, Train MSELoss: 0.00295, Val. MSELoss: 0.00168
Epoch: 295, Train MSELoss: 0.00342, Val. MSELoss: 0.00196
Epoch: 296, Train MSELoss: 0.00327, Val. MSELoss: 0.00173
Epoch: 297, Train MSELoss: 0.00332, Val. MSELoss: 0.00177
Epoch: 298, Train MSELoss: 0.00319, Val. MSELoss: 0.00227
Epoch: 299, Train MSELoss: 0.00294, Val. MSELoss: 0.00189
Epoch: 300, Train MSELoss: 0.00297, Val. MSELoss: 0.00168

损失

6. 模型预测

6.1 构建预测函数

接下来,我们通过构建 predict函数来对模型中的数据进行预测。

def predict(model, iterator):
    model.eval()
    targets = [] # 初始化空列表,存储目标值
    predictions = [] # # 初始化空列表,存储预测值

    with torch.no_grad(): # 不计算梯度
        for batch_idx, (data, target) in enumerate(iterable=iterator):
            output = model(data)
            targets.append(target)
            predictions.append(output)

    targets = torch.cat(targets)
    predictions = torch.cat(predictions, dim=0)
    return predictions, targets

这个函数的主要作用是使用给定的模型对输入的数据迭代器 iterator 中的数据进行预测,并收集相应的预测结果和真实目标值,最后返回整理好的预测值和目标值。它常用于在训练好模型之后,对测试集或者验证集等数据进行预测的场景。具体来说:

model.eval() 这行代码将模型设置为评估模式。在很多深度学习模型中,尤其是包含像 dropout、batch normalization 等在训练阶段和评估阶段行为不同的层时,调用 eval() 方法可以确保模型在预测时使用正确的计算方式。例如,在训练阶段,dropout 会按照一定概率随机丢弃神经元,而在评估阶段则不会进行这种随机丢弃操作,以保证预测结果的稳定性和确定性。

with torch.no_grad(): 不需要计算梯度。因为在预测阶段,我们并不需要进行反向传播来更新模型的参数,关闭梯度计算可以节省内存并提高计算效率。

targets = torch.cat(targets)predictions = torch.cat(predictions, dim=0):在遍历完所有批次后,使用 torch.cat 函数将收集到的 targets 列表中的张量按照维度进行拼接,使其成为一个连续的张量,方便后续对整体的预测结果和目标值进行统一的分析和处理。对于 predictions 也是同样的操作,指定了拼接维度为 0,通常在按照批次维度拼接预测结果张量时会这样做,确保拼接后的张量结构符合预期,能正确反映所有批次的预测情况。

7. 模型验证

7.1 验证集预测

# 模型预测
val_pred, val_true = predict(lstm_tf, valid_loader)

7.2 验证集评估

通过seaborn库中regplot函数绘制回归拟合图,我们可以清晰地看到模型预测值与实际数据点之间的吻合程度,即拟合度。

plt.figure(figsize=(5, 5), dpi=100)
sns.regplot(x=val_true.numpy(), y=val_pred.numpy(), scatter=True, marker="*", color='orange',line_kws={'color': 'red'})
plt.show()

回归拟合图

torchmetrics库提供了各种损失函数,我们可以直接用来计算张量的损失,而无需转换成numpy数组形式。

mae = mean_absolute_error(preds=val_pred, target=val_true)
print(f"Mean Absolute Error: {mae:.5f}")

mape = mean_absolute_percentage_error(preds=val_pred, target=val_true)
print(f"Mean Absolute Percentage Error: {mape * 100:.4f}%")

mse = mean_squared_error(preds=val_pred, target=val_true)
print(f"Mean Squared Error: {mse:.4f}")

msle = mean_squared_log_error(preds=val_pred, target=val_true)
print(f"Mean Squared Log Error: {msle:.4f}")

r2 = r2_score(preds=val_pred, target=val_true)
print(f"R²: {r2:.4f}")
Mean Absolute Error: 0.02216
Mean Absolute Percentage Error: 7.5127%
Mean Squared Error: 0.0008
Mean Squared Log Error: 0.0004: 0.9631

8. 模型测试

8.1 测试集预测

test_pred, test_true = predict(lstm_tf, test_loader)

8.2 测试集评估

通过seaborn库中regplot函数绘制回归拟合图,我们可以清晰地看到模型预测值与实际数据点之间的吻合程度,即拟合度。

plt.figure(figsize=(5, 5), dpi=100)
sns.regplot(x=test_true.numpy(), y=test_pred.numpy(), scatter=True, marker="*", color='orange',line_kws={'color': 'red'})
plt.show()

回归拟合图

torchmetrics库提供了各种损失函数,我们可以直接用来计算张量的损失,而无需转换成numpy数组形式。

mae = mean_absolute_error(preds=test_pred, target=test_true)
print(f"Mean Absolute Error: {mae:.5f}")

mape = mean_absolute_percentage_error(preds=test_pred, target=test_true)
print(f"Mean Absolute Percentage Error: {mape * 100:.4f}%")

mse = mean_squared_error(preds=test_pred, target=test_true)
print(f"Mean Squared Error: {mse:.4f}")

msle = mean_squared_log_error(preds=test_pred, target=test_true)
print(f"Mean Squared Log Error: {msle:.4f}")

r2 = r2_score(preds=test_pred, target=test_true)
print(f"R²: {r2:.4f}")
Mean Absolute Error: 0.02270
Mean Absolute Percentage Error: 3.6636%
Mean Squared Error: 0.0009
Mean Squared Log Error: 0.0003: 0.9707

预测可视化

matplotlib 库中,我们可以直接使用张量数据来绘制图表

plt.figure(figsize=(20, 5))
plt.plot(torch.cat(tensors=(val_true, test_true), dim=0), label='Actual Value')
plt.plot(torch.cat(tensors=(val_pred, test_pred), dim=0), label='Prediction Value')
plt.axvline(len(val_pred), color='red', linestyle='-.')
plt.text(0, 0.8, 'Validation', fontsize=15)
plt.text(len(val_pred), 0.8, 'Test', fontsize=15)
plt.ylabel('Close Price USD ($)')
plt.title('Prediction Results', fontsize=15)
plt.legend()
plt.grid(True)
plt.show()

预测结果

n_idx = len(svd.set.supervised_labels)
val_size = len(svd.val_idx)
test_size = len(svd.test_idx)

x_plot = list(range(len(svd.dataset)))
y_plot = svd.set.supervised_labels

x_val_plot = svd.val_idx
x_test_plot = svd.test_idx

x_val_line = x_plot[n_idx - val_size - test_size]
x_test_line = x_plot[n_idx - test_size]
plt.figure(figsize=(16, 8))
plt.title('LSTM-Transformer Prediction Results', fontsize=40, pad=20)
plt.plot(x_plot, y_plot, label='Actual Value')
plt.plot(x_val_plot, val_pred, label='Validation Prediction')
plt.plot(x_test_plot, test_pred, label='Testing Prediction')
plt.axvline(x_val_line, color='red', linestyle='-.')
plt.axvline(x_test_line, color='red', linestyle='-.')
plt.text(x_val_line, 0.8, 'Validation', fontsize=20)
plt.text(x_test_line, 0.8, 'Test', fontsize=20)
plt.text(600, 0.0,
         f'MAE: {mae:.5f}\n'+
         f'MAPE: {mape * 100:.4f}%\n'+
         f'MSE: {mse:.4f}\n'+
         f'MSLE: {msle:.4f}\n'+
         f'R²: {r2:.4f}', fontsize=15)
plt.ylabel('Close Price USD ($)', fontsize=30)
plt.legend()
plt.show()

在这里插入图片描述

完整源码(含环境配置)

通过网盘分享的文件:Apple.zip
链接: https://pan.baidu.com/s/1FyqHdJJdfYMoFaKOtoVfAA 提取码: csdn

### 使用Transformer模型进行股票市场预测的方法 #### 4.1 数据预处理与特征工程 数据清洗是任何机器学习项目中的重要环节,在使用Transformer模型进行股票价格预测之前,必须确保输入的数据质量高且无噪声。这包括去除缺失值、异常值以及平滑化处理等操作[^1]。 ```python import pandas as pd from sklearn.preprocessing import MinMaxScaler def clean_data(df): df.dropna(inplace=True) # 删除含有NaN的行 scaler = MinMaxScaler(feature_range=(0, 1)) scaled_data = scaler.fit_transform(df['Close'].values.reshape(-1, 1)) return scaled_data ``` #### 4.2 构建Transformer模型架构 为了适应时间序列的特点,可以设计一个多头自注意力机制(Multi-head Self-Attention Mechanism),它能够帮助捕捉不同时间段之间的长期依赖关系。此外,还需要加入位置编码(Positional Encoding),以便让模型理解各个时刻的位置信息[^3]。 ```python import torch.nn as nn class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len=5000): super().__init__() pe = torch.zeros(max_len, d_model) position = torch.arange(0, max_len).unsqueeze(1) div_term = torch.exp(torch.arange(0, d_model, 2) * (-torch.log(torch.tensor(10000.0)) / d_model)) pe[:, 0::2] = torch.sin(position * div_term) pe[:, 1::2] = torch.cos(position * div_term) self.register_buffer('pe', pe) def forward(self, x): x = x + self.pe[:x.size(1), :].requires_grad_(False) return x class TransformerModel(nn.Module): def __init__(self, input_dim, model_dim, num_heads, num_layers, output_dim): super().__init__() self.embedding = nn.Linear(input_dim, model_dim) self.positional_encoding = PositionalEncoding(model_dim) encoder_layer = nn.TransformerEncoderLayer(d_model=model_dim, nhead=num_heads) self.transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers) self.fc_out = nn.Linear(model_dim, output_dim) def forward(self, src): embedded_src = self.embedding(src) encoded_src = self.positional_encoding(embedded_src) transformer_output = self.transformer_encoder(encoded_src) prediction = self.fc_out(transformer_output.mean(dim=1)) return prediction ``` #### 4.3 训练过程 训练过程中需要注意设置合理的损失函数和优化器,并采用早停法(Early Stopping)防止过拟合现象发生。同时也可以尝试不同的超参数组合找到最优解。 ```python criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate) for epoch in range(num_epochs): outputs = model(train_X.float()) optimizer.zero_grad() loss = criterion(outputs.squeeze().float(), train_y.float()) loss.backward() optimizer.step() if (epoch+1)%print_interval==0: print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}') if early_stopping(loss, model): break ``` #### 实际案例分析 在实际应用中,可以通过结合传统统计学方法如ARIMA与现代深度学习框架下的Transformer相结合的方式来进行更精准有效的股市趋势判断。例如,在某篇论文中提到利用此类混合策略实现了对未来一周内股价变动方向高达87%以上的准确率。
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

矩阵猫咪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值