>- **🍨 本文为[🔗365天深度学习训练营]中的学习记录博客**
>- **🍖 原作者:[K同学啊]**

往期文章可查阅: 深度学习总结

任务说明:数据集中提供了火灾温度(Tem1)、一氧化碳浓度(CO 1)、烟雾浓度(Soot 1)随着时间变化数据,我们需要根据这些数据对未来某一时刻的火灾温度做出预测
🍺要求:
1、了解LSTM是什么,并使用其构建一个完整的程序。
2、R2达到0.83
🍻拔高:
使用第1~8个时刻的数据 预测第9~10个时刻的温度数据。

🏡 我的环境:

  • 语言环境:Python3.8
  • 编译器:Jupyter Notebook
  • 深度学习环境:Pytorch
    • torch==2.3.1+cu118
    • torchvision==0.18.1+cu118

 本文完全根据 xxx 中的内容转换为pytorch,所以前述性的内容不在一一重复,仅就pytorch的内容进行叙述。 

 一、前期工作

导入下面需要用到的库

import torch.nn.functional as F
import numpy as np
import pandas as pd
import torch
from torch import nn

运行过程中发现自己的pytorch环境中没有pandas,又重新添加,但是网速实在不给力,添加清华源地址后速度很快。各位同学在安装库的过程中应尽可能使用各种源地址,我常用的就是清华源地址,以下列出一些常用地址:

名称    地址
阿里    https://mirrors.aliyun.com/pypi/simple
豆瓣    http://pypi.douban.com/simple/
清华大学    https://pypi.tuna.tsinghua.edu.cn/simple
中国科学技术大学    https://pypi.mirrors.ustc.edu.cn/simple
华中理工大学    http://pypi.hustunique.com/simple
山东理工大学    http://pypi.sdutlinux.org/simple
网易    https://mirrors.163.com/pypi/simple/
腾讯    https://mirrors.cloud.tencent.com/pypi/simple

1. 导入数据

data=pd.read_csv("D:\THE MNIST DATABASE\RNN\R4\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

使用 .iloc 方法按位置选择数据。: 表示选择所有行,1: 表示从第二列开始选择所有列(Python中的索引从0开始)。

这段代码的作用是去掉数据框 data 的第一列,创建一个新的数据框 dataFrame。

 运行结果:

二、构建数据集

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

(1) 导入`MinMaxScaler`,这是一个用于将数据按最小最大值进行缩放的类。`MinMaxScaler`将数据缩放到给定的范围内(默认为0到1)。

(2)`data.iloc[:, 1:]`表示从`data`数据框中提取除第一列之外的所有列(假设第一列可能是索引或无关列)。然后,用`.copy()`方法创建一个新的数据框`dataFrame`,以免修改原始数据。

(3)初始化`MinMaxScaler`,指定特征缩放的范围为0到1。这个缩放器会将数据的最小值映射为0,最大值映射为1,中间的数值按比例缩放。 

(4)**逐列缩放数据**:

 for i in ['CO 1','Soot 1','Tem1']:
       dataFrame[i] = sc.fit_transform(dataFrame[i].values.reshape(-1,1))

   这段代码对`dataFrame`中的三列数据——'CO 1'、'Soot 1'、'Tem1'——进行归一化处理。具体过程:
   - `dataFrame[i].values`提取出第`i`列的数据,以NumPy数组的形式表示。
   - `reshape(-1,1)`将数组从一维变为二维(这是因为`MinMaxScaler`需要二维数组作为输入,数据通常按行进行操作)。
   - `sc.fit_transform()`会根据每一列的数据计算最小值和最大值,并将这些数据缩放到0到1之间。
   - 缩放后的数据会替换原数据框`dataFrame`中的对应列。

(5)输出数据框形状:

 print("dataFrame",dataFrame.shape)

然后将时间序列数据转换为模型训练所需的输入(X)和输出(y),以便能够在机器学习模型(例如神经网络)中进行训练。

运行结果:

(5948, 3)

2. 设置x,y

width_x=8
width_y=1

# 取前8个时间段的Tem1、CO 1、Soot 1为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)))
print(np.any(np.isnan(y)))

运行结果:

False
False

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)

三、模型训练

1. 构建模型

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:,:] #取2个预测值,否则经过lstm会得到8*2个预测
    
model=model_lstm()
model

运行结果:

model_lstm(
  (lstm0): LSTM(3, 320, batch_first=True)
  (lstm1): LSTM(320, 320, batch_first=True)
  (fc0): Linear(in_features=320, out_features=1, bias=True)
)

1.1 LSTM层:

1.1.1 self.lstm0 = nn.LSTM(input_size=3, hidden_size=320, num_layers=1, batch_first=True):
① 这是模型的第一个LSTM层,接受的输入特征维度为3,隐藏层的特征维度为320,LSTM层的层数为1。
② batch_first=True 指定输入数据的维度顺序为 (batch_size, seq_len, input_size),也就是以batch为第一维度。
1.1.2 self.lstm1 = nn.LSTM(input_size=320, hidden_size=320, num_layers=1, batch_first=True):
第二个LSTM层,接受前一个LSTM层的输出(大小为320),继续处理。

1.2 全连接层:

self.fc0 = nn.Linear(320, 1):
全连接层,将第二个LSTM层的输出从320维的特征映射到1维,用于输出最终预测结果。

1.3 前向传播 (forward method):

1.3.1 第一个LSTM层:

out, hidden1 = self.lstm0(x):将输入x传入第一个LSTM层。out是LSTM层的输出,hidden1是LSTM的隐藏状态(包括h_n和c_n)。

1.3.2第二个LSTM层:

out, _ = self.lstm1(out, hidden1):将第一个LSTM层的输出和其隐藏状态传递给第二个LSTM层,继续处理。

1.3.3 全连接层:

out = self.fc0(out):将第二个LSTM层的输出传递给全连接层,将输出维度从320变成1,得到每个时间步的预测值。

1.3.4 只保留最后一个时间步的输出:

return out[:, -1:, :]:LSTM模型通常会输出整个序列的预测,但在这段代码中,只取最后一个时间步的预测作为最终输出,这种方法常用于预测下一个时间步的值。 


观察模型的输出数据集格式:

model(torch.rand(30,8,3)).shape

运行结果:

torch.Size([30, 1, 1])

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

 学习率进行动态调整,有效改善了模型的收敛性能。学习率从初始值逐渐衰减,避免了在训练中期因学习率过大导致的训练不稳定。

3. 定义测试函数

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

4. 正式训练模型

# 设置GPU训练
device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
device

运行结果:

device(type='cuda')

#训练模型
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.00117,Test_loss:0.01199
learning rate=0.09961 Epoch: 2,Train_loss:0.01358,Test_loss:0.01160
learning rate=0.09911 Epoch: 3,Train_loss:0.01326,Test_loss:0.01119
learning rate=0.09843 Epoch: 4,Train_loss:0.01290,Test_loss:0.01076
learning rate=0.09755 Epoch: 5,Train_loss:0.01248,Test_loss:0.01028
learning rate=0.09649 Epoch: 6,Train_loss:0.01199,Test_loss:0.00975
learning rate=0.09524 Epoch: 7,Train_loss:0.01142,Test_loss:0.00916
learning rate=0.09382 Epoch: 8,Train_loss:0.01075,Test_loss:0.00851
learning rate=0.09222 Epoch: 9,Train_loss:0.00998,Test_loss:0.00779
learning rate=0.09045 Epoch:10,Train_loss:0.00911,Test_loss:0.00703
learning rate=0.08853 Epoch:11,Train_loss:0.00815,Test_loss:0.00623
learning rate=0.08645 Epoch:12,Train_loss:0.00713,Test_loss:0.00543
learning rate=0.08423 Epoch:13,Train_loss:0.00607,Test_loss:0.00464
learning rate=0.08187 Epoch:14,Train_loss:0.00503,Test_loss:0.00390
learning rate=0.07939 Epoch:15,Train_loss:0.00405,Test_loss:0.00322
learning rate=0.07679 Epoch:16,Train_loss:0.00317,Test_loss:0.00264
learning rate=0.07409 Epoch:17,Train_loss:0.00242,Test_loss:0.00215
learning rate=0.07129 Epoch:18,Train_loss:0.00180,Test_loss:0.00176
learning rate=0.06841 Epoch:19,Train_loss:0.00132,Test_loss:0.00146
learning rate=0.06545 Epoch:20,Train_loss:0.00096,Test_loss:0.00122
learning rate=0.06243 Epoch:21,Train_loss:0.00070,Test_loss:0.00105
learning rate=0.05937 Epoch:22,Train_loss:0.00051,Test_loss:0.00092
learning rate=0.05627 Epoch:23,Train_loss:0.00038,Test_loss:0.00083
learning rate=0.05314 Epoch:24,Train_loss:0.00029,Test_loss:0.00076
learning rate=0.05000 Epoch:25,Train_loss:0.00023,Test_loss:0.00071
learning rate=0.04686 Epoch:26,Train_loss:0.00019,Test_loss:0.00067
learning rate=0.04373 Epoch:27,Train_loss:0.00016,Test_loss:0.00064
learning rate=0.04063 Epoch:28,Train_loss:0.00014,Test_loss:0.00062
learning rate=0.03757 Epoch:29,Train_loss:0.00013,Test_loss:0.00060
learning rate=0.03455 Epoch:30,Train_loss:0.00012,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.00011,Test_loss:0.00057
learning rate=0.02591 Epoch:33,Train_loss:0.00011,Test_loss:0.00056
learning rate=0.02321 Epoch:34,Train_loss:0.00011,Test_loss:0.00056
learning rate=0.02061 Epoch:35,Train_loss:0.00011,Test_loss:0.00056
learning rate=0.01813 Epoch:36,Train_loss:0.00011,Test_loss:0.00056
learning rate=0.01577 Epoch:37,Train_loss:0.00012,Test_loss:0.00056
learning rate=0.01355 Epoch:38,Train_loss:0.00012,Test_loss:0.00057
learning rate=0.01147 Epoch:39,Train_loss:0.00012,Test_loss:0.00057
learning rate=0.00955 Epoch:40,Train_loss:0.00013,Test_loss:0.00058
learning rate=0.00778 Epoch:41,Train_loss:0.00013,Test_loss:0.00059
learning rate=0.00618 Epoch:42,Train_loss:0.00013,Test_loss:0.00060
learning rate=0.00476 Epoch:43,Train_loss:0.00014,Test_loss:0.00061
learning rate=0.00351 Epoch:44,Train_loss:0.00014,Test_loss:0.00062
learning rate=0.00245 Epoch:45,Train_loss:0.00014,Test_loss:0.00062
learning rate=0.00157 Epoch:46,Train_loss:0.00014,Test_loss:0.00062
learning rate=0.00089 Epoch:47,Train_loss:0.00014,Test_loss:0.00062
learning rate=0.00039 Epoch:48,Train_loss:0.00014,Test_loss:0.00062
learning rate=0.00010 Epoch:49,Train_loss:0.00014,Test_loss:0.00062
learning rate=0.00000 Epoch:50,Train_loss:0.00014,Test_loss:0.00062
==================== Done ====================

四、模型评估

1. LOSS图

import matplotlib.pyplot as plt

plt.figure(figsize=(5,3),dpi=300)

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. 调用模型进行预测 

x_test = x_test.to(device)
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=500)
#画出真实数据和预测数据的对比曲线
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 : 均方根误差  ----> 对均方误差开方
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.06313
R2: 0.82236

五、心得体会

深入体会了pytorch在时间序列的预测应用。模型总体与RNN模型较为相似,但与RNN相比,可以进行长记忆模式。

更多推荐