第R2周:LSTM-火灾温度预测

一、什么是LSTM

1.LSTM的本质

长短时记忆网络(Long Short-Term Memory, LSTM)的本质是一种特殊的循环神经网络(Recurrent Neural Network, RNN),它被设计来解决标准RNN在处理长序列数据时遇到的梯度消失或梯度爆炸问题,这些问题限制了RNN在处理长距离依赖关系时的能力。

LSTM的核心创新在于它引入了所谓的"门控机制"(gates),这些门控机制允许网络学习何时让信息进入或离开网络,以及何时保留或更新长期状态。LSTM包含三个主要门控结构:

  1. 遗忘门(Forget Gate):决定哪些信息应该从单元状态(cell state)中丢弃。它通过查看前一个隐藏状态和当前输入,输出一个介于0和1之间的值给每个在单元状态中的数。1表示"完全保留这个信息",而0表示"完全丢弃这个信息"。
  2. 输入门(Input Gate):决定哪些新的信息将被存储在单元状态中。它由两个部分组成:一个sigmoid函数决定哪些值将要更新,一个tanh函数创建一个新的候选值向量,可以被加到状态中。
  3. 输出门(Output Gate) :决定下一个隐藏状态应该是什么。隐藏状态包含关于过去的输入的信息,输出门通过决定哪些信息应该被输出,来控制这些信息的流动。
    LSTM的单元状态可以在长序列中保持相对稳定的信息流,这使得LSTM特别适合于处理需要记忆长期依赖信息的任务,如语音识别、机器翻译、时间序列预测等。
    此外,LSTM还通过引入"细胞状态"(cell state)的概念,使得信息可以在网络中流动而不被干扰,从而解决了标准RNN在长序列学习中的稳定性问题。细胞状态在LSTM单元中贯穿始终,只有通过门控结构才能对其进行修改,这使得LSTM能够有效地学习长期依赖关系。

RNN的缺点以及LSTM如何解决这些问题的对比:

RNN的缺点:

  1. 梯度消失/梯度爆炸:在训练过程中,RNN的权重更新依赖于梯度,而梯度在反向传播过程中可能会指数级减小(消失)或增大(爆炸),这使得网络难以学习长期依赖关系。
  2. 难以捕捉长期依赖:由于梯度消失的问题,RNN在处理长序列时,很难保留早期信息对当前时刻的影响,导致网络性能下降。
  3. 状态更新不灵活:在标准的RNN中,状态更新是固定的,即前一个隐藏状态直接加到当前输入上,没有机制来选择性地保留或遗忘信息。

LSTM的提升:

  1. 门控机制:LSTM通过引入输入门、遗忘门和输出门,提供了更灵活的状态更新机制。这些门控结构可以控制信息的流入、保留和流出,从而有效避免梯度消失/梯度爆炸问题。
  2. 细胞状态(Cell State):LSTM有一个单独的细胞状态,它贯穿于整个序列,允许信息在长序列中流动而不被干扰。细胞状态可以通过门控结构进行选择性的更新,使得网络能够学习长期依赖关系。
  3. 长期依赖学习 :由于上述机制,LSTM能够更好地捕捉和表示长期依赖关系,这使得它在处理需要记忆长期信息的任务上表现出色。
    总的来说,LSTM通过其特殊的网络结构和门控机制,解决了标准RNN在处理长序列数据时的主要问题,提升了网络在处理长期依赖关系时的性能。

2.LSTM的原理

从隐藏状态的产生、传递和计算角度来解释LSTM(长短时记忆网络)的原理,可以分为以下几个步骤:

隐藏状态的产生

  1. 初始化:在序列的开始,LSTM的隐藏状态通常被初始化为全零向量。
  2. 遗忘门:在处理序列的每个时间步时,LSTM首先通过遗忘门决定哪些信息应该从细胞状态中丢弃。遗忘门的输出是一个介于0和1之间的向量,表示每个在细胞状态中的信息应该被保留的程度。
  3. 输入门和候选状态:接着,LSTM通过输入门决定哪些新的信息将被存储在细胞状态中。输入门输出一个介于0和1之间的向量,表示每个候选值应该被添加到细胞状态中的程度。同时,LSTM会生成一个候选值向量,它包含了可能被添加到细胞状态中的新信息。
  4. 更新细胞状态:细胞状态在当前时间步被更新。LSTM会根据遗忘门的输出和候选值向量来更新细胞状态。
  5. 输出门:最后,LSTM通过输出门决定细胞状态中的哪些信息应该被输出。输出门的输出是一个介于0和1之间的向量,表示细胞状态中哪些信息应该影响当前时间步的隐藏状态。

隐藏状态的传递

隐藏状态在LSTM中起到了传递序列历史信息的作用。在每个时间步,隐藏状态会根据细胞状态和输入门的输出生成。这个隐藏状态包含了在当前时间步需要传递给后续层的信息,并且会作为下一个时间步的输入的一部分。

隐藏状态的计算

隐藏状态的计算是通过将细胞状态通过一个tanh函数(将值压缩到-1和1之间)然后乘以输出门的输出来生成的。这个计算过程确保了隐藏状态包含了当前时间步的细胞状态中的相关信息,并且可以传递给后续层或下一个时间步。

通过这种方式,LSTM能够有效地处理序列数据,并学习到序列中的长期依赖关系。隐藏状态的产生、传递和计算是LSTM实现这一目标的关键步骤。

二、pytorch实现

1.前期准备工作

python 复制代码
import torch.nn.functional as F  
import numpy as np  
import pandas as pd  
import torch  
from torch    import nn

(1)导入数据

python 复制代码
data = pd.read_csv("woodpine2.csv")  
print(data)

输出

(2)数据集可视化

snsseaborn 库的简称,这是一个在 Python 中用于数据可视化的库,它基于 matplotlib 库构建,提供了更为美观和高级的绘图样式。seaborn 专门针对统计图表进行了优化,使得创建具有吸引力的、信息丰富的图表变得简单。

下面是这段代码中使用的 seaborn 库的函数及其作用和参数的含义:

  1. 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] 分别指定了三个子图中的一个来绘制对应的线形图。
python 复制代码
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 中的所有行和除了第一列之外的所有列。

下面是代码的详细解释:

  • datadata 是一个 pandas DataFrame 对象,它包含了原始数据集。
  • .iloc:这是 pandas DataFrame 提供的一个索引器,用于通过行和列的索引位置进行数据选择。与 .loc 不同,.loc 是基于标签的索引器,.iloc 是基于整数位置的索引器。
  • ::这表示选择所有行。在 Python 中,单独的冒号 : 通常用作范围操作符,在这里它表示从起始位置到结束位置的全部范围。由于没有指定起始和结束,所以默认选择所有行。
  • ,1:]:这表示选择除了第一列之外的所有列。在 Python 中,切片操作 1: 表示从索引 1 开始,一直到最后一个元素。由于列的索引是从 0 开始的,所以 1: 实际上是从第二列开始,直到最后一列。

综上所述,data.iloc[:,1:] 这行代码会选择 data DataFrame 的所有行和从第二列开始到最后一列的所有列。如果你有一个具有多列的 DataFrame,并且想要排除第一列(比如排除一个索引列或者分类列),这行代码就非常有用。

python 复制代码
dataFrame = data.iloc[:,1:]
dataFrame

输出

2、构建数据集

(1)数据集预处理

python 复制代码
from sklearn.preprocessing import MinMaxScaler
  • from sklearn.preprocessing import MinMaxScaler:这行代码从 sklearn.preprocessing 模块中导入 MinMaxScaler 类。MinMaxScaler 是一个用于数据归一化的工具,它可以将每个特征缩放到一个指定的范围,通常是0到1。
python 复制代码
dataFrame = data.iloc[:,1:].copy()
  • data.iloc[:,1:]:这部分选择 data DataFrame 的所有行和从第二列开始到最后一列的所有列。
  • .copy():这个方法创建了一个新的 DataFrame,它是原始 DataFrame 的一个副本。这是为了防止后续的操作影响到原始的 data DataFrame。
python 复制代码
sc = MinMaxScaler(feature_range=(0,1))
  • sc = MinMaxScaler(feature_range=(0,1)):创建一个 MinMaxScaler 对象,名为 scfeature_range=(0,1) 参数指定了归一化的范围,即每个特征的值将被缩放到0到1之间。
python 复制代码
for i in ['CO 1', 'Soot 1', 'Tem1']:
  • 这行代码开始一个循环,循环变量 i 将遍历列表 ['CO 1', 'Soot 1', 'Tem1'] 中的每个元素。这个列表包含了要归一化的列名。
python 复制代码
    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之间。
python 复制代码
dataFrame.shape
  • dataFrame.shape:这个属性返回 DataFrame 的形状,即行数和列数。这个操作没有改变 DataFrame,只是返回其维度信息,通常用于确认数据操作后 DataFrame 的结构是否正确。
    总结来说,这段代码的作用是创建一个新的 DataFrame,然后对其中的特定列('CO 1', 'Soot 1', 'Tem1')进行归一化处理,使其值在0到1之间。最后,打印出归一化后 DataFrame 的形状。
python 复制代码
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

python 复制代码
width_x=8
width_y=1
  • width_x=8:定义变量 width_x,表示输入特征的时间段宽度,这里设置为8。
  • width_y=1:定义变量 width_y,表示预测目标的时间段宽度,这里设置为1。
python 复制代码
x=[]
y=[]
  • x=[]y=[]:初始化两个空列表 xy,用于存储输入特征和目标值。
python 复制代码
in_start =0
  • in_start =0:定义变量 in_start,它是循环中用来追踪输入数据开始索引的变量,初始值设为0。
python 复制代码
for _, _ in data.iterrows():
  • for _, _ in data.iterrows():开始一个循环,遍历 data DataFrame 的每一行。iterrows() 方法返回每一行的索引和内容,但这里我们不需要使用这些值,所以使用 _ 来忽略它们。
python 复制代码
    in_end = in_start + width_x
    out_end = in_end + width_y
  • in_end = in_start + width_x:计算当前输入特征的结束索引。
  • out_end = in_end + width_y:计算当前目标值的结束索引。
python 复制代码
    if out_end < len(dataFrame):
  • if out_end < len(dataFrame):检查是否还有足够的数据来形成一个完整的输入-输出对。如果 out_end 超出了 DataFrame 的长度,则不执行下面的代码。
python 复制代码
        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_startin_end 的行,并转换为 NumPy 数组,存储在变量 x_ 中。
  • y_ = np.array(dataFrame.iloc[in_end:out_end,0]):从 DataFrame 中提取从 in_endout_end 的第一列('Tem1'),并转换为 NumPy 数组,存储在变量 y_ 中。
python 复制代码
        x.append(x_)
        y.append(y_)
  • x.append(x_)y.append(y_):将 x_y_ 分别添加到列表 xy 中。
python 复制代码
    in_start += 1
  • in_start += 1:将 in_start 的值增加1,以便在下一次循环时移动到下一个输入窗口的开始位置。
python 复制代码
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。
python 复制代码
x.shape, y.shape
  • x.shape, y.shape:打印数组 xy 的形状。这是为了确认数据的维度是否符合预期。
    总结来说,这段代码的作用是从 dataFrame 中提取时间序列数据,形成输入特征 x 和目标值 y。输入特征 x 是连续的8个时间段的 'Tem1'、'CO 1'、'Soot 1' 数据,而目标值 y 是紧接着的第9个时间段的 'Tem1' 数据。最终,这些数据被转换为 NumPy 数组,并调整形状以供后续使用,如机器学习模型的训练。
python 复制代码
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))

python 复制代码
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
python 复制代码
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)划分数据集

python 复制代码
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))

python 复制代码
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

python 复制代码
class model_lstm(nn.Module):
  • 这行代码定义了一个名为 model_lstm 的类,该类继承自 nn.Modulenn.Module 是 PyTorch 中所有神经网络模块的基类。
python 复制代码
    def __init__(self):
        super(model_lstm, self).__init__()
  • def __init__(self)::这是类的构造函数,用于初始化类的实例。
  • super(model_lstm, self).__init__():这行代码调用父类 nn.Module 的构造函数,这是 PyTorch 中初始化模块的标准做法。
python 复制代码
        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]
python 复制代码
        self.lstm1 = nn.LSTM(input_size=320, hidden_size=320, num_layers=1, batch_first=True)
  • self.lstm1 = nn.LSTM(...):与上面类似,这行代码创建另一个 LSTM 层,并将其赋值给实例变量 self.lstm1
python 复制代码
        self.fcθ = nn.Linear(320, 1)
  • self.fcθ = nn.Linear(320, 1):这行代码创建一个全连接层(线性层),并将其赋值给实例变量 self.fcθ
    • 320:指定输入特征的数量,与 LSTM 层的隐藏层大小相同。
    • 1:指定输出特征的数量,这里为1,因为我们要预测一个值。
python 复制代码
    def forward(self, x):
  • def forward(self, x)::定义 forward 方法,这是神经网络模型的前向传播函数。
python 复制代码
        out, hidden1 = self.lstmθ(x)
  • out, hidden1 = self.lstmθ(x):这行代码对输入 x 应用第一个 LSTM 层,并返回输出 out 和最后一个时间步的隐藏状态 hidden1
python 复制代码
        out, _ = self.lstm1(out, hidden1)
  • out, _ = self.lstm1(out, hidden1):这行代码对前一个 LSTM 层的输出 out 和隐藏状态 hidden1 应用第二个 LSTM 层,并返回输出 out。下划线 _ 用于忽略返回的隐藏状态。
python 复制代码
        out = self.fcθ(out)
  • out = self.fcθ(out):这行代码将第二个 LSTM 层的输出 out 传递给全连接层 self.fcθ,得到最终的输出。
python 复制代码
        return out[::,-1:] #取2个预测值,否则经过lstm会得到8*2个预测
  • return out[::,-1:]:这行代码返回最终的预测结果。这里使用切片操作 [::,-1:] 来选择每个序列的最后一个时间步的输出,因为我们的目标是预测每个序列的最后一个值。
python 复制代码
model = model_lstm()
  • model = model_lstm():这行代码创建 model_lstm 类的一个实例,并将其赋值给变量 model
python 复制代码
model
  • model:这行代码打印模型的结构,它会显示模型中的层和它们的参数。
    总结来说,这段代码定义了一个具有两个 LSTM 层和一个全连接层的 PyTorch 模型,用于时间序列预测。模型接受维度为 [batch_size, sequence_length, input_size] 的输入,并输出每个序列的最后一个预测值。
python 复制代码
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

输出

检验模型输出数据集的格式

python 复制代码
model(torch.rand(30,8,3)).shape

输出

torch.Size(30, 1, 1)

(2)定义训练函数

这段代码定义了一个名为 train 的函数,用于训练一个 PyTorch 模型。以下是代码的逐行解释,包括每个函数的作用和参数的含义:

python 复制代码
import copy
  • 这行代码导入 Python 标准库中的 copy 模块,该模块提供了一些用于复制对象的函数。尽管在这段代码中没有直接使用 copy 模块,但它可能用于其他部分的代码中。
python 复制代码
def train(train_dl, model, loss_fn, opt, lr_scheduler=None):
  • 这行代码定义了 train 函数,它接受以下参数:
    • train_dl:训练数据的数据加载器(DataLoader),它负责批处理和打乱数据。
    • model:要训练的 PyTorch 模型。
    • loss_fn:用于计算损失的损失函数。
    • opt:优化器,用于更新模型的权重。
    • lr_scheduler:学习率调度器,它是可选的,用于在训练过程中调整学习率。
python 复制代码
    size = len(train_dl.dataset)
  • 这行代码获取训练数据集的总大小。
python 复制代码
    num_batches = len(train_dl)
  • 这行代码计算训练数据加载器中的批次数。
python 复制代码
    train_loss = 0 #初始化训练损失和正确率
  • 这行代码初始化 train_loss 变量,用于累积训练过程中的总损失。
python 复制代码
    for x, y in train_dl:
  • 这行代码开始一个循环,遍历 train_dl 中的每个批次的数据。xy 分别是批次中的输入数据和标签。
python 复制代码
        x, y = x.to(device), y.to(device)
  • 这行代码将输入数据和标签移动到计算设备上(通常是 GPU)。
python 复制代码
        pred = model(x)
  • 这行代码通过模型 model 前向传播输入数据 x,得到预测结果 pred
python 复制代码
        loss = loss_fn(pred, y)
  • 这行代码使用损失函数 loss_fn 计算预测结果 pred 和真实标签 y 之间的损失。
python 复制代码
        opt.zero_grad()
  • 这行代码清除优化器 opt 中所有参数的梯度,为下一次反向传播做准备。
python 复制代码
        loss.backward()
  • 这行代码执行反向传播,计算损失关于模型参数的梯度。
python 复制代码
        opt.step()
  • 这行代码根据计算出的梯度更新模型参数。
python 复制代码
        train_loss += loss.item()
  • 这行代码累加当前批次的损失到 train_loss 变量中。
python 复制代码
    if lr_scheduler is not None:
        lr_scheduler.step()
        print("learning rate = {:.5f}".format(opt.param_groups[0]['lr']))
  • 这行代码检查是否提供了学习率调度器 lr_scheduler。如果是,则在每个训练周期后更新学习率,并打印当前学习率。
python 复制代码
    train_loss /= num_batches
  • 这行代码计算平均训练损失,通过将累积的损失除以批次数。
python 复制代码
    return train_loss
  • 这行代码返回计算出的平均训练损失。
    总结来说,train 函数负责执行一个训练周期,包括前向传播、计算损失、反向传播、更新模型参数,以及计算和返回平均训练损失。如果提供了学习率调度器,它还会在每个周期后更新学习率。
python 复制代码
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 模型在测试数据集上的性能。以下是代码的逐行解释,包括每个函数的作用和参数的含义:

python 复制代码
def test(dataloader, model, loss_fn):
  • 这行代码定义了 test 函数,它接受以下参数:
    • dataloader:测试数据的数据加载器(DataLoader),它负责批处理数据。
    • model:要评估的 PyTorch 模型。
    • loss_fn:用于计算损失的损失函数。
python 复制代码
    size = len(dataloader.dataset)
  • 这行代码获取测试数据集的总大小。
python 复制代码
    num_batches = len(dataloader)
  • 这行代码计算测试数据加载器中的批次数。
python 复制代码
    test_loss = 0
  • 这行代码初始化 test_loss 变量,用于累积测试过程中的总损失。
python 复制代码
    with torch.no_grad():
  • 这行代码开始一个上下文管理器 with torch.no_grad(),它告诉 PyTorch 在接下来的代码块中不需要计算或存储梯度。这是因为在测试阶段,我们不需要进行梯度更新。
python 复制代码
        for x, y in dataloader:
  • 这行代码开始一个循环,遍历 dataloader 中的每个批次的数据。xy 分别是批次中的输入数据和标签。
python 复制代码
            x, y = x.to(device), y.to(device)
  • 这行代码将输入数据和标签移动到计算设备上(通常是 GPU)。
python 复制代码
            y_pred = model(x)
  • 这行代码通过模型 model 前向传播输入数据 x,得到预测结果 y_pred
python 复制代码
            loss = loss_fn(y_pred, y)
  • 这行代码使用损失函数 loss_fn 计算预测结果 y_pred 和真实标签 y 之间的损失。
python 复制代码
            test_loss += loss.item()
  • 这行代码累加当前批次的损失到 test_loss 变量中。
python 复制代码
    test_loss /= num_batches
  • 这行代码计算平均测试损失,通过将累积的损失除以批次数。
python 复制代码
    return test_loss
  • 这行代码返回计算出的平均测试损失。
    总结来说,test 函数负责执行模型的测试阶段,计算模型在测试数据集上的平均损失。它通过前向传播来获取预测结果,然后计算预测结果与真实标签之间的损失,并最终返回平均损失值。在测试过程中,由于不需要进行梯度更新,所以使用了 torch.no_grad() 来节省计算资源。
python 复制代码
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)正式训练模型

这里可能会道导致输入数据和参数数据不在同一个设备上的报错。这里教案上的带代码是

python 复制代码
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' 错误

python 复制代码
device=torch.device("cpu")  
device

输出

device(type='cpu')

这段代码是用于训练和评估一个LSTM模型的Python脚本的一部分。以下是代码的逐行解释,包括每个函数的作用和参数的含义:

python 复制代码
# 训练模型
model = model_lstm()
model = model.to(device)
  • model = model_lstm():这行代码创建了一个model_lstm类的实例。
  • model = model.to(device):这行代码将模型model移动到指定的计算设备上,通常是GPU。
python 复制代码
loss_fn = nn.MSELoss() # 创建损失函数
  • loss_fn = nn.MSELoss():这行代码创建一个均方误差(Mean Squared Error, MSE)损失函数,用于评估模型的预测与真实值之间的差异。
python 复制代码
learn_rate = 1e-1 # 学习率
  • learn_rate = 1e-1:这行代码设置学习率为0.1。
python 复制代码
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,用于防止过拟合。
python 复制代码
epochs = 50
  • epochs = 50:这行代码设置训练周期数为50。
python 复制代码
train_loss = []
test_loss = []
  • train_loss = []test_loss = []:这行代码分别初始化两个列表train_losstest_loss,用于存储训练和测试过程中的损失值。
python 复制代码
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,意味着它将从第一个周期开始调整学习率。
python 复制代码
for epoch in range(epochs):
  • 这行代码开始一个循环,循环变量 epoch 将在 0epochs-1 之间遍历。epochs 是训练周期的总数。
python 复制代码
    model.train()
  • 这行代码将模型设置为训练模式。在训练模式下,模型的某些部分(如批标准化层)会根据训练数据进行调整。
python 复制代码
    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:学习率调度器,用于在训练过程中调整学习率。
python 复制代码
    model.eval()
  • 这行代码将模型设置为评估模式。在评估模式下,模型的某些部分(如批标准化层)不会根据训练数据进行调整。
python 复制代码
    epoch_test_loss = test(test_dl, model, loss_fn)
  • 这行代码调用 test 函数,将模型在测试数据集 test_dl 上进行评估。test 函数计算当前评估周期的损失,并返回这个损失值。
    • test_dl:测试数据的数据加载器。
    • model:要评估的PyTorch模型。
    • loss_fn:用于计算损失的损失函数。
python 复制代码
    train_loss.append(epoch_train_loss)
  • 这行代码将当前训练周期的损失值 epoch_train_loss 添加到列表 train_loss 中。
python 复制代码
    test_loss.append(epoch_test_loss)
  • 这行代码将当前评估周期的损失值 epoch_test_loss 添加到列表 test_loss 中。
python 复制代码
    template = ('Epoch:{:2d}, Train_loss:{:.5f}, Test_loss:{:.5f}')
  • 这行代码定义了一个格式化字符串模板,用于打印当前周期的训练和测试损失。
    • 'Epoch:{:2d}':格式化字符串,用于打印周期的编号,格式为两位数字。
    • 'Train_loss:{:.5f}':格式化字符串,用于打印训练损失,格式为五位小数。
    • 'Test_loss:{:.5f}':格式化字符串,用于打印测试损失,格式为五位小数。
python 复制代码
    print(template.format(epoch+1, epoch_train_loss, epoch_test_loss))
  • 这行代码使用格式化字符串模板打印当前周期的训练和测试损失。epoch+1 是因为循环是从 0 开始的,而打印时通常从 1 开始。
python 复制代码
#训练模型
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图

python 复制代码
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脚本的一部分。以下是代码的逐行解释,包括每个函数的作用和参数的含义:

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 对象对数组进行反归一化处理,恢复其原始范围。
python 复制代码
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 对象对数组进行反归一化处理,恢复其原始范围。
python 复制代码
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 中的一行。
python 复制代码
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 中的一行。
python 复制代码
plt.figure(figsize=(5,3),dpi=120)
  • plt.figure(...):这行代码创建一个新的 Matplotlib 图形,并设置其大小和分辨率。
    • figsize=(5,3):这行代码指定图形的大小为 5 英寸宽和 3 英寸高。
    • dpi=120:这行代码指定图形分辨率为 120 像素/英寸。
python 复制代码
# 画出真实数据和预测数据的对比曲线
  • 这一行是注释,说明接下来将绘制真实数据和预测数据的对比曲线。
python 复制代码
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'。
python 复制代码
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值评估

python 复制代码
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() 函数的详细解释,包括每个参数的含义和用法:

python 复制代码
nn.LSTM(input_size, hidden_size, num_layers, batch_first=False, dropout=0, bidirectional=False)
  1. input_size
    • 类型:int
    • 含义:输入数据的特征维度。
    • 用法:指定每个时间步输入数据的特征数量。对于二维输入(batch_size, time_steps, input_size),input_size 就是 time_steps 维度上的大小。
  2. hidden_size
    • 类型:int
    • 含义:LSTM 层的隐藏单元数量。
    • 用法:指定每个时间步的 LSTM 层能够处理的数据量。这个值越大,模型可以处理的数据范围越广,但也会增加模型的复杂度。
  3. num_layers
    • 类型:int
    • 含义:LSTM 层的层数。
    • 用法:指定 LSTM 层的堆叠次数。例如,num_layers=2 表示有两个 LSTM 层堆叠在一起。
  4. batch_first
    • 类型:bool
    • 含义:输入数据的格式。
    • 用法:指定输入数据的格式。如果为 True,则输入数据的形状应该是 [batch_size, time_steps, input_size];如果为 False(默认值),则输入数据的形状应该是 [time_steps, batch_size, input_size]
  5. dropout
    • 类型:float
    • 含义:LSTM 层的 dropout 比率。
    • 用法:指定在 LSTM 层之间应用 dropout 的比率。这有助于防止过拟合。
  6. bidirectional
    • 类型:bool
    • 含义:LSTM 层的方向性。
    • 用法:指定是否使用双向 LSTM。如果为 True,则 LSTM 层会同时处理输入序列的前向和后向信息。这会增加模型的容量,但也会增加计算量。
      当创建一个 LSTM 层时,你可以根据你的具体需求调整这些参数。例如,如果你的输入数据是二维的,并且每个时间步有 32 个特征,你可以创建一个有 2 个隐藏单元、2 个 LSTM 层堆叠、不使用 dropout 和双向的 LSTM 层,如下所示:
python 复制代码
lstm = nn.LSTM(input_size=32, hidden_size=2, num_layers=2, batch_first=False)

如果你的输入数据是三维的,并且每个时间步有 32 个特征,你可以创建一个有 2 个隐藏单元、2 个 LSTM 层堆叠、不使用 dropout 和双向的 LSTM 层,如下所示:

python 复制代码
lstm = nn.LSTM(input_size=32, hidden_size=2, num_layers=2, batch_first=True)
相关推荐
TMT星球15 分钟前
虎鲸文娱春苗编剧计划全面升级,已孵化20个剧本进入前期开发
大数据·人工智能
奔跑中的小象7 小时前
统信UOS + 天数AI卡部署SGLang服务手册
人工智能·uos·sglang·天数智芯
DevSecOps选型指南7 小时前
中国版Mythos,为何是悬镜安全灵脉CodeAI?
人工智能·安全
Shockang7 小时前
LangGraph 状态机实战
人工智能
刹那芳华19927 小时前
循环神经网络的从零开始实现(RNN)
人工智能·rnn·深度学习
阿童木写作7 小时前
跨境图片翻译工具多合一,批量图片视频字幕翻译加智能抠图
人工智能·python·音视频·语音识别
科技之门7 小时前
AI3D从建模到贴图、绑骨和动画的完整流程怎么做?V2Fun完整工作流指南
人工智能·3d·贴图
宇宙第一小趴菜7 小时前
二、机器学习的应用领域和发展史
人工智能·机器学习
前端开发江鸟7 小时前
我写过 MCP Server,却一直以为 MCP 只有 Tool
人工智能
天国梦8 小时前
自习室智能化升级避坑指南:天学网AI智习室方案实测与选型建议
大数据·人工智能