
简介面向高校期末大作业与课程设计场景的时间序列预测项目源码基于LSTM模型实现同时提供RNN和Transformer对比模型。代码结构清晰曾获高分验证适合深度学习初学者快速理解不同循环神经网络在序列建模中的差异。压缩包共31个文件包含Python脚本、CSV时序数据集、说明文档、运行截图与项目配置整体大小28.48MB。其中CSV数据涵盖ETTh1、ETTm1、pollution等常见负荷与空气质量序列模型代码覆盖数据预处理、训练、评估和预测全流程README附有环境配置与运行说明图片直观展示训练曲线和预测效果解压后即可按说明部署运行。目前已有1026人学习下载无论用于课程设计、期末答辩还是代码复用都能提供完整可跑的参考实现。1. 时间序列预测LSTM 模型是玄学还是送分题一份高分期末源码的完整拆解时间序列预测LSTM 模型这门课在很多人眼里是玄学同样的数据有人调三天还在原地打转有人一把过还拿了高分。差别往往不在模型多高级而在数据和流程的细节对不对。这份源码是已经拿高分通过的期末大作业一个可直接打开的 PyCharm 工程内置 ETTh1、ETTm1、pollution 三套公开数据集RNN、RNN_gpu、Transformer 三个模型文件齐整README 连运行截图都配好了解压装好依赖就能复现。它解决的不是读论文而是交作业期末要做时间序列课设的学生或者想快速跑通 LSTM 完整流程的入门开发者都能直接抓起来用。下面按数据、模型、跑通、踩坑的顺序把这套工程的关键细节一次讲透。2. 数据先行ETTh1、ETTm1、pollution 三套数据集怎么选、怎么做滑窗2.1 三套数据的出身、字段与适用选题ETTh1.csv 和 ETTm1.csv 来自同一个公开的电力变压器温度数据集前者每小时一条h 后缀表示 hourly后者每 15 分钟一条m 后缀表示 minutely。特征以电力负荷、油温、风速这类物理量为主适合基于 LSTM 的变压器油温预测或电力负荷短期预测这类选题。pollution.csv 是北京 PM2.5 多变量数据集字段包括 pm2.5、DEWP露点、TEMP温度、PRES气压、cbwd风向、Iws风速、Is积雪时间、Ir降雨量做空气质量预测最合适。数据集采样粒度典型选题变量特征ETTh1.csv1 小时电力负荷 / 油温预测多变量周期以天、周为尺度ETTm1.csv15 分钟分钟级短期预测多变量周期特征更密集pollution.csv1 小时含完整日期时间PM2.5 浓度预测8 个特征含分类风向选数据只有一个原则需要弄清作业要求单变量预测就把目标列单独提出来当 y其余列要么当外部特征、要么直接舍弃要求多变量预测就整表进特征。这份工程把三套数据都配好了切换只需要改代码里的 csv 路径。2.2 归一化与划分顺序数据泄露是期末课设的第一个坑很多人在这一步翻车读 csv 之后先对整个 DataFrame 做 MinMaxScaler再切训练集和测试集。这个顺序会让测试集的 min/max 信息提前暴露给训练过程训练时相当于偷看到了未来序列的分布测试 RMSE 虚低等答辩现场换一段新数据预测立刻露馅。正确做法是先按时间切分再分别归一化scaler 只在训练集上 fit。import pandas as pd from sklearn.preprocessing import MinMaxScaler df pd.read_csv(data/pollution.csv, index_col0, parse_datesTrue) # 前 80% 时间做训练后 20% 做测试时间序列不能随机 shuffle split int(len(df) * 0.8) train_df, test_df df.iloc[:split], df.iloc[split:] scaler MinMaxScaler(feature_range(0, 1)) scaled_train scaler.fit_transform(train_df) # 只在训练集上 fit scaled_test scaler.transform(test_df) # 测试集只 transformfit_transform 和 transform 的分工是这套流程的核心fit 学的是训练集的 min 和 maxtransform 只是套用测试集一旦参与 fit就等同于提前看到了未来数据的取值范围。归一化完成后再做滑窗喂给 LSTM 的每个窗口都在同一尺度下梯度更新才稳定。2.3 滑动窗口lookback 是整天作业里最值得调的参数LSTM 不是把整条序列直接丢进网络而是构造用过去 lookback 个时间步预测下一个时间步的样本。我一般会把滑窗函数单独抽出来方便后面换 lookback 做对照实验import numpy as np def make_sequences(data, lookback24): X, y [], [] for i in range(len(data) - lookback): X.append(data[i:i lookback]) # 过去 24 个时刻 y.append(data[i lookback]) # 未来 1 个时刻 return np.array(X), np.array(y) lookback 24 X_train, y_train make_sequences(scaled_train, lookback) X_test, y_test make_sequences(scaled_test, lookback) print(X_train.shape, y_train.shape)输出形状是 (样本数, lookback, 特征数) 和 (样本数, 特征数)。lookback 的选择必须跟着采样粒度走ETTh1 是小时级、预测下一天24 起步想看周周期拉 168ETTm1 是 15 分钟级、一天有 96 步取 48 或 96 更合理pollution 小时级24 小时是默认起点。lookback 太大训练变慢且容易过拟合太小学不到周期这笔账用验证集跑两组对照再定。pollution.csv 原始数据存在缺失值常见做法是先沿时间方向 ffill 前向填充、再丢掉头部空行最后归一化df df.ffill().dropna()3. 模型实现RNN.py、RNN_gpu.py、Transformer.py 三份代码分别承担什么角色3.1 基础 RNN 为什么打不过 LSTM先弄懂门控在干什么这份工程里有 RNN.py 和 RNN_gpu.py 两个文件。基础 RNN 的短板在于梯度沿时间步逐层相乘序列一长梯度要么小到消失、要么大到爆炸结果模型对窗口早期信息几乎没记忆。LSTM 用遗忘门、输入门、输出门配合 sigmoid 和 tanh决定哪些信息扔掉、哪些写进长期记忆梯度在细胞状态路径上有相对平缓的通道长序列也能稳定训练。课设把 LSTM 当主角不是因为它概念新而是同样的数据和训练轮数下验证 loss 通常比基础 RNN 低一截。拿到源码后建议按角色分工理解RNN.py 跑 baselineRNN_gpu.py 是 LSTM 或 GRU 的 GPU 加速版本装好 GPU 版 TensorFlow 后直接跑Transformer.py 是答辩加分项。先跑通 LSTM 版本再回头补 baseline 和 Transformer 的对比报告才有内容可写。3.2 搭一个能出成果的 LSTMunits、return_sequences、dropout 怎么设工程里的 LSTM 是双层的典型课设结构第一层输出完整时间步给第二层第二层只留最后一个时间步接全连接from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout n_features X_train.shape[2] model Sequential([ LSTM(64, return_sequencesTrue, input_shape(lookback, n_features), dropout0.2), LSTM(32, dropout0.2), Dense(n_features) ]) model.compile(optimizeradam, lossmse, metrics[mae]) model.summary()参数含义逐个说清楚第一个 LSTM 的 units64 是隐藏神经元数return_sequencesTrue 表示输出每个时间步的隐藏状态给下一层第二层 return_sequences 不设默认 False只输出最后一步的向量再接 Dense。input_shape(lookback, n_features)即滑窗长度和特征数。dropout 放在 LSTM 内部抑制过拟合课设从 0.2 起步稳妥。优化器 adam、损失 mse 是回归预测的默认组合mae 作为辅助评估指标。单变量预测时 n_features1多变量时是 8 或更多Dense 输出维度跟着改。模型保存的约定是 model 目录放权重文件state 目录放训练状态或归一化 scaler 信息model.save(model/lstm_pollution.h5)3.3 Transformer.py 的定位对照实验怎么做才不显得堆材料Transformer 对时间序列的优势是并行处理整个窗口用自注意力直接建模任意两个时间步的关系。这类课设工程里的 Transformer.py 通常是简化版编码器结构位置编码、多头注意力、前馈网络、层归一化。答辩时用白板画清楚输入先加位置编码再过多头注意力层注意力权重表示第 3 小时的油温变化会强烈影响第 20 小时的预测——这是 LSTM 很难直观解释的一点。from tensorflow.keras.layers import Layer, MultiHeadAttention, LayerNormalization, Dense class TransformerBlock(Layer): def __init__(self, d_model64, num_heads4, ff_dim128): super().__init__() self.att MultiHeadAttention(num_headsnum_heads, key_dimd_model) self.ffn Sequential([ Dense(ff_dim, activationrelu), Dense(d_model), ]) self.layernorm1 LayerNormalization(epsilon1e-6) self.layernorm2 LayerNormalization(epsilon1e-6) def call(self, inputs): attn_output self.att(inputs, inputs) # 自注意力 out1 self.layernorm1(inputs attn_output) # 残差 层归一化 out2 self.layernorm2(out1 self.ffn(out1)) return out2注意 Transformer 对数据量的要求比 LSTM 高这份工程在 pollution.csv 上能出合理结果是因为序列长度和样本量正好够用。换更小的数据集时先降 d_model 和 num_heads否则训练 loss 压不下去。做对照组时固定同一批数据、同一个 lookback、同一个 scaler只换模型结构答辩老师才挑不出毛病。4. 完整复现路径从配环境、跑通三份代码到画出预测曲线4.1 环境准备Python 版本与依赖清单工程里带着 .idea 目录用 PyCharm 直接打开项目根目录即可运行配置都在包里不用手动建环境再逐个配参数。Python 建议用 3.8 到 3.10TensorFlow 2.x 在这几个版本下最稳定。依赖就四件套pip install numpy pandas matplotlib scikit-learn tensorflow只跑 LSTM 的话CPU 版 TensorFlow 完全够用要跑 RNN_gpu.py 的 GPU 路径需要确认机器的 CUDA 和 cuDNN 版本跟 TensorFlow 匹配。遇到 Python 版本太新装不上 TensorFlow 的情况最省事的解法是建一个 3.10 以下的虚拟环境再在这个环境里重装依赖。4.2 按 README 顺序跑通三套模型README.md 里每一步截图都放好了照着顺序执行就行python RNN.py # baseline python RNN_gpu.py # GPU/CPU 均可跑内部自动判断设备 python Transformer.py # 对照模型每个脚本独立运行输出统一写到 model 和 state 目录。第一次跑建议只跑 RNN_gpu.py确认数据路径和 TensorFlow 版本没问题后再补跑另外两个。脚本里写的是相对路径 data/ETTh1.csv解压后如果把数据文件移走了会直接报文件不存在先检查当前工作目录。4.3 训练参数速查表与调参顺序参数常见值说明lookback24 / 48 / 96 / 168滑窗长度小时级数据 24 起步units32 / 64 / 128第一层 LSTM 神经元数dropout0.1 - 0.3防过拟合课设 0.2 起步batch_size16 / 32 / 64越大批次训练越快但可能收敛到较差解epochs50 - 150配合早停使用lossmse / mae回归任务默认 mseoptimizeradam学习率 0.001 起步调参顺序我习惯先固定 lookback配上 epochs 和早停再动 units最后才碰 dropout。一上来同时改三个参数最后训完根本说不清效果变化是谁带来的。4.4 训练完看什么loss 曲线和真实值预测值曲线README 截图里最核心的图是真实值和预测值的曲线对比。自己验证时用这段代码出图import matplotlib.pyplot as plt inv_pred scaler.inverse_transform(pred.reshape(-1, n_features)) inv_true scaler.inverse_transform(y_test.reshape(-1, n_features)) plt.figure(figsize(12, 4)) plt.plot(inv_true[:200], labeltrue) plt.plot(inv_pred[:200], labelpred) plt.legend() plt.title(LSTM Prediction vs True) plt.show()pred 是模型输出的归一化值画图前必须用训练时的 scaler 反归一化回去否则纵轴范围只有 0-1跟真实值完全对不上。反归一化之前要把 pred reshape 成 (样本数, 特征数) 再调 inverse_transform。两条曲线趋势一致、局部波动有一点相位差是正常的看到预测曲线几乎是平移的直线说明训练环节出了问题大概率是数据泄露或滑窗构造错了下一章逐个排查。5. 避坑实录这份源码复现路上最常见的 5 个翻车点5.1 预测曲线是一条笔直的躺平线现象训练 loss 还在降验证 loss 也正常画出来的预测曲线却像把上一天的值平移了一下变化极小。原因绝大多数情况是归一化和划分顺序做反了scaler 在切分前对整个数据集 fit造成数据泄露也可能是 lookback 太短模型退化成只学会复制最近一个点还有可能是 Dense 输出维度对应不上特征数模型拟合到均值上去。解决回到第 2 章的流程确认 scaler 只在训练集上 fit把 lookback 从 24 往上调试 48、96逼模型看更长历史检查数据有没有排序错乱的行时间序列所有操作都不能 shuffle。5.2 inverse_transform 报 ValueError维度对不上现象报错类似 Found input variables with inconsistent numbers of samples或者 reshape 时长度正好差一个 lookback。原因训练集和测试集构造滑窗后样本数不一致测试样本数等于原始测试行数减 lookback画图和计算指标时拿错长度。解决所有涉及 y_test 和 pred 的地方统一用同一个切片长度predict 返回的结果先 reshape 成 (n_samples, n_features)再传给 scaler.inverse_transform。5.3 模型构建直接报 incompatibleexpected ndim3, found ndim2现象构建 Sequential 时TensorFlow 报 Input 0 of layer lstm is incompatible: expected ndim3, found ndim2。原因LSTM 要求输入三维格式是 (样本数, lookback, 特征数)把二维数组直接丢进去了说明滑窗没做或数据被 reshape 压平了。解决打印 X_train.shape 确认是三维滑窗函数没写对回第 2 章重新构造多变量预测时把 n_features 和 Dense 输出维度对齐。5.4 验证 loss 先降后升训练 loss 还在掉现象前几十个 epoch 两条 loss 一起降往后验证 loss 开始反弹训练 loss 却继续走低。原因典型过拟合。LSTM 参数量不小pollution 这类数据量也就几千行训练太久模型把训练集噪声当成了规律。解决把 dropout 加到 0.2 以上加早停是更省事的办法patience 设 10-15监控 val_loss连续没改善就回滚到最佳权重from tensorflow.keras.callbacks import EarlyStopping early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) model.fit(X_train, y_train, validation_data(X_test, y_test), epochs100, batch_size32, callbacks[early_stop])5.5 加载保存的模型后预测全 NaN现象训练完保存了 model/lstm_pollution.h5换个脚本重新加载预测输出大面积 NaN。原因加载和训练用了不同版本的 TensorFlow或者保存的是自定义 Transformer 层、加载脚本没注册对应的层定义另一个常见原因是归一化前的数据本身带 NaN训练时表面正常保存时权重已经被污染。解决加载和训练必须同一环境自定义层对象要在加载脚本里先 import 进来回到第 2 章df.ffill().dropna() 先把数据清干净再归一化。这条习惯能躲掉九成 NaN 闹鬼。6. 把作业从能跑改到能答辩多步预测、回归指标和 baseline 对照6.1 单步改多步滚动预测和它的误差累积单步预测是用过去 24 小时预测下 1 小时答辩时老师通常会追问能不能预测未来 24 小时。最小改动是滚动预测把上一步的预测结果当成新窗口输入循环 24 次。def forecast(model, last_window, steps24): preds [] window last_window.copy() for _ in range(steps): p model.predict(window[np.newaxis, :, :], verbose0)[0] preds.append(p) window np.concatenate([window[1:], p[np.newaxis, :]], axis0) return np.array(preds)window 形状是 (lookback, n_features)每预测一步把窗口往前推一格末尾补上刚预测的值。多步预测的坑是误差累积第 24 步的误差远大于第 1 步答辩时主动承认这一点反而显得你理解序列预测的本质。6.2 回归三件套RMSE、MAE、R2 出一张指标表光有曲线不够看答辩还需要指标表。计算后按 RMSE、MAE、R2 排三行直接进报告from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score rmse mean_squared_error(inv_true, inv_pred, squaredFalse) mae mean_absolute_error(inv_true, inv_pred) r2 r2_score(inv_true, inv_pred) print(fRMSE{rmse:.3f}, MAE{mae:.3f}, R2{r2:.3f})新版 sklearn 里 squaredFalse 是老版 mean_squared_error 的替代写法老接口会报警告但不影响数值。R2 接近 1 说明拟合好负值说明模型比直接预测均值还差指标表里顺手把 baseline 也算上。6.3 朴素 baseline让 LSTM 的优势有对照答辩被问凭什么说 LSTM 有效时最实在的回答是给一个基线用过去 24 小时的均值当预测结果算同一套 RMSE、MAE 对比。通常 LSTM 能把 RMSE 压到基线的一半左右这个数字比讲任何原理都有说服力。建议做实验时固定同一个 scaler 和后处理流程算出 LSTM、RNN baseline、Transformer 三行指标形成一张干干净净的对比表课程报告直接把表贴进去。这套工程解压后对照 README 用省掉的配置时间能把三个模型都跑一遍。从那以后我每次跑序列预测课设都强制自己先打印数据行列数、确认归一化只在训练集上 fit、再检查滑窗 shape三个检查项过完才谈调参这套流程帮我避开了绝大多数翻车现场希望帮到你。本文还有配套的精品资源点击获取