简介这份资源面向计算机、人工智能、通信工程、自动化等专业的在校学生与教师以及希望进阶学习时间序列预测的开发者提供一套可直接运行的 LSTM 时间序列分析预测完整方案可用于毕业设计、课程设计、大作业或项目初期立项演示。压缩包共 6 个文件包含 3 个 Python 源码、2 个 CSV 数据集和 1 个 Markdown 说明文档整体约 929KB其中源码覆盖数据预处理、序列展示与模型训练预测等环节CSV 提供原始与处理后数据Markdown 则记录项目说明与运行指引。目前已有 96 人学习关注。代码均经过测试运行成功答辩评审平均分达到 96 分读者可据此快速复现 PM2.5 预测流程理解 LSTM 建模思路并在此基础上修改扩展功能适合作为学习参考与实战模板。1. 从一份能跑通的 LSTM 时间序列预测代码说起它到底解决了什么问题很多人第一次接触时间序列预测是从股票收盘价、电力负荷、设备传感器读数这类带时间戳的数据开始的。你手上有一列按时间排好的数值想预测下一段会怎么走用线性回归拟合出来的曲线总是慢半拍用 ARIMA 又要反复调参和差分。这时候 LSTM 时间序列分析预测就成了一个绕不开的方案它靠门控结构记住长期依赖对非平稳、带噪声的序列比传统统计方法更耐受。这份「完整代码数据可直接运行」的东西本质是把数据加载、归一化、滑窗切分、模型搭建、训练、反归一化、指标评估这一整条链路打包好让你不用从零拼装就能看到预测曲线。它适合两类人一类是要交大作业、需要一份结构完整且能复现的 Python 源码和文档说明的学生另一类是刚上手 PyTorch LSTM 源码、想先跑通一个最小闭环再改自己数据的工程师。下面我按实际落地的顺序把这份方案拆开讲清楚包括数据集怎么处理、参数怎么设、哪里最容易翻车。2. LSTM 时间序列预测的数据准备与滑窗构造2.1 数据集长什么样先做哪几项检查拿到一份时间序列数据集别急着往模型里灌。我一般先做三件事看时间列是否连续、看目标列有没有缺失和异常值、看数值量纲跨度大不大。常见的时间序列数据集通常是一列时间戳加一列或多列数值比如电力负荷、气象记录、设备运行参数。如果时间列有跳变说明采样不规律直接按行滑窗会把不连续的两段拼到一起预测结果会莫名其妙地差。检查用 pandas 几行就能完成重点是确认缺失比例和数值范围import pandas as pd import numpy as np # 读取数据集时间列解析为 datetime df pd.read_csv(dataset.csv, parse_dates[timestamp]) df df.sort_values(timestamp).reset_index(dropTrue) # 1. 检查时间间隔是否均匀 diff df[timestamp].diff().dropna() print(时间间隔分布\n, diff.value_counts().head()) # 2. 检查缺失与异常 print(缺失比例\n, df.isna().mean()) print(目标列统计\n, df[value].describe()) # 3. 简单异常值处理超出 3 倍标准差的点用前后均值替换 mean, std df[value].mean(), df[value].std() mask (df[value] - mean).abs() 3 * std df.loc[mask, value] np.nan df[value] df[value].interpolate()这段代码的逻辑是先按时间排序保证顺序正确再用diff看采样间隔是否一致间隔种类过多说明数据不规整。缺失和统计量用来判断要不要插值。异常值用 3σ 原则标记后插值比直接删除更稳因为时间序列删一行会破坏连续性。参数上3 * std这个倍数可以按业务调整传感器数据噪声大时可以放宽到 4 倍。2.2 归一化与滑窗把序列切成监督学习样本LSTM 对输入尺度敏感没归一化的数据训练时损失会剧烈震荡。常见做法是 Min-Max 归一化到 [0,1]或者用训练集的均值和标准差做标准化。这里有个血泪经验归一化的参数只能用训练集算验证集和测试集要用训练集的参数来变换否则就是数据泄漏评估指标会虚高。滑窗是把一维序列变成「输入-输出」对的关键。假设用过去 24 个点预测下一个点窗口就在序列上滑动from sklearn.preprocessing import MinMaxScaler # 按时间顺序切分前 70% 训练中间 15% 验证后 15% 测试 n len(df) train_end int(n * 0.7) val_end int(n * 0.85) scaler MinMaxScaler() # 只用训练集拟合 scaler scaler.fit(df[value].values[:train_end].reshape(-1, 1)) scaled scaler.transform(df[value].values.reshape(-1, 1)).flatten() def make_windows(series, lookback24, horizon1): X, y [], [] for i in range(len(series) - lookback - horizon 1): X.append(series[i:i lookback]) y.append(series[i lookback:i lookback horizon]) return np.array(X), np.array(y) lookback 24 # 回看步数按采样频率定 horizon 1 # 预测步长 X, y make_windows(scaled, lookback, horizon) # 按切分点划分注意窗口索引与原始索引的偏移 train_X, train_y X[:train_end - lookback], y[:train_end - lookback] val_X, val_y X[train_end - lookback:val_end - lookback], y[train_end - lookback:val_end - lookback] test_X, test_y X[val_end - lookback:], y[val_end - lookback:] # 转成 LSTM 需要的三维张量[样本数, 时间步, 特征数] train_X train_X[..., np.newaxis] val_X val_X[..., np.newaxis] test_X test_X[..., np.newaxis]逻辑说明make_windows把序列切成固定长度的历史窗口和对应标签。lookback决定模型能看到多长的历史太小记不住周期太大训练慢且容易过拟合。horizon是预测未来几步单步预测设 1多步预测就调大。切分时减去lookback是为了对齐窗口索引这一步很多人会算错导致训练集和测试集有重叠。最后加np.newaxis是因为 PyTorch 的 LSTM 要求输入是[batch, seq_len, input_size]三维。提示归一化参数务必只从训练集拟合这是时间序列里最常见也最隐蔽的坑。3. PyTorch LSTM 模型搭建与训练参数怎么定3.1 模型结构几层、多少隐藏单元才够用LSTM 模型代码的核心就几个参数input_size、hidden_size、num_layers、output_size。单变量时间序列input_size就是 1output_size是预测步长。hidden_size是隐藏状态维度常见从 32 到 128 试起序列越复杂越大。num_layers一般 1 到 2 层够用堆到 3 层以上在小数据集上基本是过拟合。import torch import torch.nn as nn class LSTMForecaster(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2, output_size1, dropout0.2): super().__init__() self.hidden_size hidden_size self.num_layers num_layers # batch_firstTrue 让输入为 [batch, seq, feature] self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0) self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # h0、c0 默认全零也可显式初始化 out, (h_n, c_n) self.lstm(x) # 取最后一个时间步的输出接全连接 last out[:, -1, :] return self.fc(last)逻辑说明nn.LSTM返回的out包含每个时间步的隐藏输出预测任务通常只取最后一步out[:, -1, :]因为它聚合了整段历史信息。dropout只在多层时生效单层加 dropout 会被 PyTorch 忽略。fc把隐藏维度映射到预测维度。参数上hidden_size64、num_layers2、dropout0.2是一个比较稳的起点数据量小就降到 32 和 1 层。3.2 训练循环、损失函数与早停训练部分要盯住三样东西损失函数、优化器、学习率。回归任务用 MSE 或 MAEMSE 对大误差更敏感MAE 更鲁棒。优化器 Adam 学习率 1e-3 起步配合早停防止过拟合。from torch.utils.data import DataLoader, TensorDataset def to_loader(X, y, batch_size32, shuffleFalse): ds TensorDataset(torch.tensor(X, dtypetorch.float32), torch.tensor(y, dtypetorch.float32)) return DataLoader(ds, batch_sizebatch_size, shuffleshuffle) train_loader to_loader(train_X, train_y, shuffleTrue) val_loader to_loader(val_X, val_y) device torch.device(cuda if torch.cuda.is_available() else cpu) model LSTMForecaster().to(device) criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) best_val, patience, wait float(inf), 10, 0 for epoch in range(200): model.train() for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() loss criterion(model(xb), yb) loss.backward() # 梯度裁剪防止 LSTM 梯度爆炸 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() model.eval() val_loss 0.0 with torch.no_grad(): for xb, yb in val_loader: xb, yb xb.to(device), yb.to(device) val_loss criterion(model(xb), yb).item() * len(xb) val_loss / len(val_loader.dataset) if val_loss best_val: best_val, wait val_loss, 0 torch.save(model.state_dict(), best_lstm.pt) else: wait 1 if wait patience: print(f早停于 epoch {epoch}) break逻辑说明每个 epoch 先训练再验证验证损失连续patience轮不下降就停。clip_grad_norm_是 LSTM 训练的后悔药梯度爆炸时没有它损失会直接变 NaN。best_lstm.pt保存验证集最优权重避免用最后一轮过拟合的模型。参数上batch_size32适合几千条样本样本上万可以加到 64 或 128patience10是经验值数据噪声大可以放宽。3.3 反归一化与评估指标预测出来的值还在 [0,1] 区间必须用训练集的 scaler 反变换回原始量纲否则指标没有意义。评估常用 MAE、RMSE、MAPE。model.load_state_dict(torch.load(best_lstm.pt)) model.eval() with torch.no_grad(): pred model(torch.tensor(test_X, dtypetorch.float32).to(device)).cpu().numpy() # 反归一化 pred_inv scaler.inverse_transform(pred) true_inv scaler.inverse_transform(test_y) mae np.mean(np.abs(pred_inv - true_inv)) rmse np.sqrt(np.mean((pred_inv - true_inv) ** 2)) mape np.mean(np.abs((pred_inv - true_inv) / true_inv)) * 100 print(fMAE{mae:.3f} RMSE{rmse:.3f} MAPE{mape:.2f}%)逻辑说明inverse_transform把预测和真实值都还原到原始尺度再算指标。MAPE 在真实值接近 0 时会爆炸遇到这种情况改用 MAE 或 RMSE。参数上评估一定要在测试集上做且测试集从头到尾没参与归一化拟合和训练。4. 环境配置与完整跑通流程4.1 Python 环境与依赖安装这份方案依赖 PyTorch、pandas、numpy、scikit-learn、matplotlib。Python 版本建议 3.8 到 3.11太新可能遇到 PyTorch 轮子不匹配。用虚拟环境隔离避免和系统包打架。# 创建并激活虚拟环境 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装依赖CPU 版 PyTorch 足够跑通 pip install torch pandas numpy scikit-learn matplotlib逻辑说明虚拟环境保证依赖干净pip install torch默认装 CPU 版有显卡且配好 CUDA 再装对应版本。如果下载慢换国内镜像源即可。装完用python -c import torch; print(torch.__version__)验证。4.2 从数据到预测曲线的执行顺序把前面的代码按顺序串起来就是完整流程读数据、清洗、归一化、滑窗、切分、建模型、训练、早停、加载最优权重、反归一化、算指标、画图。建议把配置项集中到文件开头方便改。CONFIG { csv_path: dataset.csv, time_col: timestamp, target_col: value, lookback: 24, horizon: 1, hidden_size: 64, num_layers: 2, dropout: 0.2, batch_size: 32, lr: 1e-3, epochs: 200, patience: 10, }逻辑说明把超参数集中管理换数据集时只改lookback和hidden_size。lookback要结合采样频率小时级数据看过去一天就设 24分钟级数据看过去一小时就设 60。画图用 matplotlib 把真实值和预测值叠在一起肉眼判断相位有没有滞后。注意如果预测曲线整体平移了一段多半是 lookback 太短或归一化泄漏先查这两处。5. 避坑与排查LSTM 时间序列预测最常见的 5 个翻车点5.1 损失降到某个值就不动了现象训练损失几轮后卡住验证损失也不降。原因通常是学习率太大导致在最优解附近震荡或者输入没归一化。解决把学习率降到 1e-4 试确认归一化只用了训练集再检查滑窗标签有没有错位。5.2 预测曲线比真实值滞后一个相位现象预测形状对但整体晚一步。原因模型倾向于输出上一个观测值lookback 太短或模型容量不足。解决增大lookback增加hidden_size或在输入里加入时间特征如小时、星期。5.3 验证损失远低于训练损失现象验证比训练还好不合常理。原因数据泄漏滑窗切分时训练集和验证集重叠或归一化用了全量数据。解决严格按时间切分归一化 scaler 只在训练集 fit切分点减去 lookback 对齐索引。5.4 损失突然变成 NaN现象训练中途 loss 变 NaN。原因梯度爆炸学习率过大或序列过长。解决加clip_grad_norm_降低学习率检查数据里有没有 inf 或极端异常值。5.5 换自己的数据集后指标崩了现象原数据跑得好换数据 MAPE 飙升。原因新数据量纲、周期、缺失情况不同超参数没跟着调。解决重新做数据检查调整lookback匹配新周期重新拟合 scaler必要时降低模型复杂度。6. 让预测更稳的两个进阶技巧多步预测与残差校正单步预测跑通后很多人会想预测未来一整段。直接把horizon调大会发现误差累积得很快因为模型每一步都基于上一步的预测。我一般用两种办法缓解。第一种是直接多输出让fc输出horizon维一次预测整段避免误差逐步累积。第二种是滚动预测加残差校正先用模型滚动预测再用一个简单模型对残差建模。直接多输出的改动很小把output_size设成horizon标签y也切成对应长度class MultiStepLSTM(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2, horizon6, dropout0.2): super().__init__() self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0) self.fc nn.Linear(hidden_size, horizon) def forward(self, x): out, _ self.lstm(x) return self.fc(out[:, -1, :])逻辑说明fc输出维度等于预测步长一次前向就得到未来horizon个点。这样训练时每个样本的标签是一段连续值损失是整段的 MSE。参数上horizon越大越难学建议从 3 到 6 起步再逐步加长。评估时按每个预测步分别算 RMSE能看出误差随步长增长的速度。残差校正的思路是先用 LSTM 得到预测再用真实值减预测得到残差序列对残差用一个小模型或移动平均去拟合最后把校正量加回去。这招在设备寿命预测、负荷预测里很实用因为 LSTM 抓的是主趋势残差里往往还有可建模的周期成分。验证方法上我习惯留一段最近的数据做滚动回测每次用历史窗口预测下一步然后把真实值并入历史继续预测模拟真实上线场景。这样得到的指标比一次性切分更接近实际表现。最后说个我自己的习惯每次换数据集先只跑 5 个 epoch 看损失有没有正常下降确认链路通了再放开训练。这个笨办法帮我省过很多次排查环境的时间。希望帮到你。本文还有配套的精品资源点击获取