目录
前言
一、LSTM简介
1.1 LSTM的本质
1.2 LSTM的提出
1.3 LSTM的原理
1.3.1 RNN原理介绍
1.3.2 LSTM原理介绍
二、前期准备
2.1 导入库、设置GPU
2.2 导入数据
2.3 构建数据集
2.3.1 数据集预处理
2.3.2 设置X,y
2.3.3 缺失值检测
2.3.4 划分数据集
三、模型训练
3.1 构建模型
3.1.1 nn.LSTM()函数详解
3.1.2 模型搭建
3.2 训练函数
3.3 测试函数
3.4 正式训练
四、模型评估
4.1 Loss图
4.2 调用模型进行预测
4.3 R^2值评估
五、拔高尝试
总结
前言
🍨 本文为[🔗365天深度学习训练营](https://mp.weixin.qq.com/s/0dvHCaOoFnW8SCp3JpzKxg) 中的学习记录博客
🍖 原作者:[K同学啊](https://mtyjkh.blog.csdn.net/)
说在前面
本周目标:对时序数据进行预测,了解LSTM是什么,并使用其构建一个完整的程序,并使最后的决定系数R^2达到0.83;拔高目标——使用第1~8个时刻的数据预测第9~10个时刻的温度数据
我的环境:Python3.8、Pycharm2020、torch1.12.1+cu113
数据来源:[K同学啊](https://mtyjkh.blog.csdn.net/)
一、LSTM简介
LSTM全称为长短期记忆网络(Long Short Term Memeory networks),是一种特殊的RNN,能够
析任务而创建的。LSTM由Hochreiter & Schmidhuber(1997)提出,许多研究者进行一系列的工作对其改进并使之发扬光大。LSTM在许多问题上效果非常好,现在被广泛使用。
1.1 LSTM的本质
RNN面临的问题:RNN在处理长序列时面临的主要问题——短时记忆和梯度消失/爆炸
1)短时记忆:
问题:RNN在处理长序列时,由于信息的传递是通过隐藏状态进行的,随着时间的推移,较早时间步的信息可能会在传递到后面的时间步时逐渐消失或被覆盖。
影响:导致RNN难以捕捉和利用序列中的长期依赖关系,从而限制其在处理复杂任务时的性能
2)梯度消失/梯度爆炸
问题:在RNN的反向传播过程中,梯度会随着时间步的推移而逐渐消失(变得非常小)或爆炸(变得非常大)
影响:梯度消失使得RNN在训练时难以学习到长期依赖关系,因为较早时间步的梯度信息在反向传播到初始层时几乎为零。梯度爆炸则可能导致训练过程不稳定,权重更新过大,甚至导致数值溢出。
1.2 LSTM的提出
LSTM是一种RNN的辩题,解决了传统RNN在处理长序列数据时的问题,特别是在面对长时间滞后关系时容易出现的梯度消失或梯度爆炸问题。
LSTM通过引入“门”的结构,包括输入门、遗忘门和输出门,以及细胞状态,解决了一下问题:
1)长期依赖问题:在传统的RNN中,当序列非常长时,网络往往会遇到梯度消失或梯度爆炸的问题,导致难以捕捉到序列中长距离的依赖关系。LSTM通过细胞状态来传递信息,避免了梯度消失或爆炸,从而更好地捕捉长期依赖关系;
2)遗忘和记忆:LSTM的门控结构允许网络选择性地忘记或记住某些信息,这使得网络可以更好地处理序列中的噪声或不重要的信息,同时保留重要的长期依赖关系;
3)梯度传播:LSTM通过门控结构,似的梯度可以在时间更好地传播,从而使得网络的训练更加稳定和高效。
所以LSTM通过引入门控结构和细胞状态,有效地解决了传统RNN在处理长序列数据时遇到的梯度消失、长期依赖等问题,使得其在语言建模、时间序列预测、机器翻译等任务中取得显著的进展。
1.3 LSTM的原理
1.3.1 RNN原理介绍
1)隐藏状态的传递
过程描述:在处理序列数据时,RNN将前一时间步的隐藏状态传递给下一个时间步
作用:隐藏状态充当了神经网络的“记忆”,它包含了网络之前所见过的数据的相关信息
重要性:这种传递机制使得RNN能够捕捉序列中的时序依赖关系
将隐藏状态传递给下一个时间步
2)隐藏状态的计算
细胞结构:RNN的一个细胞接收当前时间步的输入和前一时间步的隐藏状态
组合方式:当前输入和先前隐藏状态被组合成一个向量,这个向量融合了当前和先前的信息
激活函数:组合后的向量经过一个tanh激活函数的处理,输出新的隐藏状态。这个新的隐藏状态既包含了当前输入的信息,也包含了之前所有输入的历史信息。
tanh激活函数(区间-1~1)
输出:新的隐藏状态被输出,并传递给下一个时间步,继续参与序列的处理过程
RNN的细胞结构和运算
1.3.2 LSTM原理介绍
原始RNN的隐藏层只有一个状态,即h,它对于短期的输入非常敏感。那么如果我们再增加一个门(gate)机制用于控制特征的流通和损失,即c,让它来保存长期的状态,这就是长短时记忆网络(Long Short Term Memory,LSTM)
新增加的状态c称为单元状态,把LSTM按照时间维度展开:
在t时刻,LSTM的输入有三个:当前时刻网络的输出值、上一时刻LSTM的输出值、以及上一时刻的记忆单元向量;LSTM的输出有两个:当前时刻的隐藏状态向量、和当前时刻的记忆单元状态向量
1)遗忘门
叫做遗忘门,表示的哪些特征被用于计算。是一个向量,向量的每个元素均位于(0~1)范围内。通常我们使用sigmoid作为激活函数,sigmoid的输出是一个介于(0~1)区间内的值,但是当观察一个训练好的LSTM时,会发现门的值绝大多数都非常接近0或者1,其余的值少之又少。
作用:决定哪些旧信息应该从记忆单元中遗忘或移除
组成:遗忘门仅由一个sigmoid激活函数组成
sigmoid激活函数(区间0~1)
2)输入门
作用:决定哪些新信息应该被添加到记忆单元中
组成:输入门由一个sigmoid激活函数和一个tanh激活函数组成。sigmoid函数决定哪些信息是重要的,而tanh函数则生成新的候选信息
运算:输入门的输出与候选信息相乘,得到的结果将在记忆单元更新时被考虑
输入门(sigmoid激活函数+tanh激活函数)
3)输出门
作用:决定记忆单元中的哪些信息应该被输出到当前时间步的隐藏状态中
组成:输出门同样由一个sigmoid激活函数和一个tanh激活函数组成。sigmoid函数决定哪些信息应该被输出,而tanh函数则处理记忆单元的状态以准备输出
运算:sigmoid函数的输出与经过tanh函数处理的记忆单元状态相乘,得到的结果即为当前时间步的隐藏状态
输出门(sigmoid激活函数+tanh激活函数)
二、前期准备
2.1 导入库、设置GPU
代码如下:
import torch.nn.functional as F
import numpy as np
import pandas as pd
import torch
from torch import nn
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import MinMaxScaler
from torch.utils.data import TensorDataset, DataLoader
from sklearn import metrics
import warnings
warnings.filterwarnings("ignore")device = "cuda" if torch.cuda.is_available() else "cpu"
print("Using {} device".format(device))
2.2 导入数据
代码如下:
data = pd.read_csv("woodpine2.csv")
#data.to(device)
print(data.head())#数据集可视化
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:]
print(dataFrame)
打印输出:
Time Tem1 CO 1 Soot 1
0 0.000 25.0 0.0 0.0
1 0.228 25.0 0.0 0.0
2 0.456 25.0 0.0 0.0
3 0.685 25.0 0.0 0.0
4 0.913 25.0 0.0 0.0Tem1 CO 1 Soot 1
0 25.0 0.000000 0.000000
1 25.0 0.000000 0.000000
2 25.0 0.000000 0.000000
3 25.0 0.000000 0.000000
4 25.0 0.000000 0.000000
... ... ... ...
5943 295.0 0.000077 0.000496
5944 294.0 0.000077 0.000494
5945 292.0 0.000077 0.000491
5946 291.0 0.000076 0.000489
5947 290.0 0.000076 0.000487[5948 rows x 3 columns]
2.3 构建数据集
数据集介绍:数据集中提供了火灾温度(Tem1)、一氧化碳浓度(CO 1)、烟雾浓度(Soot 1)随着时间变化的数据
2.3.1 数据集预处理
代码如下:
#数据集处理
dataFrame = data.iloc[:, 1:].copy()
sc = MinMaxScaler(feature_range=(0, 1))for i in ['CO 1', 'Soot 1', 'Tem1']:dataFrame[i] = sc.fit_transform(dataFrame[i].values.reshape(-1, 1))print(dataFrame.shape)
打印输出:(5948, 3)
2.3.2 设置X,y
代码如下:
#设置X、y
width_X = 8
width_y = 1
##取前8个时间段的Tem11、 CO 1、Soot 1为x,第9个时间段的Tem1为y
X = []
y = []in_start = 0
for _, _ in data.iterrows():in_end = in_start + width_Xout_end = in_end + width_yif 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 += 1X = np.array(X)
y = np.array(y).reshape(-1, 1, 1)
print(X.shape, y.shape)
打印输出:(5939, 8, 3) (5939, 1, 1)
2.3.3 缺失值检测
代码如下:
##检查数据集中是否有空值
print(np.any(np.isnan(X)))
print(np.any(np.isnan(y)))
打印输出:
False
False
2.3.4 划分数据集
代码如下:
#划分数据集
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)
print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)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)
打印输出:
torch.Size([5000, 8, 3]) torch.Size([5000, 1, 1]) torch.Size([939, 8, 3]) torch.Size([939, 1, 1])
三、模型训练
3.1 构建模型
3.1.1 nn.LSTM()函数详解
3.1.2 模型搭建
代码如下:
#模型训练
##构建模型
class model_lstm(nn.Module):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):out, hidden1 = self.lstm0(x)out, _ = self.lstm1(out, hidden1)out = self.fc0(out)return out[:, -1:, :] #取两个预测值,否则经过lstm会得到8*2个预测model = model_lstm()
print(model)#查看模型的输出数据集格式
print(model(torch.rand(30,8,3)).shape)
打印输出:
torch.Size([30, 1, 1])
3.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() #梯度归零loss.backward() #反向传播opt.step() #每一步自动更新#记录losstrain_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_batchesreturn train_loss
3.3 测试函数
代码如下:
##定义测试函数
def test(dataloader, model, loss_fn):size = len(dataloader.dataset)num_batches = len(dataloader)test_loss = 0with torch.no_grad():for x, y in dataloader:x, y = x.to(device), y.to(device)y_pred = model(x)loss = loss_fn(y_pred, y)test_loss += loss.item()test_loss /= num_batchesreturn test_loss
3.4 正式训练
代码如下:
##正式训练模型
device = "cuda" if torch.cuda.is_available() else "cpu"
print(device)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)
best_val = [0, 1e5]
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)if best_val[1] > epoch_test_loss:best_val = [epoch, epoch_test_loss]best_model_wst = copy.deepcopy(model.state_dict())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)
训练过程如下:
截取部分
四、模型评估
4.1 Loss图
代码如下:
#模型评估
##LOSS图
plt.figure(figsize=(5, 3),dpi=120)
plt.plot(train_loss, label='LSTM Training Loss')
plt.plot(test_loss, label='LSTM Validation Loss')plt.title('Training and Validation loss')
plt.legend()
plt.show()
打印输出:
4.2 调用模型进行预测
代码如下:
##调用模型进行预测
model.load_state_dict(best_model_wst)
model.to("cpu")
predicted_y_lstm = sc.inverse_transform(model(X_test).detach().numpy().reshape(-1, 1)) #对经过LSTM模型预测得到的数据进行逆值转换,将其转换回原始的数据空间中
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()
输出如下:
4.3 R^2值评估
代码如下:
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)
打印输出:
均方根误差: 6.80718
R2: 0.84027
五、拔高尝试
使用第1~8个时刻的数据预测第9~10个时刻的温度数据,实际上也是把原来的单步预测修改为多步预测,主要改动部分在模型输出部分
这是原始模型
修改后的代码
预测代码如下:
#拔高练习
test_1 = torch.tensor(dataFrame.iloc[:8 , ].values,dtype=torch.float32).reshape(1,-1,3)
pred_ = model(test_1)
pred_ = np.round(sc.inverse_transform(pred_.detach().numpy().reshape(1,-1)).reshape(-1),2) #NumPy中的round函数将结果四舍五入保留两位小数
real_tem = data.Tem1.iloc[:2].values
print(f"第9~10时刻的温度预测:", pred_)
print("第9~10时刻的真实温度:", real_tem)
打印输出:
第9~10时刻的温度预测: [33.61 32.73]
第9~10时刻的真实温度: [25. 25.]
总结
- 学习对比了LSTM与RNN的区别,以及LSTM的优势
- 实际操作搭建了LSTM模型,运行也达到了预期结果
- 对于时序数据导出以及处理步骤
- 拔高部分实现了将LSTM修改为多步输出