- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
一、什么是LSTM
1.LSTM的本质
长短时记忆网络(Long Short-Term Memory, LSTM)的本质是一种特殊的循环神经网络(Recurrent Neural Network, RNN),它被设计来解决标准RNN在处理长序列数据时遇到的梯度消失或梯度爆炸问题,这些问题限制了RNN在处理长距离依赖关系时的能力。
LSTM的核心创新在于它引入了所谓的“门控机制”(gates),这些门控机制允许网络学习何时让信息进入或离开网络,以及何时保留或更新长期状态。LSTM包含三个主要门控结构:
- 遗忘门(Forget Gate):决定哪些信息应该从单元状态(cell state)中丢弃。它通过查看前一个隐藏状态和当前输入,输出一个介于0和1之间的值给每个在单元状态中的数。1表示“完全保留这个信息”,而0表示“完全丢弃这个信息”。
- 输入门(Input Gate):决定哪些新的信息将被存储在单元状态中。它由两个部分组成:一个sigmoid函数决定哪些值将要更新,一个tanh函数创建一个新的候选值向量,可以被加到状态中。
- 输出门(Output Gate):决定下一个隐藏状态应该是什么。隐藏状态包含关于过去的输入的信息,输出门通过决定哪些信息应该被输出,来控制这些信息的流动。
LSTM的单元状态可以在长序列中保持相对稳定的信息流,这使得LSTM特别适合于处理需要记忆长期依赖信息的任务,如语音识别、机器翻译、时间序列预测等。
此外,LSTM还通过引入“细胞状态”(cell state)的概念,使得信息可以在网络中流动而不被干扰,从而解决了标准RNN在长序列学习中的稳定性问题。细胞状态在LSTM单元中贯穿始终,只有通过门控结构才能对其进行修改,这使得LSTM能够有效地学习长期依赖关系。
RNN的缺点以及LSTM如何解决这些问题的对比:
RNN的缺点:
- 梯度消失/梯度爆炸:在训练过程中,RNN的权重更新依赖于梯度,而梯度在反向传播过程中可能会指数级减小(消失)或增大(爆炸),这使得网络难以学习长期依赖关系。
- 难以捕捉长期依赖:由于梯度消失的问题,RNN在处理长序列时,很难保留早期信息对当前时刻的影响,导致网络性能下降。
- 状态更新不灵活:在标准的RNN中,状态更新是固定的,即前一个隐藏状态直接加到当前输入上,没有机制来选择性地保留或遗忘信息。
LSTM的提升:
- 门控机制:LSTM通过引入输入门、遗忘门和输出门,提供了更灵活的状态更新机制。这些门控结构可以控制信息的流入、保留和流出,从而有效避免梯度消失/梯度爆炸问题。
- 细胞状态(Cell State):LSTM有一个单独的细胞状态,它贯穿于整个序列,允许信息在长序列中流动而不被干扰。细胞状态可以通过门控结构进行选择性的更新,使得网络能够学习长期依赖关系。
- 长期依赖学习:由于上述机制,LSTM能够更好地捕捉和表示长期依赖关系,这使得它在处理需要记忆长期信息的任务上表现出色。
总的来说,LSTM通过其特殊的网络结构和门控机制,解决了标准RNN在处理长序列数据时的主要问题,提升了网络在处理长期依赖关系时的性能。
2.LSTM的原理
从隐藏状态的产生、传递和计算角度来解释LSTM(长短时记忆网络)的原理,可以分为以下几个步骤:
隐藏状态的产生
- 初始化:在序列的开始,LSTM的隐藏状态通常被初始化为全零向量。
- 遗忘门:在处理序列的每个时间步时,LSTM首先通过遗忘门决定哪些信息应该从细胞状态中丢弃。遗忘门的输出是一个介于0和1之间的向量,表示每个在细胞状态中的信息应该被保留的程度。
- 输入门和候选状态:接着,LSTM通过输入门决定哪些新的信息将被存储在细胞状态中。输入门输出一个介于0和1之间的向量,表示每个候选值应该被添加到细胞状态中的程度。同时,LSTM会生成一个候选值向量,它包含了可能被添加到细胞状态中的新信息。
- 更新细胞状态:细胞状态在当前时间步被更新。LSTM会根据遗忘门的输出和候选值向量来更新细胞状态。
- 输出门:最后,LSTM通过输出门决定细胞状态中的哪些信息应该被输出。输出门的输出是一个介于0和1之间的向量,表示细胞状态中哪些信息应该影响当前时间步的隐藏状态。
隐藏状态的传递
隐藏状态在LSTM中起到了传递序列历史信息的作用。在每个时间步,隐藏状态会根据细胞状态和输入门的输出生成。这个隐藏状态包含了在当前时间步需要传递给后续层的信息,并且会作为下一个时间步的输入的一部分。
隐藏状态的计算
隐藏状态的计算是通过将细胞状态通过一个tanh函数(将值压缩到-1和1之间)然后乘以输出门的输出来生成的。这个计算过程确保了隐藏状态包含了当前时间步的细胞状态中的相关信息,并且可以传递给后续层或下一个时间步。
通过这种方式,LSTM能够有效地处理序列数据,并学习到序列中的长期依赖关系。隐藏状态的产生、传递和计算是LSTM实现这一目标的关键步骤。
二、pytorch实现
1.前期准备工作
import torch.nn.functional as F
import numpy as np
import pandas as pd
import torch
from torch import nn
(1)导入数据
data = pd.read_csv("woodpine2.csv")
print(data)
输出
(2)数据集可视化
sns
是 seaborn
库的简称,这是一个在 Python 中用于数据可视化的库,它基于 matplotlib
库构建,提供了更为美观和高级的绘图样式。seaborn
专门针对统计图表进行了优化,使得创建具有吸引力的、信息丰富的图表变得简单。
下面是这段代码中使用的 seaborn
库的函数及其作用和参数的含义:
sns.lineplot
:- 作用:这个函数用于绘制线形图,非常适合展示数据随时间或其他连续变量的变化趋势。
- 参数:
data
:这个参数接受一个 pandas Series、DataFrame 或数组。在这个例子中,它接受的是data["Tem1"]
、data["CO 1"]
和data["Soot 1"]
,它们应该是 DataFrame 中的列,表示不同的数据集。ax
:这个参数允许你指定一个轴(matplotlib Axes 对象),在这个轴上绘制图形。在提供的代码中,ax=ax[0]
、ax=ax[1]
和ax=ax[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()
输出
这行代码的作用是选择 data
这个 pandas DataFrame 中的所有行和除了第一列之外的所有列。
下面是代码的详细解释:
data
:data
是一个 pandas DataFrame 对象,它包含了原始数据集。.iloc
:这是 pandas DataFrame 提供的一个索引器,用于通过行和列的索引位置进行数据选择。与.loc
不同,.loc
是基于标签的索引器,.iloc
是基于整数位置的索引器。:
:这表示选择所有行。在 Python 中,单独的冒号:
通常用作范围操作符,在这里它表示从起始位置到结束位置的全部范围。由于没有指定起始和结束,所以默认选择所有行。,1:]
:这表示选择除了第一列之外的所有列。在 Python 中,切片操作1:
表示从索引 1 开始,一直到最后一个元素。由于列的索引是从 0 开始的,所以1:
实际上是从第二列开始,直到最后一列。
综上所述,data.iloc[:,1:]
这行代码会选择 data
DataFrame 的所有行和从第二列开始到最后一列的所有列。如果你有一个具有多列的 DataFrame,并且想要排除第一列(比如排除一个索引列或者分类列),这行代码就非常有用。
dataFrame = data.iloc[:,1:]
dataFrame
输出
2、构建数据集
(1)数据集预处理
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import MinMaxScaler
:这行代码从sklearn.preprocessing
模块中导入MinMaxScaler
类。MinMaxScaler
是一个用于数据归一化的工具,它可以将每个特征缩放到一个指定的范围,通常是0到1。
dataFrame = data.iloc[:,1:].copy()
data.iloc[:,1:]
:这部分选择data
DataFrame 的所有行和从第二列开始到最后一列的所有列。.copy()
:这个方法创建了一个新的 DataFrame,它是原始 DataFrame 的一个副本。这是为了防止后续的操作影响到原始的data
DataFrame。
sc = MinMaxScaler(feature_range=(0,1))
sc = MinMaxScaler(feature_range=(0,1))
:创建一个MinMaxScaler
对象,名为sc
。feature_range=(0,1)
参数指定了归一化的范围,即每个特征的值将被缩放到0到1之间。
for i in ['CO 1', 'Soot 1', 'Tem1']:
- 这行代码开始一个循环,循环变量
i
将遍历列表['CO 1', 'Soot 1', 'Tem1']
中的每个元素。这个列表包含了要归一化的列名。
dataFrame[i] = sc.fit_transform(dataFrame[i].values.reshape(-1,1))
dataFrame[i]
:选择 DataFrame 中名为i
的列。.values
:这个属性返回列中的值,作为 NumPy 数组。.reshape(-1,1)
:这个方法改变数组的形状,使其成为一个二维数组,其中-1
表示自动计算行数,1
表示列数。MinMaxScaler
需要二维数组作为输入。sc.fit_transform()
:这是一个组合方法,fit
方法计算数据的转换参数(例如,每个特征的最大值和最小值),然后transform
方法使用这些参数来转换数据。这里,它将dataFrame[i]
的值缩放到0到1之间。
dataFrame.shape
dataFrame.shape
:这个属性返回 DataFrame 的形状,即行数和列数。这个操作没有改变 DataFrame,只是返回其维度信息,通常用于确认数据操作后 DataFrame 的结构是否正确。
总结来说,这段代码的作用是创建一个新的 DataFrame,然后对其中的特定列(‘CO 1’, ‘Soot 1’, ‘Tem1’)进行归一化处理,使其值在0到1之间。最后,打印出归一化后 DataFrame 的形状。
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
width_x=8
width_y=1
width_x=8
:定义变量width_x
,表示输入特征的时间段宽度,这里设置为8。width_y=1
:定义变量width_y
,表示预测目标的时间段宽度,这里设置为1。
x=[]
y=[]
x=[]
和y=[]
:初始化两个空列表x
和y
,用于存储输入特征和目标值。
in_start =0
in_start =0
:定义变量in_start
,它是循环中用来追踪输入数据开始索引的变量,初始值设为0。
for _, _ in data.iterrows():
for _, _ in data.iterrows()
:开始一个循环,遍历data
DataFrame 的每一行。iterrows()
方法返回每一行的索引和内容,但这里我们不需要使用这些值,所以使用_
来忽略它们。
in_end = in_start + width_x
out_end = in_end + width_y
in_end = in_start + width_x
:计算当前输入特征的结束索引。out_end = in_end + width_y
:计算当前目标值的结束索引。
if out_end < len(dataFrame):
if out_end < len(dataFrame)
:检查是否还有足够的数据来形成一个完整的输入-输出对。如果out_end
超出了 DataFrame 的长度,则不执行下面的代码。
x_ = np.array(dataFrame.iloc[in_start:in_end,])
y_ = np.array(dataFrame.iloc[in_end:out_end,0])
x_ = np.array(dataFrame.iloc[in_start:in_end,])
:从 DataFrame 中提取从in_start
到in_end
的行,并转换为 NumPy 数组,存储在变量x_
中。y_ = np.array(dataFrame.iloc[in_end:out_end,0])
:从 DataFrame 中提取从in_end
到out_end
的第一列(‘Tem1’),并转换为 NumPy 数组,存储在变量y_
中。
x.append(x_)
y.append(y_)
x.append(x_)
和y.append(y_)
:将x_
和y_
分别添加到列表x
和y
中。
in_start += 1
in_start += 1
:将in_start
的值增加1,以便在下一次循环时移动到下一个输入窗口的开始位置。
x = np.array(x)
y = np.array(y).reshape(-1,1,1)
x = np.array(x)
:将列表x
转换为 NumPy 数组。y = np.array(y).reshape(-1,1,1)
:将列表y
转换为 NumPy 数组,并重新调整其形状,以适应模型输入的要求。-1
表示自动计算行数,1
表示有两个维度,每个维度大小为1。
x.shape, y.shape
x.shape, y.shape
:打印数组x
和y
的形状。这是为了确认数据的维度是否符合预期。
总结来说,这段代码的作用是从dataFrame
中提取时间序列数据,形成输入特征x
和目标值y
。输入特征x
是连续的8个时间段的 ‘Tem1’、‘CO 1’、‘Soot 1’ 数据,而目标值y
是紧接着的第9个时间段的 ‘Tem1’ 数据。最终,这些数据被转换为 NumPy 数组,并调整形状以供后续使用,如机器学习模型的训练。
width_x=8
width_y=1
##取前8个时间段的Tem1、CO1、Soot1为x,第9个时间段的Tem1为y。
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
输出
((5939, 8, 3), (5939, 1, 1))
print(np.any(np.isnan(x)))
np.isnan(x)
:np.isnan
是 NumPy 库中的一个函数,用于检测数组x
中的元素是否为 NaN(Not a Number),即非数字值。它返回一个布尔数组,其中True
表示相应的元素是 NaN。np.any()
:np.any
是 NumPy 库中的一个函数,用于测试数组中是否至少有一个元素为True
。如果数组中至少有一个True
,则返回True
,否则返回False
。print(np.any(np.isnan(x)))
:这行代码首先检查数组x
中是否有任何 NaN 值,然后打印结果。如果x
中至少有一个 NaN 值,将打印True
,否则打印False
。
print(np.any(np.isnan(x)))
print(np.any(np.isnan(y)))
输出
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
输出
(torch.Size([5000, 8, 3]), torch.Size([5000, 1, 1]))
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)
3、模型训练
(1)构建模型
这段代码定义了一个名为 model_lstm
的 PyTorch 模型类,它继承自 nn.Module
。
class model_lstm(nn.Module):
- 这行代码定义了一个名为
model_lstm
的类,该类继承自nn.Module
。nn.Module
是 PyTorch 中所有神经网络模块的基类。
def __init__(self):
super(model_lstm, self).__init__()
def __init__(self):
:这是类的构造函数,用于初始化类的实例。super(model_lstm, self).__init__()
:这行代码调用父类nn.Module
的构造函数,这是 PyTorch 中初始化模块的标准做法。
self.lstmθ = nn.LSTM(input_size=3, hidden_size=320, num_layers=1, batch_first=True)
self.lstmθ = nn.LSTM(...)
:这行代码创建一个 LSTM 层,并将其赋值给实例变量self.lstmθ
。input_size=3
:指定输入特征的维度,这里为3,意味着每个时间步有3个特征。hidden_size=320
:指定 LSTM 层的隐藏层大小,这里为320。num_layers=1
:指定 LSTM 层的层数,这里为1。batch_first=True
:指定输入数据的格式,如果为True
,则输入数据的形状应该是[batch_size, sequence_length, input_size]
。
self.lstm1 = nn.LSTM(input_size=320, hidden_size=320, num_layers=1, batch_first=True)
self.lstm1 = nn.LSTM(...)
:与上面类似,这行代码创建另一个 LSTM 层,并将其赋值给实例变量self.lstm1
。
self.fcθ = nn.Linear(320, 1)
self.fcθ = nn.Linear(320, 1)
:这行代码创建一个全连接层(线性层),并将其赋值给实例变量self.fcθ
。320
:指定输入特征的数量,与 LSTM 层的隐藏层大小相同。1
:指定输出特征的数量,这里为1,因为我们要预测一个值。
def forward(self, x):
def forward(self, x):
:定义forward
方法,这是神经网络模型的前向传播函数。
out, hidden1 = self.lstmθ(x)
out, hidden1 = self.lstmθ(x)
:这行代码对输入x
应用第一个 LSTM 层,并返回输出out
和最后一个时间步的隐藏状态hidden1
。
out, _ = self.lstm1(out, hidden1)
out, _ = self.lstm1(out, hidden1)
:这行代码对前一个 LSTM 层的输出out
和隐藏状态hidden1
应用第二个 LSTM 层,并返回输出out
。下划线_
用于忽略返回的隐藏状态。
out = self.fcθ(out)
out = self.fcθ(out)
:这行代码将第二个 LSTM 层的输出out
传递给全连接层self.fcθ
,得到最终的输出。
return out[::,-1:] #取2个预测值,否则经过lstm会得到8*2个预测
return out[::,-1:]
:这行代码返回最终的预测结果。这里使用切片操作[::,-1:]
来选择每个序列的最后一个时间步的输出,因为我们的目标是预测每个序列的最后一个值。
model = model_lstm()
model = model_lstm()
:这行代码创建model_lstm
类的一个实例,并将其赋值给变量model
。
model
model
:这行代码打印模型的结构,它会显示模型中的层和它们的参数。
总结来说,这段代码定义了一个具有两个 LSTM 层和一个全连接层的 PyTorch 模型,用于时间序列预测。模型接受维度为[batch_size, sequence_length, input_size]
的输入,并输出每个序列的最后一个预测值。
class model_lstm(nn.Module):
def __init__(self):
super(model_lstm, self).__init__()
self.lstmθ = 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.fcθ = nn.Linear(320, 1)
def forward(self, x):
out, hidden1 = self.lstmθ(x)
out, _ = self.lstm1(out, hidden1)
out = self.fcθ(out)
return out[::,-1:] #取2个预测值,否则经过lstm会得到8*2个预测
model = model_lstm()
model
输出
检验模型输出数据集的格式
model(torch.rand(30,8,3)).shape
输出
torch.Size([30, 1, 1])
(2)定义训练函数
这段代码定义了一个名为 train
的函数,用于训练一个 PyTorch 模型。以下是代码的逐行解释,包括每个函数的作用和参数的含义:
import copy
- 这行代码导入 Python 标准库中的
copy
模块,该模块提供了一些用于复制对象的函数。尽管在这段代码中没有直接使用copy
模块,但它可能用于其他部分的代码中。
def train(train_dl, model, loss_fn, opt, lr_scheduler=None):
- 这行代码定义了
train
函数,它接受以下参数:train_dl
:训练数据的数据加载器(DataLoader),它负责批处理和打乱数据。model
:要训练的 PyTorch 模型。loss_fn
:用于计算损失的损失函数。opt
:优化器,用于更新模型的权重。lr_scheduler
:学习率调度器,它是可选的,用于在训练过程中调整学习率。
size = len(train_dl.dataset)
- 这行代码获取训练数据集的总大小。
num_batches = len(train_dl)
- 这行代码计算训练数据加载器中的批次数。
train_loss = 0 #初始化训练损失和正确率
- 这行代码初始化
train_loss
变量,用于累积训练过程中的总损失。
for x, y in train_dl:
- 这行代码开始一个循环,遍历
train_dl
中的每个批次的数据。x
和y
分别是批次中的输入数据和标签。
x, y = x.to(device), y.to(device)
- 这行代码将输入数据和标签移动到计算设备上(通常是 GPU)。
pred = model(x)
- 这行代码通过模型
model
前向传播输入数据x
,得到预测结果pred
。
loss = loss_fn(pred, y)
- 这行代码使用损失函数
loss_fn
计算预测结果pred
和真实标签y
之间的损失。
opt.zero_grad()
- 这行代码清除优化器
opt
中所有参数的梯度,为下一次反向传播做准备。
loss.backward()
- 这行代码执行反向传播,计算损失关于模型参数的梯度。
opt.step()
- 这行代码根据计算出的梯度更新模型参数。
train_loss += loss.item()
- 这行代码累加当前批次的损失到
train_loss
变量中。
if lr_scheduler is not None:
lr_scheduler.step()
print("learning rate = {:.5f}".format(opt.param_groups[0]['lr']))
- 这行代码检查是否提供了学习率调度器
lr_scheduler
。如果是,则在每个训练周期后更新学习率,并打印当前学习率。
train_loss /= num_batches
- 这行代码计算平均训练损失,通过将累积的损失除以批次数。
return train_loss
- 这行代码返回计算出的平均训练损失。
总结来说,train
函数负责执行一个训练周期,包括前向传播、计算损失、反向传播、更新模型参数,以及计算和返回平均训练损失。如果提供了学习率调度器,它还会在每个周期后更新学习率。
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']))
train_loss /= num_batches
return train_loss
(3)定义测试函数
这段代码定义了一个名为 test
的函数,用于评估一个 PyTorch 模型在测试数据集上的性能。以下是代码的逐行解释,包括每个函数的作用和参数的含义:
def test(dataloader, model, loss_fn):
- 这行代码定义了
test
函数,它接受以下参数:dataloader
:测试数据的数据加载器(DataLoader),它负责批处理数据。model
:要评估的 PyTorch 模型。loss_fn
:用于计算损失的损失函数。
size = len(dataloader.dataset)
- 这行代码获取测试数据集的总大小。
num_batches = len(dataloader)
- 这行代码计算测试数据加载器中的批次数。
test_loss = 0
- 这行代码初始化
test_loss
变量,用于累积测试过程中的总损失。
with torch.no_grad():
- 这行代码开始一个上下文管理器
with torch.no_grad()
,它告诉 PyTorch 在接下来的代码块中不需要计算或存储梯度。这是因为在测试阶段,我们不需要进行梯度更新。
for x, y in dataloader:
- 这行代码开始一个循环,遍历
dataloader
中的每个批次的数据。x
和y
分别是批次中的输入数据和标签。
x, y = x.to(device), y.to(device)
- 这行代码将输入数据和标签移动到计算设备上(通常是 GPU)。
y_pred = model(x)
- 这行代码通过模型
model
前向传播输入数据x
,得到预测结果y_pred
。
loss = loss_fn(y_pred, y)
- 这行代码使用损失函数
loss_fn
计算预测结果y_pred
和真实标签y
之间的损失。
test_loss += loss.item()
- 这行代码累加当前批次的损失到
test_loss
变量中。
test_loss /= num_batches
- 这行代码计算平均测试损失,通过将累积的损失除以批次数。
return test_loss
- 这行代码返回计算出的平均测试损失。
总结来说,test
函数负责执行模型的测试阶段,计算模型在测试数据集上的平均损失。它通过前向传播来获取预测结果,然后计算预测结果与真实标签之间的损失,并最终返回平均损失值。在测试过程中,由于不需要进行梯度更新,所以使用了torch.no_grad()
来节省计算资源。
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)
y_pred = model(x)
loss = loss_fn(y_pred, y)
test_loss += loss.item()
test_loss /= num_batches
return test_loss
(4)正式训练模型
这里可能会道导致输入数据和参数数据不在同一个设备上的报错。这里教案上的带代码是
device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
device
但是后面训练后在调用模型进行微调那一步会出现报错如下
这里我是把参数张量也转移到cpu上了
可以参考这篇文章
Pytorch 如何解决在 pytorch 中的 ‘Input and hidden tensors are not at the same device’ 错误
device=torch.device("cpu")
device
输出
device(type=‘cpu’)
这段代码是用于训练和评估一个LSTM模型的Python脚本的一部分。以下是代码的逐行解释,包括每个函数的作用和参数的含义:
# 训练模型
model = model_lstm()
model = model.to(device)
model = model_lstm()
:这行代码创建了一个model_lstm
类的实例。model = model.to(device)
:这行代码将模型model
移动到指定的计算设备上,通常是GPU。
loss_fn = nn.MSELoss() # 创建损失函数
loss_fn = nn.MSELoss()
:这行代码创建一个均方误差(Mean Squared Error, MSE)损失函数,用于评估模型的预测与真实值之间的差异。
learn_rate = 1e-1 # 学习率
learn_rate = 1e-1
:这行代码设置学习率为0.1。
opt = torch.optim.SGD(model.parameters(), lr=learn_rate, weight_decay=1e-4)
opt = torch.optim.SGD(...)
:这行代码创建一个Stochastic Gradient Descent (SGD)优化器,用于更新模型的权重。model.parameters()
:这行代码指定优化器将更新model
的所有参数。lr=learn_rate
:这行代码指定学习率为learn_rate
。weight_decay=1e-4
:这行代码指定权重衰减系数为0.0001,用于防止过拟合。
epochs = 50
epochs = 50
:这行代码设置训练周期数为50。
train_loss = []
test_loss = []
train_loss = []
和test_loss = []
:这行代码分别初始化两个列表train_loss
和test_loss
,用于存储训练和测试过程中的损失值。
lr_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(opt, epochs, last_epoch=-1)
lr_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(...)
:这行代码创建一个余弦退火学习率调度器。opt
:这行代码指定优化器,学习率调度器将应用于该优化器。epochs
:这行代码指定训练周期数,学习率调度器将在每个周期结束时调整学习率。last_epoch=-1
:这行代码指定学习率调度器的起始周期为-1,意味着它将从第一个周期开始调整学习率。
for epoch in range(epochs):
- 这行代码开始一个循环,循环变量
epoch
将在0
到epochs-1
之间遍历。epochs
是训练周期的总数。
model.train()
- 这行代码将模型设置为训练模式。在训练模式下,模型的某些部分(如批标准化层)会根据训练数据进行调整。
epoch_train_loss = train(train_dl, model, loss_fn, opt, lr_scheduler)
- 这行代码调用
train
函数,将模型在训练数据集train_dl
上进行训练。train
函数计算当前训练周期的损失,并返回这个损失值。train_dl
:训练数据的数据加载器。model
:要训练的PyTorch模型。loss_fn
:用于计算损失的损失函数。opt
:优化器,用于更新模型的权重。lr_scheduler
:学习率调度器,用于在训练过程中调整学习率。
model.eval()
- 这行代码将模型设置为评估模式。在评估模式下,模型的某些部分(如批标准化层)不会根据训练数据进行调整。
epoch_test_loss = test(test_dl, model, loss_fn)
- 这行代码调用
test
函数,将模型在测试数据集test_dl
上进行评估。test
函数计算当前评估周期的损失,并返回这个损失值。test_dl
:测试数据的数据加载器。model
:要评估的PyTorch模型。loss_fn
:用于计算损失的损失函数。
train_loss.append(epoch_train_loss)
- 这行代码将当前训练周期的损失值
epoch_train_loss
添加到列表train_loss
中。
test_loss.append(epoch_test_loss)
- 这行代码将当前评估周期的损失值
epoch_test_loss
添加到列表test_loss
中。
template = ('Epoch:{:2d}, Train_loss:{:.5f}, Test_loss:{:.5f}')
- 这行代码定义了一个格式化字符串模板,用于打印当前周期的训练和测试损失。
'Epoch:{:2d}'
:格式化字符串,用于打印周期的编号,格式为两位数字。'Train_loss:{:.5f}'
:格式化字符串,用于打印训练损失,格式为五位小数。'Test_loss:{:.5f}'
:格式化字符串,用于打印测试损失,格式为五位小数。
print(template.format(epoch+1, epoch_train_loss, epoch_test_loss))
- 这行代码使用格式化字符串模板打印当前周期的训练和测试损失。
epoch+1
是因为循环是从0
开始的,而打印时通常从1
开始。
#训练模型
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)
输出
learning rate = 0.09990
Epoch: 1, Train_loss:0.00125, Test_loss:0.01216
learning rate = 0.09961
Epoch: 2, Train_loss:0.01388, Test_loss:0.01182
learning rate = 0.09911
Epoch: 3, Train_loss:0.01361, Test_loss:0.01145
learning rate = 0.09843
Epoch: 4, Train_loss:0.01330, Test_loss:0.01106
learning rate = 0.09755
Epoch: 5, Train_loss:0.01294, Test_loss:0.01063
learning rate = 0.09649
Epoch: 6, Train_loss:0.01252, Test_loss:0.01014
learning rate = 0.09524
Epoch: 7, Train_loss:0.01201, Test_loss:0.00960
learning rate = 0.09382
Epoch: 8, Train_loss:0.01141, Test_loss:0.00899
learning rate = 0.09222
Epoch: 9, Train_loss:0.01071, Test_loss:0.00832
learning rate = 0.09045
Epoch:10, Train_loss:0.00989, Test_loss:0.00758
learning rate = 0.08853
Epoch:11, Train_loss:0.00897, Test_loss:0.00680
learning rate = 0.08645
Epoch:12, Train_loss:0.00797, Test_loss:0.00599
learning rate = 0.08423
Epoch:13, Train_loss:0.00691, Test_loss:0.00518
learning rate = 0.08187
Epoch:14, Train_loss:0.00583, Test_loss:0.00439
learning rate = 0.07939
Epoch:15, Train_loss:0.00479, Test_loss:0.00366
learning rate = 0.07679
Epoch:16, Train_loss:0.00382, Test_loss:0.00302
learning rate = 0.07409
Epoch:17, Train_loss:0.00296, Test_loss:0.00246
learning rate = 0.07129
Epoch:18, Train_loss:0.00224, Test_loss:0.00200
learning rate = 0.06841
Epoch:19, Train_loss:0.00166, Test_loss:0.00164
learning rate = 0.06545
Epoch:20, Train_loss:0.00122, Test_loss:0.00136
learning rate = 0.06243
Epoch:21, Train_loss:0.00088, Test_loss:0.00115
learning rate = 0.05937
Epoch:22, Train_loss:0.00064, Test_loss:0.00099
learning rate = 0.05627
Epoch:23, Train_loss:0.00047, Test_loss:0.00087
learning rate = 0.05314
Epoch:24, Train_loss:0.00036, Test_loss:0.00079
learning rate = 0.05000
Epoch:25, Train_loss:0.00028, Test_loss:0.00073
learning rate = 0.04686
Epoch:26, Train_loss:0.00022, Test_loss:0.00068
learning rate = 0.04373
Epoch:27, Train_loss:0.00018, Test_loss:0.00065
learning rate = 0.04063
Epoch:28, Train_loss:0.00016, Test_loss:0.00062
learning rate = 0.03757
Epoch:29, Train_loss:0.00014, Test_loss:0.00060
learning rate = 0.03455
Epoch:30, Train_loss:0.00013, Test_loss:0.00058
learning rate = 0.03159
Epoch:31, Train_loss:0.00012, Test_loss:0.00057
learning rate = 0.02871
Epoch:32, Train_loss:0.00012, Test_loss:0.00056
learning rate = 0.02591
Epoch:33, Train_loss:0.00011, Test_loss:0.00055
learning rate = 0.02321
Epoch:34, Train_loss:0.00011, Test_loss:0.00055
learning rate = 0.02061
Epoch:35, Train_loss:0.00011, Test_loss:0.00055
learning rate = 0.01813
Epoch:36, Train_loss:0.00011, Test_loss:0.00055
learning rate = 0.01577
Epoch:37, Train_loss:0.00012, Test_loss:0.00055
learning rate = 0.01355
Epoch:38, Train_loss:0.00012, Test_loss:0.00055
learning rate = 0.01147
Epoch:39, Train_loss:0.00012, Test_loss:0.00056
learning rate = 0.00955
Epoch:40, Train_loss:0.00012, Test_loss:0.00057
learning rate = 0.00778
Epoch:41, Train_loss:0.00013, Test_loss:0.00058
learning rate = 0.00618
Epoch:42, Train_loss:0.00013, Test_loss:0.00058
learning rate = 0.00476
Epoch:43, Train_loss:0.00013, Test_loss:0.00059
learning rate = 0.00351
Epoch:44, Train_loss:0.00014, Test_loss:0.00060
learning rate = 0.00245
Epoch:45, Train_loss:0.00014, Test_loss:0.00060
learning rate = 0.00157
Epoch:46, Train_loss:0.00014, Test_loss:0.00060
learning rate = 0.00089
Epoch:47, Train_loss:0.00014, Test_loss:0.00060
learning rate = 0.00039
Epoch:48, Train_loss:0.00014, Test_loss:0.00060
learning rate = 0.00010
Epoch:49, Train_loss:0.00014, Test_loss:0.00060
learning rate = 0.00000
Epoch:50, Train_loss:0.00014, Test_loss:0.00060
==================== Done ====================
4.模型评估
(1)Loss图
import matplotlib.pyplot as plt
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()
输出
(2)调用模型进行预测
这段代码是用于绘制时间序列预测模型的真实值和预测值对比曲线的Python脚本的一部分。以下是代码的逐行解释,包括每个函数的作用和参数的含义:
predicted_y_lstm = sc.inverse_transform(model(x_test).detach().numpy().reshape(-1,1))
predicted_y_lstm = sc.inverse_transform(...)
:这行代码使用sc
对象(可能是scikit-learn
库中的MinMaxScaler
对象)对模型model
在测试数据x_test
上的预测结果进行反归一化处理。model(x_test)
:这行代码通过模型model
前向传播输入数据x_test
,得到预测结果。.detach()
:这行代码将预测结果从计算图中分离出来,使其变为一个不依赖于任何计算图的 Tensor。.numpy()
:这行代码将 Tensor 转换为 NumPy 数组。.reshape(-1,1)
:这行代码将数组重塑为二维数组,其中-1
表示自动计算行数,1
表示列数。sc.inverse_transform(...)
:这行代码使用sc
对象对数组进行反归一化处理,恢复其原始范围。
y_test_1 = sc.inverse_transform(y_test.reshape(-1,1))
y_test_1 = sc.inverse_transform(...)
:这行代码使用sc
对象对测试数据集的真实标签y_test
进行反归一化处理。y_test.reshape(-1,1)
:这行代码将真实标签y_test
重塑为二维数组,以便与预测结果进行比较。sc.inverse_transform(...)
:这行代码使用sc
对象对数组进行反归一化处理,恢复其原始范围。
y_test_one = [i[0] for i in y_test_1]
y_test_one = [i[0] for i in y_test_1]
:这行代码将反归一化后的真实标签y_test_1
转换为一个列表,其中每个元素是列表y_test_1
中的一行。
predicted_y_lstm_one = [i[0] for i in predicted_y_lstm]
predicted_y_lstm_one = [i[0] for i in predicted_y_lstm]
:这行代码将反归一化后的预测结果predicted_y_lstm
转换为一个列表,其中每个元素是列表predicted_y_lstm
中的一行。
plt.figure(figsize=(5,3),dpi=120)
plt.figure(...)
:这行代码创建一个新的 Matplotlib 图形,并设置其大小和分辨率。figsize=(5,3)
:这行代码指定图形的大小为 5 英寸宽和 3 英寸高。dpi=120
:这行代码指定图形分辨率为 120 像素/英寸。
# 画出真实数据和预测数据的对比曲线
- 这一行是注释,说明接下来将绘制真实数据和预测数据的对比曲线。
plt.plot(y_test_one[:2000], color='red', label='real_temp')
plt.plot(...)
:这行代码在当前图形中绘制真实标签y_test_one
的前2000个值。y_test_one[:2000]
:这行代码指定要绘制的真实标签的范围,即前2000个值。color='red'
:这行代码指定真实标签的线条颜色为红色。label='real_temp'
:这行代码指定真实标签的图例标签为 ‘real_temp’。
predicted_y_lstm = sc.inverse_transform(model(x_test).detach().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()
输出
(3)R2值评估
from sklearn import metrics
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)
输出
均方根误差:24.17460
R2:0.82963
三、nn.LSTM( )函数详解
nn.LSTM()
是 PyTorch 库中用于创建 LSTM(长短期记忆)层的函数。LSTM 是一种特殊类型的循环神经网络(RNN),它特别擅长处理和预测时间序列数据。以下是 nn.LSTM()
函数的详细解释,包括每个参数的含义和用法:
nn.LSTM(input_size, hidden_size, num_layers, batch_first=False, dropout=0, bidirectional=False)
input_size
:- 类型:int
- 含义:输入数据的特征维度。
- 用法:指定每个时间步输入数据的特征数量。对于二维输入([batch_size, time_steps, input_size]),
input_size
就是time_steps
维度上的大小。
hidden_size
:- 类型:int
- 含义:LSTM 层的隐藏单元数量。
- 用法:指定每个时间步的 LSTM 层能够处理的数据量。这个值越大,模型可以处理的数据范围越广,但也会增加模型的复杂度。
num_layers
:- 类型:int
- 含义:LSTM 层的层数。
- 用法:指定 LSTM 层的堆叠次数。例如,
num_layers=2
表示有两个 LSTM 层堆叠在一起。
batch_first
:- 类型:bool
- 含义:输入数据的格式。
- 用法:指定输入数据的格式。如果为
True
,则输入数据的形状应该是[batch_size, time_steps, input_size]
;如果为False
(默认值),则输入数据的形状应该是[time_steps, batch_size, input_size]
。
dropout
:- 类型:float
- 含义:LSTM 层的 dropout 比率。
- 用法:指定在 LSTM 层之间应用 dropout 的比率。这有助于防止过拟合。
bidirectional
:- 类型:bool
- 含义:LSTM 层的方向性。
- 用法:指定是否使用双向 LSTM。如果为
True
,则 LSTM 层会同时处理输入序列的前向和后向信息。这会增加模型的容量,但也会增加计算量。
当创建一个 LSTM 层时,你可以根据你的具体需求调整这些参数。例如,如果你的输入数据是二维的,并且每个时间步有 32 个特征,你可以创建一个有 2 个隐藏单元、2 个 LSTM 层堆叠、不使用 dropout 和双向的 LSTM 层,如下所示:
lstm = nn.LSTM(input_size=32, hidden_size=2, num_layers=2, batch_first=False)
如果你的输入数据是三维的,并且每个时间步有 32 个特征,你可以创建一个有 2 个隐藏单元、2 个 LSTM 层堆叠、不使用 dropout 和双向的 LSTM 层,如下所示:
lstm = nn.LSTM(input_size=32, hidden_size=2, num_layers=2, batch_first=True)