新闻详情

新闻详情

首页 / 资讯中心 / 详情

LSTM时间序列预测:空气质量PM2.5预测与Python实战

发布时间:2026/9/14 4:34:39来源:尧图网络
LSTM时间序列预测:空气质量PM2.5预测与Python实战
简介面向郑州地区空气质量预测的Python源码主要服务环境数据分析、机器学习实践者以及相关毕业设计课题用于解决区域空气质量建模与预测问题。压缩包共20个文件、大小仅652KB覆盖5个XML配置、4个Python核心源码、5个文本说明、3张PNG图像和1个HDF5权重文件等其中Python代码依次承担数据预处理、模型构建、训练与预测任务配合loss.png、model.png等图像可清晰看到损失变化和模型结构降低理解门槛。目前已有395人学习适合希望参考完整工程组织方式并开展空气质量预测实验的读者。通过研读源码、配置与记录可快速接手LSTM/CNN等时序模型的搭建流程并依托HDF5历史数据继续调参优化为毕业设计、课设或科研实践提供可直接复用的基准。1. 郑州空气质量预测模型不是第一个该看的文件接到这份郑州空气质量预测模型的 Python 源码时我刻意先打开utils.py而不是model.py。原因很简单郑州国控站点逐小时数据里SO₂、NO₂、CO、O₃、PM10、PM2.5 经常不在同一个时间戳对齐哪怕 LSTM 结构写得再漂亮样本没对齐预测结果也是废的。这套项目包含了data.txt、x_params_list.txt、y_params.txt和 4 个 Python 文件正好把“原始浓度读入 → 滑动窗口样本构造 → LSTM 训练 → 滚动预测”这条链路完整落地。适合正在做时间序列预测、想直接拿 Python 改数据管线和模型参数的工程师也适合想从源码层面理解空气质量预测模型设计的入门者。2. data.txt 到监督学习样本滑动窗口与参数列表构造这份源码里真正决定预测效果上限的不是网络层数而是utils.py里如何把连续监测数据变成“特征-标签”对。data.txt每一行是一条按时间排序的记录前几列是污染物浓度和气象因子数值列之间用逗号分隔像日期时间这样的文本列在这个项目里没有混入data.txt而是单独记录在record.txt里。x_params_list.txt保存了输入列的索引y_params.txt保存预测目标列的索引这样的设计让特征选择和训练代码解耦换一组特征时不需要改动模型主体。2.1 特征列和目标列参数列表决定数据对齐方式常见配置里x_params_list.txt的内容是0,1,2,3,4,5,6,7代表从data.txt取第 0 到第 7 列作为输入特征y_params.txt里写5表示预测第 5 列 PM2.5。特征含义可以对应这张表data.txt 列索引字段含义在本项目中的角色0SO₂μg/m³输入特征1NO₂μg/m³输入特征2COmg/m³输入特征3O₃μg/m³输入特征4PM10μg/m³输入特征5PM2.5μg/m³输入特征也是默认预测目标6温度℃输入特征7相对湿度%输入特征如果想预测 AQI 而不是 PM2.5只要在data.txt末尾追加一列 AQI 数值再把y_params.txt改成对应列索引即可模型结构不用动。注意loadtxt读取时如果文件第一行是表头必须用skiprows1跳过否则把所有字段当成数值去解析数据矩阵整体错位。项目里常见做法是data.txt不带表头只在record.txt里说明字段顺序这样省去字符串解析也避免中文编码问题。2.2 滑动窗口拼接把二维监测表变成 LSTM 能吃的三维张量原始数据整理后是二维矩阵行是时间点列是特征。LSTM 需要的输入是三维张量(样本数, 时间步长, 特征数)所以要构造滑动窗口用前 24 小时的特征预测下一个小时的 PM2.5。这里有一段典型的utils.py逻辑# utils.py 中的滑动窗口构造 import numpy as np def load_params(param_path): with open(param_path, r, encodingutf-8) as fp: values fp.read().strip().split(,) return [int(v) for v in values if v ! ] def make_sequences(x, y, seq_len24, horizon1): xs, ys [], [] total len(x) - seq_len - horizon 1 for i in range(total): # 取连续 seq_len 个时间步作为输入 xs.append(x[i:i seq_len, :]) # 取窗口结束后的 horizon 个值作为目标 ys.append(y[i seq_len:i seq_len horizon]) return np.array(xs), np.array(ys)代码里seq_len24是用过去一天的数据预测下一天horizon1是预测未来 1 小时。ys切片得到的是长度horizon的数组所以即便把horizon改成 3也不需要改数据结构模型输出层的神经元数量对应改成 3 就可以。窗口长度不建议拍脑袋定 24郑州的污染过程往往持续 1 到 3 天备选值可以放 24、48、72 对比。样本构造时先按时间顺序切片不要在这里手动 shuffle打乱由训练时的model.fit(shuffleTrue)负责否则时间序列的顺序关系会被破坏。2.3 标准化顺序先分训练集再 fit scaler空气质量数据量纲差异很大CO 是 mg/m³其他污染物是 μg/m³不归一化会让 LSTM 训练过程偏向数值大的特征。但标准化有个容易踩坑的细节必须先切分训练集和验证集再在训练集上fitscaler验证集只做transform。# 标准化与训练/验证集划分 from sklearn.preprocessing import StandardScaler split int(len(xs) * 0.8) x_train_seq, x_val_seq xs[:split], xs[split:] y_train_seq, y_val_seq ys[:split], ys[split:] scaler_x StandardScaler() # 把三维训练样本重塑成二维后 fit得到每个特征维度的均值和方差 x_train_2d x_train_seq.reshape(-1, x_train_seq.shape[-1]) scaler_x.fit(x_train_2d) x_train_norm scaler_x.transform(x_train_2d).reshape(x_train_seq.shape) x_val_2d x_val_seq.reshape(-1, x_val_seq.shape[-1]) x_val_norm scaler_x.transform(x_val_2d).reshape(x_val_seq.shape)reshape(-1, n_features)是把所有时间窗口拼成一个大二维表对每个特征维度求统计量。验证集只用transform如果这里也调fit_transform相当于验证集信息提前参与了标准化预测时会高估模型表现。项目里没有单独保存 scaler 文件实际使用时有几种补法一是把scaler_x.mean_和scaler_x.scale_存成npz二是在x_params_list.txt旁边加一个偏置文件。我一般会直接np.savez(scaler_params.npz, meanscaler_x.mean_, scalescaler_x.scale_)预测脚本里重新构造StandardScaler再赋值。3. model.py 的 LSTM 结构设计从 3 维输入到 PM2.5 浓度输出数据已经整理成(样本数, 24, 8)的三维张量接下来 model.py 要解决的是“怎么从时间序列里提取污染趋势”。项目落地时没有直接调第三方时间序列库的默认参数而是显式写了两层 LSTM 加全连接输出。直接看源码可能会觉得网络简单但空气质量预测本质上是一个小样本回归问题郑州单站一年逐小时数据也就 8760 条去掉缺失值后更少模型容量太大会在重污染过程上过拟合。3.1 输入和输出形状先想清楚样本长什么样LSTM 层的输入形状是三部分batch_size、time_steps、n_features。经过utils.py构造后每批数据形状是(None, 24, 8)其中None是批次维度24是时间步长8是特征数。输出层只有一个神经元因为任务是预测连续浓度值属于回归任务所以最后一层不加激活函数。如果将来改成 AQI 等级分类才需要把最后的Dense(1)换成Dense(num_classes, activationsoftmax)损失函数也要换成categorical_crossentropy。3.2 两层 LSTM 加 Dropout结构和参数明细model.py的核心结构可以这样写# model.py from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout def build_model(seq_len24, n_features8, lstm_units64, dropout0.2): model Sequential() model.add(LSTM( lstm_units, return_sequencesTrue, # 保持时间步供第二层 LSTM 使用 input_shape(seq_len, n_features) )) model.add(Dropout(dropout)) model.add(LSTM(lstm_units // 2, return_sequencesFalse)) model.add(Dropout(dropout)) model.add(Dense(16, activationrelu)) model.add(Dense(1)) # 回归任务输出连续值 model.compile(optimizeradam, lossmse, metrics[mae]) return model模型各层信息可以概览成下表层名输出形状说明LSTM 1(None, 24, 64)64 个单元保留每个时间步输出Dropout(None, 24, 64)训练时随机丢弃 20% 神经元LSTM 2(None, 32)32 个单元只输出最后一个时间步Dropout(None, 32)继续抑制过拟合Dense(None, 16)ReLU 激活做非线性映射Dense(None, 1)输出 PM2.5 或 AQI第二层 LSTM 的return_sequencesFalse是关键模型在最后一个时间步输出一个状态向量再经过全连接层映射成浓度值。如果这里也改成True输出仍然是 24 个时间步还需要额外决定取最后一个还是所有时间步的均值徒增复杂度。Dropout 放在 LSTM 层后面是常见做法但dropout不建议超过 0.3否则模型学习短期污染过程会变得很吃力。lstm_units从 32 起步一般到 128 就够用再往上提升有限且训练时间成倍增加。3.3 保存权重到 HDF5model_weights.h5 的加载方式model_weights.h5是训练过程中由ModelCheckpoint保存的权重文件。加载前必须先重建模型结构常见做法是复用同一个build_model(seq_len, n_features)函数再调用model.load_weights(model_weights.h5)。如果直接用load_model去读只有权重的 HDF5 文件会报结构缺失或 KeyError。这份项目里保留了独立的model.py说明设计意图就是“结构定义与权重文件分离”加载时保持参数一致即可。3.4 超参数既有结论seq_len、lstm_units、dropout 怎么搭配seq_len 影响模型能看到多长的污染过程。24 小时适合短期过程48 小时能覆盖一天的昼夜循环和一次完整的污染积累72 小时则尝试捕捉连续重污染过程。郑州秋冬季节 PM2.5 容易积累夏季则 O₃ 突出单一固定窗口很难同时适配两个季节所以建议把 seq_len 放进网格搜索和 lstm_units 一起比较验证集 MAE。实际调参时先固定 seq_len24把 lstm_units 从 32 调到 128看验证集 loss 是上升还是下降如果下降不明显问题往往不在模型容量而在上游特征对齐。4. train_model.py 训练循环用 loss.png 判断该加数据还是加参数数据准备好了模型也建起来了接下来就是训练。看train_model.py时我最关心三件事是否划分验证集、是否做早停、loss.png是否同时画 train 和 val 两条线。很多代码只在最后打印一个 loss 数值那样很难区分过拟合还是欠拟合也无法判断该加数据还是加网络容量。4.1 回调和训练主循环常见的训练代码会带上三个回调EarlyStopping、ReduceLROnPlateau、ModelCheckpoint。它们的作用分别是提前停止、动态降低学习率、保存最优权重。# train_model.py 中的关键训练逻辑 from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint callbacks [ EarlyStopping(monitorval_loss, patience8, restore_best_weightsTrue), ReduceLROnPlateau(monitorval_loss, factor0.5, patience3, min_lr1e-5), ModelCheckpoint(model_weights.h5, monitorval_loss, save_best_onlyTrue, save_weights_onlyTrue) ] history model.fit( x_train_norm, y_train_seq, validation_data(x_val_norm, y_val_seq), epochs100, batch_size64, callbackscallbacks, verbose1 )patience8表示验证集 loss 连续 8 轮不下降就停止训练并回滚到最优权重避免后期过拟合。ReduceLROnPlateau的学习率衰减因子设为 0.5意思是验证集 loss 卡住 3 轮后学习率减半让模型更细腻地收敛。ModelCheckpoint的save_weights_onlyTrue对应第 3 章说的分开保存方式。batch_size 用 64 时单站一年数据大概 100 多轮一个 epoch训练速度很快如果数据量只有几千条建议把 batch_size 降到 32梯度更新更平滑。4.2 loss 曲线不是只看 loss 大小而是看两条线的间距训练结束可以把 history 里的 loss 画成loss.pngimport matplotlib.pyplot as plt plt.figure(figsize(8, 4)) plt.plot(history.history[loss], labeltrain_loss) plt.plot(history.history[val_loss], labelval_loss) plt.xlabel(epoch) plt.ylabel(loss) plt.legend() plt.savefig(loss.png, dpi150)如果 train_loss 持续下降val_loss 先降后升说明模型开始记忆训练集中的噪声也就是过拟合。此时不应该继续增加训练轮数而是考虑增大 dropout、缩小 lstm_units或者检查训练数据里是不是混入了重复的缺失值填充记录。如果两条曲线都在高位不降说明模型容量不足先把 lstm_units 从 64 调到 128或者把滑动窗口从 24 调到 48。项目里的model_struct.png是模型结构图loss.png是训练过程图两个文件配合看能快速定位“结构问题”还是“数据问题”。4.3 用 MAE 和 RMSE 评估多少算可用训练完成后不要只盯着 loss 值还要在时间轴上计算回归指标。评估时常用下面三个指标表达式在 PM2.5 预测里的经验区间MAEmean(abs(actual - pred))10~20 μg/m³ 说明趋势可用RMSEsqrt(mean((actual - pred)^2))明显高于 MAE 时说明有极端误差R²1 - (残差平方和 / 总平方和)0.85 以上比较理想MAE 更直观但 RMSE 对重污染日的低估更敏感。郑州秋冬季出现静稳天气时PM2.5 容易突然冲高模型如果总是把峰值预测低RMSE 会明显高于 MAE。项目里的model.png一般就是预测值与实测值的散点图看散点是否贴近 45 度线。训练结束可以打印model.evaluate(x_val_norm, y_val_seq)拿到验证集的 MSE 和 MAE再额外算 RMSE 和 R²。5. predict.py 加载权重做 24 小时滚动预测避免误差累积训练完成拿到model_weights.h5后predict.py要做的是用最近 24 小时的真实监测序列滚动预测未来 24 小时 PM2.5。这一步最大的坑不是模型加载而是滚动时用预测值去替换新输入导致误差在一个小时内被放大。5.1 加载权重并复用标准化参数预测脚本必须复用训练时的参数列表和标准化参数不能重新fitscaler。训练环境里保存的scaler_params.npz来自第 2 章预测时先重建模型结构# predict.py 片段 model build_model(seq_len24, n_features8) model.load_weights(model_weights.h5) # 读取最近 24 小时原始数据并应用训练时的标准化参数 x_recent load_recent_window(data.txt, x_params_listx_params_list.txt) x_last x_recent[-24:]加载权重时seq_len和n_features必须和训练时一致否则权重形状对不上。5.2 滚动预测时只替换目标列滚动预测的核心是每次把窗口向前移动一步用刚才的预测值填充最新一行再预测下一个小时。这里建议只替换目标列其他气象特征继续用最近真实值def rolling_forecast(model, scaler_x, scaler_y, last_window, steps24, target_col0): preds [] cur scaler_x.transform(last_window) for _ in range(steps): pred_scaled model.predict(cur[np.newaxis, :, :], verbose0)[0, 0] pred scaler_y.inverse_transform([[pred_scaled]])[0, 0] preds.append(pred) # 将新预测值放回目标列其他特征保持最近真实观测 cur np.roll(cur, -1, axis0) cur[-1, target_col] pred_scaled return np.array(preds)np.roll把整个窗口往前平移一个时间步target_col是 PM2.5 在特征矩阵中的位置。只替换目标列能避免预测误差向温度、湿度等其他特征扩散。另一个技巧是每拿到一个真实监测值就立刻更新last_window而不是连续预测 24 小时之后再校正如果气象数据每 3 小时才更新一次就把新观测按时间戳插进窗口。把这个“谁更新、谁保留”的逻辑写成配置项多站点同时预测时会省下大量重复代码。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

WorkBuddy平台个人开发者接入实战:从零构建Agent应用 2026/9/14 5:22:49

WorkBuddy平台个人开发者接入实战:从零构建Agent应用

WorkBuddy 开放平台个人开发者接入实战:从零到 Agent 应用的完整路径 最近很多朋友在问 Agent 开发到底怎么落地,尤其是个人开发者,手里没有大厂那套底层训练资源,也没有一支完整的工程团队,怎么才能把一个能用的 Age…

阅读更多 →
在电脑上玩Switch游戏:免费开源yuzu模拟器3步上手完整指南 2026/9/14 5:22:49

在电脑上玩Switch游戏:免费开源yuzu模拟器3步上手完整指南

在电脑上玩Switch游戏:免费开源yuzu模拟器3步上手完整指南 【免费下载链接】yuzu 任天堂 Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/yu/yuzu 想把客厅里的Switch游戏搬到电脑或手机上玩?yuzu是一款免费开源的任天堂Switch模拟…

阅读更多 →
用TensorFlow实现LeNet-5:从MNIST手写数字识别入门卷积神经网络 2026/9/14 5:22:49

用TensorFlow实现LeNet-5:从MNIST手写数字识别入门卷积神经网络

简介:这是一份基于TensorFlow构建LeNet-5卷积神经网络的手写数字识别项目,网络结构包含卷积层、池化层、全连接层等典型模块,面向毕业设计、课程设计、工程实训和大作业等场景。资源内含MNIST数据集压缩文件、Python训练脚本、多轮迭代后的模…

阅读更多 →
llm_wiki:面向大语言模型的知识蒸馏与可信知识治理系统 2026/9/14 5:22:49

llm_wiki:面向大语言模型的知识蒸馏与可信知识治理系统

1. 项目概述:这不是一个维基百科镜像,而是一套面向LLM训练与验证的结构化知识治理系统“llm_wiki”这个名称乍看容易让人联想到“大语言模型版维基百科”,但实际完全不是一回事。我第一次看到这个词是在某次开源模型评测社区的讨论帖里&#…

阅读更多 →
从零搭建DeskcommCRM:统一客户通信与工单管理的实战复盘 2026/9/14 5:22:49

从零搭建DeskcommCRM:统一客户通信与工单管理的实战复盘

上周复盘服务数据时,我把后台翻了个底朝天:同一个客户的名字出现在微信备注、邮件落款和工单系统里,三个地方的来源渠道互不相通,处理同一问题的聊天记录被分散在三个不同的表格里。这样的场景,做客服运营或客户成功的…

阅读更多 →
用 Obsidian 打造你的 LLM 知识库:大模型学习与个人 Wiki 实践指南 2026/9/14 5:19:49

用 Obsidian 打造你的 LLM 知识库:大模型学习与个人 Wiki 实践指南

llm_wiki:用 Obsidian 建立你的第一座大模型知识库 入行做 NLP 这几年,我电脑里的资料比头发掉得还快。PDF 论文、微信公众号文章、GitHub 上的教程、飞书文档链接,散落在各个文件夹里,真到用的时候什么都找不到。后来我花了一个…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞