资讯动态

基于LSTM的空气质量指数预测实战:原理、代码与避坑指南

发布时间:2026/10/14 11:38:23 来源:尧图企业网站定制
简介这是一份基于LSTM的空气质量指数预测Python源码项目专为高校期末大作业、课程设计与毕业设计打造的高分参考适合已有Python基础、希望快速落地完整预测系统的学习者。项目代码包含详尽注释经严格调试可直接运行曾被导师评为98分。压缩包共2000个文件大小约29.33MB其中1989个CSV文件存放北京等城市空气质量历史数据可作为训练与验证数据集4个Python脚本覆盖数据预处理、LSTM模型构建、训练及评估流程另含5个XML配置文件和1份说明文档结构清晰便于学习。目前已有222人学习下载。通过该资源读者既能了解LSTM处理时序预测问题的完整思路也可直接部署使用作为课程答辩或毕业设计的演示成果系统功能完善、操作简单具有很高的实用价值。1. 为什么是这题期末大作业里的LSTM空气质量预测到底难在哪期末大作业选「基于LSTM的空气质量指数预测」是个看起来很稳、实际容易翻车的选题——网上源码一抓一大把但能直接跑通的不多跑通了的又多半停留在「抄过来改个数据集」的程度答辩时被导师追问一句「你滑窗窗口为什么取12」就卡壳。这份项目真正要解决的问题不是「预测得准」而是把一个时序预测问题从头到尾做到自圆其说异常值要不要剔、归一化怎么做才不泄漏、时间步长取多少、损失函数用MSE还是Huber、指标看MAE还是R²——每一环都得有说法。适合的是有Python基础、想用一份结构完整且能讲清原理的代码拿下课程高分的人也适合第一次接触LSTM、想知道「模型到底在学什么」的人。后面所有内容都围绕一个目标交上去的工程能复现、答辩问不倒、改参数不慌。2. 先把地基打好AQI数据预处理与LSTM选型背后的三个理由2.1 为什么是LSTM而不是RNN或普通全连接网络空气质量指数是一个典型的时间序列今天的污染状况和过去几天甚至几周的气象条件、排放变化都有关联。普通全连接网络把每个时刻的数据当独立样本处理丢掉了时间顺序传统RNN虽然引入了循环结构但在处理超过1015步的序列时梯度很容易指数级衰减或爆炸出现「远期记忆丢失」的问题。LSTM长短期记忆网络在RNN的基础上加入了遗忘门、输入门和输出门。这三个门控机制让信息可以选择性通过遗忘门决定上一时刻的哪些记忆要丢弃输入门决定当前时刻的新信息有多少写入记忆输出门决定最终的隐藏状态如何输出。简单说LSTM给网络增加了一条可以长距离传输的「记忆通道」这让它天然适合AQI这种带有周期性和滞后效应的数据。做期末项目时还有个现实的理由LSTM在Keras/TensorFlow里的实现非常成熟一个tf.keras.layers.LSTM层加上前向填充的数据预处理几十行内就能搭建一个可用模型。相比自己实现RNN的反向传播或者去调TransformerLSTM的调试成本和算力要求都低得多这在期末时间紧、显卡可能还是共享的情况下很重要。2.2 数据清洗缺失值、异常值处理的两个关键动作大多数公开的空气质量数据集都有原始站点观测的格式问题某几天站点检修导致数据为空、传感器漂移产生极大极小异常值、不同城市的时间戳对齐方式不一致。第一次做这个题目的人最常见的操作是df.dropna()一把梭但这样做有两个隐患一是缺失率超过2%时删除会导致时间序列出现断裂LSTM在滑窗时把这些断裂也当成了有效序列二是删掉的时间点会破坏相邻时刻的依赖关系预测结果在断裂处会突然跳动。我一般会用这样一套清洗流程import pandas as pd import numpy as np df pd.read_csv(air_quality.csv, parse_dates[date]) df df.set_index(date).sort_index() # 先看缺失率而不是直接删 missing_rate df.isnull().mean() print(missing_rate[missing_rate 0]) # 缺失率低于5%的列用前向填充再配合线性插值修正 df df.fillna(methodffill).interpolate(methodlinear) # 异常值处理用滚动窗口的Z-score方法而不是全局Z-score from scipy import stats def remove_outliers(series, window24, threshold4): mean series.rolling(window, centerTrue).mean() std series.rolling(window, centerTrue).std() z_score (series - mean) / std return series.mask(abs(z_score) threshold) df[AQI] remove_outliers(df[AQI])这段代码里的两个关键点ffill后接interpolate是为了避免连续缺失时出现一大段相同值——LSTM会把「长时间不变化」误学习为「平稳期」异常值检测用的是滚动窗口内Z-score而不是全局Z-score原因是AQI本身有季节性波动冬天的300可能正常夏天的300可能就是异常全局统计会把这些合法波动误杀。注意清洗操作必须在滑窗之前完成。先滑窗再清洗会导致某些窗口内部出现空值Keras虽然能接受NaN作为输入但在反向传播中会产生NaN梯度训练过程直接发散。2.3 滑窗与归一化让模型真正学会时间依赖的最小步骤LSTM输入必须是(样本数, 时间步长, 特征数)的三维张量。把连续的AQI序列变成这种结构靠的就是滑窗。窗口大小look_back的选取是第一个关键超参——它代表模型每次看多长的历史来预测下一时刻。def create_dataset(data, look_back12): X, y [], [] for i in range(len(data) - look_back): X.append(data[i:i look_back]) y.append(data[i look_back]) return np.array(X), np.array(y) look_back 12 X, y create_dataset(df[AQI].values.reshape(-1, 1), look_back) # 划分数集严格按时间顺序不做随机打乱 train_size int(len(X) * 0.8) X_train, X_test X[:train_size], X[train_size:] y_train, y_test y[:train_size], y[train_size:] from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) # 关键先reshape成二维再fit避免按单个时间点归一的坑 X_train_flat X_train.reshape(-1, 1) scaler.fit(X_train_flat) X_train_norm scaler.transform(X_train.reshape(-1, 1)).reshape(X_train.shape) X_test_norm scaler.transform(X_test.reshape(-1, 1)).reshape(X_test.shape) y_train_norm scaler.transform(y_train.reshape(-1, 1)).flatten() y_test_norm scaler.transform(y_test.reshape(-1, 1)).flatten()这里最容易犯错的是归一化时机。很多人习惯先把全量数据做scaler.fit_transform再切分训练集和测试集——这在学术上叫数据泄漏因为测试集的信息在训练阶段就被模型看到了导致验证指标虚高答辩时老师一眼就能看出来。正确的做法是scaler只在训练集上fit测试集用同一个标度做transform。窗口大小look_back12的含义是「用过去12小时的AQI预测下一小时」这是一个经验性起点。如果你处理的是日均数据而不是小时数据12天历史可能太短可以尝试24或者7×24一周周期如果你的数据有明确的周末效应窗口至少应该覆盖7天。这个参数后面要单独做敏感性分析这是拿高分的关键铺垫。3. 从零到一搭模型TensorFlow/Keras下的LSTM训练完整代码3.1 模型结构设计层数、神经元数量怎么定单层LSTM是期末作业的主流选择不是因为双层不好而是你在答辩时要把「为什么加第二层」讲清楚。我通常的建议是先用单层LSTM加一个Dense输出层跑通基线再在进阶部分比较单层和双层的效果差异用数据说明问题。神经元数量的选择有玄学成分但有个可行策略输入的特征数乘4到8倍再向上凑个整数。如果只用AQI单变量预测units64就够如果后面扩展成多变量输入PM2.5、PM10、温度、风速units128更稳妥。import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau tf.random.set_seed(42) np.random.seed(42) model Sequential([ LSTM(units64, return_sequencesFalse, input_shape(X_train_norm.shape[1], X_train_norm.shape[2])), Dropout(0.2), Dense(16, activationrelu), Dense(1, activationlinear) ]) model.compile( optimizertf.keras.optimizers.Adam(learning_rate0.001), lossmse, metrics[mae] ) model.summary()两个容易讲不清的细节一是return_sequences单层LSTM输出的是最后时刻的隐藏状态设置为False即可只有当你再接一层LSTM时第一层必须返回完整序列。二是Dropout放在LSTM层之后而不是之前——Keras的LSTM本身有dropout参数但它是作用在循环连接上的和标准Dropout的含义不同期末答辩容易混淆。3.2 训练脚本compile、fit和三个回调函数的完整写法训练环节最影响分数的是「你怎么证明训练是收敛的」。不要只贴一行model.fit要加上早停和学习率衰减这样在答辩时面对「如果loss一直不下降怎么办」这类问题才有话可说。early_stop EarlyStopping( monitorval_loss, patience10, restore_best_weightsTrue ) reduce_lr ReduceLROnPlateau( monitorval_loss, factor0.5, patience5, min_lr1e-5 ) history model.fit( X_train_norm, y_train_norm, batch_size64, epochs100, validation_split0.1, callbacks[early_stop, reduce_lr], verbose1 ) model.save(lstm_aqi_model.h5)patience10的含义是连续10轮验证集loss没有下降就停止训练这能防止过拟合restore_best_weightsTrue保证最终保存的是验证集上表现最好的那组权重而不是最后一轮的权重——很多初版代码忘了这个参数导致结果忽好忽坏。ReduceLROnPlateau在验证loss连续5轮不降时自动把学习率减半这比手工调学习率稳定得多。batch_size的选择和你的数据量相关。小时级数据三年大约26000条滑窗后大约25988个样本batch_size64是合理区间。如果数据集更小比如只有半年建议用32。validation_split0.1会在训练集内部切出10%做验证注意它不影响之前已经切分好的测试集。3.3 预测与反归一化结果怎么还原到真实尺度训练完成后的预测环节是翻车重灾区。model.predict输出的是0到1之间的归一化值直接拿去画图曲线形状没错但坐标轴是错的更隐蔽的是inverse_transform的形状问题——MinMaxScaler要求输入二维(样本数, 1)而模型输出是(样本数, 1)的二维数组直接转回一维再还原时容易把形状弄混。y_pred_norm model.predict(X_test_norm) # 反归一化前先保证shape是二维 y_pred scaler.inverse_transform(y_pred_norm.reshape(-1, 1)).flatten() y_true scaler.inverse_transform(y_test_norm.reshape(-1, 1)).flatten() # 计算评价指标 from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score mae mean_absolute_error(y_true, y_pred) rmse np.sqrt(mean_squared_error(y_true, y_pred)) r2 r2_score(y_true, y_pred) print(fMAE{mae:.2f}, RMSE{rmse:.2f}, R2{r2:.3f})这里写y_pred_norm.reshape(-1, 1)是必需的因为scaler.inverse_transform要求输入特征数和fit时一致——你fit的是(n,1)的数据输入就必须是(n,1)。如果直接传入(n,)的一维数组部分版本的sklearn会报错另一部分会静默算出错误结果。一个更值得关注的细节是「预测曲线整体右移一格」的现象。这是因为我们的任务是「用过去12小时预测下一小时」预测值和真实值天然差一个时间步。画图对比时应该把预测曲线整体左移一位或者改用「用过去12小时预测未来6小时」的多步预测来回避这个视觉错位。这个问题放在第5章讲但你在答辩前一定要自己先想明白因为它是被问频率最高的问题之一。4. 期末大作业血泪避坑五个让LSTM预测跑飞的真实案例4.1 现象预测曲线贴着真实值但整体右移一格第一次跑通模型时看到预测曲线和真实曲线几乎重合很多人会兴奋地截图发给导师。但仔细对比会发现预测曲线像是真实曲线向右平移了一个时间单位——这不是模型学得好而是模型学会了「把上一时刻的值输出出来」。原因当look_back较小且数据自相关性较强时LSTM可以退化成「拷贝上一时刻」的捷径解法这本质上是欠拟合的表征。解决把look_back从12增大到24或48并对比MAE变化更彻底的做法是改成多步预测比如用过去48小时预测未来12小时的均值任何「移动一格」的捷径策略在多步预测中都会暴露。4.2 现象训练集loss正常下降测试集指标却差到离谱训练过程中训练集MSE降到0.01以下但测试集MAE有几十甚至上百。查看模型在测试集上的具体预测值会发现输出全部趋于一个恒定值。原因归一化时全量数据一起fit_transform造成数据泄漏是其一其二是AQI数据存在极端值比如某天突发的沙尘暴使AQI超过500MinMaxScaler会把正常值压缩到0~1区间内极小的一段导致模型学不到区分度。解决先切分训练集再分别归一化对异常极端值在清洗阶段就用滚动Z-score剔除或用上限截断。如果极端值确实代表真实污染事件则改用RobustScaler它对离群点更鲁棒。4.3 现象模型每次跑出来的结果都不一样时好时坏同一份代码跑三次三次的MAE分别是18、26、22——同学之间交换代码复现时结果也对不上。原因神经网络初始化是随机的Dropout也有随机性TensorFlow在GPU上的算子本身还存在非确定性。期末作业要看可复现性这是评分维度之一。解决在代码开头固定随机种子固定tf.keras.utils.set_random_seed(42)同时设置tf.config.experimental.enable_op_determinism()注意它会降低训练速度但结果可精确复现。固定种子后再跑三次指标应该完全一致。4.4 现象loss在训练中期卡住不动像是进入了平台期训练到第20轮左右loss卡在0.02附近无论怎么增大epochs都不下降。原因最常见的是学习率设置过大或过小——过大时loss会在最小值附近震荡但无法进入更优区域过小时前期下降极慢看起来就像卡住。其次可能是滑窗构造的样本内部存在大量重复模式模型已学到数据中所有规律。解决给Adam设置初始学习率0.001并接入ReduceLROnPlateau让它在平台期自动减半如果减半后仍无变化检查训练数据是否存在长时间重复序列比如周末数据整体缺失被前向填充填平了。4.5 现象预测值出现负值或超过500的离谱数值AQI理论上不会为负但模型输出的反归一化值出现了-35或者700这样的数值。原因Dense输出层的激活函数是linear模型神经元的输出理论上可以是任意实数拟合误差稍大时就会超出物理范围。这不是模型坏掉了是范围外推问题。解决输出层改为sigmoid激活后再反归一化放缩到0~1之间再映射回原始范围或者在后处理中加一个np.clip(pred, 0, 500)强制截断。答辩时也可以主动提一句「物理约束后处理」这在很多工程实践中确实是标准操作。5. 从完成到高分把LSTM预测做成一等项目的四个花絮技巧基线模型跑通只是及格线。想把分数从B推到A需要在「工作量」和「系统性」上做文章。下面四个技巧按投入产出比排序你可以根据剩余时间挑选组合。第一把单变量改成多变量输入。只用AQI历史值预测未来模型只能学到时间自相关本质上还是在「外推一条曲线的形状」。加上PM2.5、PM10、O₃、风速、相对湿度后模型学到的是污染物生成和扩散的物理关系预测误差通常能下降15%30%。做法很简单create_dataset函数改成接收多个列特征维度从1扩展到5~7。这里有个小坑——不同变量的量纲差异很大风速是个位数AQI是三位数MinMaxScaler必须按列分别fit不要混在一起拟合。第二做一张「超参数敏感性分析表」。选3个超参数——look_back取6/12/24/48units取32/64/128batch_size取32/64/128——各跑一组固定其他参数的控制变量实验用表格列出每组实验的MAE、RMSE、R²和训练时长。导师看到这张表大概率会认为你理解每个参数的意义。表里的注释部分写清楚「当look_back从12增到48时MAE先降后升说明过长的历史引入了噪声」这一句话就能体现你对模型边界条件的理解。第三加一组「基线对比模型」。拿同样的训练数据跑一个线性回归把过去12小时的值展平作为特征和一个简单ARIMA模型在测试集上比较三个模型的MAE和R²。LSTM的效果可能只比线性回归好一点这反而是个好的讨论点——说明该数据集中线性趋势占主导LSTM的优势体现在捕捉非线性突变。诚实呈现对比结果比只吹LSTM好得多评委对「刻意美化结果」非常敏感。第四图表的完成度。别只画一张预测折线图。至少准备三张图训练集的loss曲线和验证集loss曲线在同一张图里对比测试集真实值vs预测值的曲线并标注出预测误差最大的3个时间点——去查一下那几天的天气往往能发现是沙尘或大风导致的气象突变真实值和预测值的散点图加对角线直观展示是否存在系统性偏差。答辩时每张图背后都要有一句话可以讲这张图说明了什么、为什么会有这个现象、如果重做会怎么改。我做这个方向的习惯是先把单步预测跑到能稳定复现再加多变量和对比实验最后才补充图表和注释。很多同学一上来就做双LSTM加注意力机制结果训练时间拉长、指标反而变差最后只能靠删掉复杂模块收场。结构完整的基线加上扎实的消融对比比一个花哨但脆弱的模型拿分更稳。最后说句实在话期末大作业的技术深度永远排第二第一是「经得起追问」。你自己写不出来每个模块为什么这么做那就算代码跑得再漂亮答辩也是坐立难安。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑