前言
系列专栏:【深度学习:算法项目实战】✨︎
涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域,讨论了各种复杂的深度神经网络思想,如卷积神经网络、循环神经网络、生成对抗网络、门控循环单元、长短期记忆、自然语言处理、深度强化学习、大型语言模型和迁移学习。
在金融市场的分析中,股票价格预测一直是一个充满挑战且备受关注的领域。Transformer模型通过其独特的自注意力机制,能够有效地捕捉到时间序列数据中的长期依赖关系,这在股票价格预测等金融时序预测任务中显得尤为重要。然而,Transformer模型在处理局部依赖和时序信息方面可能不如LSTM等循环神经网络模型。因此,结合LSTM和Transformer的混合模型应运而生,旨在充分利用LSTM在处理时序信息和短期依赖方面的优势,以及Transformer在捕捉长期依赖关系方面的能力。本文将介绍一种基于LSTM-Transformer混合模型的股票价格多变量时序预测方法,该方法结合了LSTM和Transformer的优点,旨在提高股票价格预测的准确性。我们将使用PyTorch框架来实现该模型,并通过实验验证其在股票价格预测任务中的有效性。
目录
1. 数据集介绍
archive文档股票数据集,包含 AAPL.csv
、AMZN.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
(要求是pandas
的DataFrame
类型)传递给父类进行初始化,这样就完成了数据相关的基础设置,比如数据的归一化准备以及相关属性的初始化等操作,这些都是从父类继承而来的功能。然后,初始化了一个新的属性self.lag
,它表示时间序列数据中的滞后步数,默认值为10
,这个参数在后续构建有监督学习的数据格式时会起到关键作用,用于确定输入特征和对应的目标标签之间在时间序列上的对应关系。此外,还初始化了两个缓存属性self._supervised_values_cache
和self._supervised_labels_cache
,初始值都设为None
,它们的作用与父类中的缓存属性类似,是为了缓存经过特定计算得到的有监督学习的输入特征数据和对应的目标标签数据,避免重复计算,提高数据获取效率。
_compute_supervised_values
方法用于计算有监督学习的输入特征数据。它通过列表推导式来构建一个列表x
,其中对于每一个索引i
(范围是从0
到self.__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_df
(sliced_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_idx
、self.val_idx
和self.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
R²: 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
R²: 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