LSTM时间序列预测实战:从数据清洗到多步预测的完整指南
简介该资源是一套基于LSTM神经网络的时间序列预测完整项目面向需要进行时序数据建模与预测的开发者、科研人员及高校学生尤其适合课程设计、毕业设计或企业实战场景。项目覆盖数据清洗、特征提取、建模训练与预测全流程代码结构完整下载即可运行无需额外修改。压缩包共125个文件、约5.42MB包含75个Python源码文件、26个CSV数据集、15个txt说明文档并附带模型权重文件.h5、checkpoint等其中CSV数据可供直接训练测试txt说明便于按模块理解代码逻辑。目前已有1356人学习下载。资源不仅提供可直接调用的预测脚本还包含训练好的模型文件可帮助读者快速掌握从数据预处理到结果预测的完整工程链路是入门LSTM时序预测、复现实验或二次开发的实用参考资料。1. LSTM时间序列预测这份资源为什么值得拆开看做时间序列预测的人十有八九都栽进过同一个坑手里有数据却不知道怎么把 CSV 变成 LSTM 能吃进去的结构。这份「LSTM 神经网络时间序列预测源码模型」压缩包恰好把这条路完整走了一遍——数据清洗、特征提取、建模、预测四段流程全部落地用的是经典的空气污染数据集含 checkpoint 权重文件和多个版本的 CSV 数据代码下载后不需要改动就能跑出 95 分以上的效果。适合两类人一类是入门 LSTM 预测但被数据预处理劝退的新手另一类是赶课程设计或毕业设计、需要一套干净可复现代码的从业者。下面我按实际拆包顺序把里头的技术细节、参数选型和踩坑点逐层讲清楚。2. 数据清洗与特征工程从 air_pollution.csv 到模型输入2.1 数据集结构与字段含义压缩包里出现了 pollution.csv、air_pollution.csv、air_pollution_new.csv 等多个 CSV 文件它们本质上是同一份源数据的不同处理阶段产物。我按常见做法把这份数据理解为北京 PM2.5 空气质量监测数据集每条记录按小时粒度采集核心字段包括字段名含义典型取值/范围pm2.5当前小时 PM2.5 浓度μg/m³0 ~ 600存在 N/ADEWP露点温度℃-50 ~ 20TEMP环境温度℃-20 ~ 40PRES大气压强hPa990 ~ 1030cbwd组合风向东南西北四种方向Iws累计风速m/s0 ~ 50Is积雪时间小时0 ~ 10Ir累计降雨量mm0 ~ 30air_pollution_new.csv 是清洗后的版本air_pollution.csv 则可能是中间产物。拿到手的第一步我会先用 pandas 扫描缺失值和数据类型确认哪些列是连续数值、哪些列是类别变量。cbwd 风向这一列是字符串LSTM 不能直接处理后续需要转换。2.2 缺失值处理先看分布再决定策略经典污染数据集中pm2.5 列存在 N/A 缺失值。常见且稳妥的做法是前向填充ffill理由很简单——空气污染物浓度是连续变化的物理量相邻小时的相关性极强用前一个时刻的值填充比用全局均值填充更符合物理直觉。import pandas as pd import numpy as np df pd.read_csv(air_pollution_new.csv, encodingutf-8) print(清洗前数据形状:, df.shape) # 统计每列缺失比例超过 20% 的列直接删除 missing_ratio df.isnull().sum() / len(df) print(缺失比例:\n, missing_ratio) # pm2.5 使用前向填充其他数值列用线性插值 df[pm2.5] df[pm2.5].fillna(methodffill) df df.interpolate(methodlinear, limit_directionboth) df df.dropna().reset_index(dropTrue) print(清洗后数据形状:, df.shape)这段代码的逻辑分三层先统计缺失比例判断哪些列值得救pm2.5 用 ffill 保持序列连续性其余数值列用线性插值兜底因为线性插值在短间隔缺失时误差可控。注意limit_directionboth让首尾端也参与插值避免头部数据被整行丢弃。如果你拿到的数据集也带时间戳我建议把时间列设为索引后再做插值否则跨天边界处的插值可能失真。2.3 特征提取相关性筛选与风向编码不是所有 8 个特征都对 pm2.5 预测有贡献。我一般先跑一遍 Pearson 相关性矩阵把与目标列相关系数低于 0.1 的弱相关特征剔除减少模型噪声。在这个数据集上TEMP 和 DEWP 与 pm2.5 通常呈弱负相关而 Iws 风速与 pm2.5 的负相关性较明显这符合「大风扩散污染物」的常识。from scipy.stats import pearsonr target pm2.5 feature_cols [DEWP, TEMP, PRES, Iws, Is, Ir] selected [] for col in feature_cols: corr, _ pearsonr(df[col], df[target]) print(f{col} 与 pm2.5 相关系数: {corr:.4f}) if abs(corr) 0.1: selected.append(col) # 对风向做独热编码 wind_dummies pd.get_dummies(df[cbwd], prefixwind) df pd.concat([df, wind_dummies], axis1) # 最终特征列数值特征 风向独热编码 final_features selected list(wind_dummies.columns) [target]这里的取舍逻辑是独热编码会把 4 种风向拆成 4 个二值列虽然增加了维度但避免了「把风向字符串映射成 1、2、3、4 导致模型误判方向间有大小关系」的陷阱。风向是分类变量用自然数编码会让 LSTM 学到错误的序数关系独热是更稳的选择。如果你做的是多步预测还可以额外添加滞后特征——把 t-1、t-2、t-3 时刻的 pm2.5 作为新列拼进去这个操作在缺乏外生变量时尤其有效。2.4 数据集划分与归一化时序数据不能乱洗牌时间序列预测和普通分类任务最大的差别在于不能随机打乱数据。一旦 shuffle时间依赖关系就被破坏训练集里混入未来的信息验证集指标会虚高模型上线后立刻翻车。正确做法是按时间顺序切分前 80% 训练、后 20% 验证且归一化时只在训练集上求 min 和 max。from sklearn.preprocessing import MinMaxScaler train_size int(len(df) * 0.8) train_df df.iloc[:train_size] test_df df.iloc[train_size:] # 关键先 fit 训练集再 transform 测试集 scaler MinMaxScaler(feature_range(0, 1)) train_scaled scaler.fit_transform(train_df[final_features]) test_scaled scaler.transform(test_df[final_features])注意代码里的 fit 和 transform 顺序scaler.fit_transform只作用在训练集上test_df只用transform。这样做避免了测试集的归一化参数泄露到训练过程中——如果你对全量数据做了 fit测试集的 min/max 信息就间接参与了训练评估结果会偏乐观这是初学者最常踩的隐藏坑。归一化范围选 0~1 是因为 LSTM 默认使用 tanh 作为激活函数输入落在 0~1 区间内更容易让梯度保持在合理范围。3. LSTM 建模与参数设计从序列构造到模型结构3.1 滑动窗口构造look_back 参数怎么定LSTM 的输入是三维张量形状为样本数时间步长特征数。所谓时间步长就是每次预测时回看多少个小时的数据这个参数在项目里对应 look_back。取值没有绝对标准但在这个小时粒度的空气质量数据上我一般从 24 起步——因为 PM2.5 的日周期性非常明显回看 24 小时相当于模型能看到完整的「昨天」规律。def create_dataset(data, look_back24): X, y [], [] for i in range(look_back, len(data)): X.append(data[i - look_back:i, :]) y.append(data[i, target_index]) return np.array(X), np.array(y) look_back 24 train_X, train_y create_dataset(train_scaled, look_back) test_X, test_y create_dataset(test_scaled, look_back) print(训练集形状:, train_X.shape) # (样本数, 24, 特征数) print(测试集形状:, test_X.shape)create_dataset 的核心逻辑是滑窗采样从第 look_back 个样本开始每次取前 24 行作为特征、当前行作为标签窗口每次移动一步。这样构造出的样本之间高度重叠会带来一定的冗余计算但这是 LSTM 时序预测的标准做法重叠反而让模型更容易捕捉平滑变化的趋势。如果你觉得训练太慢可以把步长改为 2 或 3减少样本量代价是可能丢失部分细节。3.2 模型结构LSTM 层 Dropout Dense这份资源的核心模型结构并不复杂是典型的单层或多层 LSTM 回归网络。我在处理类似项目时的惯用结构是第一层 LSTM 带 50 个单元return_sequencesTrue 以便堆叠第二层第二层 LSTM 不返序列中间夹 Dropout 抑制过拟合输出层用 Dense(1) 做回归预测。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint model Sequential() model.add(LSTM(units50, return_sequencesTrue, input_shape(train_X.shape[1], train_X.shape[2]))) model.add(Dropout(0.2)) model.add(LSTM(units50, return_sequencesFalse)) model.add(Dropout(0.2)) model.add(Dense(units1)) model.compile(optimizeradam, lossmse, metrics[mae]) model.summary()结构选择上的考虑两层 LSTM 比单层能学到更高阶的时间依赖但参数量翻倍训练时间变长。units50 是这个量级数据的甜点值——太小拟合不足太大容易在几万条样本上过拟合。Dropout0.2 是 LSTM 比较常用的比例低于 0.1 几乎不起作用高于 0.4 会破坏时间依赖的传递。如果你的数据量很大超过 10 万条可以把 units 提到 128Dropout 降到 0.15。3.3 训练配置回调函数决定训练上限训练 LSTM 最怕两件事过拟合和 loss 震荡。EarlyStopping 负责在验证集 loss 连续 N 轮不下降时终止训练ModelCheckpoint 负责保存最优权重。这份资源里的 checkpoint 文件就是这么生成的加载后可以直接做预测。from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) checkpoint ModelCheckpoint( filepathcheckpoint_lstm.h5, monitorval_loss, save_best_onlyTrue, modemin ) history model.fit( train_X, train_y, validation_data(test_X, test_y), epochs100, batch_size72, callbacks[early_stop, checkpoint], verbose1 )batch_size72 我解释一下为什么这么定空气质量数据有明显的小时规律72 恰好是 3 天的整倍数每个 batch 内包含完整的多日周期梯度更新方向更稳定。如果你用 32 或 16 这种小 batchloss 曲线会剧烈震荡如果用 256 以上容易陷入局部最优。epochs 设 100 是上限实际 EarlyStopping 通常在第 30~50 轮触发因为 PM2.5 预测任务收敛速度不慢。train_X 的第二维是 look_back第三维是特征数input_shape 直接用它俩不需要写死。3.4 训练过程监控loss 曲线怎么读训练结束后我会先画 loss 曲线再谈效果。判断标准很简单训练 loss 和验证 loss 同步下降且验证 loss 不再显著下降说明模型状态健康如果训练 loss 很低但验证 loss 走高就是过拟合的信号。import matplotlib.pyplot as plt plt.figure(figsize(10, 5)) plt.plot(history.history[loss], labeltrain_loss) plt.plot(history.history[val_loss], labelval_loss) plt.xlabel(Epoch) plt.ylabel(Loss (MSE)) plt.legend() plt.title(Training Loss Curve) plt.savefig(loss_curve.png)读取 loss 曲线的技巧看前 10 轮如果 val_loss 不降反升大概率是学习率过高或数据未归一化如果 loss 在第 20 轮后仍在缓慢下降可以调高 EarlyStopping 的 patience 到 15。我还习惯把 val_loss 的最小值打印出来和训练 loss 对比差值在 10%~20% 之间算正常超过 50% 就要回去检查特征工程是否有问题。4. 模型预测与评估加载 checkpoint 并输出结果4.1 加载模型权重两种方式有一点差别checkpoint 文件保存的是训练中验证集 loss 最低时刻的权重。加载方式取决于文件格式如果保存的是完整模型含网络结构直接 load_model如果只保存了权重则要先重建模型结构再 load_weights。压缩包里的 checkpoint 我建议优先用 load_model 尝试。from tensorflow.keras.models import load_model # 方式一完整模型直接加载推荐先用这个 model load_model(checkpoint_lstm.h5) # 方式二纯权重文件需要先有模型结构 # model build_lstm_model(train_X.shape[1], train_X.shape[2]) # model.load_weights(checkpoint_lstm.h5)两种方式的区别在文件体积和灵活性完整模型文件大但不需要重新定义网络结构纯权重文件小方便迁移到不同结构上实验。如果 load_model 报错提示 unknown layer 或 custom object说明模型里用了自定义层或自定义损失函数这时需要检查关键词里的损失函数是否被正确注册。这份资源的模型是标准 MSE 损失正常不会出现这种问题。4.2 反归一化预测结果别忘还原训练时数据被压缩到了 0~1 区间预测出的结果也是 0~1 的小数必须反归一化才能得到真实的 PM2.5 浓度。这里有个最容易犯的错直接对目标列做反归一化会把预测值还原到原始量纲但实际需要还原的只是第 target_index 列。# 预测并反归一化 pred_scaled model.predict(test_X) # 构造与训练时相同列数的数组便于反归一化 pred_full np.zeros((len(pred_scaled), len(final_features))) pred_full[:, target_index] pred_scaled[:, 0] pred_inv scaler.inverse_transform(pred_full)[:, target_index] # 同理还原真实值 test_full np.zeros((len(test_y), len(final_features))) test_full[:, target_index] test_y test_inv scaler.inverse_transform(test_full)[:, target_index]这段代码的核心在于用全特征数组做反变换——MinMaxScaler 在 fit 时记住了每一列的 min 和 maxinverse_transform 时输入必须是完整的特征列数。如果你只把单列的 0~1 数据丢给 inverse_transform数组形状不匹配会直接报错。我用这种方法是为了保留标量缩放器的一致性避免手动计算原值原始值 归一化值 * (max - min) min。target_index 是 pm2.5 在 final_features 列表中的位置建议在构造数据集时动态获取不要写死数字。4.3 评估指标RMSE 和可视化对比回归任务不能只看 lossRMSE 和 MAE 才是可解释的指标。RMSE 对大的预测误差敏感MAE 反映平均偏差水平。在 PM2.5 浓度预测里RMSE 在 20~40 μg/m³ 之间是合理的——因为 PM2.5 本身的波动范围就有几百绝对误差控制在 30 以内说明模型捕捉到了主要趋势。from sklearn.metrics import mean_squared_error, mean_absolute_error import numpy as np rmse np.sqrt(mean_squared_error(test_inv, pred_inv)) mae mean_absolute_error(test_inv, pred_inv) print(fRMSE: {rmse:.2f} μg/m³) print(fMAE: {mae:.2f} μg/m³) # 可视化预测值和真实值 plt.figure(figsize(12, 4)) plt.plot(test_inv[:200], labelTrue PM2.5, colorblue) plt.plot(pred_inv[:200], labelPredicted PM2.5, colorred, linestyle--) plt.legend() plt.xlabel(Hour) plt.ylabel(PM2.5 Concentration (μg/m³)) plt.savefig(prediction_result.png)可视化时我只看前 200 个点因为测试集可能很长全部画出来曲线挤成一团看不出趋势前 200 个点足以判断模型对上升沿和下降沿的跟随能力。如果预测曲线整体滞后于真实曲线说明模型学的是「复制上一时刻的值」而不是真正的变化规律这个问题我在第五章展开讲。5. 避坑指南LSTM 时间序列预测的常见问题与排查5.1 LSTM 输入维度报错expected ndim3, found ndim2现象model.fit 或 model.predict 时报错提示输入维度为 2 维期望 3 维。原因create_dataset 返回的 X 是二维数组比如形状为样本数, 特征数没有把时间步长维度显式构造出来。解决检查 X 的 reshape。模型输入要求样本, 时间步, 特征数最小修改是train_X train_X.reshape(train_X.shape[0], look_back, feature_dim) test_X test_X.reshape(test_X.shape[0], look_back, feature_dim)本质原因是数据经过滑窗后仍然保留的是二维结构需要人工补上时间维度。我在 create_dataset 里直接返回三维数组就是为了省掉这一步如果你拿到手的资源里没有记得加这行。5.2 预测曲线整体滞后一个时间步现象预测值和真实值画在一起预测曲线明显向右平移了一格峰值对不上。原因LSTM 学到的是「最近一小时的 PM2.5 是最佳预测」因为 t 时刻的值和 t-1 时刻高度相关用前一时刻的输入去预测下一时刻输出自然逼近输入。模型偷懒了——它发现复制过去比学习规律更省力。解决这是单步预测的已知缺陷常见做法是改用多步预测结构把输出维度改成预测未来 N 个时间步或者引入外生特征打破「输入≈输出」的惰性。这属于结构性问题调参解决不了。压缩包里的模型如果出现轻微滞后属于正常现象不影响 95 分以上的评分要求。5.3 checkpoint 加载报错无法识别模型结构现象load_model 报错Unknown layer: LSTM或ValueError: cannot convert.原因常见是 Keras 和 TensorFlow 版本不匹配比如模型在 TF 2.4 下保存、用 TF 2.10 加载或者序列化格式H5 vs SavedModel不一致。解决先确认环境版本然后做两个尝试pip list | grep tensorflow # 如果版本相差过大降版本比升版本更快 # pip install tensorflow2.4.0我一般先试 load_model(checkpoint_lstm.h5)失败就改为重建模型结构再 load_weights后者的兼容性容错更高。如果压缩包里同时提供了模型构造脚本和 checkpoint直接走 load_weights 路线最稳。5.4 数据归一化时 fit 了全量数据现象训练和验证效果都很好RMSE 十几但模型拿到新数据上一测效果暴跌。原因训练时 MinMaxScaler 对全量数据做了 fit_transform测试集的 min/max 信息泄露进了训练过程导致评估结果虚高。新数据进来后缩放器参数对不上预测自然失真。解决严格先 fit 训练集再 transform 测试集代码我在 2.4 节写过。这是一个隐蔽的评估作弊问题很多课程设计代码都有这个毛病你自己写项目时一定要避开。5.5 loss 始终不下降现象训练多轮后 loss 在高位震荡val_loss 比初始值还高。原因最常见的学习率太大导致梯度震荡或者归一化没做导致数值范围过大还有一种是特征列里混入了字符串类型的脏数据。解决先打印 df.dtypes 检查数据类型再确认所有特征都在 0~1 区间。学习率可以手动调低from tensorflow.keras.optimizers import Adam model.compile(optimizerAdam(learning_rate0.0005), lossmse)默认的 0.001 在部分数据上偏大调低到 0.0005 或 0.0001 后 loss 曲线一般会平滑下来。如果还没反应检查是否忘了转换 dataframe 到 numpy 数组——有些 API 调用会静默失败loss 停在某个固定值不动。6. 落地技巧多步预测改造与超参数调优实战拿到手的资源能跑通单步预测但真实业务里往往要预测未来 24 小时的 PM2.5 变化曲线这就需要把模型改造成多步预测。我常用的有两种方案递归预测和直接预测。递归预测的做法是把预测值当作下一步输入循环调用 model.predict直接预测则是把 Dense 输出层改成 N 个神经元一次输出未来 N 个时间步。前者模型简单但对误差敏感误差会逐步累积后者训练目标更明确但输出维度增加、需要更多数据。我在项目里更倾向直接预测因为误差不累积且代码改动只有一行model.add(Dense(units24)) # 直接预测未来 24 小时超参数调优方面我总结出三个优先级第一是 look_back 窗口用 24、48、72 三档做对比实验看验证集 RMSE 哪个最低第二是 LSTM units在 32、50、100 三档间搜索第三是 Dropout 比例固定在 0.2~0.3 之间微调。每次实验只动一个参数记录对应的 RMSE 和训练时间不要同时改多个——否则调参变成玄学你根本不知道是哪个改动带来了提升。我一般在完整跑一轮实验前先用 5000 条子样本做快速验证确认模型方向和参数不跑偏再上全量数据精调。这种先粗后细的策略能省一半以上时间。还有一个实战技巧训练完成后用 SHAP 或特征重要性分析去找模型的决策依据。如果 wind_NE 和 wind_NW 这一类风向特征贡献度异常高而温度湿度贡献很低先别急着删特征——回看数据的采样时间跨度看是否有季节性趋势没被模型捕捉。我在一次实验中就是因为忽略了下半年与上半年的风向分布差异导致模型在冬季预测偏差很大后来在特征里加了一个「月份」的循环编码列才解决。从那以后我每次做时间序列预测都会强制走一遍「清洗→相关性分析→滑窗构造→归一化→单步验证→多步改造」这个完整流程顺序不乱跳每一步的中间结果都落盘保存方便回头排查。这份资源里压缩包的代码把单步预测路径铺好了你自己动手改一改就能把它扩展成可直接上线用的预测服务。希望帮到你。本文还有配套的精品资源点击获取