资讯详情

PyTorch LSTM时间序列预测实战:从滑窗构造到多步预测

📅 2026/9/14 3:21:01 | 华诺云谱 👁 阅读
PyTorch LSTM时间序列预测实战:从滑窗构造到多步预测
简介这是基于长短期记忆网络的时间序列预测Python项目源码面向需要完成课程设计、期末大作业的本科生以及希望快速实现预测功能的新手开发者。压缩包共27个文件整体大小8.85MB主要文件类型包括Python脚本、编译生成的pyc缓存、XML工程配置文件、Excel示例数据、PNG/JPG结果可视化图片、LSTM模型权重文件、Markdown说明文档以及PDF使用手册涵盖数据读取与预处理、模型构建、训练评估、预测输出和图表展示等完整流程。代码中加入了详细注释关键函数和网络结构均有解释配合示例数据与训练好的权重下载解压后即可运行体验。目前已有339人学习下载作为期末大作业或课设参考既可用作学习长短期记忆网络的基础案例也能在此基础上扩展改造项目整体完成度高适合作为高分答辩的支撑材料。1. LSTM时间序列预测不是只有论文里那种写法拿到时间序列数据很多人第一反应是直接扔给LSTM然后被Input 0 of layer lstm is incompatible with the layer这类报错卡住。等你把(samples, timesteps, features)捋顺又会发现预测曲线比真实值慢了一拍看起来拟合得很漂亮实际是上一时刻观测值在“硬搬”。这些问题不是模型不够好而是数据构造和训练流程的顺序没摆对。LSTM做时间序列预测代码层面其实就是一个固定套路滑窗生成样本、归一化、定义模型、训练、反归一化、评估。这套流程适合单变量、多变量、单步、多步预测也适合写毕业论文、竞赛起步、给工程项目做快速验证。下面按我平时调试的顺序把能直接抄的Python程序和背后的取舍一次讲清。2. 什么数据能喂给LSTM先把时序问题改成监督学习2.1 LSTM需要的输入形状是 (samples, timesteps, features)LSTM本身不知道什么是“时间”它只认张量。PyTorch里LSTM的输入形状是(seq_len, batch_size, input_size)如果设置了batch_firstTrue则是(batch_size, seq_len, input_size)。所以要先把一维序列变成“用过去look_back个点预测未来某个点”的样本集。import numpy as np def create_sequences(data, look_back10): X, y [], [] for i in range(len(data) - look_back): X.append(data[i:i look_back]) y.append(data[i look_back]) return np.array(X), np.array(y)look_back就是窗口大小也叫历史步长。循环里从i取到i look_back - 1预测i look_back这个点。这里有个容易错的地方range(len(data) - look_back)已经排除了越界最后一个样本的末尾正好是序列倒数第二个点。如果数据量少look_back别设太大否则样本数会急剧减少。输入形状敲定后下面这张表能帮你快速定位问题。参数含义常见取值look_back用多少个历史时刻预测未来单变量10~30多变量按周期来seq_len张量里的时间步等于look_back固定值input_size每步的特征数单变量为1多变量为特征列数batch_size每次喂给模型的样本数16/32/64小数据用162.2 归一化这步做错预测曲线会整体漂移LSTM默认激活函数对输入范围敏感不归一化会出现 loss 不降或者预测值全是同一个数的情况。常见做法是用MinMaxScaler把数据压到 0~1 之间。注意一个细节MinMaxScaler必须只用训练集fit再用同一套参数转换验证集和测试集。from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) train_scaled scaler.fit_transform(train.reshape(-1, 1)) test_scaled scaler.transform(test.reshape(-1, 1))如果先对整个序列fit_transform再做切分测试集的取值范围已经提前参与计算属于数据泄露测试误差会被低估。预测输出后要反归一化用scaler.inverse_transform(pred)把结果还原成原始量纲才能和真实值画在同一张图里。2.3 按时间顺序切分不能随机打乱时间序列和普通分类任务不一样样本之间有顺序依赖。按时间先后切分是硬规矩前70%训练、中间15%验证、最后15%测试。这里不推荐用train_test_split(shuffleTrue)随机打乱会破坏时间结构导致模型“偷看”未来数据。n len(train_scaled) train_data train_scaled[:int(0.7 * n)] val_data train_scaled[int(0.7 * n):int(0.85 * n)] test_data train_scaled[int(0.85 * n):] X_train, y_train create_sequences(train_data, look_back12) X_val, y_val create_sequences(val_data, look_back12) X_test, y_test create_sequences(test_data, look_back12)切分后样本数量会减少look_back个因为序列开头没有足够历史数据。如果你的验证集也要构造样本务必要从验证集自己的起点开始取不能用前一段末尾的数据去拼否则验证集里混进训练数据信息。做PyTorch张量时再unsqueeze(-1)把特征维度补上就行。3. 用PyTorch写一个能直接跑的LSTM预测程序3.1 最小LSTM模型输入层、LSTM层、全连接层LSTM做回归并不需要堆很多层一个单层LSTM加一个全连接层就够用。以下代码是能直接跑的最小结构输入维度1、隐藏单元64、输出维度1。所谓“无脑代码”就是指这种结构不需要额外魔改默认参数下也能出合理结果。import torch import torch.nn as nn class LSTMRegressor(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers1): super().__init__() self.lstm nn.LSTM(input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue) self.fc nn.Linear(hidden_size, 1) def forward(self, x): out, _ self.lstm(x) # out: (batch, seq_len, hidden) out out[:, -1, :] # 只取最后一个时间步的隐状态 return self.fc(out)out[:, -1, :]是这段代码的关键。LSTM每个时间步都会输出一个隐状态但预测的是未来一个值只需要最后一个时间步的隐状态。num_layers在数据量不大时不要超过2层数越深参数越多小数据集上更容易过拟合。3.2 训练循环里的关键参数lr、epochs、batch_size训练循环不用写成框架三件套MSE损失、Adam优化器、训练循环里清零梯度。下面这段可以直接替换数据路径后运行。model LSTMRegressor(input_sizeX_train.shape[-1], hidden_size64) optimizer torch.optim.Adam(model.parameters(), lr0.001) criterion nn.MSELoss() X_train_t torch.tensor(X_train, dtypetorch.float32) y_train_t torch.tensor(y_train, dtypetorch.float32) batch_size 32 dataset torch.utils.data.TensorDataset(X_train_t, y_train_t) loader torch.utils.data.DataLoader(dataset, batch_sizebatch_size, shuffleFalse) for epoch in range(100): model.train() total_loss 0 for xb, yb in loader: optimizer.zero_grad() pred model(xb) loss criterion(pred, yb.unsqueeze(-1)) loss.backward() optimizer.step() total_loss loss.item() if (epoch 1) % 20 0: print(fepoch {epoch1:3d}, loss {total_loss / len(loader):.6f})shuffle必须为False理由和切分一样。lr0.001是Adam默认值大多数单变量时间序列任务不用改。loss小于训练集量级的一半时基本收敛过小说明很可能在硬记数据。每次打印的平均total_loss / len(loader)才是每个样本的平均损失否则会随batch大小波动。3.3 预测未来N步的两种方式递归预测与直接多步预测未来24个点最简单的是递归预测把上一次预测值拼到窗口后面再继续预测下一步。这种方式误差会累积但代码最少。model.eval() with torch.no_grad(): input_window X_test[0] # 第一个测试样本 preds [] for _ in range(24): pred model(torch.tensor(input_window, dtypetorch.float32).unsqueeze(0).unsqueeze(-1)) pred_value pred.item() preds.append(pred_value) input_window np.append(input_window[1:], pred_value) # 滑窗如果你想要前几步更准用直接多步法训练时把标签改成未来N个点模型最后一层输出维度改成N。代价是每个预测步共享一个隐状态步数越多精度下降越明显。实际项目里先用递归预测验证模型是否学到了趋势再考虑换直接多步。4. 评价预测好坏与误差排查从loss曲线到残差4.1 loss曲线怎么判断训练到位了训练loss下降但验证loss一路涨是典型的过拟合。时间序列数据量通常不大LSTM参数量却很大所以先看曲线再谈调参。画loss曲线不需要TensorBoardmatplotlib一行就能存图。import matplotlib.pyplot as plt plt.plot(train_losses, labeltrain loss) plt.plot(val_losses, labelval loss) plt.xlabel(epoch) plt.ylabel(MSE) plt.legend() plt.savefig(loss.png, dpi150)判断标准训练和验证loss都平滑下降最后差值小于训练损失的一半这是健康状态。验证loss跌到一定幅度后反弹立即回调到最低点对应的epoch。遇到这种曲线先降lr到 0.0001 再训20轮不要直接加层数。评估回归预测用RMSE和MAE比loss更直观。from sklearn.metrics import mean_squared_error, mean_absolute_error import numpy as np y_true scaler.inverse_transform(y_test.reshape(-1, 1)) y_pred scaler.inverse_transform(np.array(preds).reshape(-1, 1)) rmse np.sqrt(mean_squared_error(y_true, y_pred)) mae mean_absolute_error(y_true, y_pred) print(fRMSE: {rmse:.4f}, MAE: {mae:.4f})RMSE对异常点更敏感MAE反映平均误差水平。两个数值差距过大说明预测里有几个离群误差点多半出现在波动剧烈的拐点附近。4.2 遗忘门在时间序列里到底管什么很多教程把LSTM讲成黑盒但遗忘门其实可以直接对应到序列特征。遗忘门的输入是h_{t-1}和x_t输出0到1之间的值控制上一时刻细胞状态C_{t-1}有多少保留下来。放到水位、销量这类数据里遗忘门学会的是“昨天的异常波动要不要带向明天”。如果序列有强周期性遗忘门会倾向接近1让长期信息持续流动如果是随机性很强的数据遗忘门接近0模型更依赖最近几步。这也是为什么LSTM比普通RNN能缓解梯度消失梯度可以沿着 cell state 这条通路穿过多个时间步。对比TransformerTransformer靠注意力显式地让远距离位置直接交互但不带天然的顺序归纳偏置小数据上LSTM往往更好上手。4.3 预测结果滞后一拍多半是窗口和标签的问题最常见的“假预测”是预测曲线整体比真实曲线右移一个点。原因是窗口的最后一个值就是真实值t-1模型学到的其实是“把窗口最后那个值搬到输出”。解决办法有两个一是把预测目标改成两步之后用data[ilook_back]改成data[ilook_back1]强制模型多推一步二是把窗口最后一个时间步的输入去掉或加噪声破坏这种捷径。不要一上来就调大hidden_size先检查是不是滞后问题。滞后问题会在滞后一拍的图上显得误差非常小RMSE却仍然很大。用下面代码可以快速看残差分布而不是只看预测图。residuals y_true.flatten() - y_pred.flatten() print(f残差均值: {residuals.mean():.4f}, 残差标准差: {residuals.std():.4f})残差均值接近0但标准差大说明模型方差高残差均值明显偏离0说明有系统性偏差常见于没有做差分或归一化边界截断。5. 给“高分项目”加分的几个细节滚动预测、模型保存与误差分布5.1 一个完整的predict_future函数把前面零散步骤封装成函数项目里反复调用不脏代码。def predict_future(model, scaler, historical_data, steps24, look_back12): model.eval() window historical_data[-look_back:].astype(np.float32) result [] with torch.no_grad(): for _ in range(steps): x torch.tensor(window, dtypetorch.float32).reshape(1, look_back, 1) pred model(x).item() result.append(pred) window np.append(window[1:], pred) return scaler.inverse_transform(np.array(result).reshape(-1, 1)).flatten()这个函数输入原始尺度序列输出原始尺度预测值内部负责归一化和滑窗更新。使用时把训练好的模型和scaler传入即可。5.2 保存模型和scaler预测时不用重新训练训练一次几十分钟保存模型和归一化器是项目基本素养。PyTorch里推荐保存状态字典不推荐整个模型pickle。torch.save(model.state_dict(), lstm_model.pth) model.load_state_dict(torch.load(lstm_model.pth, weights_onlyTrue)) model.eval()还需要把scaler保存下来否则预测新数据时无法对齐归一化范围。import joblib joblib.dump(scaler, scaler.save) loaded_scaler joblib.load(scaler.save)注意加载模型时如果你的输入特征数和训练时不一致最后一层会报错。所以保存模型的同时把hidden_size、look_back、feature_size写进一个小的配置json避免下次打开忘了参数。5.3 画误差分布图比画拟合图更有说服力高分项目通常不只展示“预测曲线和真实曲线重叠”还要证明误差不是只靠一两个点拉低的。画残差直方图和逐点误差折线图一眼能看出哪个时间段预测失效。plt.hist(residuals, bins30, edgecolorblack) plt.xlabel(residual) plt.ylabel(count) plt.savefig(residual_hist.png, dpi150)如果残差直方图呈偏态检查原始序列是否有突变点。对突变点做一阶差分后再预测往往比直接预测原始值稳定得多。这也是LSTM时间序列预测项目里唯一值得花时间做的“高级”操作。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。