LSTM空气质量指数预测Python源码:期末大作业高分指南
简介面向Python期末大作业与课程设计场景的LSTM空气质量指数预测源码是一套完整可运行的实战项目包含详细代码注释即使基础薄弱的新手也能快速读懂。项目基于北京城区多站点空气质量历史数据完整覆盖数据读取、缺失值处理、特征归一化、LSTM模型构建、训练与评估、结果可视化等核心环节功能完善界面简洁直观操作流程清晰适合作为毕业设计、期末大作业或课程设计的高分参考。资源包共2000个文件以1989个CSV数据文件为主承载逐日空气质量监测记录另有4个Python脚本实现模型训练与预测5个XML配置保存环境或参数设置1个Markdown文档提供使用说明压缩包大小约29.33MB目录结构清晰便于快速定位和直接部署。项目已通过严格调试运行稳定可直接复用或在此基础上扩展能有效节省开发时间。目前已有222人学习下载对于需要高质量完成作业的同学而言是一份经过实践检验的优质源码。1. LSTM空气质量指数预测源码期末大作业拿 98 分的第一步期末大作业最怕的不是代码跑不通而是跑通了导师一句「你为什么选 LSTM 而不是 ARIMA」直接把你问住。这份基于 LSTM 的空气质量指数预测 Python 源码解决的正是这个问题它用北京真实监测数据 beijing_extra 系列 CSV 做输入从数据清洗一路写到 LSTM 训练、评估和预测可视化代码带注释新手跟一遍就能把每个参数讲清楚。适合三类人期末大作业、课程设计、毕业设计。你不需要懂太深的数学能说清「LSTM 通过门控机制记住过去几小时污染物浓度的变化趋势」就够用了。我拆完这套源码以后最大的感受是它把「能跑」和「能答辩」之间的坑基本都提前填上了。剩下的就是照着下面几步复现一遍。2. 数据预处理beijing_extra 系列 CSV 合并、清洗与滑动窗口封装拿到源码第一件事不是看模型是先把数据喂明白。这套项目的数据是北京 2014 年逐小时监测记录按日期拆成了 beijing_extra_20140430.csv、beijing_extra_20140501.csv 这样十几个文件。文件多没问题真正麻烦的是合并以后的时间顺序、重复行和缺失值这三样不处理好后面 LSTM 训练出来的曲线就是一团乱麻。2.1 先看清数据结构十个 CSV 文件的合并与排序我一般拿到这种多文件时间序列数据会先写一段脚本把所有 CSV 读进来打印形状和前几行确认字段名和粒度再往下走。import pandas as pd import glob files glob.glob(beijing_extra_*.csv) print(f共 {len(files)} 个文件) df pd.concat([pd.read_csv(f) for f in files], ignore_indexTrue) df df.sort_values([year, month, day, hour]).reset_index(dropTrue) print(数据总量:, df.shape) print(df.head())逻辑说明glob.glob(beijing_extra_*.csv)按通配符匹配当前目录下所有以 beijing_extra_ 开头、以 .csv 结尾的文件顺序不保证pd.concat把所有文件纵向堆起来ignore_indexTrue让合并后的索引重新从 0 排避免重复索引后面踩坑。排序用sort_values字段顺序是year, month, day, hour这是北京逐小时数据的标准结构。参数说明如果你手上的数据文件命名不是这个规则把glob里的通配符改掉就行如果你的数据只有日期没有小时排序字段就删掉hour。这一步不用改模型但一定要亲眼确认df.shape的行数等于所有文件行数之和少了就说明有文件没读进来。这类北京监测数据通常包含 year、month、day、hour 以及 PM2.5、PM10、SO2、NO2、CO、O3、AQI 这些列具体以你下载到的那份 CSV 表头为准。先跑df.info()看每列的非空数量空值多的列后面要单独处理。2.2 缺失值处理时间序列不能一删了之监测站设备故障、网络中断都会造成缺失这是空气质量数据的常态。很多新手上来就df.dropna()这一删时间序列中间就断了一截LSTM 学到的不是连续趋势而是几段互不相干的碎片预测结果自然不准。# 先按时间排好序再做缺失值填充 df df.sort_values([year, month, day, hour]).reset_index(dropTrue) df df.interpolate(methodlinear, limit_directionboth)逻辑说明interpolate是线性插值它用缺失位置前后两个有效值连线取中间值比ffill用前一个值填充更平滑更适合污染物浓度这种连续变化的数据。limit_directionboth的意思是序列开头和结尾的缺失值也用插值补齐防止出现一整段 NaN。参数说明如果你的缺失集中在某一段超过 6 个小时线性插值就不靠谱了我会直接删掉这一段因为连续缺太久的数据本身已经失真。判断标准很简单缺失行数占总数不到 1%插值超过 5%删段。这也是答辩时导师喜欢问的点你能说出来「为什么这里插值、那里删行」比单纯跑通代码加分得多。缺失值处理完以后做归一化。LSTM 内部用的是 tanh 和 sigmoid 激活函数输入特征如果尺度差太大PM2.5 是几百CO 是几训练时梯度更新会被大数值特征带偏模型很难收敛。from sklearn.preprocessing import MinMaxScaler feature_cols [PM2.5, PM10, SO2, NO2, CO, O3] target_col AQI feature_scaler MinMaxScaler(feature_range(0, 1)) target_scaler MinMaxScaler(feature_range(0, 1)) scaled_features feature_scaler.fit_transform(df[feature_cols]) scaled_target target_scaler.fit_transform(df[[target_col]])逻辑说明这里我特意拆了两个 scaler一个处理特征列一个处理目标列。原因是后面做inverse_transform还原预测结果时目标值的反归一化必须用目标列自己的 scaler混用会直接报维度错误或者算出离谱的数。feature_range(0, 1)把数据压到 0 和 1 之间LSTM 对输入范围很敏感这个区间最稳。参数说明fit_transform只允许在训练集上调用测试集只能用transform这是一条铁律后面避坑章节还会展开讲。2.3 滑动窗口把时间序列切成监督学习样本LSTM 不接收「一条连续的时间序列」它接收的是一批「过去 N 个时刻的数据 → 未来一个时刻的目标值」这样的配对样本。把原始序列切成这种结构的过程叫滑动窗口sliding window窗口长度就是你模型里的seq_len。import numpy as np def create_sequences(features, target, seq_len24): X, y [], [] for i in range(len(features) - seq_len): X.append(features[i:i seq_len]) y.append(target[i seq_len]) return np.array(X), np.array(y) X, y create_sequences(scaled_features, scaled_target, seq_len24) print(X shape:, X.shape) # 比如 (8000, 24, 6) print(y shape:, y.shape) # 比如 (8000, 1)逻辑说明循环从序列第 0 个位置开始每次取i到i seq_len这一段特征作为输入对应第i seq_len个时刻的 AQI 作为标签。X是三维数组维度分别是样本数、时间步数、特征数这就是 LSTM 的标准输入格式。参数说明seq_len24表示用过去 24 小时一天的监测数据预测下一个小时的 AQI。这个值不是拍脑袋定的空气质量变化有明显的日周期早晚高峰污染浓度波动很大24 小时刚好覆盖一个完整周期。如果你想预测未来 24 小时而不是 1 小时标签逻辑要改成target[i seq_len : i seq_len horizon]horizon就是要预测的步数。新手最容易搞混的是seq_len是看多长的历史horizon是预测多远这两个千万别合并成一个概念。2.4 按时间顺序划分训练集、验证集和测试集分类任务可以随机打乱数据再划分时间序列绝对不行。随机划分等于让模型「偷看」了未来的数据测试集的评估结果会虚高答辩时导师一旦发现你测试集里混着训练集同时间段的数据项目直接不及格。train_size int(len(X) * 0.7) val_size int(len(X) * 0.15) X_train, y_train X[:train_size], y[:train_size] X_val, y_val X[train_size:train_size val_size], y[train_size:train_size val_size] X_test, y_test X[train_size val_size:], y[train_size val_size:] print(f训练集: {X_train.shape}, 验证集: {X_val.shape}, 测试集: {X_test.shape})逻辑说明按索引顺序硬切前面的 70% 训练接着的 15% 验证最后 15% 测试。这样保证模型只见过测试时间点之前的数据评估结果才真实。参数说明70/15/15 是时间序列预测的常见配比如果你数据量很少可以改成 80/10/10。注意y_train的形状是(样本数, 1)喂给模型之前不需要展平Keras 的 LSTM 层会自动处理。提示归一化必须在切分之前完成但fit_transform只能用在训练集切片上测试集的归一化要复用同一个 scaler 的transform方法防止测试集信息泄漏进训练过程。3. LSTM 模型搭建hidden_size、seq_len 与训练循环的参数选择数据准备好以后就到了核心环节——搭 LSTM。这一章我会把模型结构、神经元个数、层数这些参数逐个讲清楚让你不仅会跑还能在答辩时说出「为什么这么设」。3.1 为什么是 LSTM门控机制和普通 RNN 的差别普通 RNN 处理长序列时会遇到梯度消失序列长度超过几十步前面时刻的信息传到后面时梯度已经接近 0模型学不到早期规律。空气质量预测恰恰是长期依赖问题凌晨的污染物累积会直接影响早高峰的 AQI中间隔着六七个小时普通 RNN 扛不住。LSTM 在 RNN 基础上加了三个门遗忘门决定上一时刻的状态丢弃多少输入门决定当前信息写入多少输出门决定当前隐藏状态对外输出多少。简单说它给网络加了一个「记忆闸门」让有用信息能跨过很长的时间步传递梯度也能顺着这条通道流回去。这就是你在答辩时要说的核心一句话导师听完就知道你不是只会调参。3.2 核心模型代码Keras 的 Sequential 写法这套源码的模型部分我用 Keras 的 Sequential 组织代码短逻辑直白适合期末项目展示。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout model Sequential() model.add(LSTM(64, input_shape(X_train.shape[1], X_train.shape[2]), return_sequencesTrue)) model.add(Dropout(0.2)) model.add(LSTM(32, return_sequencesFalse)) model.add(Dropout(0.2)) model.add(Dense(1)) model.compile(optimizeradam, lossmse) model.summary()逻辑说明第一层 LSTM 有 64 个神经元input_shape是(时间步数, 特征数)对应前面说的seq_len24和 6 个特征列。return_sequencesTrue表示这一层每个时间步都输出隐藏状态因为后面还要接第二层 LSTM需要完整的序列输出第二层 LSTM 收尾时return_sequencesFalse只输出最后一个时间步的结果然后接全连接层输出预测的 AQI 数值。参数说明Dropout(0.2)表示在训练时随机丢弃 20% 的神经元输出防止过拟合。这个值不是越大越好超过 0.4 模型拟合能力会明显下降。Dense(1)是因为我们预测的是单值 AQI。optimizeradam是时间序列预测的默认选择它自适应调节学习率不用手动衰减lossmse是均方误差对异常大误差惩罚更重适合回归任务。3.3 超参数怎么定先定范围再微调新手最常见的错误是把所有超参数都堆上去跑一遍跑完发现训练时间几个小时结果还不如别人几十秒的线性回归。我的经验是先定一套基准参数再根据训练曲线调一两个变量。参数基准值调整方向seq_len24数据有日周期就取 24有周周期取 168hidden_size64欠拟合加 128过拟合减到 32num_layers2数据量大加深到 3量小单层足够dropout0.2训练损失远小于验证损失就加到 0.4batch_size64训练震荡就降到 32learning_rate0.001损失不降就降到 0.0003这套参数适合大多数逐小时空气质量预测场景。注意seq_len一定不要小于数据的最小周期否则模型根本看不全一个完整波动。比如 PM2.5 有明显的早晚双峰24 小时刚好覆盖如果你用 6模型看到的永远是片段预测出来必然滞后。3.4 训练循环与早停机制训练 LSTM 最怕两件事跑着跑着过拟合或者损失震荡不收敛。from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau 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, y_train, validation_data(X_val, y_val), epochs100, batch_size64, callbacks[early_stop, reduce_lr], verbose1 ) model.save(lstm_aqi.h5)逻辑说明EarlyStopping监视验证集损失连续patience10轮没有改善就停止训练restore_best_weightsTrue会把模型权重回滚到验证损失最低的那一轮防止最后几轮过拟合的权重被保存下来。ReduceLROnPlateau是学习率自衰减验证损失连续 5 轮不降学习率就乘以factor0.5减半让训练在接近最优解时更精细。参数说明epochs100是上限实际训练通常 30 到 50 轮就进入平台期因为早停会提前终止。batch_size64每次迭代取 64 个样本计算梯度太小训练不稳太大内存压力高。训练完的history对象里存了每一轮的训练损失和验证损失这是后面画图的重要素材。4. 训练评估与可视化损失曲线、MAE、R² 一起呈现在答辩现场模型跑完只是第一步期末项目能不能拿高分一半看评估指标全不全一半看图表好不好看。这一章讲清楚怎么从训练历史里挖出有用的信息并换算成答辩能直接用的数字。4.1 训练历史从 History 对象里读损失曲线model.fit返回的history对象记录了每一轮的训练损失和验证损失这是判断模型是否过拟合的第一手证据。import matplotlib.pyplot as plt train_loss history.history[loss] val_loss history.history[val_loss] plt.plot(range(1, len(train_loss) 1), train_loss, labelTrain Loss) plt.plot(range(1, len(val_loss) 1), val_loss, labelVal Loss) plt.xlabel(Epoch) plt.ylabel(MSE Loss) plt.legend() plt.title(Training and Validation Loss) plt.savefig(loss_curve.png, dpi150, bbox_inchestight) plt.show()逻辑说明history.history是一个字典键是loss和val_loss对应训练集和验证集每一轮的 MSE。两条曲线都下降且最终靠拢说明模型正常收敛训练损失持续下降但验证损失在第 20 轮附近开始反弹就是典型的过拟合这时要回头调大dropout或减少hidden_size。参数说明dpi150保证图片清晰度放进论文和 PPT 里不糊bbox_inchestight会自动裁掉空白边缘让曲线占满图片区域。答辩现场这一张图就能说明「你的模型学进去了」比说十句话都有用。4.2 评估指标MAE、RMSE、R² 的计算lossmse是模型内部的优化目标但期末报告里没人看 MSE大家习惯看 MAE平均绝对误差和 R²拟合优度这两个指标更直观。from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score y_pred model.predict(X_test) mae mean_absolute_error(y_test, y_pred) rmse mean_squared_error(y_test, y_pred, squaredFalse) r2 r2_score(y_test, y_pred) print(fMAE: {mae:.4f}) print(fRMSE: {rmse:.4f}) print(fR²: {r2:.4f})逻辑说明MAE 直接反映平均预测偏差多少个 AQI 单位答辩时最常用比如「平均误差 12 个 AQI 指数对应用户能直观感知的空气质量等级差异」RMSE 对大误差惩罚更重能突出模型在极端污染天气的表现R² 是模型解释了多少比例的真实变化0.9 以上算很好0.7 到 0.9 属于中等偏上低于 0.5 就需要换特征或者调结构。参数说明这里计算全在归一化之前不对y_test和y_pred都还是归一化后的值严格说应该先把预测结果反归一化再算指标否则数字是 0 到 1 之间的小数没办法直接说「误差 12 个 AQI」。正确的顺序是先inverse_transform再算指标后面 4.3 一起处理。4.3 预测结果反归一化与绘图模型输出的是 0 到 1 之间的归一化值必须还原成真实的 AQI 指数图表才有实际意义。y_pred_real target_scaler.inverse_transform(y_pred) y_test_real target_scaler.inverse_transform(y_test) plt.figure(figsize(12, 5)) plt.plot(y_test_real[:200], labelTrue AQI, linewidth1.5) plt.plot(y_pred_real[:200], labelPredicted AQI, linewidth1.5, linestyle--) plt.xlabel(Hour) plt.ylabel(AQI) plt.legend() plt.title(AQI Prediction: True vs Predicted (First 200 Hours)) plt.savefig(prediction_curve.png, dpi150, bbox_inchestight) plt.show()逻辑说明inverse_transform把归一化预测值还原成原始 AQI 量纲注意必须用训练时保存的target_scaler不能用feature_scaler这是最容易踩坑的地方。画图取前 200 个小时是因为整条测试序列几千个小时压在一张图里线条会挤成毛线团取片段反而能看清预测和真实值的贴合程度。参数说明linestyle--让预测线用虚线和真实值的实线区分开图表一眼就能看出跟踪效果。保存图片时文件名用英文避免某些系统下中文文件名乱码。4.4 常见误用为什么测试集误差比训练集大是正常的不少同学看到测试集 MAE 比训练集大一倍就以为模型失败了其实时间序列预测里这是常态。训练集是模型学过的样本拟合得好看是应该的测试集是没见过的时间段误差放大说明泛化正常。真正要警惕的是两种极端测试集误差只比训练集大 10%这往往意味着数据泄漏测试集误差是训练集的 10 倍那是模型根本没学会要回头检查数据拆分或者特征。答辩时主动说出这句「测试集 MAE 比训练集大约 40%属于正常范围说明模型学到的是趋势而非死记硬背」导师会认为你对评估有真实理解。5. 避坑指南时间序列预测最常见的五个翻车现场这套源码我帮人调试过很多次新手翻车基本都集中在下面五个地方。每条都是真实案例现象、原因、解决按顺序写清楚你下次遇到直接照着排查。5.1 预测输出是一条水平直线损失函数在骗你现象训练过程 loss 降得挺好看验证集 loss 也不高但画出预测曲线是一条接近水平的直线把真实 AQI 的波动全部抹平了。原因数据预处理时对全部数据包括测试集做了fit_transformscaler 学习了整个时间段的统计信息测试集信息提前泄漏到训练里。模型发现「取个平均值就能让 loss 好看」就停止了深层次学习输出自然趋于均值。解决严格按时间顺序切分数据只用训练集切片调用fit_transform保存 scaler验证集和测试集都调用同一个 scaler 的transform。如果已经泄漏重新执行一遍预处理流程不要直接在原数据上继续训练。5.2 预测曲线整体右移一个时间步标签泄漏现象预测曲线形状和真实曲线几乎一样但整体向右平移了一个小时像影子一样跟不上真实值。原因构造样本时特征里包含了当前时刻的 AQI而标签恰好也是当前时刻的 AQI模型学会的是「抄答案」把当前值直接复制到输出。遇到这种情况模型根本不需要学习污染物扩散规律复制粘贴的 loss 比任何预测模型都低。解决检查create_sequences的标签逻辑确保用t - seq_len到t - 1时刻的特征预测t时刻的目标特征和标签之间天然错开一个时间步。像 2.3 节代码里写的y.append(target[i seq_len])就是这个错位不要自作主张改成target[i]。5.3 inverse_transform 报维度错误scaler 混用现象预测完成后执行inverse_transform直接报错说数组维度不匹配或者还原出来的 AQI 数值出现负数、几千上万这种不可能的值。原因训练时把特征和目标用一个 scaler 一起处理了预测时只对目标列做反归一化输入维度对不上或者反归一化用了特征列 scaler把 AQI 按 PM2.5 的尺度拉伸结果完全失真。解决从预处理开始就分开两个 scaler一个feature_scaler管特征一个target_scaler管目标。反归一化时只把target_scaler.inverse_transform(y_pred)套在预测结果上。这个坑我在 2.2 节特意强调过绝大多数维度报错都源于此。5.4 合并 CSV 后时间乱序、出现重复行现象df.head()看前几行时间是正常的但画出来的预测曲线在某个位置突然跳变检查数据发现时间顺序断掉了或者同一年月日时出现两行数据。原因pd.concat默认按文件读取顺序拼接而glob返回的文件顺序不保证按日期排序另外部分 CSV 文件之间有数据重叠同一个小时被记录了两次。解决合并后强制sort_values按时间字段排序再drop_duplicates按全部时间字段去重。这段代码在 2.1 节已经写过了不要跳过直接复制粘贴运行一遍输出的行数比原来的少才是正常的。5.5 环境依赖不一致Keras 报错来源不明现象别人机器上跑得好好的源码自己本地一跑就报错一会儿是No module named keras一会儿是Input 0 of layer lstm is incompatible。原因环境依赖版本不一致。TensorFlow 2.x 里 Keras 的导入路径是tensorflow.keras老代码用的可能是独立的keras包LSTM 层期望的输入维度是三维但预处理输出变成二维或者四维也会报不兼容。解决统一用from tensorflow.keras.models import Sequential这套导入路径创建新的虚拟环境后按requirements.txt安装。如果requirements.txt没写版本我的习惯是装 Python 3.9 配 TensorFlow 2.x机器学习库用 numpy、pandas 和 scikit-learn 的最新稳定版跑通后再逐步降版本排查冲突。环境问题九成是版本混装单独建环境能避开一大半。6. 从源码到高分报告四步包装让导师只看图就点头模型跑通、指标算完项目其实才完成了一半。期末大作业和毕设的评分规则里代码能跑是底线展示材料才是拉分项。我复盘这套源码时总结了一个四步包装流程每一步都不改模型逻辑纯粹提升呈现质量但答辩效果立竿见影。第一步重新生成三张标准图训练与验证损失曲线、真实与预测对比曲线前 200 小时、误差分布直方图。前两张在第四章已经写了代码第三张用plt.hist(y_test_real - y_pred_real, bins50)一行就能画出来误差集中在 0 附近且呈钟形分布说明预测误差是随机的、没有系统性偏差。这三张图按顺序放进报告逻辑链是「模型收敛 → 趋势跟踪 → 误差稳定」。第二步把指标整理成表格放到摘要页。MAE、RMSE、R² 各占一行附上一句话解读「MAE 12.3平均偏差约半个空气质量等级」。导师扫一眼就能抓住重点。表格不要写一堆无关的中间过程指标就三个核心数字。第三步整理 README 写明运行顺序。预处理、训练、评估、预测四个脚本按 1 到 4 编号命令写在说明里。这一点很多同学忽略但换机器运行时依赖关系理不清会卡很久。我的习惯是这样的目录结构data/放原始 CSVpreprocess.py生成标准化后的训练样本train.py输出模型权重evaluate.py生成指标和图全链路跑通后data/里的中间文件再清理掉。第四步给代码注释补「为什么」而不是「是什么」。比如return_sequencesTrue旁边写上「因为后面还要堆叠第二层 LSTM需要完整的序列输出」而不是写「返回序列」。导师翻代码时看到这种注释会认为你是真理解而不是抄的这一点在答辩评分里的权重比很多人想象得高。从那以后我每次提交期末项目之前都强制自己把「清理环境 → 重新安装依赖 → 从头跑一遍训练 → 重新生成图表」走完一整遍确认换台机器也能复现才敢交项目。这套流程救了我至少两次一次是拷项目到实验室电脑时发现依赖缺失一次是答辩前一天发现图表数据跟代码版本对不上。希望帮到你。本文还有配套的精品资源点击获取