简介:这份资源面向计算机、人工智能、通信工程、自动化等专业的在校学生与教师,以及需要完成时间序列预测课程设计、毕业设计或大作业的学习者,提供一套可直接运行的 LSTM 时间序列分析预测完整方案。压缩包共 6 个文件,包含 3 个 Python 源码、2 个 CSV 数据集和 1 个 Markdown 说明文档,整体约 929KB,源码覆盖数据预处理、序列展示与模型训练预测等环节,数据集可直接用于 PM2.5 等时序场景的建模实验。目前已有 96 人学习下载。项目代码均经过测试运行成功,答辩评审平均分达到 96 分,读者可据此快速理解 LSTM 时序预测的完整流程,掌握数据清洗、特征构造、模型搭建与结果可视化等关键步骤,也可在现有代码基础上修改以适配其他预测任务,适合作为课程作业、毕设项目或入门进阶的参考模板。
1. 从一份能跑的 LSTM 时间序列预测源码说起
如果你正在找一份能直接跑起来的 LSTM 时间序列分析预测代码,大概率是三种情况之一:课程设计要交、大作业要演示、或者毕设开题想先拿个能出结果的基线。这份 PM2.5Prediction 资源就是冲着这个场景来的——它把数据预处理、序列可视化、模型训练、预测评估串成了一条完整链路,压缩包里带了 raw.csv 和 pollution.csv 两份原始数据,不用你再去网上东拼西凑数据集。目录结构很直白:main.py 是入口,dataPreprocessing.py 负责清洗和滑窗构造,seriesShow.py 画时序图,README.md 写运行说明。它解决的不是"从零理解 LSTM 数学推导"的问题,而是"我今晚就要看到预测曲线和误差指标"的问题。适合计算机、人工智能、通信、自动化方向的在校学生,也适合想快速验证某个时序建模思路的从业者。下面我按实际拆包复现的顺序,把这份资源从环境到调参到踩坑讲透。
2. 环境搭建与数据流拆解:先让 main.py 跑出第一张图
2.1 依赖清单与 Python 版本选择
拿到压缩包先别急着 python main.py,翻一下 import 语句再决定装什么。这类 LSTM 时序项目常见依赖是 numpy、pandas、matplotlib、scikit-learn,深度学习框架可能是 TensorFlow/Keras 或 PyTorch。从文件名和项目风格判断,Keras 版本的概率更高,因为 dataPreprocessing.py 和 seriesShow.py 这种拆分方式在 Keras 教学项目里很常见。
我一般会先建独立虚拟环境,避免和系统里已有的框架版本打架:
# 创建虚拟环境,Python 3.8~3.10 兼容性最好 python -m venv venv_lstm # 激活(Windows) venv_lstm\Scripts\activate # 激活(macOS / Linux) source venv_lstm/bin/activate # 先装基础科学计算栈 pip install numpy pandas matplotlib scikit-learn # 深度学习框架二选一,先试 Keras pip install tensorflow参数说明:Python 版本不要盲目上 3.12,很多老项目的 Keras 接口在新版本里已经改名或废弃,3.8 到 3.10 是安全区。tensorflow 装完自带 Keras,不用单独 pip install keras。如果你机器有 NVIDIA 显卡且想用 GPU 加速,装 tensorflow-gpu 或对应 CUDA 版本的 torch,但时序预测这种数据量,CPU 跑几分钟也能出结果,不必强求。
装完先验证框架能不能正常导入:
import tensorflow as tf print(tf.__version__) from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout print("Keras layers OK")如果这一步报ImportError或DLL load failed,八成是版本不匹配,先降 Python 版本或降 tensorflow 版本,别硬扛。
2.2 数据文件结构与预处理逻辑
这份资源给了两份 CSV:raw.csv 和 pollution.csv。raw.csv 通常是原始逐时或逐日记录,pollution.csv 一般是经过初步整理、列名规范化的版本。dataPreprocessing.py 干的事无非几件:读 CSV、处理缺失值、把时间列转成索引、对特征做归一化、用滑动窗口把时序切成监督学习样本。
滑窗是 LSTM 时序预测的核心操作,理解它比调模型参数更重要。假设你有 1000 个时间步,用过去 24 步预测下一步,那输入就是 shape 为 (976, 24, 特征数) 的三维数组。我一般会先单独跑一遍预处理脚本,把中间结果打印出来确认维度:
import pandas as pd import numpy as np # 读取数据,注意 parse_dates 把时间列解析成 datetime df = pd.read_csv('pollution.csv', parse_dates=['date'], index_col='date') # 查看前几行和列类型 print(df.head()) print(df.dtypes) print("缺失值统计:\n", df.isnull().sum()) # 归一化,MinMax 把值压到 0~1,LSTM 对量纲敏感 from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler(feature_range=(0, 1)) scaled = scaler.fit_transform(df.values) print("归一化后 shape:", scaled.shape)逻辑说明:parse_dates把字符串时间转成 datetime 索引,后面画图和滑窗都依赖它。isnull().sum()是必查项,PM2.5 数据经常有缺测,直接喂给 LSTM 会得到 NaN 损失。归一化用 MinMaxScaler 而不是 StandardScaler,是因为 LSTM 的激活函数对 0~1 区间更友好,这是时序预测的常见做法。
滑窗构造部分,如果 dataPreprocessing.py 里已经写好函数,直接调用;如果没有,我一般会补一个:
def create_dataset(data, look_back=24): X, y = [], [] for i in range(len(data) - look_back): X.append(data[i:i + look_back, :]) y.append(data[i + look_back, 0]) # 假设第 0 列是预测目标 return np.array(X), np.array(y) X, y = create_dataset(scaled, look_back=24) print("X shape:", X.shape, "y shape:", y.shape)参数说明:look_back是回看窗口,24 代表用过去 24 个时间步预测下一步。这个值不是越大越好,太大容易过拟合且训练慢,太小捕捉不到周期规律。PM2.5 有明显日周期,24 是个合理起点。y.append(data[i + look_back, 0])里的 0 是目标列索引,如果你的预测目标不是第 0 列,要改成对应列号。
2.3 先跑 seriesShow.py 建立数据直觉
很多人上来就训练模型,结果 loss 不降也不知道是数据问题还是模型问题。我的习惯是先跑 seriesShow.py,把原始序列画出来看一眼。时序图能告诉你三件事:有没有明显趋势、有没有周期性、有没有异常尖峰。
import matplotlib.pyplot as plt plt.figure(figsize=(14, 5)) plt.plot(df.index, df['pm2.5'], linewidth=0.8) plt.title('PM2.5 Time Series') plt.xlabel('Date') plt.ylabel('PM2.5') plt.tight_layout() plt.show()如果图里出现一段长时间的水平线,那多半是缺测被填充成了固定值,这种段落在训练前要处理掉。如果看到周期性波动,说明 look_back 设成 24 或 48 是合理的。这一步花两分钟,能省掉后面半小时的瞎调参。
3. LSTM 模型搭建与训练:main.py 里到底发生了什么
3.1 网络结构逐层拆解
main.py 是整份资源的入口,通常包含模型定义、编译、训练、预测、评估这几块。LSTM 时序预测的经典结构是:输入层 → LSTM 层 → Dropout → 全连接层 → 输出层。我按常见写法还原一下:
from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout model = Sequential() # 第一层 LSTM,return_sequences=False 表示只输出最后一个时间步 model.add(LSTM(50, return_sequences=False, input_shape=(look_back, X.shape[2]))) model.add(Dropout(0.2)) # 全连接层压缩特征 model.add(Dense(25, activation='relu')) # 输出层,单值预测 model.add(Dense(1)) model.compile(optimizer='adam', loss='mean_squared_error') model.summary()参数说明:LSTM(50)里的 50 是隐藏单元数,太小欠拟合,太大训练慢且容易过拟合,50 到 128 是常见区间。return_sequences=False是因为我们只需要最后一个时间步的输出做预测,如果后面还要接 LSTM 层,就得设成 True。Dropout(0.2)随机丢弃 20% 神经元,抑制过拟合。input_shape是 (时间步, 特征数),必须和预处理后的 X 维度对上,对不上会直接报错。优化器用 adam,损失用 MSE,这是回归预测的标准配置。
如果你的框架是 PyTorch,结构逻辑一样,只是写法不同:
import torch import torch.nn as nn class LSTMModel(nn.Module): def __init__(self, input_size, hidden_size=50, output_size=1): super().__init__() self.lstm = nn.LSTM(input_size, hidden_size, batch_first=True) self.dropout = nn.Dropout(0.2) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): out, _ = self.lstm(x) out = out[:, -1, :] # 取最后一个时间步 out = self.dropout(out) return self.fc(out)两种写法等价,选你环境里已经装好的那个。别为了跑这份代码专门换框架,时间成本不划算。
3.2 训练过程与关键超参数
模型定义完就是 fit。这一步有几个参数直接决定你能不能出结果:
history = model.fit( X_train, y_train, epochs=50, batch_size=32, validation_split=0.1, verbose=1 )参数说明:epochs=50是训练轮数,太少 loss 没降下来,太多过拟合。建议先跑 50 看 loss 曲线,如果还在降就加到 100。batch_size=32是每批样本数,显存或内存不够就降到 16 或 8。validation_split=0.1从训练集里切 10% 做验证,能帮你判断有没有过拟合。verbose=1打印进度条,看着心里有底。
训练完把 loss 曲线画出来,这是判断模型好坏的第一手证据:
plt.plot(history.history['loss'], label='train_loss') plt.plot(history.history['val_loss'], label='val_loss') plt.legend() plt.title('Training vs Validation Loss') plt.show()如果 train_loss 一直降但 val_loss 先降后升,说明过拟合了,解决办法是加 Dropout、减小隐藏单元数、或者增加数据量。如果两条线都不降,检查数据归一化和 look_back 设置。
3.3 预测与反归一化
模型输出的是 0~1 之间的值,必须反归一化才能和真实 PM2.5 对比:
# 预测 pred = model.predict(X_test) # 反归一化,注意 scaler 是在全量数据上 fit 的 pred_inv = scaler.inverse_transform( np.concatenate([pred, np.zeros((pred.shape[0], scaled.shape[1] - 1))], axis=1) )[:, 0] # 真实值同样反归一化 y_test_inv = scaler.inverse_transform( np.concatenate([y_test.reshape(-1, 1), np.zeros((y_test.shape[0], scaled.shape[1] - 1))], axis=1) )[:, 0]逻辑说明:MinMaxScaler 是对多列一起 fit 的,反归一化时输入维度必须和 fit 时一致,所以要用 zeros 补齐其他列,再取第 0 列。这是新手最容易翻车的地方——直接scaler.inverse_transform(pred)会报维度错误。如果你在预处理时只对目标列做了 scaler,那这里就不用补零,直接反变换即可。
评估指标用 RMSE 和 MAE:
from sklearn.metrics import mean_squared_error, mean_absolute_error import math rmse = math.sqrt(mean_squared_error(y_test_inv, pred_inv)) mae = mean_absolute_error(y_test_inv, pred_inv) print(f"RMSE: {rmse:.2f}, MAE: {mae:.2f}")最后把预测曲线和真实曲线叠在一起画,直观判断模型有没有捕捉到趋势:
plt.figure(figsize=(14, 5)) plt.plot(y_test_inv, label='True') plt.plot(pred_inv, label='Prediction') plt.legend() plt.title('PM2.5 Prediction vs True') plt.show()如果预测曲线明显滞后于真实曲线,说明 look_back 不够或者模型容量不足;如果预测曲线过于平滑,说明过拟合到均值了,需要调整结构。
4. 避坑与排查:这份代码最容易卡住的五个地方
4.1 报错 "cannot import name 'Sequential'"
现象:运行 main.py 直接报 ImportError,说 Keras 里找不到 Sequential。
原因:TensorFlow 2.x 之后 Keras 被整合进 tf.keras,但有些老代码写的是from keras.models import Sequential,而环境里装的是独立 keras 或者版本不匹配。
解决:统一改成from tensorflow.keras.models import Sequential,或者确认pip show keras和pip show tensorflow版本兼容。最省事的办法是卸掉独立 keras,只用 tensorflow 自带的。
4.2 数据里有 NaN,loss 直接变 nan
现象:训练第一个 epoch 后 loss 显示 nan,模型废掉。
原因:CSV 里有缺失值,pandas 读进来是 NaN,归一化和滑窗之后 NaN 扩散到整个数组。
解决:在 dataPreprocessing.py 里加一步df = df.fillna(method='ffill').fillna(method='bfill'),前向填充再后向填充。如果缺失比例超过 20%,考虑直接删掉那段或者用插值。处理完再print(df.isnull().sum())确认一遍。
4.3 预测结果全是一条直线
现象:画出来的预测曲线几乎水平,完全不跟随真实波动。
原因:通常是归一化范围不对,或者模型根本没学到东西。也可能是 y 取错了列,预测目标变成了一个近似常数的特征。
解决:先检查y.append(data[i + look_back, 0])里的列索引是不是真正的预测目标。再检查 scaler 是不是对所有列一起 fit 的,如果目标列本身方差很小,归一化后区分度不够。可以单独对目标列做 scaler,或者换 StandardScaler 试试。
4.4 训练集和测试集划分用了未来数据
现象:测试集指标好得离谱,RMSE 低到不真实。
原因:时序数据不能随机 shuffle 划分,必须按时间顺序切。如果代码里用了train_test_split(shuffle=True),那就是用未来数据预测过去,属于数据泄露。
解决:手动按时间点切分,比如前 80% 做训练,后 20% 做测试:
split = int(len(X) * 0.8) X_train, X_test = X[:split], X[split:] y_train, y_test = y[:split], y[split:]顺序不能乱,这是时序预测的铁律。
4.5 画图中文乱码或负号显示异常
现象:seriesShow.py 画出来的图标题是方框,负号变成方块。
原因:matplotlib 默认字体不支持中文。
解决:在画图脚本开头加两行:
plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = FalseWindows 用 SimHei,macOS 可以换成 Arial Unicode MS 或 Heiti TC。如果只是自己看,把标题改成英文最省事。
5. 从能跑到好用:滑窗调参与多步预测的进阶技巧
把 main.py 跑通只是起点,真正让这份资源发挥价值的是理解滑窗参数和预测步长之间的关系。我见过太多人拿着能跑的代码交完作业就扔了,其实稍微改几行就能变成自己的东西。
先说 look_back 的调法。默认 24 是日周期假设,但 PM2.5 还受周周期和季节影响。你可以写个循环,把 look_back 从 12 试到 72,每次记录测试集 RMSE:
results = {} for lb in [12, 24, 36, 48, 72]: X, y = create_dataset(scaled, look_back=lb) split = int(len(X) * 0.8) X_train, X_test = X[:split], X[split:] y_train, y_test = y[:split], y[split:] model = build_model(lb, X.shape[2]) # 封装好的建模型函数 model.fit(X_train, y_train, epochs=30, batch_size=32, verbose=0) pred = model.predict(X_test) rmse = math.sqrt(mean_squared_error(y_test, pred)) results[lb] = rmse print(f"look_back={lb}, RMSE={rmse:.4f}")跑完你会得到一张 look_back 和误差的对照表,通常存在一个最优值,超过之后误差反而上升。这个实验花不了多少时间,但能让你在答辩或汇报时说出"我对比了不同窗口长度"而不是"我用了 24"。
再说多步预测。原始代码多半是单步预测,即用过去 24 步预测下一步。如果你想预测未来 6 步或 12 步,有两种改法:一是直接把输出层改成 Dense(6),标签改成未来 6 个值;二是递归预测,用预测出的下一步拼回输入继续预测。第一种更稳,第二种误差会累积。我一般推荐第一种:
# 多步预测的标签构造 def create_multistep_dataset(data, look_back=24, predict_steps=6): X, y = [], [] for i in range(len(data) - look_back - predict_steps + 1): X.append(data[i:i + look_back, :]) y.append(data[i + look_back:i + look_back + predict_steps, 0]) return np.array(X), np.array(y)输出层相应改成Dense(predict_steps),损失函数不变。这样模型一次输出未来 6 个值,评估时分别算每一步的 RMSE,能看到误差随预测步长如何增长。
还有一个容易被忽略的点:验证集不能只用一次。原始代码用validation_split=0.1是从训练集尾部切的,但时序数据尾部可能和测试集分布更接近,导致验证指标偏乐观。更严谨的做法是用滚动窗口做交叉验证,或者至少把验证集和测试集在时间上隔开。如果只是交作业,validation_split够用;如果要做正式实验,建议手动切三段:训练、验证、测试,比例 6:2:2,按时间顺序排。
最后说模型保存和复现。训练完不保存权重,下次还得重跑,这是血泪经验。加一行回调:
from tensorflow.keras.callbacks import ModelCheckpoint checkpoint = ModelCheckpoint('best_lstm.h5', monitor='val_loss', save_best_only=True) history = model.fit(X_train, y_train, epochs=100, batch_size=32, validation_split=0.1, callbacks=[checkpoint], verbose=1)这样每次验证损失创新低就自动存权重,训练中断也不怕。加载时model.load_weights('best_lstm.h5')就能恢复。从那以后我每次跑时序模型都强制走一遍"先画图、再切分、存权重"的流程,省下来的返工时间够跑好几组对比实验了。希望这份拆解能帮你把这份 LSTM 时间序列预测资源真正用起来,而不是停在"下载了但没跑"的状态。
本文还有配套的精品资源,点击获取