☰
LSTM+Transformer混合模型时间序列预测实战:从数据读取到预测画图
2026/10/2 14:06:46 网站建设 项目流程

简介:这份资源面向具备一定深度学习基础、希望上手时间序列预测实战的开发者与学习者,聚焦LSTM与Transformer混合模型的构建与落地。压缩包共13个文件,约1.74MB,以csv数据集、py脚本、xml配置、txt需求文档和png结果图为主,涵盖训练与测试数据、模型实现代码、数据读取与预处理脚本,以及预测值与真实值对比的可视化结果,另附项目需求说明便于理解任务目标。内容围绕LSTM捕捉序列局部依赖、Transformer自注意力机制建模全局模式这一组合思路展开,同时涉及数据清洗、归一化、缺失值填充等预处理环节,并给出模型性能评估的可视化方式。已有1099人学习下载,适合想系统理解混合模型预测流程、对照代码复现实验并掌握评估方法的读者参考。

1. 拆开这个 LSTM+Transformer 混合预测包:它到底能跑出什么结果

拿到一个名为“混合模型时间序列预测实战-讲了.rar”的压缩包,第一反应通常不是急着解压,而是先判断它值不值得花时间。这个包的核心价值在于它把 LSTM 和 Transformer 拼在一起做时间序列预测,并且附带了真实值预测值画图.png、data.csv、ceshi.csv 以及数据读取测试.py。换句话说,它不是纯理论笔记,而是一套能直接跑起来、能看到预测曲线对比的工程文件。适合谁?适合已经了解 LSTM 基本结构、想看看 Transformer 自注意力机制怎么塞进时序预测里的人;也适合手头有类似单变量或多变量时序数据、想找个能改的代码骨架的从业者。它解决的不是“从零教你深度学习”,而是“给你一个混合模型的落地起点,让你少写几百行胶水代码”。但要注意,这个包里的 .idea 目录和 .iml 文件是 PyCharm 工程配置,跟模型逻辑无关,别被它们分散注意力。

2. LSTM+Transformer 混合结构:为什么这样拼,以及数据怎么喂进去

2.1 混合模型的选型逻辑:LSTM 管局部,Transformer 管全局

时间序列预测里,LSTM 擅长捕捉短期依赖和顺序模式,比如前几个时间步对当前值的影响。但遇到长序列里跨较远位置的关联,LSTM 的遗忘门再强也会漏信息。Transformer 的自注意力机制可以直接计算任意两个时间步之间的关系权重,不依赖递归,所以对长距离依赖更敏感。把两者串起来,常见做法是:先用 LSTM 层提取序列的局部时序特征,输出的隐藏状态序列再送入 Transformer 编码器,让自注意力在更高层做全局加权。这样既保留了 LSTM 对顺序的归纳偏置,又补上了 Transformer 的全局视野。在这个包里,lstm+transformer.py 大概率就是按这个思路搭的。你拿到代码后,先看 forward 函数里 LSTM 的输出是取了最后一步还是完整序列——这决定了 Transformer 接收的是单个向量还是序列,直接影响后续维度。

2.2 数据读取与预处理:data.csv 和 ceshi.csv 怎么用

包里有 data.csv 和 ceshi.csv,还有数据读取测试.py。通常 data.csv 是训练集,ceshi.csv 是测试集或验证集。时间序列预测不能随机打乱顺序,否则就泄露了未来信息。常见做法是按时间切分,比如前 80% 做训练,后 20% 做测试。数据读取测试.py 里应该包含读取、归一化、滑动窗口构造样本的代码。归一化建议用 MinMaxScaler 或 StandardScaler,注意 scaler 只能在训练集上 fit,然后 transform 测试集,否则测试集的分布信息会渗进训练。滑动窗口的窗口长度是一个关键参数,比如用过去 24 个时间步预测下一个点,那 window_size=24。这个值需要根据数据周期调整,太小会欠拟合,太大会引入噪声。

import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler # 读取数据,假设 data.csv 只有一列目标值 df = pd.read_csv('data.csv') values = df.values.astype('float32') # 归一化:只在训练集上 fit scaler = MinMaxScaler(feature_range=(0, 1)) train_size = int(len(values) * 0.8) train_data = values[:train_size] test_data = values[train_size:] scaler.fit(train_data) train_scaled = scaler.transform(train_data) test_scaled = scaler.transform(test_data) # 构造滑动窗口样本 def create_dataset(data, window_size=24): X, y = [], [] for i in range(len(data) - window_size): X.append(data[i:i+window_size, 0]) y.append(data[i+window_size, 0]) return np.array(X), np.array(y) window_size = 24 X_train, y_train = create_dataset(train_scaled, window_size) X_test, y_test = create_dataset(test_scaled, window_size) # 调整为 LSTM 需要的形状 [样本数, 时间步, 特征数] X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 1)) X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 1))

这段代码的逻辑是:先按时间顺序切分,再归一化,最后用滑动窗口把一维序列变成监督学习样本。参数 window_size 控制回看长度,reshape 里的 1 表示单变量。如果你的数据是多变量,把特征数改成对应列数,并在 create_dataset 里保留所有列。

2.3 模型搭建:LSTM 层与 Transformer 编码器的拼接细节

在 PyTorch 里搭这个混合模型,核心是定义好 LSTM 的输出维度和 Transformer 的输入维度匹配。常见写法是 LSTM 输出 hidden_size,然后 TransformerEncoderLayer 的 d_model 也设成 hidden_size。如果 LSTM 是双向的,输出维度会翻倍,需要加一个线性层压回 d_model。另外,Transformer 本身没有位置信息,需要加位置编码。时间序列的位置编码可以用正弦余弦,也可以用一个可学习的 Embedding。下面是一个可跑的骨架:

import torch import torch.nn as nn import math class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len=500): super().__init__() pe = torch.zeros(max_len, d_model) position = torch.arange(0, max_len).unsqueeze(1).float() div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) pe[:, 0::2] = torch.sin(position * div_term) pe[:, 1::2] = torch.cos(position * div_term) self.register_buffer('pe', pe.unsqueeze(0)) def forward(self, x): return x + self.pe[:, :x.size(1), :] class LSTMTransformer(nn.Module): def __init__(self, input_dim=1, d_model=64, nhead=4, num_layers=2, dropout=0.1): super().__init__() self.lstm = nn.LSTM(input_dim, d_model, batch_first=True, bidirectional=False) self.pos_encoder = PositionalEncoding(d_model) encoder_layer = nn.TransformerEncoderLayer(d_model=d_model, nhead=nhead, dropout=dropout, batch_first=True) self.transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers) self.fc = nn.Linear(d_model, 1) def forward(self, x): lstm_out, _ = self.lstm(x) # [batch, seq_len, d_model] lstm_out = self.pos_encoder(lstm_out) trans_out = self.transformer_encoder(lstm_out) out = self.fc(trans_out[:, -1, :]) # 取最后一个时间步 return out

逻辑说明:LSTM 先把输入序列编码成隐藏状态序列,位置编码给 Transformer 提供顺序信息,TransformerEncoder 做全局注意力,最后取最后一个时间步的全连接输出预测值。参数 d_model 要和 LSTM 隐藏单元数一致,nhead 必须能整除 d_model。如果训练时 loss 不降,先检查输入是否归一化、位置编码是否加对、以及 Transformer 的 dropout 是否过大。

3. 训练、预测与画图:把真实值预测值画图.png 复现出来

3.1 训练循环与损失函数选择

时间序列回归常用 MSELoss 或 SmoothL1Loss。MSE 对异常值敏感,如果数据里有尖峰,SmoothL1 更稳。优化器用 Adam,学习率从 1e-3 开始试。训练时注意 batch_size 不要太大,时序数据样本间相关性高,大 batch 容易陷入平坦梯度。每个 epoch 后可以在验证集上算 loss,保留验证 loss 最低的模型权重。下面是一个训练片段:

from torch.utils.data import DataLoader, TensorDataset # 转为 tensor X_train_t = torch.tensor(X_train, dtype=torch.float32) y_train_t = torch.tensor(y_train, dtype=torch.float32).unsqueeze(1) train_loader = DataLoader(TensorDataset(X_train_t, y_train_t), batch_size=32, shuffle=False) model = LSTMTransformer(input_dim=1, d_model=64, nhead=4, num_layers=2) criterion = nn.SmoothL1Loss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) for epoch in range(50): model.train() total_loss = 0 for batch_x, batch_y in train_loader: optimizer.zero_grad() pred = model(batch_x) loss = criterion(pred, batch_y) loss.backward() optimizer.step() total_loss += loss.item() print(f'Epoch {epoch+1}, Loss: {total_loss/len(train_loader):.6f}')

注意 shuffle=False,因为时序数据不能打乱。batch_size 和 epoch 数根据数据量调,数据少就减小 batch、增加 epoch,但要注意过拟合。

3.2 预测与反归一化:把曲线画回原始量纲

训练完必须反归一化才能和真实值对比。用训练时 fit 的 scaler 对预测结果做 inverse_transform。然后画图,横轴时间,纵轴数值,真实值一条线,预测值一条线。这个包里的真实值预测值画图.png 应该就是这种对比图。如果预测曲线整体滞后或平移,通常是窗口构造时错位了;如果预测值几乎是一条直线,可能是模型没学到东西,检查学习率和归一化。

model.eval() with torch.no_grad(): X_test_t = torch.tensor(X_test, dtype=torch.float32) pred_scaled = model(X_test_t).numpy() # 反归一化 pred = scaler.inverse_transform(pred_scaled) y_true = scaler.inverse_transform(y_test.reshape(-1, 1)) import matplotlib.pyplot as plt plt.figure(figsize=(12, 5)) plt.plot(y_true, label='真实值') plt.plot(pred, label='预测值') plt.legend() plt.savefig('真实值预测值画图.png') plt.show()

参数说明:inverse_transform 的输入形状要和 fit 时一致,这里都是二维。画图时如果测试集太长,可以只画前 200 个点,否则曲线挤在一起看不出细节。

3.3 评估指标:除了看图,还要算 MAE 和 RMSE

图能直观看出趋势,但量化评估不能少。MAE 反映平均绝对误差,RMSE 对大误差更敏感。两个都算一下,如果 RMSE 远大于 MAE,说明存在个别预测偏差很大的点,可能是异常值或模型在某些区段失效。

from sklearn.metrics import mean_absolute_error, mean_squared_error mae = mean_absolute_error(y_true, pred) rmse = np.sqrt(mean_squared_error(y_true, pred)) print(f'MAE: {mae:.4f}, RMSE: {rmse:.4f}')

如果 MAE 和 RMSE 都很大,先别调模型,回头检查数据里有没有缺失值被填成 0、归一化范围是否合理、以及窗口是否跨越了训练集和测试集边界。

4. 避坑与排查:跑这个混合模型时最容易翻车的五个地方

4.1 现象:loss 降到某个值就不动了,预测全是均值

原因:学习率太大导致在局部极小值震荡,或者 LSTM 和 Transformer 的维度没对齐,梯度传不下去。解决:把学习率降到 1e-4 试,检查 LSTM 输出维度和 Transformer 的 d_model 是否一致,双向 LSTM 要加线性层压维。

4.2 现象:预测曲线比真实值滞后一个窗口

原因:构造样本时把未来值当成了输入,或者反归一化时索引错位。解决:检查 create_dataset 里 X 和 y 的切片范围,确保 y 是窗口后的下一个点,不是窗口内最后一个点。

4.3 现象:测试集 loss 远小于训练集 loss

原因:归一化时用了全部数据 fit scaler,测试集信息泄露。解决:严格只在训练集上 fit scaler,测试集只 transform。另外检查是否在训练前就构造了测试窗口。

4.4 现象:Transformer 层加进去后效果反而变差

原因:数据量太小,Transformer 参数量大,过拟合了。解决:减少 Transformer 层数(num_layers 降到 1),增大 dropout,或者先只用 LSTM 跑一个基线,再逐步加 Transformer。

4.5 现象:画图时中文显示成方框

原因:matplotlib 默认字体不支持中文。解决:在画图前设置plt.rcParams['font.sans-serif'] = ['SimHei'],或者把标签改成英文。

5. 进阶技巧:用这个包做多变量预测和超参搜索

这个包目前看起来是单变量预测,但 data.csv 如果有多列,完全可以改成多变量输入。做法是把 create_dataset 里的data[i:i+window_size, 0]改成data[i:i+window_size, :],同时把 LSTM 的 input_dim 改成特征列数。输出如果还是预测某一列,y 就取那一列的偏移值。多变量时归一化要对每一列分别做,或者用同一个 scaler 但 fit 整个训练矩阵。

超参搜索不用上大框架,手写几个循环就行。重点调三个:window_size、d_model、nhead。window_size 试 12、24、48;d_model 试 32、64、128;nhead 试 2、4、8。每次跑完记录验证集 MAE,选最小的组合。注意 nhead 必须整除 d_model,否则 PyTorch 会报错。

还有一个容易忽略的点:Transformer 的位置编码在时间序列里不一定非要用正弦余弦。如果你的序列有明显的周期性,可以试试可学习的位置嵌入,或者直接把时间特征(小时、星期)作为额外输入拼进去。我一般会先跑一个不加位置编码的版本,看看效果掉多少,再决定要不要花时间调位置编码。

最后说一个血泪经验:这个包里的 .idea 目录和 .iml 文件不要跟着代码一起提交到 git,.gitignore 里应该把它们排除掉。另外,每次改完模型结构,先把 batch_size 设成 2 跑一个 mini batch,确认 forward 不报错、loss 能反传,再开全量训练。从那以后我每次拿到新的时序包,都强制先跑一遍数据读取测试.py,确认数据形状和归一化范围,再动模型代码。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询