R3:LSTM-火灾温度预测
- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
一、实验目的:
- 了解LSTM是什么,并使用其构建一个完整的程序
- R2达到0.83
拔高:使用第1-8个时刻的数据预测第9-10个时刻的温度数据
二、实验环境:
- 语言环境:python 3.8
- 编译器:Jupyter notebook
- 深度学习环境:Pytorch
- torch==2.4.0+cu124
- torchvision==0.19.0+cu124
三、长短期记忆网络(LSTM)
长短期记忆网络(Long Short-Term Memory,LSTM)是一种特殊的循环神经网络(Recurrent Neural Network,RNN)结构,主要用于处理序列数据,如时间序列数据、自然语言处理任务等。与传统的 RNN 相比,LSTM 能够更好地处理长期依赖问题,避免了梯度消失和梯度爆炸的问题,从而可以有效地学习长期的时间依赖关系。
LSTM 的核心思想是通过引入特殊的记忆单元和三个门控机制(输入门、遗忘门、输出门)来控制信息的流动和保存。记忆单元可以存储长期的信息,而门控机制可以根据当前的输入和上一时刻的状态来决定哪些信息应该被保存、哪些信息应该被遗忘以及哪些信息应该被输出。这样,LSTM 能够有选择地记住重要的信息,忘记不重要的信息,并在需要的时候输出合适的信息。
回顾一下,所有的RNN都是如下图所示的结构,把RNN看成一个黑盒子的话,它会有一个“隐状态”来“记忆”一些重要的信息。当前时刻的输出除了受当前输入影响之外,也受这个“隐状态”影响。并且在这个时刻结束时,除了输出之外,这个“隐状态”的内容也会发生变化——它“记忆”了新的信息同时有“遗忘”了一些旧的信息。
LSTM也是这样的结构,但相比于原始的RNN,它的内部结构更加复杂。普通的RNN就是一个全连接的层,而LSTM有四个用于控制”记忆”和运算的门,如下图所示。
在上图中,每条有向边代表向量,黄色的方框代表神经网络层,粉色的圆圈代表逐点运算(Pointwise Operation)。两条边的合并代表向量的拼接(concatenation),边的分叉代表把一个向量复制到两个地方。
三个门的细节
-
遗忘门(Forget Gate):
- 作用:决定从细胞状态中丢弃哪些信息。
- 计算方式:遗忘门接收当前输入 x t x_t xt 和上一时刻的隐藏状态 h t − 1 h_{t - 1} ht−1,通过一个激活函数(通常是 sigmoid 函数)将它们映射到一个 0 到 1 之间的值。这个值决定了上一时刻的细胞状态 C t − 1 C_{t - 1} Ct−1 中有多少信息应该被遗忘。
- 公式: f t = σ ( W f ⋅ [ h t − 1 , x t ] + b f ) f_t=\sigma(W_f\cdot[h_{t - 1},x_t]+b_f) ft=σ(Wf⋅[ht−1,xt]+bf),其中 W f W_f Wf 是遗忘门的权重矩阵, b f b_f bf 是偏置项, σ \sigma σ 是 sigmoid 函数。
-
输入门(Input Gate):
- 作用:决定哪些新的信息应该被添加到细胞状态中。
- 计算方式:输入门也接收当前输入 x t x_t xt 和上一时刻的隐藏状态 h t − 1 h_{t - 1} ht−1,通过 sigmoid 函数和一个 tanh 函数分别计算两个值。sigmoid 函数的输出决定哪些信息应该被更新,tanh 函数的输出生成一个候选的细胞状态 C ~ t \tilde{C}_t C~t。
- 公式: i t = σ ( W i ⋅ [ h t − 1 , x t ] + b i ) i_t=\sigma(W_i\cdot[h_{t - 1},x_t]+b_i) it=σ(Wi⋅[ht−1,xt]+bi), C ~ t = tanh ( W C ⋅ [ h t − 1 , x t ] + b C ) \tilde{C}_t=\tanh(W_C\cdot[h_{t - 1},x_t]+b_C) C~t=tanh(WC⋅[ht−1,xt]+bC),其中 W i W_i Wi 和 W C W_C WC 分别是输入门和候选细胞状态的权重矩阵, b i b_i bi 和 b C b_C bC 是偏置项。
-
输出门(Output Gate):
- 作用:决定哪些信息应该被输出作为当前时刻的隐藏状态。
- 计算方式:输出门同样接收当前输入 x t x_t xt 和上一时刻的隐藏状态 h t − 1 h_{t - 1} ht−1,通过 sigmoid 函数和一个 tanh 函数分别计算两个值。sigmoid 函数的输出决定哪些信息应该被输出,tanh 函数对当前时刻的细胞状态 C t C_t Ct 进行处理。最后,将这两个值相乘得到当前时刻的隐藏状态 h t h_t ht。
- 公式: o t = σ ( W o ⋅ [ h t − 1 , x t ] + b o ) o_t=\sigma(W_o\cdot[h_{t - 1},x_t]+b_o) ot=σ(Wo⋅[ht−1,xt]+bo), h t = o t ∗ tanh ( C t ) h_t = o_t*\tanh(C_t) ht=ot∗tanh(Ct),其中 W o W_o Wo 是输出门的权重矩阵, b o b_o bo 是偏置项。
四、前期准备
import torch.nn.functional as F
import numpy as np
import pandas as pd
import torch
from torch import nn
1. 导入数据
每个数据的标签含义:
- Time:时间
- Tem1:火灾温度
- CO1:一氧化碳浓度
- Soot1:烟雾浓度
data = pd.read_csv("woodpine2.csv") data = pd.read_csv(woodpine2.csv)(woodpine2.csv)
data
2. 数据集可视化
import matplotlib.pyplot as plt
import seaborn as sns
plt.rcParams['savefig.dpi'] = 500 #图片像素
plt.rcParams['figure.dpi'] = 500 #分辨率
fig, ax =plt.subplots(1,3,constrained_layout=True, figsize=(14, 3))
sns.lineplot(data=data["Tem1"], ax=ax[0])
sns.lineplot(data=data["CO 1"], ax=ax[1])
sns.lineplot(data=data["Soot 1"], ax=ax[2])
plt.show()
dataFrame = data.iloc[:,1:]
dataFrame
五、构建数据集
对特定列的数据进行归一化处理,对dataFrame
中指定的列(‘CO 1’、‘Soot 1’、‘Tem1’)进行归一化处理,将这些列的值缩放到0到1之间,以便在后续处理或建模时,这些特征的数值范围保持一致。
1. 数据集预处理
导入MinMaxScaler
,这是一个用于将数据按最小最大值进行缩放的类。MinMaxScaler
将数据缩放到给定的范围内(默认为0到1)
data.iloc[:, 1:]
表示从data
数据框中提取除第一列之外的所有列(假设第一列可能是索引或无关列)。然后,用.copy()
方法创建一个新的数据框dataFrame
,以免修改原始数据。
初始化MinMaxScaler
,指定特征缩放的范围为0到1。这个缩放器会将数据的最小值映射为0,最大值映射为1,中间的数值按比例缩放。
from sklearn.preprocessing import MinMaxScaler
dataFrame = data.iloc[:,1:].copy()
sc = MinMaxScaler(feature_range=(0, 1)) #将数据归一化,范围是0到1
for i in ['CO 1', 'Soot 1', 'Tem1']:
dataFrame[i] = sc.fit_transform(dataFrame[i].values.reshape(-1, 1))
dataFrame.shape
(5948, 3)
2. 设置X、y
将时间序列数据转换为模型训练所需的输入(X)和输出(y),以便能够在模型中进行训练。
取前8个时间段的Tem1、CO 1、Soot 1为X,而第9、10个时间段的Tem1为y。
width_X = 8
width_y = 1
X = []
y = []
in_start = 0
for _, _ in data.iterrows():
in_end = in_start + width_X
out_end = in_end + width_y
if out_end < len(dataFrame):
X_ = np.array(dataFrame.iloc[in_start:in_end , ])
y_ = np.array(dataFrame.iloc[in_end :out_end, 0])
X.append(X_)
y.append(y_)
in_start += 1
X = np.array(X)
y = np.array(y).reshape(-1,1,1)
X.shape, y.shape
检查数据集中是否有空值
print(np.any(np.isnan(X)))
print(np.any(np.isnan(y)))
3. 划分数据集
X_train = torch.tensor(np.array(X[:5000]), dtype=torch.float32)
y_train = torch.tensor(np.array(y[:5000]), dtype=torch.float32)
X_test = torch.tensor(np.array(X[5000:]), dtype=torch.float32)
y_test = torch.tensor(np.array(y[5000:]), dtype=torch.float32)
X_train.shape, y_train.shape
from torch.utils.data import TensorDataset, DataLoader
train_dl = DataLoader(TensorDataset(X_train, y_train),
batch_size=64,
shuffle=False)
test_dl = DataLoader(TensorDataset(X_test, y_test),
batch_size=64,
shuffle=False)
六、模型训练
1. 构建LSTM模型
# 构建模型
class model_lstm(nn.Module): classmodel_lstm(classmodel_lstm):
def __init__(self):
super(model_lstm, self).__init__()
self.lstm0 = nn.LSTM(input_size=3 ,hidden_size=320,
num_layers=1, batch_first=True)
self.lstm1 = nn.LSTM(input_size=320 ,hidden_size=320,
num_layers=1, batch_first=True)
self.fc0 = nn.Linear(320, 1)
def forward(self, x): Defforward(self,x):
out, hidden1 = self.lstm0(x)
out, _ = self.lstm1(out, hidden1)
out = self.fc0(out)
return out[:, -1:, :] #取2个预测值,否则经过lstm会得到8*2个预测
model = model_lstm()
model model模型
model(torch.rand(30,8,3)).shape
2. 定义训练/测试函数
# 定义训练函数
import copy
def train(train_dl, model, loss_fn, opt, lr_scheduler=None):
size = len(train_dl.dataset)
num_batches = len(train_dl)
train_loss = 0 # 初始化训练损失和正确率
for x, y in train_dl:
x, y = x.to(device), y.to(device)
# 计算预测误差
pred = model(x) # 网络输出
loss = loss_fn(pred, y) # 计算网络输出和真实值之间的差距
# 反向传播
opt.zero_grad() # grad属性归零
loss.backward() # 反向传播
opt.step() # 每一步自动更新
# 记录loss
train_loss += loss.item()
if lr_scheduler is not None:
lr_scheduler.step()
print("learning rate = {:.5f}".format(opt.param_groups[0]['lr']), end=" ")
train_loss /= num_batches
return train_loss
# 定义测试函数
def test (dataloader, model, loss_fn):
size = len(dataloader.dataset) # 测试集的大小
num_batches = len(dataloader) # 批次数目
test_loss = 0
# 当不进行训练时,停止梯度更新,节省计算内存消耗
with torch.no_grad():
for x, y in dataloader:
x, y = x.to(device), y.to(device)
# 计算loss
y_pred = model(x)
loss = loss_fn(y_pred, y)
test_loss += loss.item()
test_loss /= num_batches
return test_loss
3. 正式训练
#设置GPU训练
device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
device
X_test = X_test.to(device)
#训练模型
model = model_lstm()
model = model.to(device)
loss_fn = nn.MSELoss() # 创建损失函数
learn_rate = 1e-1 # 学习率
opt = torch.optim.SGD(model.parameters(),lr=learn_rate,weight_decay=1e-4)
epochs = 50
train_loss = []
test_loss = []
lr_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(opt,epochs, last_epoch=-1)
for epoch in range(epochs):
model.train()
epoch_train_loss = train(train_dl, model, loss_fn, opt, lr_scheduler)
model.eval()
epoch_test_loss = test(test_dl, model, loss_fn)
train_loss.append(epoch_train_loss)
test_loss.append(epoch_test_loss)
template = ('Epoch:{:2d}, Train_loss:{:.5f}, Test_loss:{:.5f}')
print(template.format(epoch+1, epoch_train_loss, epoch_test_loss))
print("="*20, 'Done', "="*20)
! [](https://i—blog.csdnimg.cn/direct/faa55e40e4b146d19d17b47c751b5929.png)
七、模型评估
1. LOSS图
import matplotlib.pyplot as plt importmatplotlib.pyplotasplt
plt.figure(figsize=(5, 3),dpi=120) 图(图大小=(5.3),dpi=120)
plt.plot(train_loss , label='LSTM Training Loss') plt.plot(train_loss,Label ='LSTM训练损失')
plt.plot(test_loss, label='LSTM Validation Loss') plt.plot(test_loss,tag ="LSTM验证丢失")
plt.title('Training and Validation Loss') plt.title(“培训和验证损失”)
plt.legend() legend()
plt.show() plt.show)搜索结果
2. 调用模型进行预测
# 测试集输入模型进行预测
# predicted_y_lstm = sc.inverse_transform(model(X_test).detach().numpy().reshape(-1,1))
predicted_y_lstm = sc.inverse_transform(model(X_test).detach().cpu().numpy().reshape(-1,1))
y_test_1 = sc.inverse_transform(y_test.reshape(-1,1))
y_test_one = [i[0] for i in y_test_1]
predicted_y_lstm_one = [i[0] for i in predicted_y_lstm]
plt.figure(figsize=(5, 3),dpi=120)
# 画出真实数据和预测数据的对比曲线
plt.plot(y_test_one[:2000], color='red', label='real_temp')
plt.plot(predicted_y_lstm_one[:2000], color='blue', label='prediction')
plt.title('Title')
plt.xlabel('X')
plt.ylabel('Y')
plt.legend()
plt.show()
原代码运行后提示:RuntimeError: Input and parameter tensors are not at the same device, found input tensor at cpu and parameter tensor at cuda:0
这个错误是因为输入数据和模型参数不在同一设备上导致的。解决方法是确保你的输入数据和模型在同一设备上。如果你的模型在 GPU 上(如 cuda:0),那么你的输入数据也应该在 GPU 上。如果你的模型在 CPU 上,输入数据也应该在 CPU 上。
先将张量移到 CPU 上,然后再转换为 NumPy 数组即可解决问题。已将原代码注释掉了。
3. R2值评估
from sklearn import metrics
"""
RMSE :均方根误差 -----> 对均方误差开方
R2 :决定系数,可以简单理解为反映模型拟合优度的重要的统计量
"""
RMSE_lstm = metrics.mean_squared_error(predicted_y_lstm_one, y_test_1)**0.5
R2_lstm = metrics.r2_score(predicted_y_lstm_one, y_test_1)
print('均方根误差: %.5f' % RMSE_lstm)
print('R2: %.5f' % R2_lstm)
均方根误差: 7.08231
R2: 0.82297
八、总结
1. 数据方面
- 如之前提到的进行数据增强、特征工程和数据清洗操作,以提高数据质量和多样性。
2. 模型方面
- 增加模型复杂度:可以尝试增加 LSTM 层的层数,比如将
num_layers
参数设置为大于 1 的值,或者尝试添加更多的 LSTM 模块串联,以提高模型的表达能力,更好地捕捉数据中的复杂模式。 - 引入其他类型的层:考虑在模型中引入一些其他类型的层,如卷积层(如果数据具有空间结构)、注意力机制层等,以增强模型对关键信息的关注和提取能力。
- 调整全连接层:可以尝试调整全连接层的参数,增加或减少神经元的数量,或者添加多个全连接层来进一步处理 LSTM 的输出。
3. 训练方面
- 优化学习率策略:虽然已经使用了余弦退火学习率调整策略,但可以进一步调整其参数,如调整初始学习率、最小学习率等,以找到更适合模型训练的学习率变化曲线。
- 尝试不同的优化器:除了 SGD,可以尝试其他优化器,如 Adam、Adagrad 等,观察是否能提高模型的训练效果和收敛速度。
- 增加早停机制:设置一个验证集上的性能指标阈值,当模型在验证集上的性能在一定轮数内没有提升时,停止训练,防止过拟合并节省训练时间。
- 增加训练数据量:如果可能的话,收集更多的训练数据,以提高模型的泛化能力。
- 调整权重衰减:尝试不同的权重衰减值,以找到在防止过拟合和保持模型性能之间的平衡。
.
以下是一种使用 LSTM 实现用前 8 个时刻数据预测第 9 和第 10 个时刻温度数据的方法,结合你提供的代码框架进行调整:
一、数据准备
假设你的原始数据为一个时间序列数据集,将其整理成适合 LSTM 输入的格式。每个样本应该包含连续的 8 个时刻的数据作为输入特征,对应的第 9 和第 10 个时刻的数据作为标签。
例如,假设有一个时间序列 data = [t1, t2, t3,..., tN]
,可以将其转换为以下形式的样本和标签对:
X = [[t1, t2, t3, t4, t5, t6, t7, t8], [t2, t3, t4, t5, t6, t7, t8, t9],...]
y = [[t9, t10], [t10, t11],...]
二、模型调整
- 修改模型输入和输出形状:
class model_lstm(nn.Module):
def __init__(self):
super(model_lstm, self).__init__()
self.lstm0 = nn.LSTM(input_size=8, hidden_size=320, num_layers=1, batch_first=True)
self.lstm1 = nn.LSTM(input_size=320, hidden_size=320, num_layers=1, batch_first=True)
self.fc0 = nn.Linear(320, 2) # 输出 2 个时刻的温度预测
def forward(self, x):
out, hidden1 = self.lstm0(x)
out, _ = self.lstm1(out, hidden1)
out = self.fc0(out)
return out
三、训练过程调整
在训练循环中,确保输入数据和标签的对应关系正确。
model = model_lstm()
model = model.to(device)
loss_fn = nn.MSELoss()
learn_rate = 1e-1
opt = torch.optim.SGD(model.parameters(), lr=learn_rate, weight_decay=1e-4)
epochs = 50
train_loss = []
test_loss = []
lr_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(opt, epochs, last_epoch=-1)
for epoch in range(epochs):
model.train()
epoch_train_loss = 0
for X_batch, y_batch in train_dl:
opt.zero_grad()
y_pred = model(X_batch.to(device))
loss = loss_fn(y_pred, y_batch.to(device))
loss.backward()
opt.step()
epoch_train_loss += loss.item()
epoch_train_loss /= len(train_dl)
model.eval()
epoch_test_loss = 0
with torch.no_grad():
for X_test_batch, y_test_batch in test_dl:
y_test_pred = model(X_test_batch.to(device))
test_loss_batch = loss_fn(y_test_pred, y_test_batch.to(device))
epoch_test_loss += test_loss_batch.item()
epoch_test_loss /= len(test_dl)
train_loss.append(epoch_train_loss)
test_loss.append(epoch_test_loss)
template = ('Epoch:{:2d}, Train_loss:{:.5f}, Test_loss:{:.5f}')
print(template.format(epoch + 1, epoch_train_loss, epoch_test_loss))
print("=" * 20, 'Done', "=" * 20)
这样,模型就可以使用前 8 个时刻的数据来预测第 9 和第 10 个时刻的温度数据了。在实际应用中,可以根据具体情况进一步调整模型结构、优化器参数等以提高预测性能。