前言
系列专栏:【深度学习:算法项目实战】✨︎
涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域,讨论了各种复杂的深度神经网络思想,如卷积神经网络、循环神经网络、生成对抗网络、门控循环单元、长短期记忆、自然语言处理、深度强化学习、大型语言模型和迁移学习。
在当今数字化与智能化快速发展的时代,电力系统的高效稳定运行对于社会的各个方面都起着至关重要的作用。负荷预测作为一项关键环节,对于保障电力供需平衡、优化资源配置具有至关重要的作用。准确的电力负荷预测有助于降低发电成本、减少能源浪费,并提升电力系统的可靠性与灵活性。
近年来,深度学习技术在时间序列预测领域取得了显著进展,尤其是TCN(Temporal Convolutional Networks,时间卷积网络)、Transformer以及KAN(Kolmogorov-Arnold Networks,柯尔莫哥洛夫-阿诺德网络)等模型的提出,为负荷预测提供了新的思路和方法。TCN作为一种专门为时间序列数据设计的卷积神经网络,通过膨胀卷积有效捕捉长程依赖关系,并具备良好的并行化能力;Transformer则凭借其自注意力机制,在自然语言处理领域取得巨大成功后,也被广泛应用于时间序列数据的全局特征提取;KAN则基于Kolmogorov-Arnold表示定理,通过可学习的激活函数逼近复杂函数关系,展现出较高的准确性和可解释性。
鉴于上述模型各自的优势,本文提出了一种基于TCN-Transformer-KAN混合模型的电力负荷时序预测方法,旨在通过结合三者的长处,进一步提高负荷预测的精度和效率。具体而言,TCN负责提取时间序列数据的局部特征和短期依赖关系,Transformer捕捉全局特征和长程依赖关系,而KAN则利用其强大的函数逼近能力处理复杂的非线性关系。三者相辅相成,共同提升模型的预测性能。
本文详细阐述了TCN-Transformer-KAN混合模型的构建过程,包括数据预处理、模型结构设计、训练与优化策略等。通过实际电力负荷数据的实验验证,展示了该混合模型在提高预测精度和效率方面的显著效果。有望为智能电网的规划与运营提供有力支持。我们相信,随着深度学习技术的不断发展,该模型将在更多领域展现出其独特的优势和广泛的应用前景。
目录
1. 数据集介绍
本文用到的数据集是ETTh1.csv,ETTh1数据集是电力变压器数据集(ETDataset)的一部分,旨在用于长序列时间序列预测问题的研究。该数据集收集了中国两个不同县两年的数据,以预测特定地区的电力需求情况。
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
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, \
mean_absolute_percentage_error, \
mean_squared_error, root_mean_squared_error, \
r2_score
import torch
import torch.nn as nn
from torch.nn.utils.parametrizations import weight_norm
from torch.utils.data import DataLoader, TensorDataset, Dataset
from torchinfo import summary
from kan import KAN
np.random.seed(0)
torch.manual_seed(0)
data = pd.read_csv('ETTh1.csv')
data.head(5).style.background_gradient()
2. 数据预处理
在数据处理和分析过程中,经常需要将日期和时间的字符串表示转换为 Pandas 的 Timestamp 对象,以便进行后续的时间序列分析、日期筛选、时间差计算等操作。pandas.to_datetime
将数据转换为 datetime 类型。
print(type(data['OT'].iloc[0]), type(data['date'].iloc[0]))
# Convert the data type of timestamp column to datatime format
data['date'] = pd.to_datetime(data['date'])
print(type(data['OT'].iloc[0]), type(data['date'].iloc[0]))
data = data.set_index('date').sort_index()
<class 'numpy.float64'> <class 'str'>
<class 'numpy.float64'> <class 'pandas._libs.tslibs.timestamps.Timestamp'>
3. 数据可视化
时序数据往往包含大量的时间点记录,直接查看原始数据可能既耗时又难以捕捉关键信息。通过数据可视化,可以能够迅速把握数据的整体趋势和波动情况。接下来我们观察一下OT列的特征趋势。
sns.set_style('whitegrid')
# plt.style.use('seaborn-v0_8-whitegrid')
fig, ax = plt.subplots(figsize=(10, 6))
# 绘制数据
ax.plot(data['OT'], color='darkorange' ,label='Trend')
# 设置x轴为时间轴,并显示具体日期
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('OT Trend', fontdict={'family': 'Times New Roman', 'fontsize': 15, 'color':'black'})
plt.xticks(rotation=45) # 旋转刻度标签以提高可读性
plt.ylabel('Temp', fontdict={'family': 'Times New Roman', 'fontsize': 14})
plt.legend(loc="upper right", prop={'family': 'Times New Roman'})
plt.show()
4. 特征工程
特征工程有助于从现有特征中派生出一些有价值的特征。这些额外的功能有时有助于显著提高模型的性能,当然也有助于更深入地了解数据。这里我们选择 OT 单个特征来训练模型。。。通过特征相关性其实我们可以看出 OT 与其他特征相关性很低。
values = data['OT'].values.reshape(-1, 1)
reshape
方法用于改变数组的形状。第一个参数 -1
表示自动推断该维度的大小,使得数组在保持总元素数量不变的情况下,根据第二个参数的要求进行重塑。第二个参数 1
指定了新数组的第二维度为 1
。举例说明,如果原始的 data
是一个一维数组 [1, 2, 3, 4]
,执行 data.reshape(-1, 1)
后,data
将变为一个二维数组 [[1], [2], [3], [4]]
。如果原始的 data
是一个二维数组,比如 [[1, 2], [3, 4]]
,执行该操作后会将其转换为一个二维数组,其中每个元素都被转换为一个包含单个元素的列表,即 [[1], [2], [3], [4]]
。
4.1 特征缩放(归一化)
# 创建 MinMaxScaler实例,对特征进行拟合和变换,生成NumPy数组
scaler = MinMaxScaler()
values_scaled = scaler.fit_transform(values)
print(values_scaled.shape)
在 scikit-learn 库中,MinMaxScaler()
函数将数据集中的每个特征[列]进行线性变换,使得特征值映射到一个指定的区间,通常是 [0, 1]
。对于特征中的每个
x
x
x 值,转换公式为
x
n
e
w
=
x
−
x
m
i
n
x
m
a
x
−
x
m
i
n
x_{new}=\frac{x-x_{min}} {x_{max}-x_{min}}
xnew=xmax−xminx−xmin,其中
x
m
i
n
x_{min}
xmin是该特征的最小值,
x
m
a
x
x_{max}
xmax是该特征的最大值。这种缩放方法可以帮助改善算法的收敛速度和性能,特别是在特征尺度差异较大的情况下。
4.2 构建监督学习数据
在时间序列预测中,我们将时间序列数据转换为监督学习格式,可以方便地应用各种机器学习模型进行预测。
time_steps = 10
X_list = []
y_list = []
for i in range(len(values_scaled) - time_steps):
X_list.append(values_scaled[i:i+time_steps])
y_list.append(values_scaled[i+time_steps])
X = np.array(X_list) # [samples, time_steps, num_features]
y = np.array(y_list) # [target]
上述代码的目的是进行时间序列数据的预处理,将原始的时间序列数据转换为适合机器学习模型输入的监督学习数据格式。time_steps
:表示每个样本中包含的时间步数,它决定了模型在预测时考虑的历史数据长度。X_list
:用于存储分割后的特征数据样本的列表。y_list
:用于存储每个特征数据样本对应的目标值的列表。
for i in range(len(values_scaled) - time_steps)
:使用for
循环遍历values_scaled
,但会在倒数第time_steps
个元素处停止,因为后面不足time_steps
个数据来构建特征。X_list.append(values_scaled[i:i + time_steps])
:对于每个索引i
,从标准化后的时间序列数据values_scaled
中提取从i
到i + time_steps
行的数据。例如,如果time_steps = 10
,当i = 0
时,它会选择第 0 \text{0} 0 行到第 9 \text{9} 9 行的数据。y_list.append(values_scaled[i +time_steps])
:这里是提取目标值。对于每个索引i
,选择i + time_steps
行的元素作为目标值。例如,当i = 0
且time_steps = 10
时,会选择第 10 \text{10} 10 行的元素作为目标值。这个目标值被添加到y_list
中。
4.3 数据集划分
train_test_split
函数将数组或矩阵随机分成训练子集和测试子集。
X_temp, X_test, y_temp, y_test = train_test_split(X, y, test_size=0.2, random_state=42, shuffle=False)
X_train, X_val, y_train, y_val = train_test_split(X_temp, y_temp, test_size=0.25, random_state=42, shuffle=False) # 0.25 是相对于剩余 80% 数据的比例
print(X_train.shape, X_val.shape, X_test.shape, y_train.shape, y_val.shape, y_test.shape)
(10446, 10, 1) (3482, 10, 1) (3482, 10, 1) (10446, 1) (3482, 1) (3482, 1)
以上代码中 random_state=45
设置了随机种子,以确保每次运行代码时分割结果的一致性。shuffle=False
表示在分割数据时不进行随机打乱。如果设置为True
(默认值),则会在分割之前对数据进行随机打乱,这样可以增加数据的随机性,但时间序列数据具有连续性,所以设置为False
。
4.4 数据加载器
class DataHandler(Dataset):
def __init__(self, batch_size):
self.X_train_tensor = torch.tensor(X_train, dtype=torch.float32)
self.X_val_tensor = torch.tensor(X_val, dtype=torch.float32)
self.y_train_tensor = torch.tensor(y_train, dtype=torch.float32)
self.y_val_tensor = torch.tensor(y_val, dtype=torch.float32)
self.batch_size = batch_size
def __len__(self):
return len(self.X_train_tensor)
def __getitem__(self, idx, dataset_type='train'):
if dataset_type == 'train':
sample = self.X_train_tensor[idx]
labels = self.y_train_tensor[idx]
elif dataset_type == 'val':
sample = self.X_val_tensor[idx]
labels = self.y_val_tensor[idx]
return sample, labels
def train_loader(self):
train_dataset = TensorDataset(self.X_train_tensor, self.y_train_tensor)
return DataLoader(train_dataset, batch_size=self.batch_size, shuffle=True)
def valid_loader(self):
valid_dataset = TensorDataset(self.X_val_tensor, self.y_val_tensor)
return DataLoader(valid_dataset, batch_size=self.batch_size, shuffle=False)
在上述代码中,定义了一个名为 DataHandler
的类,它继承自 torch.utils.data.Dataset
__init__
方法用于接收数据和标签。__len__
方法返回数据集的长度。__getitem__
方法根据给定的索引 idx
返回相应的数据样本和标签。
batch_size= 256
data_handler = DataHandler(batch_size=batch_size)
train_loader = data_handler.train_loader()
valid_loader = data_handler.valid_loader()
在上述代码中,创建了一个数据处理对象 data_handler
,并通过该对象生成训练集数据加载器 train_loader
和验证集数据加载器valid_loader
。通过这种方式,可以方便地管理和加载训练集和验证集数据,为深度学习模型的训练和评估提供了数据支持。
5. 构建时序模型(TSF)
5.1 定义TCN神经网络
TemporalConvNet,或称时间卷积网络(Temporal Convolutional Networks, TCN),是一种专门用于处理时间序列数据的深度神经网络架构。1
class Chomp1d(nn.Module):
def __init__(self, chomp_size):
super(Chomp1d, self).__init__()
self.chomp_size = chomp_size
def forward(self, x):
return x[:, :, :-self.chomp_size].contiguous()
class TemporalBlock(nn.Module):
def __init__(self, n_inputs, n_outputs, kernel_size, stride, dilation, padding, dropout=0.2):
super(TemporalBlock, self).__init__()
self.conv1 = weight_norm(nn.Conv1d(n_inputs, n_outputs, kernel_size,
stride=stride, padding=padding, dilation=dilation))
self.chomp1 = Chomp1d(padding)
self.bn1 = nn.BatchNorm1d(n_outputs)
self.relu1 = nn.ReLU()
self.dropout1 = nn.Dropout(dropout)
self.conv2 = weight_norm(nn.Conv1d(n_outputs, n_outputs, kernel_size,
stride=stride, padding=padding, dilation=dilation))
self.chomp2 = Chomp1d(padding)
self.bn2 = nn.BatchNorm1d(n_outputs)
self.relu2 = nn.ReLU()
self.dropout2 = nn.Dropout(dropout)
self.net = nn.Sequential(self.conv1, self.chomp1, self.bn1, self.relu1, self.dropout1,
self.conv2, self.chomp2, self.bn2, self.relu2, self.dropout2)
self.downsample = nn.Conv1d(n_inputs, n_outputs, 1) if n_inputs != n_outputs else None
self.relu = nn.ReLU()
self.init_weights()
def init_weights(self):
self.conv1.weight.data.normal_(0, 0.01)
self.conv2.weight.data.normal_(0, 0.01)
if self.downsample is not None:
self.downsample.weight.data.normal_(0, 0.01)
def forward(self, x):
out = self.net(x)
res = x if self.downsample is None else self.downsample(x)
return self.relu(out + res)
class TemporalConvNet(nn.Module):
def __init__(self, num_inputs, num_channels, kernel_size=2, dropout=0.2):
super(TemporalConvNet, self).__init__()
layers = []
num_levels = len(num_channels)
for i in range(num_levels):
dilation_size = 2 ** i
in_channels = num_inputs if i == 0 else num_channels[i-1]
out_channels = num_channels[i]
layers += [TemporalBlock(in_channels, out_channels, kernel_size, stride=1, dilation=dilation_size,
padding=(kernel_size-1) * dilation_size, dropout=dropout)]
self.network = nn.Sequential(*layers)
def forward(self, x):
return self.network(x)
依据原作者的代码,博主加入了 nn.BatchNorm1d
层,nn.BatchNorm1d
是PyTorch框架中的一个模块,它用于对一维数据(通常是时间序列数据或全连接层的输出)进行批归一化。批归一化是一种在神经网络训练过程中常用的技术,它有助于加速训练过程,提高模型的稳定性和性能。2
5.2 定义TCN-Transformer-KAN网络结构
TCN-Transformer-KAN \text{TCN-Transformer-KAN} TCN-Transformer-KAN 网络结构是一个融合了时间卷积网络 Temporal Convolutional Network, TCN \text{Temporal Convolutional Network, TCN} Temporal Convolutional Network, TCN、 Transformer \text{Transformer} Transformer 和 Kolmogorov-Arnold Network, KAN \text{Kolmogorov-Arnold Network, KAN} Kolmogorov-Arnold Network, KAN 3 的复合神经网络结构。这种结构旨在结合各自组件的优势,以提高神经网络的性能、可解释性和适应性。以下是对该网络结构的详细定义:
class TCN_Transformer_KAN(nn.Module):
def __init__(self, input_size, output_size, num_channels, hidden_dim, kernel_size, transformer_heads, transformer_layers ,dropout):
super(TCN_Transformer_KAN, self).__init__()
self.tcn = TemporalConvNet(input_size, num_channels, kernel_size = kernel_size, dropout = dropout)
transformer_encoder_layer = nn.TransformerEncoderLayer(d_model=hidden_dim, nhead=transformer_heads, dim_feedforward=hidden_dim * 2, dropout=dropout, batch_first=True)
self.transformer_encoder = nn.TransformerEncoder(transformer_encoder_layer, num_layers=transformer_layers)
self.kan = KAN([hidden_dim, output_size])
def forward(self, x):
x = x.permute(0, 2, 1) # 调整输入形状 TemporalConvNet 的期望输入 [N, C_in, L_in]
x = self.tcn(x) # [N, C_out, L_out=L_in]
x = x.permute(0, 2, 1) # 调整输出形状 [N, L_out, C_out]
x = self.transformer_encoder(x)
x = x[:, -1, :] # 最后一个时间步的输出 [N, C_out]
return self.kan(x)
上述代码self.kan = KAN([hidden_dim, output_size])
:创建了一个 KAN (从 from kan import KAN
导入)实例,它接收一个包含两个元素的列表作为参数,用于将经过前面网络处理后的特征维度从 hidden_dim
转换为最终期望的输出维度 output_size
4
5.3 实例化模型、损失函数和优化器
params = {
# 'input_size',C_in
'input_size': 1,
# 单步,预测未来一个时刻
'output_size': 1,
'num_channels': [128] * 3,
'kernel_size': 4,
'hidden_dim': 128,
'transformer_heads': 8,
'transformer_layers': 3,
'dropout': .5,
}
model = TCN_Transformer_KAN(**params)
criterion_mse = nn.MSELoss() # 定义均方误差损失函数
criterion_mae = nn.L1Loss() # 定义平均绝对误差损失
optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 定义优化器
5.4 模型概要
# batch_size, seq_len, input_size(in_channels)
summary(model, (batch_size, time_steps, num_features))
==============================================================================================================
Layer (type:depth-idx) Output Shape Param #
==============================================================================================================
TCN_Transformer_KAN [256, 1] --
├─TemporalConvNet: 1-1 [256, 128, 10] --
│ └─Sequential: 2-1 [256, 128, 10] --
│ │ └─TemporalBlock: 3-1 [256, 128, 10] 67,328
│ │ └─TemporalBlock: 3-2 [256, 128, 10] 132,096
│ │ └─TemporalBlock: 3-3 [256, 128, 10] 132,096
├─TransformerEncoder: 1-2 [256, 10, 128] --
│ └─ModuleList: 2-2 -- --
│ │ └─TransformerEncoderLayer: 3-4 [256, 10, 128] 132,480
│ │ └─TransformerEncoderLayer: 3-5 [256, 10, 128] 132,480
│ │ └─TransformerEncoderLayer: 3-6 [256, 10, 128] 132,480
├─MultKAN: 1-3 [256, 1] 4
│ └─ModuleList: 2-3 -- --
│ │ └─KANLayer: 3-7 [256, 1] 2,432
│ └─SiLU: 2-4 [256, 128] --
│ └─ModuleList: 2-5 -- --
│ │ └─Symbolic_KANLayer: 3-8 [256, 1] 640
==============================================================================================================
Total params: 732,036
Trainable params: 730,496
Non-trainable params: 1,540
Total mult-adds (Units.MEGABYTES): 52.07
==============================================================================================================
Input size (MB): 0.01
Forward/backward pass size (MB): 57.67
Params size (MB): 0.81
Estimated Total Size (MB): 58.49
==============================================================================================================
6. 模型训练与可视化
6.1 定义训练与评估函数
在模型训练之前,我们通常需先定义 train
函数来执行模型训练过程
def train(model, iterator, optimizer):
epoch_loss_mse = 0
epoch_loss_mae = 0
model.train() # 确保模型处于训练模式
for batch in iterator:
optimizer.zero_grad() # 清空梯度
inputs, targets = batch # 获取输入和目标值
outputs = model(inputs) # 前向传播
loss_mse = criterion_mse(outputs, targets) # 计算损失
loss_mae = criterion_mae(outputs, targets)
combined_loss = loss_mse + loss_mae # 可以根据需要调整两者的权重
combined_loss.backward()
optimizer.step()
epoch_loss_mse += loss_mse.item() # 累计损失
epoch_loss_mae += loss_mae.item()
average_loss_mse = epoch_loss_mse / len(iterator) # 计算平均损失
average_loss_mae = epoch_loss_mae / len(iterator)
return average_loss_mse, average_loss_mae
上述代码定义了一个名为 train
的函数,用于训练给定的模型。它接收模型、数据迭代器、优化器作为参数,并返回训练过程中的平均损失。
def evaluate(model, iterator):
epoch_loss_mse = 0
epoch_loss_mae = 0
model.eval() # 将模型设置为评估模式,例如关闭 Dropout 等
with torch.no_grad(): # 不需要计算梯度
for batch in iterator:
inputs, targets = batch
outputs = model(inputs) # 前向传播
loss_mse = criterion_mse(outputs, targets) # 计算损失
loss_mae = criterion_mae(outputs, targets)
epoch_loss_mse += loss_mse.item() # 累计损失
epoch_loss_mae += loss_mae.item()
return epoch_loss_mse / len(iterator), epoch_loss_mae / len(iterator)
上述代码定义了一个名为 evaluate
的函数,用于评估给定模型在给定数据迭代器上的性能。它接收模型、数据迭代器作为参数,并返回评估过程中的平均损失。这个函数通常在模型训练的过程中定期被调用,以监控模型在验证集或测试集上的性能。通过评估模型的性能,可以了解模型的泛化能力和训练的进展情况。
6.2 模型训练与损失记录
epoch = 500
train_mselosses = []
valid_mselosses = []
train_maelosses = []
valid_maelosses = []
for epoch in range(epoch):
train_loss_mse, train_loss_mae = train(model, train_loader, optimizer)
valid_loss_mse, valid_loss_mae = evaluate(model, valid_loader)
train_mselosses.append(train_loss_mse)
valid_mselosses.append(valid_loss_mse)
train_maelosses.append(train_loss_mae)
valid_maelosses.append(valid_loss_mae)
print(f'Epoch: {epoch + 1:02}, Train MSELoss: {train_loss_mse:.5f}, Train MAELoss: {train_loss_mae:.3f}, Val. MSELoss: {valid_loss_mse:.5f}, Val. MAELoss: {valid_loss_mae:.3f}')
Epoch: 01, Train MSELoss: 0.05760, Train MAELoss: 0.162, Val. MSELoss: 0.01492, Val. MAELoss: 0.114
Epoch: 02, Train MSELoss: 0.00706, Train MAELoss: 0.066, Val. MSELoss: 0.00140, Val. MAELoss: 0.028
Epoch: 03, Train MSELoss: 0.00532, Train MAELoss: 0.057, Val. MSELoss: 0.00640, Val. MAELoss: 0.074
Epoch: 04, Train MSELoss: 0.00448, Train MAELoss: 0.052, Val. MSELoss: 0.00098, Val. MAELoss: 0.023
Epoch: 05, Train MSELoss: 0.00383, Train MAELoss: 0.048, Val. MSELoss: 0.00219, Val. MAELoss: 0.039
Epoch: 06, Train MSELoss: 0.00352, Train MAELoss: 0.046, Val. MSELoss: 0.00496, Val. MAELoss: 0.066
Epoch: 07, Train MSELoss: 0.00332, Train MAELoss: 0.045, Val. MSELoss: 0.00100, Val. MAELoss: 0.025
Epoch: 08, Train MSELoss: 0.00325, Train MAELoss: 0.044, Val. MSELoss: 0.00197, Val. MAELoss: 0.037
Epoch: 09, Train MSELoss: 0.00270, Train MAELoss: 0.040, Val. MSELoss: 0.00279, Val. MAELoss: 0.048
Epoch: 10, Train MSELoss: 0.00268, Train MAELoss: 0.040, Val. MSELoss: 0.00059, Val. MAELoss: 0.018
Epoch: 491, Train MSELoss: 0.00048, Train MAELoss: 0.015, Val. MSELoss: 0.00030, Val. MAELoss: 0.012
Epoch: 492, Train MSELoss: 0.00046, Train MAELoss: 0.015, Val. MSELoss: 0.00032, Val. MAELoss: 0.013
Epoch: 493, Train MSELoss: 0.00046, Train MAELoss: 0.015, Val. MSELoss: 0.00034, Val. MAELoss: 0.014
Epoch: 494, Train MSELoss: 0.00048, Train MAELoss: 0.015, Val. MSELoss: 0.00049, Val. MAELoss: 0.016
Epoch: 495, Train MSELoss: 0.00047, Train MAELoss: 0.015, Val. MSELoss: 0.00025, Val. MAELoss: 0.011
Epoch: 496, Train MSELoss: 0.00047, Train MAELoss: 0.015, Val. MSELoss: 0.00025, Val. MAELoss: 0.011
Epoch: 497, Train MSELoss: 0.00047, Train MAELoss: 0.015, Val. MSELoss: 0.00025, Val. MAELoss: 0.012
Epoch: 498, Train MSELoss: 0.00046, Train MAELoss: 0.015, Val. MSELoss: 0.00023, Val. MAELoss: 0.011
Epoch: 499, Train MSELoss: 0.00046, Train MAELoss: 0.015, Val. MSELoss: 0.00021, Val. MAELoss: 0.011
Epoch: 500, Train MSELoss: 0.00046, Train MAELoss: 0.015, Val. MSELoss: 0.00021, Val. MAELoss: 0.010
上述代码主要进行了模型的训练和评估过程,并记录了每个 epoch 的训练和验证集上的均方误差损失(MSE Loss)
和平均绝对误差损失(MAE Loss)
。这里仅展示了首尾各10个epoch,我们可以清晰的看到loss一直在降低,还有下降的趋势。
6.3 绘制损失曲线
fig, ax = plt.subplots(ncols=1, nrows=2, figsize=(10, 12))
# MSE 损失曲线
ax[0].plot(train_mselosses, label='Train MSELoss')
ax[0].plot(valid_mselosses, label='Valid MSELoss')
ax[0].set_title('MSE Loss Curve', fontdict={'family': 'Times New Roman', 'fontsize': 16, 'fontweight': 'bold', 'color': 'blue'})
ax[0].set_xlabel('Iterations', fontdict={'family': 'Times New Roman', 'fontsize': 14})
ax[0].set_ylabel('MSE', fontdict={'family': 'Times New Roman', 'fontsize': 14})
ax[0].legend(prop={'family': 'Times New Roman'})
ax[0].set_yscale("log") # 如果需要对数刻度
# MAE 损失曲线
ax[1].plot(train_maelosses, label='Train MAELoss')
ax[1].plot(valid_maelosses, label='Valid MAELoss')
ax[1].set_title('MAE Loss Curve', fontdict={'family': 'Times New Roman', 'fontsize': 16, 'fontweight': 'bold', 'color': 'blue'})
ax[1].set_xlabel('Iterations', fontdict={'family': 'Times New Roman', 'fontsize': 14})
ax[1].set_ylabel('MAE', fontdict={'family': 'Times New Roman', 'fontsize': 14})
ax[1].legend(prop={'family': 'Times New Roman'})
ax[1].set_yscale("log") # 如果需要对数刻度
plt.tight_layout()
plt.show()
7. 模型评估与可视化
7.1 构建预测函数
定义预测函数 prediction
方便调用
# 定义 prediction函数
def prediction(model, iterator):
all_targets = []
all_predictions = []
model.eval()
with torch.no_grad():
for batch in iterator:
inputs, targets = batch
predictions = model(inputs)
all_targets.extend(targets.numpy())
all_predictions.extend(predictions.numpy())
return all_targets, all_predictions
这段代码定义了一个名为 prediction
的函数,其主要目的是使用给定的模型对输入数据进行预测,并收集所有的目标值和预测值。
7.2 验证集预测
# 模型预测
y_true, y_pred = prediction(model, valid_loader)
y_true_denormalized = scaler.inverse_transform(y_true).ravel()
y_pred_denormalized = scaler.inverse_transform(y_pred).ravel()
# 计算标准误差 (Standard Deviation)
standard_dev = np.std(y_pred_denormalized, ddof=1) / np.sqrt(len(y_pred_denormalized))
# 根据正态分布的特性,计算z分数
# 对于95%的置信水平,z分数约为1.96(这是基于标准正态分布的双侧区间)
z_score = 1.96
# 计算置信区间的上下界
lower_bound = y_pred_denormalized - z_score * standard_dev
upper_bound = y_pred_denormalized + z_score * standard_dev
plt.figure(figsize=(10,6))
x_coords = np.arange(len(y_pred_denormalized))
plt.plot(y_true_denormalized, label='Actual Values')
plt.plot(y_pred_denormalized, label='Predicted Values')
plt.fill_between(x=x_coords,
y1=lower_bound, y2=upper_bound,
color='pink', alpha=0.3, label='Confidence Interval')
plt.title('Comparison of validation set prediction results',
fontdict={'family': 'Times New Roman',
'fontsize': 16, 'fontweight': 'bold', 'color': 'blue'})
plt.ylabel('Temp', fontdict={'family': 'Times New Roman', 'fontsize': 14})
plt.legend(prop={'family': 'Times New Roman'})
plt.show()
7.3 回归拟合图
使用 regplot()
函数绘制数据图,拟合预测值与真实值的线性回归图。
plt.figure(figsize=(5, 5), dpi=100)
sns.regplot(x=y_true_denormalized, y=y_pred_denormalized, scatter=True, marker="*", color='orange',line_kws={'color': 'red'})
plt.show()
7.4 评估指标
以下代码使用了一些常见的评估指标:平均绝对误差(MAE)、平均绝对百分比误差(MAPE)、均方误差(MSE)、均方根误差(RMSE)和决定系数(R²)来衡量模型预测的性能。这里我们将通过调用 sklearn.metrics
模块中的 mean_absolute_error
mean_absolute_percentage_error
mean_squared_error
root_mean_squared_error
r2_score
函数来对模型的预测效果进行评估。
mae = mean_absolute_error(targets, predictions)
print(f"MAE: {mae:.4f}")
mape = mean_absolute_percentage_error(targets, predictions)
print(f"MAPE: {mape * 100:.4f}%")
mse = mean_squared_error(targets, predictions)
print(f"MSE: {mse:.4f}")
rmse = root_mean_squared_error(targets, predictions)
print(f"RMSE: {rmse:.4f}")
r2 = r2_score(targets, predictions)
print(f"R²: {r2:.4f}")
MAE: 0.0105
MAPE: 6.1967%
MSE: 0.0002
RMSE: 0.0147
R²: 0.9719