LSTM实战:回归 - 实现交通流预测

发布于:2025-09-02 ⋅ 阅读:(23) ⋅ 点赞:(0)

LSTM时间序列预测代码详解

我将逐部分解释这个代码,让初学者能够理解每个部分的作用。

1. 导入必要的库

import random
import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler
  • torch: PyTorch深度学习框架
  • nn: PyTorch的神经网络模块
  • numpy: 数值计算库
  • matplotlib.pyplot: 绘图库
  • MinMaxScaler: 数据归一化工具

2. 设置随机种子

torch.manual_seed(42)
np.random.seed(42)

这确保了每次运行代码时,随机数生成的结果都是一样的,使得实验可重复。

3. 数据生成函数

def generate_data():
    x = [424, 405, 441, ...]  # 原始数据
    data = np.array(x, dtype=np.float32)
    scaler = MinMaxScaler(feature_range=(0, 1))
    data = scaler.fit_transform(data.reshape(-1, 1)).flatten()
    return data

这个函数做了三件事:

  1. 定义了一个包含交通流量数据的列表
  2. 将列表转换为NumPy数组
  3. 使用MinMaxScaler将数据归一化到0-1范围(这是为了让神经网络更容易学习)

4. 创建数据集函数

def create_dataset(data, lookback=10):
    X, y = [], []
    for i in range(len(data) - lookback):
        X.append(data[i:i+lookback])
        y.append(data[i+lookback])
    return np.array(X), np.array(y)

这个函数将时间序列数据转换为监督学习格式:

  • X: 包含过去lookback个时间点的数据
  • y: 包含下一个时间点的数据(我们要预测的值)

例如,如果lookback=3,那么:

  • X[0] = [data[0], data[1], data[2]]
  • y[0] = data[3]

5. LSTM模型定义

class SimpleLSTM(nn.Module):
    def __init__(self, input_size=1, hidden_size=50, output_size=1, num_layers=1):
        super(SimpleLSTM, 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.linear = nn.Linear(hidden_size, output_size)
    
    def forward(self, x):
        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).requires_grad_()
        c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).requires_grad_()
        
        out, (hn, cn) = self.lstm(x, (h0.detach(), c0.detach()))
        out = self.linear(out[:, -1, :])
        return out

这个类定义了一个简单的LSTM模型:

  • __init__方法初始化模型结构
    • nn.LSTM: LSTM层,处理序列数据
    • nn.Linear: 线性层,将LSTM输出转换为预测值
  • forward方法定义了数据如何通过模型
    • 初始化隐藏状态h0和细胞状态c0
    • 将输入数据通过LSTM层
    • 取LSTM输出的最后一个时间步,通过线性层得到预测结果

LSTM神经网络设计解析

这个SimpleLSTM类的设计是一个典型的用于时间序列预测的LSTM神经网络架构。下面我将详细解释为什么这样设计:

1. 网络结构设计

def __init__(self, input_size=1, hidden_size=50, output_size=1, num_layers=1):
    super(SimpleLSTM, 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.linear = nn.Linear(hidden_size, output_size)

输入输出维度

  • input_size=1: 每个时间步的输入特征维度为1,因为我们处理的是单变量时间序列(只有一个数值特征)
  • output_size=1: 输出维度为1,因为我们预测的是单个数值(下一个时间步的值)
  • hidden_size=50: 隐藏状态的维度,这是一个超参数,50是一个中等大小的选择,平衡了模型复杂度和计算效率

层数设计

  • num_layers=1: 使用单层LSTM,对于相对简单的时间序列问题,单层通常足够
  • batch_first=True: 使输入张量的形状为(batch_size, sequence_length, input_size),更符合直觉

线性层

  • nn.Linear(hidden_size, output_size): 将LSTM的隐藏状态映射到输出空间
  • 这种设计是因为LSTM输出的是高维隐藏状态,而我们需要的是单个预测值

2. 前向传播设计

def forward(self, x):
    h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).requires_grad_()
    c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).requires_grad_()

    out, (hn, cn) = self.lstm(x, (h0.detach(), c0.detach()))
    out = self.linear(out[:, -1, :])
    return out

隐藏状态初始化

  • h0c0初始化为全零张量,这是LSTM的标准初始化方式
  • .requires_grad_()允许这些初始状态在训练过程中被优化(虽然通常效果有限)
  • .detach()确保不会在反向传播时计算这些初始状态的梯度,避免不必要的计算

输出处理

  • out[:, -1, :]: 只取LSTM输出的最后一个时间步
  • 这种设计基于一个假设:对于时间序列预测,最近的信息最重要
  • 对于多步预测,代码中使用递归方式(在predict_multiple_steps函数中)逐步生成预测

3. 设计哲学

简单性与有效性

  • 这个设计遵循"简单但有效"的原则
  • 对于许多时间序列预测问题,简单的LSTM架构已经能提供不错的结果
  • 更复杂的架构(如添加更多层、注意力机制等)可能会提高性能,但也会增加过拟合风险和计算成本

针对时间序列的特性

  • LSTM特别适合处理时间序列数据,因为它能捕捉长期依赖关系
  • 单变量设计简化了问题,专注于时间模式而非特征间的关系

可扩展性

  • 虽然当前设计简单,但架构允许轻松扩展:
    • 可以增加hidden_sizenum_layers来提高模型容量
    • 可以修改为多变量输入(修改input_size
    • 可以添加Dropout层防止过拟合
    • 可以添加额外的全连接层增加非线性

4. 适用场景

这种设计特别适合:

  • 单变量时间序列预测
  • 中等复杂度的模式识别
  • 需要平衡准确性和计算效率的场景
  • 作为更复杂模型的基线

5. 可能的改进

虽然当前设计已经足够用于许多场景,但可以考虑以下改进:

  1. 添加Dropout层防止过拟合
  2. 使用双向LSTM捕捉前后文信息
  3. 添加注意力机制关注重要时间点
  4. 使用更复杂的输出层处理多步预测

总之,这个LSTM设计是一个经典且实用的时间序列预测模型,平衡了复杂性、效果和计算效率,非常适合作为时间序列预测任务的起点。

6. 主函数

6.1 数据准备

data = generate_data()
train_size = int(len(data)*0.8)
train_data = data[:train_size]
test_data = data[train_size:]

将数据分为训练集(80%)和测试集(20%)

6.2 创建数据集

lookback = 24
X_train, y_train = create_dataset(train_data, lookback)
X_test, y_test = create_dataset(test_data, lookback)

使用过去24个时间点的数据预测下一个时间点

6.3 数据转换

X_train = torch.from_numpy(X_train).float().unsqueeze(-1)
y_train = torch.from_numpy(y_train).float().unsqueeze(-1)
X_test = torch.from_numpy(X_test).float().unsqueeze(-1)
y_test = torch.from_numpy(y_test).float().unsqueeze(-1)

将NumPy数组转换为PyTorch张量,并调整形状以适应LSTM输入要求

6.4 模型初始化

model = SimpleLSTM(1, 50, 1, 1)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
  • 创建LSTM模型
  • 使用均方误差损失函数
  • 使用Adam优化器

6.5 训练循环

epochs = 500
train_losses = []

for epoch in range(epochs):
    model.train()
    optimizer.zero_grad()
    
    outputs = model.forward(X_train)
    loss = criterion(outputs, y_train)
    
    loss.backward()
    optimizer.step()
    
    train_losses.append(loss.item())
    
    if (epoch + 1) % 10 == 0:
        print(f'Epoch [{epoch + 1}/{epochs}], Loss: {loss.item():.6f}')

训练过程:

  1. 设置模型为训练模式
  2. 清零梯度
  3. 前向传播计算预测值
  4. 计算损失
  5. 反向传播计算梯度
  6. 更新模型参数
  7. 记录损失值

6.6 模型评估

model.eval()
with torch.no_grad():
    test_outputs = model.forward(X_test)
    test_loss = criterion(test_outputs, y_test)
    print(f'Test Loss: {test_loss.item():.6f}')

在测试集上评估模型性能

6.7 结果可视化

plt.figure(figsize=(12, 6))

# 绘制训练损失
plt.subplot(4,1,1)
plt.plot(train_losses)
plt.title('Training Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')

# 绘制测试集上的真实值和预测值
plt.subplot(4, 1, 2)
plt.plot(y_test.numpy(), label='True Values')
plt.plot(test_outputs.numpy(), label='Predictions')
plt.title('Test Data: True vs Predicted')
plt.legend()

# 绘制所有真实值
true_values1 = np.concatenate([y_train.numpy(), y_test.numpy()])
plt.subplot(4, 1, 3)
plt.plot(true_values1, label='True Values')
plt.title('All True Values')
plt.legend()

# 绘制所有真实值和预测值
true_values2 = np.concatenate([y_train.numpy(), test_outputs.numpy()])
plt.subplot(4, 1, 4)
plt.plot(true_values1, label='True Values')
plt.plot(true_values2, label='Predict Values')
plt.title('True vs Predicted (Whole Dataset)')
plt.legend()

plt.tight_layout()
plt.show()

创建四个子图:

  1. 训练损失随epoch的变化
  2. 测试集上的真实值和预测值对比
  3. 所有真实值(训练集+测试集)
  4. 所有真实值和预测值对比

7. 程序入口

if __name__ == "__main__":
    main()

当直接运行此脚本时,执行main函数。

总结

这个代码实现了一个使用LSTM进行时间序列预测的完整流程:

  1. 数据准备和预处理(归一化)
  2. 将时间序列转换为监督学习格式
  3. 定义LSTM模型
  4. 训练模型
  5. 评估模型性能
  6. 可视化结果

LSTM特别适合处理时间序列数据,因为它能够捕捉数据中的长期依赖关系。在这个例子中,模型学习了过去24个时间点的交通流量模式,用来预测下一个时间点的流量。


网站公告

今日签到

点亮在社区的每一天
去签到