LSTM时间序列预测全流程:PM2.5数据清洗、滑窗建模与训练实战
2026/9/15 3:57:38 网站建设 项目流程

简介:一份面向LSTM时间序列分析与预测学习的可运行项目资源,适合高校人工智能、计算机、自动化等专业学生用于课程作业、毕业设计或入门进阶。内容以PM2.5浓度预测为场景,包含原始与处理后的污染物数据集,完整覆盖数据预处理、序列建模、训练评估与结果可视化流程,核心代码均已调试通过,配合说明文档可快速复现实验并理解时间序列预测的落地方法。项目还提供数据预处理模块与序列展示脚本,方便比对清洗前后数据差异、观察预测曲线与实际值走势。资源共6个文件,包括3个Python源码脚本、2个CSV格式数据集和1个Markdown说明文档,压缩包大小约929KB,结构精简,便于直接运行或在此基础上做二次修改。当前已有96人学习浏览,适合需要实战参考、答辩展示或作业保底方案的读者下载使用。

1. PM2.5 时间序列预测:课程设计从 ARIMA 换到 LSTM 之后

拿到某城市逐小时 PM2.5 监测数据时,最初用 ARIMA 试跑,残差里明显还残留着周期性,于是把特征工程后的序列换成了 LSTM 来做时间序列预测。这套工程就是对应的完整实现,压缩包里包含 main.py、dataPreprocessing.py、seriesShow.py 三个脚本,raw.csv 是原始逐小时监测记录,pollution.csv 是清洗后的标准数据集,README.md 里有启动说明。代码按「读数据 → 预处理 → 滑窗构造样本 → 训练 → 反归一化画曲线」的顺序组织,环境依赖只有 pandas、numpy、scikit-learn、tensorflow 和 matplotlib,下载后补齐依赖就能跑。

这套资源适合三类人:正在做课程设计或者毕设、需要一份能稳定复现且结构清晰的 LSTM 参考实现的人;已经跑通过 LSTM 分类任务、想迁移到时间序列预测场景的开发者;以及手头有类似逐小时监测数据、想快速验证深度学习预测方案的工程师。工程本身不算复杂,但它把时间序列预测里最容易出错的几个环节——数据切分、归一化范围、look_back 构造、反归一化——都串成了可执行的代码,这正是比单独读论文或教程有价值的地方。下面按文件拆开讲,每个环节给出参数含义和常见的错误用法。

2. pollution.csv 清洗与时间序列划分,凭什么不能随机打散

2.1 raw.csv 到 pollution.csv 的清洗链路

原始数据 raw.csv 保存的是监测站上传的逐小时记录,通常包含年月日时分列、PM2.5 浓度、露点、温度、气压、风速等字段。这类 CSV 最大的问题不是格式乱,而是时间列被拆成多个字段、中间有缺失记录、风向用字符串表示。直接用 pandas 读进来后必须先把时间合并成 DatetimeIndex,否则后续按时间滑窗时会因为索引不连续导致序列断裂。

dataPreprocessing.py 里常见的第一步是这样:

import pandas as pd # raw.csv 是未清洗的逐小时原始监测记录 df = pd.read_csv('raw.csv', encoding='utf-8') # 如果 raw.csv 把时间拆成了 year/month/day/hour 四个字段 df['datetime'] = pd.to_datetime(df[['year', 'month', 'day', 'hour']]) df.set_index('datetime', inplace=True) df = df.sort_index() # 连续列用线性插值,避免前向填充把台阶误差带进序列 df = df.interpolate(method='linear').dropna() df.to_csv('pollution.csv', encoding='utf-8')

这段代码的关键在interpolate(method='linear')。缺失值处理通常有两个选择:ffill()前向填充适合短时间缺失,比如停表 1 小时,用上一小时浓度补上问题不大;但如果是连续 5 小时以上缺失,前向填充会产生一段水平直线,LSTM 会把这当作真实规律学进去,预测曲线会出现拖尾。线性插值在缺失段首尾之间拉一条直线,更接近浓度缓慢变化的时间序列特性。dropna()放在插值之后,是为了清掉序列头部和尾部没有边界值可插的缺口。

2.2 时间序列的划分规则与数据泄漏

比清洗更容易踩坑的是训练测试划分。分类任务里随机打散数据没问题,但时间序列预测里样本之间有先后依赖关系——测试集的时间必须在训练集之后,一旦随机切分,模型等于提前看到了未来数据,训练时的 loss 会很好看,但上线后立刻失效。这种泄漏问题在 LSTM 模型里表现得比线性模型更隐蔽,因为序列本身没有显式的「未来」列,泄漏藏在索引顺序里。

正确的切分方式是按时间顺序截取:

# 按时间顺序划分,test 在 train 之后,绝不能随机 shuffle train_size = int(len(df) * 0.8) train_df = df.iloc[:train_size] test_df = df.iloc[train_size:]

train_size取 0.8 是经验值,数据量大可以提到 0.9。如果数据本身有强季节性,比如 11 月污染明显高于 7 月,那么训练集和测试集会跨不同季节分布,模型表现会偏保守。课程设计场景里不需要额外做交叉验证,画出测试集上的预测曲线就足够说明问题。

还有一个被忽略的点:对完整序列做归一化时,fit必须在训练集上完成,测试集只能transform。下面的表格列出两种做法和后果:

做法代码后果
全量数据 fit 后切分先归一化再划分测试集统计量混入归一化参数,训练时模型通过归一化系数间接获取未来信息,验证指标虚高
先切分再对训练集 fit只在 train_df 上调用 fit_transform归一化参数只反映训练分布,测试集转换后的数值分布可能略偏,这才是真实预测环境

判断是否泄漏有一个笨办法:训练完后把模型在训练集上的 RMSE 和测试集上的 RMSE 对比。如果训练误差远低于测试误差,说明有泄漏或者过拟合;如果两者接近且测试误差不大,那这套流程基本是干净的。

2.3 保存中间文件的意义

把预处理结果单独输出成 pollution.csv,而不是在 main.py 里重新处理,是为了让每一步可校验。处理完的 CSV 用 Excel 打开就能检查时间索引是否连续、数值范围是否正常,也方便和 raw.csv 对比,快速定位是数据问题还是网络训练问题。debug 的代价往往不在代码而在数据,保留中间产物能省掉大量反复排查的时间。

3. look_back 滑窗与 MinMaxScaler:把单变量序列变成监督学习样本

3.1 为什么 LSTM 不能直接吃原始序列

LSTM 的输入形状是三维的 (样本数, 时间步数, 特征数),原始 CSV 是二维表格,必须经过滑窗把「过去 N 小时」拼成一个样本,预测目标是对应的「下一小时」浓度。这里 N 就是 look_back,也常叫 time_step。look_back 的选择直接影响模型能学到的依赖长度:取值 1 等价于用上一小时预测本小时,模型学不到周期性;取值过大比如 168(一周),样本数量骤降,训练时间变长且容易过拟合。

空气质量逐小时数据通常存在 24 小时周期,我一般先取 24 作为起点,看损失曲线再决定要不要加长。实际运行中 24 小时窗口已经能捕获日变化周期,模型也比较好收敛。

3.2 MinMaxScaler 的 fit 时机和还原逻辑

LSTM 对输入尺度敏感,sigmoid 和 tanh 激活函数在输入绝对值过大时梯度会饱和,归一化到 0-1 区间几乎是标配。用 scikit-learn 的 MinMaxScaler 时,有一个容易被忽略的点:fit_transform只应作用于训练序列,测试序列要用同一个 scaler 做transform。很多课程设计代码是把整列数据扔进去归一化再切分,这种方式在课程设计答辩时通常不会被追问,但实际做工程会翻车。

from sklearn.preprocessing import MinMaxScaler import numpy as np # scaler 只在训练段上 fit,避免把测试段的统计信息带进来 scaler = MinMaxScaler(feature_range=(0, 1)) train_scaled = scaler.fit_transform(train_df[['pollution']]) test_scaled = scaler.transform(test_df[['pollution']]) def create_dataset(series, look_back=24): X, y = [], [] for i in range(len(series) - look_back): # 每 look_back 个连续时刻作为输入,下一个时刻作为目标 X.append(series[i: i + look_back, 0]) y.append(series[i + look_back, 0]) return np.array(X), np.array(y) X_train, y_train = create_dataset(train_scaled, look_back=24) X_test, y_test = create_dataset(test_scaled, look_back=24) # reshape 成 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)

代码里create_dataset的时间复杂度是 O(n),数据量超过百万行时会偏慢,可以用滑动窗口的 stride 参数控制采样密度。这里的look_back=24表示用过去 24 小时预测第 25 小时,也就是下一小时。如果想预测未来第 24 小时,只需要把y.append(series[i + look_back, 0])改成series[i + look_back + 23, 0],模型结构不用动。

还有一个细节:滑窗后样本数减少了 look_back 个,因为序列开头没有足够的历史数据。这意味着测试集的预测起点会晚于测试集起点 look_back 小时,画图对比时要对齐坐标,否则曲线会整体平移,看起来像预测滞后了一个阶段。

3.3 连续值与类目特征的分开处理

pollution.csv 里如果还包含风向这种字符串类目字段,不能直接塞进 MinMaxScaler。常见做法是对风向做 one-hot 编码,或者直接剔掉只用连续数值特征。单变量模型只需要 pollution 列,多变量扩展时才需要考虑口径统一。这套工程里保持单变量简单结构,能有效控制变量——如果模型效果差,问题要么在滑窗要么在数据,而不会因为特征组合方式干扰判断。

4. LSTM 训练主循环的参数、损失与反归一化落地

4.1 网络结构选择与 return_sequences 的关系

主训练逻辑在 main.py 里,结构是典型的 LSTM + Dropout + Dense。两层 LSTM 比单层能捕捉更高阶的序列模式,但参数规模翻倍,数据量不够时更容易过拟合。课程设计的数据量通常在小几万行量级,两层 64/32 单位的组合是性价比比较高的配置。

from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping model = Sequential() # 第一层 LSTM 输出完整序列,供第二层 LSTM 继续处理 model.add(LSTM(units=64, return_sequences=True, input_shape=(X_train.shape[1], X_train.shape[2]))) model.add(Dropout(0.2)) # 第二层只输出最后一个时间步的结果 model.add(LSTM(units=32, return_sequences=False)) model.add(Dropout(0.2)) model.add(Dense(units=1)) # 输出下一时刻的浓度 model.compile(optimizer='adam', loss='mse', metrics=['mae']) model.summary()

return_sequences=True是第一层和第二层的衔接关键。第一层返回每个时间步的隐藏状态,这样第二层 LSTM 能继续处理长度为 look_back 的时间轴;第二层设为False,只有最后一个时间步的输出会进入 Dense 层完成单步预测。如果第一层忘了设True,第二层收到的输入形状不匹配,模型会在build阶段直接报错。Dense(units=1)是回归任务的输出层,和分类任务的softmax不同,不需要激活函数。

4.2 训练参数与 EarlyStopping 的经验取值

训练时batch_sizeepochslook_back三个参数互相制约,常见初始配置如下表:

参数建议值调整逻辑
look_back24按数据周期选,逐小时数据先试 24,不够再加到 48
batch_size64 / 128样本量大用大 batch 提速,序列长度差异大时用小 batch
epochs80 起步配合 EarlyStopping,min_delta=1e-3 就够
LSTM units64 / 32数据量小就减半,避免验证集震荡
lossmse回归任务默认,异常值少也可以试 mae

训练过程里,validation_split=0.1会在训练集尾部自动切出一段做验证,不需要手动构造。但注意validation_split是从训练序列末尾取的,如果训练集本身跨了季节,这部分验证集和后续的测试集可能分布重合度偏高,这在课程设计场景下可以接受,工程上则需要人工指定验证集时间范围。

history = model.fit( X_train, y_train, batch_size=64, epochs=80, validation_split=0.1, verbose=1 ) model.save('lstm_pm25.h5')

4.3 预测结果的反归一化与坐标对齐

训练完成后,预测值拿到的是 0-1 区间的归一化结果,画图前必须用训练时的 scaler 还原成真实浓度。这一步出错会表现为曲线形状对、但纵轴数值整体偏小或者范围不对。

# 预测测试集并还原到原始量纲 pred_scaled = model.predict(X_test) pred = scaler.inverse_transform(pred_scaled) actual_scaled = y_test.reshape(-1, 1) actual = scaler.inverse_transform(actual_scaled)

这里的反归一化只对预测目标列做。如果模型输入的是多列特征,反归一化和训练时的数据形状要保持一致。另外model.predict返回的数组形状是 (样本数, 1),与y_test的 (样本数,) 不同,reshape 这一步经常被忽略,画图时会因为维度不匹配报错。

4.4 常见训练异常与排查顺序

损失曲线不下降时,先检查 sca ler 是否把所有特征都压缩到了合理范围,feature_range=(0,1)是常规操作;再看第一层 LSTM 的input_shape是否和X_train.shape[1:]一致,这是新手最常出的错误。如果训练 loss 下降快、验证 loss 剧烈震荡,优先减小batch_size到 32 同时加大Dropout到 0.3,震荡通常意味着梯度更新方向不稳定。这里有一个细节:时间序列预测里不要对训练样本做shuffle=True,Keras 的model.fit默认会打乱每个 epoch 的样本顺序,其结果就是滑窗之间相互独立的部分被打乱了,虽然 LSTM 的隐状态在样本间不传递,但这样的随机顺序会让早期 epoch 的收敛曲线很不稳定,课程设计里一般保持默认就行。

5. seriesShow.py 画出的对照图怎么读每个弯曲的含义

5.1 绘图脚本到底画了什么

seriesShow.py 做的是最后一步可视化,输入是反归一化后的真实值与预测值,输出是一张带图例的折线对比图。标准的做法是把测试集真实曲线和预测曲线画在一张图上,红色画真实值,蓝色虚线画预测值,横轴是时间索引。如果文件里展示的不只是测试段,而是一整段时间跨度,通常还包含训练集部分的回测曲线,那就要先看清楚图例,只关注测试段的重合程度。最简单的方法是做一个独立的小脚本,只画自己关心的一段时间,一眼就能看明白,对比效果也更直接。训练集上的高重合度参考意义有限,因为它反映的是模型对历史数据的记忆能力,对未来的泛化能力没有代表性。

import matplotlib.pyplot as plt # actual 和 pred 都是 inverse_transform 后的原始量纲序列 plt.figure(figsize=(12, 5)) x_axis = range(len(actual)) plt.plot(x_axis, actual, color='red', linewidth=1.2, label='actual') plt.plot(x_axis, pred, color='blue', linestyle='--', linewidth=1.2, label='predicted') plt.xlabel('Sample Index in Test Set') plt.ylabel('PM2.5 Concentration') plt.legend() plt.grid(alpha=0.3) plt.savefig('prediction_compare.png', dpi=150) plt.show()

绘图脚本最重要的参数是dpi。这里设成 150 是为了论文和答辩展示清晰度,屏幕预览 100 就够了,导出到文档再考虑提高到 200。figsize根据时间跨度调整,如果测试集只有 100 个点,(12, 5)合适;如果跨度超过 1000 个点,曲线会被压扁,看不出来逐时刻的贴合程度,需要改成(16, 5)或者分段绘图。

5.2 曲线形态对应的三类问题

图上的表现可能原因检查方向
预测曲线滞后于真实曲线约一个时间步look_back 取值过小,模型只学到了最近的短时依赖把 look_back 从 24 提到 48,看滞后是否缩小
真实曲线的波峰被削平,预测值整体偏向均值归一化范围太窄或模型容量不够,峰值区域样本少很难学检查 MinMaxScaler 的 feature_range,或增大 LSTM units
预测曲线与真实曲线整体错位,但形状一致反归一化时用了错误的偏移,测试集切分点不对齐核对 test_scaled 的起始索引,确认 y 序列没有整体平移
预测结果几乎是一条平线训练没有收敛或学习率过快,网络输出停在均值附近检查 loss 曲线是否下降,调小学习率重训

峰值被削平是时间序列回归任务里最常见的现象。PM2.5 浓度的重污染峰值持续几小时后骤降,这类样本在数据集中占比小,MSE 损失函数对偏离均值的小误差惩罚不痛不痒,所以模型倾向于输出中间值来降低整体误差。对抗这个现象可以从损失函数入手,改成 Huber loss 或者在训练时对峰值样本加权,但课程设计要求稳定出图的话,用 MSE 加足够多的 epochs 是最稳妥的。

5.3 误差指标要怎么看

seriesShow.py 只负责画图,定量评估还是要算 RMSE、MAE 和 R²。一个容易被忽略的口径问题:误差是算在归一化空间还是原始浓度空间。很多人直接用归一化后的pred_scaledy_test算 RMSE,得到的数值虽然也能对比,但和真实污染物浓度单位不一致,答辩时很容易被问住。正确口径是先反归一化再算指标,这时候 RMSE 的单位就是 μg/m³,直观上能对应空气质量指数等级。

这三个指标里,MAE 对异常值不敏感,RMSE 对大误差敏感,R² 是 1 减残差平方和除以总平方和,当预测曲线整体偏离真实值时 R² 仍然可能很高,因为它在衡量的是形状拟合而不是绝对误差。我在交付报告时通常三张图一起给:损失下降曲线、预测对照图、误差指标的横向对比表,这样比单给一个 0.9 的 R² 更有说服力。

6. 多变量输入与多步预测的改造,验证口径跟着变

6.1 三个改动点把单变量扩成多变量

拿到这套工程后,最常见的改造方向是把 wind speed、温度、气压等字段一起作为输入特征。整个 LSTM 结构不用动,改动只有三处。第一处是数据列的选择,从df[['pollution']]换成df[['pollution', 'dew', 'temp', 'press', 'wnd_speed']];第二处是 normalize 后的特征矩阵在滑窗构造时保留所有列,但y仍然只取 PM2.5 那一列;第三处是 reshape 的第三维从 1 变成特征数 5,input_shape跟着改。

# 多变量版本的滑窗构造 def create_dataset_multi(features, target, look_back=24): X, y = [], [] for i in range(len(features) - look_back): X.append(features[i: i + look_back, :]) # 保留全部特征维度 y.append(target[i + look_back, 0]) # 目标列仍是污染浓度 return np.array(X), np.array(y) # features 是 5 列归一化后的二维数组 X_train, y_train = create_dataset_multi(features_train, target_train, look_back=24) X_train = X_train.reshape(X_train.shape[0], X_train.shape[1], 5)

多变量场景最需要注意的是特征之间的数值尺度差异。温度和 PM2.5 浓度数量级不同,归一化之后才能放在同一个输入空间里,否则大的数值会主导梯度。改造时只对连续数值特征做 MinMaxScaler,风向这类离散字段要单独处理或者剔除。

6.2 多步预测的两种实现

如果要做未来 24 小时预测,最常见的选择是多输出模型和滚动预测。多输出模型是在 Dense 层直接输出 24 个值,训练数据要把 y 构造成长度为 24 的向量,网络结构改成Dense(24),模型单次预测就能拿到连续 24 个时刻的浓度。滚动预测则是把单步模型的预测结果塞回输入序列末尾,逐步向后滚动,但误差会随步长累积,预测越远越偏,最终大概率收敛到均值。课程设计里如果想把预测曲线画得更好看,多输出模型的稳定性和答辩效果都更好。需要说明的是,多步预测的效果验证不能只看第一步的 RMSE,要按步长分别统计误差,通常误差会随预测步长线性增长,这一步能直接暴露模型的信心边界。

6.3 验证方法

改造完之后把小批量实验的样本量固定下来,比如训练集末尾留出 500 个连续时间点做 hold-out 验证。原因是一次性的切分对超参数选择不够敏感,留出连续时间段多跑两三组 look_back 对比,预测后三段的 RMSE 波动就知道模型是否稳定。对做水文径流或者金融时序预测的场景来说,这套「单变量起步 → 多变量扩展 → 多步预测」的改造路径是通用的,换掉 CSV 和字段名,主流程可以直接复用。

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

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

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

立即咨询