基于pytorch的LSTM模型训练与预测(附源码)

1.LSTM模型

        关于这个模型的基本概念长短时记忆网络(LSTM)(超详细 |附训练代码)_lstm代码-CSDN博客可以查看这篇文章,写得很详细,我觉得能大概看明白反向传递各个部分的推导就行了。

2.pytorch的安装

        我使用的是conda环境,在参照网上的安装过程后,运行出现了报错OSError: [WinError 126] 找不到指定的模块。 Error loading "D:\WORK\anaconda\lib\site-packages\torch\lib\c10_cuda.dll" or one of its dependencies.

        重新创建了一个python3.9的conda环境后,再次安装torch就可以了。具体过程可以参考这篇文章安装PyTorch详细过程_pytorch安装-CSDN博客

3.LSTM训练

        本文只是简略的一个训练和预测过程,适合感兴趣的人初步学习。所有代码都已经写有详细的注释,希望对读者的阅读能有帮助。整个项目的流程包括爬虫,数据处理,还有数据集等等我已经放在GitHub上,感兴趣的小伙伴可以拿去学习或者修改出更好的训练代码,github链接(最新版)

3.1 LSTMModel.py

        编写一个简单的LSTM模块,pytroch已经内置了LSTM,我们只需要编写调用就行了,不用实现LSTM的内部功能。

import torch
import torch.nn as nn

# input_size输入特征的维度
# hidden_size隐藏层的维度,即每个LSTM单元的隐藏状态向量的维度。
# output_size:输出的维度。
# num_layers:LSTM层的数量,默认为1。


class LSTMModel(nn.Module):
    def __init__(self, input_size, hidden_size, output_size, num_layers=1):
        super(LSTMModel, self).__init__()
        self.hidden_size = hidden_size
        self.num_layers = num_layers

        # 定义lsmt层
        # batch_first=True表示输入数据的形状是(batch_size, sequence_length, input_size)
        # 而不是默认的(sequence_length, batch_size, input_size)。
        # batch_size是指每个训练批次中包含的样本数量
        # sequence_length是指输入序列的长度
        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)

        # 定义全连接层,将LSTM层的输出映射到最终的输出空间。
        self.fc = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        # 初始化了隐藏状态h0和细胞状态c0,并将其设为零向量。
        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
        c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)

        # LSTM层前向传播
        # 将输入数据x以及初始化的隐藏状态和细胞状态传入LSTM层
        # 得到输出out和更新后的状态。
        # out的形状为(batch_size, sequence_length, hidden_size)。
        out, _ = self.lstm(x, (h0, c0))

        # 全连接层前向传播
        # 使用LSTM层的最后一个时间步的输出out[:, -1, :](形状为(batch_size, hidden_size))作为全连接层的输入,得到最终的输出。
        out = self.fc(out[:, -1, :])

        return out

3.2 load_data.py

        加载数据集模块,读取本地的CSV数据集,对数据集提取目标变量和特征变量。

# 加载数据集并划分数据集和测试集
import torch
from torch.utils.data import Dataset, DataLoader, random_split
import pandas as pd
from sklearn.preprocessing import StandardScaler


# 自定义一个数据集类
class CustomDataset(Dataset):
    def __init__(self, csv_file, transform=None):
        self.data = pd.read_csv(csv_file, encoding='GBK')
        self.transform = transform


        # 三次指数平滑
        # self.X = self.X.apply(self.triple_exponential_smoothing, axis=0)


        # 提取特征和目标变量
        self.X = self.data.drop(columns=['当日票房(万)'])  # 除了目标剩下的都是特征
        self.Y = self.data['当日票房(万)']  # 目标

        # 使用StandardScaler对数据进行标准化处理,以确保训练过程中的数值稳定性(可选)
        # pd.DataFrame()函数,可以将数据从不同的数据源(如列表、字典、NumPy数组等)转换成数据帧
        self.scaler = StandardScaler()
        self.X = pd.DataFrame(self.scaler.fit_transform(self.X), columns=self.X.columns)

    def triple_exponential_smoothing(self, series, alpha=0.2):
        """
        三次指数平滑
        series: 时间序列数据
        alpha: 平滑系数,取值范围为 [0, 1]
        """
        model = ExponentialSmoothing(series, trend='add', seasonal='add', seasonal_periods=12)
        
        model_fit = model.fit(smoothing_level=alpha, smoothing_trend=alpha, smoothing_seasonal=alpha)
        return model_fit.fittedvalues

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        # 从self.X中选择索引为idx的行,并将其赋值给X_sample
        X_sample = self.X.iloc[idx]
        Y_sample = self.Y.iloc[idx]

        # 特征数据和标签数据都被转换为float32类型的PyTorch张量。
        sample = {'X': torch.tensor(X_sample.values, dtype=torch.float32),
                  'Y': torch.tensor(Y_sample, dtype=torch.float32)}

        # 在这里进行数据预处理,如果需要的话
        if self.transform:
            sample = self.transform(sample)

        return sample


def load_data():
    # 读取CSV文件
    csv_file = 'D:/WORK/all.movie.new.csv'

    # 创建数据集实例
    custom_dataset = CustomDataset(csv_file)

    # 划分训练集和测试集
    train_size = int(0.9 * len(custom_dataset))  # 训练集占比90%
    test_size = len(custom_dataset) - train_size  # 测试集占比10%
    train_dataset, test_dataset = random_split(custom_dataset, [train_size, test_size])  # 按照比例随机划分

    # 创建数据加载器
    # batch_size参数用于指定每个批次(batch)中包含的样本数量。
    # 通常情况下,较大的batch_size可以加快训练速度,但可能会占用更多的内存资源。
    train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
    test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)

    # 检查数据加载器
    for batch in train_loader:
        print(batch['X'].shape, batch['Y'].shape)
        break

    return train_loader, test_loader, custom_dataset

在加载数据的时候可选是否使用三次指数平滑(代码中已经注释掉了) ,使用三次指数平滑可以使数据对时间的变化效果更好一些。

# 三次指数平滑
# self.X = self.X.apply(self.triple_exponential_smoothing, axis=0)

下图是只使用归一化的效果 

下图是先使用了三次指数平滑,再使用归一化的效果

可以看到我们的预测曲线明显平滑了许多 

 3.3 train.py

        训练模块,通过调用LSTMModel和load_data,来实例化模型和加载数据。然后进行前向传播和反向传递的无限循序训练。

import torch
from torch import nn
import load_data
import LSTMmodel as lstm
import torch.optim as optim


def evaluate_model(model, data_loader, criterion):
    # 评估模块
    model.eval()
    total_loss = 0
    with torch.no_grad():
        for batch in data_loader:
            X_batch = batch['X'].unsqueeze(1).to('cuda')
            Y_batch = batch['Y'].unsqueeze(1).to('cuda')

            outputs = model(X_batch)
            loss = criterion(outputs, Y_batch)
            total_loss += loss.item()
    return total_loss / len(data_loader)


def main():
    # 加载训练集和测试集和对应的数据加载器
    train_loader, test_loader, custom_dataset = load_data.load_data()

    # 设置超参数
    input_size = len(custom_dataset.X.columns)  # 特征数量
    hidden_size = 32  # 隐藏层大小
    # 隐藏层大小是指每个LSTM单元中的隐藏状态向量的维度,它在很大程度上影响了模型的表示能力和性能
    # 如果隐藏层太大,模型可能会过拟合训练数据,对测试数据表现不佳。
    # 如果隐藏层太小,模型可能无法捕捉到足够的信息,导致欠拟合。

    output_size = 1  # 输出大小(预测当日票房)

    num_layers = 4  # LSTM层数
    # 单层LSTM:适用于简单的序列建模任务,结构简单,计算效率高。
    # 多层LSTM:适用于复杂的序列建模任务,能够捕捉更复杂的模式和长距离依赖,但需要更多的计算资源。
    # 层数选择:需要通过实验来确定,考虑任务复杂度、数据量和计算资源。

    learning_rate = 0.01  # 学习率
    # 对于较小的网络或简单任务,较大的学习率(如 0.01)可能是合适的。
    # 对于较深的网络或复杂任务,较小的学习率(如 0.0001)可能是必要的。

    # 实例化模型、损失函数和优化器
    model = lstm.LSTMModel(input_size, hidden_size, output_size, num_layers).to('cuda')  # 在GPU上训练
    criterion = nn.SmoothL1Loss()  # 均方误差损失函数
    # 回归损失函数
    # torch.nn.MSELoss 用于回归任务,计算预测值与目标值之间的均方误差。
    # torch.nn.L1Loss 用于回归任务,计算预测值与目标值之间的平均绝对误差。
    # torch.nn.SmoothL1Loss 结合了 L1Loss 和 MSELoss 的优点,对于回归任务更为稳健。

    optimizer = optim.RMSprop(model.parameters(), lr=learning_rate)
    # model.parameters(),获取模型中所有需要训练的参数(权重和偏置)
    # SGD:适合大规模数据和需要较好泛化性能的任务,可以通过调节学习率和添加动量(Momentum)来改进。
    # RMSprop:适合处理非平稳目标,可以自动调整学习率。
    # Adam:适用于大多数情况,特别是有噪声的梯度和稀疏梯度的情形。

    # 训练模型
    try:
        epoch = 0
        model.train()
        while True:
            for batch in train_loader:
                X_batch = batch['X'].unsqueeze(1).to('cuda')  # 增加序列维度
                Y_batch = batch['Y'].unsqueeze(1).to('cuda')

                # 前向传播
                outputs = model(X_batch)  # 经过LSTM层,全连接层,生成输出
                loss = criterion(outputs, Y_batch)  # 计算模型输出与目标值之间的损失

                # 反向传播及优化
                optimizer.zero_grad()  # 梯度清零,防止堆积

                # 反向传播PyTorch会自动计算损失相对于模型参数的梯度
                # 在这一步中,PyTorch会执行以下操作:
                # 从损失开始,沿计算图的反向方向计算梯度。
                # 对于LSTM层,这意味着计算损失相对于LSTM权重和偏置的梯度。LSTM层的反向传播包括计算输入门、遗忘门、输出门和候选记忆细胞的梯度。
                # 对于全连接层(Linear layer),计算损失相对于线性层权重和偏置的梯度。
                loss.backward()

                torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)  # 添加梯度裁剪,防止梯度爆炸

                # 调用优化器的step函数,使用计算得到的梯度来更新模型参数
                # 优化器会使用学习率来缩放梯度。
                # 对于每个参数,优化器会减去相应的梯度乘以学习率,从而更新参数。
                optimizer.step()

            if (epoch + 1) % 20 == 0:
                # 每20epoch使用测试集计算一次损失,并保存模型
                test_loss = evaluate_model(model, test_loader, criterion)
                print(f'Epoch [{epoch + 1}], Train Loss: {loss.item():.4f}, Test Loss: {test_loss:.4f}')
                # 保存模型
                torch.save(model.state_dict(), f'D:/WORK/lstm_model_{epoch+1}.pth')
                print(f"训练完成并保存模型(lstm_model_{epoch+1}.pth)。")
                model.train()
            else:
                print(f'Epoch [{epoch + 1}], Train Loss: {loss.item():.4f}')

            epoch += 1

    except KeyboardInterrupt:
        # ctrl+c中止训练
        print("训练已中止。")


if __name__ == "__main__":
    main()

3.4 train_and_display.py

        通过调用windows的命令提示窗口来执行训练过程

import subprocess

# 在新的命令提示符窗口中启动另一个Python脚本执行训练,并将信息显示在窗口中
subprocess.Popen(["start", "cmd", "/k", "python", "train.py"], shell=True, creationflags=subprocess.CREATE_NEW_CONSOLE)

3.5 模型训练与调参

        训练

        运行train_and_display.py后开始训练,训练过中程序每20个epoch就会输出一次测试损失并保存模型。想要结束训练按ctrl+c。

        理论上,当训练达到一定的epoch后,loss损失会趋向于一个值,或者会在这个值附件浮动。如果想要继续降低损失,就需要进行调参。当训练过多以后,测试集的loss值可能会不降反而增加,这可能是模型过拟合导致的。

        调参

        在训练的过程中,通过Loss损失的反馈,我们需要对程序内的参数进行调整来训练出更好的模型好的模型(loss更小)。

        已下参数都我根据自己需求设置的。具体每一个参数的作用,代码中对应部分都有详细的注解。

  • hidden_size(隐藏层大小):32
  • output_size(输出大小):1
  • num_layers(LSTM层数):4
  • learning_rate(学习率):0.01
  • optimizer(优化器):RMSprop
  • criterion(损失函数):SmoothL1Loss
  • batch_size(指定每个批次中包含的样本数量):32

        在调参过程中,建议每次调整只对一个参数进行。

        经过不断调参后得到的训练损失:

4.模型预测 

新补充:predict.py的代码已经换成最新的了,添加了对预测数据的归一化操作。因为训练的时候是对归一化的数据进行的了训练,所以预测的时候要把预测数据归一化了再进行预测,不然输出结果可能是一条直线(即无论输入什么数据,输出结果都一样)

        predict.py

        这是一个简单的预测模块,通过手动输入预测数据,然后调用模型对这些数据进行分析最后得出预测结果。

import torch
import LSTMmodel as lstm
import load_data


def predict(model, input_data, scaler):
    model.eval()  # 将模型设置为评估模式
    with torch.no_grad():  # 使用torch.no_grad()上下文管理器来关闭梯度计算,以便在推断过程中不计算梯度。
        input_data = scaler.transform([input_data])
        # 将输入数据转换为torch张量(tensor),并进行一些维度调整,最后将其移动到GPU上
        input_tensor = torch.tensor(input_data, dtype=torch.float32).unsqueeze(0).to('cuda')
        output = model(input_tensor)  # 将输入数据传递给模型,获取输出结果
        return output.item()


def main():
    # 加载数据
    _, _, custom_dataset = load_data.load_data()

    # 构建模型,参数需要与训练的时候相同
    input_size = len(custom_dataset.X.columns)
    hidden_size = 32
    output_size = 1
    num_layers = 4
    model = lstm.LSTMModel(input_size, hidden_size, output_size, num_layers).to('cuda')

    # 加载模型
    model.load_state_dict(torch.load('D:/WORK/lstm_model_560.pth'))

    # 输入预测数据
    # 上座率(%)	场均人次	    票房占比(%)	已上映天数
    # 排片场次	排片占比(%)   当日总出票   当日总场次
    # 出品国家	电影类别1	    电影类别2	    电影类别3
    # 电影评分	男性占比(%)	女性占比(%)	节假日
    input_data = [0.015, 2.0,   0.188, 6,
                  79039, 0.190, 74.0,  37.2,
                  1,     1,     22,    0,
                  9.4,   0.314, 0.686, 13]  # 示例输入数据

    # 进行预测
    prediction = predict(model, input_data, custom_dataset.scaler)

    print(f'模型预测今日票房结果: {prediction:.1f}万')


if __name__ == "__main__":
    main()

        预测结果

测试一

        预测6月13日《我才不要和你做朋友呢》电影的票房信息

       

        现在是6月14号,在猫眼专业版的网站上,我们可以查询到昨日的总票房

测试二

        预测13号《神偷奶爸4》的票房

 

测试三 

        继续预测14号的票房

 

5.总结

        准确率计算公式:   Accuracy=\left ( 1-\frac{|Predict-Actual|}{Actual} \right ) ,带入计算后可以得知准确率还是可以的。

        因为精力有限,并没有对每一个样例进行详细预测。训练模型的时候训练集的特征数量也选得比较简单。要想提高模型的准确率,可以对训练集的特征进行特征工程,这又涉及到了很多其它知识,感兴趣的话大家可以去学习学习。

  • 35
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于PyTorch LSTM(长短期记忆网络)进行时间序列预测的代码如下所示: ```python import torch import torch.nn as nn import numpy as np # 定义LSTM模型 class LSTM(nn.Module): def __init__(self, input_size, hidden_size, num_layers, output_size): super(LSTM, self).__init__() self.hidden_size = hidden_size self.num_layers = num_layers self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(device) c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(device) out, _ = self.lstm(x, (h0, c0)) out = self.fc(out[:, -1, :]) return out # 设置随机种子,以便复现结果 torch.manual_seed(42) # 超参数 input_size = 1 hidden_size = 32 num_layers = 2 output_size = 1 num_epochs = 100 learning_rate = 0.001 # 创建数据集(假设我们有一个包含100个数据点的时间序列) data = np.sin(np.arange(0, 10*np.pi, 0.1)) data = data[:, None] # 划分训练集和测试集 train_size = int(len(data) * 0.8) test_size = len(data) - train_size train_data = data[:train_size, :] test_data = data[train_size:, :] # 将数据集转化为PyTorch的张量 train_data = torch.Tensor(train_data).float() test_data = torch.Tensor(test_data).float() # 定义设备 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 初始化模型 model = LSTM(input_size, hidden_size, num_layers, output_size).to(device) # 定义损失函数和优化器 criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate) # 训练模型 for epoch in range(num_epochs): model.train() # 前向传播和计算损失 output = model(train_data) loss = criterion(output, train_data) # 反向传播和优化 optimizer.zero_grad() loss.backward() optimizer.step() if (epoch+1) % 10 == 0: print(f"Epoch: {epoch+1}, Loss: {loss.item()}") # 在测试集上进行预测 model.eval() with torch.no_grad(): # 训练数据结果 train_result = model(train_data).detach().numpy() # 测试数据结果 test_result = model(test_data).detach().numpy() # 绘制结果图 import matplotlib.pyplot as plt plt.figure(figsize=(12, 8)) plt.plot(train_data.numpy(), label='Original data') plt.plot(range(train_size, len(data)), train_result, label='Train prediction') plt.plot(range(train_size, len(data)), test_result, label='Test prediction') plt.legend() plt.show() ``` 上述代码首先定义了一个LSTM模型,然后设置了超参数,创建了一个包含100个数据点的时间序列,并将其划分为训练集和测试集。接下来,将数据转换为PyTorch张量,并定义了设备(CPU或GPU)。 然后,初始化模型,并定义损失函数和优化器。在训练过程中,进行前向传播、计算损失、反向传播和优化,然后将损失打印出来。 在测试阶段,使用训练好的模型训练集和测试集进行预测,并将结果可视化显示出来。最后,使用matplotlib库绘制了原始数据、训练预测和测试预测的图形。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值