十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于LSTM的空气质量预测:从数据可视化到模型训练全流程解析

基于LSTM的空气质量预测:从数据可视化到模型训练全流程解析 简介面向计算机相关专业在校生与实战学习者的LSTM空气质量分析项目源码适合作为期末大作业、课程设计或毕业设计的完整参考。压缩包共260个文件体积约7.03MB文件类型丰富包含Python源码、HTML可视化页面、CSS/SCSS样式、JavaScript交互脚本、CSV数据文件及SQLite数据库等其中css与scss负责界面样式js实现图表交互py为模型训练与预测逻辑csv和sqlite3存储实验数据目录结构清晰配置好环境后即可直接运行。已有99人学习该项目其经导师指导并获得评审99分质量经过验证。代码覆盖空气质量数据预处理、特征分析、LSTM时序预测、误差评估以及可视化展示等环节可实现污染物浓度变化趋势的预测与交互式浏览并附有多个展示页面用于查看不同维度的分析结果既帮助初学者快速上手深度学习模型也为计算机专业学生提供一份可直接复用的高分项目模板。1. 为什么期末大作业选LSTM做空气质量预测如果你的期末作业要求里同时出现“Python”“可视化分析”“预测”和“源代码”LSTM几乎是综合性价比最高的选择。它不是最简单的模型最简单的可能是线性回归但恰恰因为有超参数可调、有序列依赖可讲、有可解释的预测曲线答辩时能讲的内容远比线性回归丰富。空气质量数据本身就是天然的时间序列PM2.5、PM10、CO、NO2、风速、温度这些指标随时间变化且彼此耦合。LSTM的门控结构正好能捕捉小时级的前后依赖比普通RNN更稳比Transformer更容易在课程项目里跑通。这篇内容就按“数据可视化 → 序列构造 → 模型训练 → 预测评估 → 答辩技巧”的顺序把整份源代码的思路拆开讲清楚。2. 空气质量数据长什么样先做可视化再谈预测2.1 数据集字段与缺失值处理绝大多数公开空气质量数据集比如UCI的Air Quality、中国环境监测总站的历史数据都长这样Date、Time或直接是时间戳随后是PM2.5、PM10、SO2、NO2、CO、O3等浓度列再往后是气象特征TEMP、PRES、DEWP、RAIN、WSPM。很多原始数据里PM2.5会有空值比如传感器维护、长时段断电。做监测数据分析的人都知道不能直接删掉带空值的整行——时间序列删行会破坏时间连续性后面做滑动窗口时会出错。常见做法是先用isnull().sum()统计缺失量再按列选择填充策略。光看总量不够还要看缺失是否集中在某一段如果连续缺失超过窗口长度就应该考虑用前后24小时均值填充否则会制造出虚假的下降尖峰。import pandas as pd import numpy as np df pd.read_csv(air_quality.csv, index_col0, parse_dates[0]) print(df.isnull().sum()) df[PM2.5] df[PM2.5].fillna(df[PM2.5].rolling(24, min_periods1).mean()) df df.fillna(methodffill)这里rolling(24, min_periods1).mean()意思是取当前时刻往前24条记录的平均值min_periods1保证前几个数据点也能有值。ffill则是拿上一个有效值往后兜底避免填充后还残留空值。这样处理既保住了时间连续性又比全局均值填充更能反映局部污染水平的变化趋势。2.2 用Matplotlib/Seaborn画出污染趋势可视化不是为了出一张好看的图而是要回答三个问题第一整体趋势是升还是降是否存在周期性峰值第二有没有明显的季节或日变化规律第三异常点是否与某个气象变量相关。我一般会先画PM2.5的长时间折线图再叠加原始数据的滚动均值这样能把短期噪点和长期趋势分开看。import matplotlib.pyplot as plt fig, ax plt.subplots(figsize(12, 6)) ax.plot(df.index, df[PM2.5], labelPM2.5 raw, alpha0.6) ax.plot(df.index, df[PM2.5].rolling(72).mean(), label72h rolling mean, colorred) ax.set_title(PM2.5 hourly trend) ax.legend() plt.tight_layout() plt.show()这里rolling(72)是72小时的均值适合小时级数据能抹掉昼夜短周期波动让污染过程看得更清楚。如果原始数据是日平均窗口换成7或14。图上如果红色滚动均值线有明显的“山包”说明该城市有持续数日的污染累积过程这正是LSTM需要学习的长依赖模式。2.3 相关性热力图筛选特征把气象和污染物全部丢进LSTM不一定更好尤其当数据量只有几千条时特征过多会让模型学到噪声。可视化阶段就可以用seaborn.heatmap打出相关系数矩阵优先保留与预测目标通常是PM2.5相关性较高、但彼此相关性不高的特征。import seaborn as sns corr df.corr() plt.figure(figsize(10, 8)) sns.heatmap(corr, annotTrue, fmt.2f, cmapcoolwarm) plt.show()看热力图时重点看PM2.5那一列如果PM10和PM2.5的相关系数超过0.9两者几乎共线只保留PM2.5自己作为历史特征即可CO和NO2通常与PM2.5中度相关可以作为辅助输入温度、风速等气象变量的相关性不稳定但往往对污染的扩散条件有非线性影响建议先保留等模型跑起来再看重要性。特征缩放后再做相关性分析没有意义所以这一步必须放在归一化之前。3. 把时间序列改造成LSTM能吃的样本3.1 滑动窗口原理与look_back参数LSTM本身不接受“一整列时间序列”作为输入它接受的是形状为(样本数, 时间步长, 特征数)的三维张量。假设我们有一天的小时级PM2.5数据共24个点想预测下一个小时的PM2.5那么一个样本就是t-23到t这24个时刻的特征矩阵标签是t1时刻的PM2.5。这个窗口长度叫look_back。窗口太小模型学不到日周期窗口太大训练样本数量下降而且不一定提升精度。经验值小时数据用24一天或48两天先跑通再调。def create_sequences(data, look_back24): X, y [], [] for i in range(len(data) - look_back): X.append(data[i:ilook_back]) y.append(data[ilook_back, target_idx]) return np.array(X), np.array(y)这个函数里data必须是经过归一化后的二维数组(总时刻数, 特征数)target_idx是目标列比如PM2.5在特征矩阵中的列索引。循环从0扫到len(data) - look_back - 1每个窗口取i到ilook_back共look_back行标签取窗口结束后下一时刻的目标值。注意这里没有做任何滑窗重叠限制常规做法就是让窗口连续滑动让样本数量最大化。3.2 MinMaxScaler归一化与反归一化LSTM内部使用sigmoid和tanh激活函数输入不归一化会导致梯度爆炸或训练不收敛。空气质量数据里PM2.5的数值范围是0500而温度可能只有-1040如果不缩放PM2.5的数值会主导误差。使用MinMaxScaler把每个特征线性压缩到[0,1]区间是为了让各个特征的量纲一致。这里有三个坑必须对全部数据一起fit而不是先切训练集再分别fit否则测试集的信息在训练阶段就泄漏了。保存scaler对象预测完成后需要用它做inverse_transform才能把结果还原成真实浓度。如果目标列也要反归一化scaler要单独对目标列设置或者把预测结果拼回特征维度再做反变换。from sklearn.preprocessing import MinMaxScaler features df[[PM2.5, PM10, CO, TEMP, WSPM]].values scaler MinMaxScaler() scaled_features scaler.fit_transform(features) target_idx 0 # 假设第一列是PM2.5 X, y create_sequences(scaled_features, look_back24)fit_transform完成的是“先计算每列min和max再执行缩放”两步训练集和测试集必须用同一个scaler。这里的target_idx0对应PM2.5如果你的列顺序不同改成对应的列序号。3.3 训练集/验证集/测试集切分时间序列不能使用train_test_split的默认随机切分否则模型会看到未来的数据导致评估结果虚高。正确做法是按时间顺序切分。一般比例是7:1.5:1.5或6:2:2但要保证最早的训练数据不参与验证集构造。同时注意创建序列函数之后再做切分否则窗口会跨越切分边界。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_sizeval_size], y[train_size:train_sizeval_size] X_test, y_test X[train_sizeval_size:], y[train_sizeval_size:] print(X_train.shape, X_val.shape, X_test.shape) # 输出示例(12345, 24, 5) (2645, 24, 5) (2645, 24, 5)这里X_train.shape[1]是look_backshape[2]是特征数5。如果发现X_train样本数太少比如只有几百可以减小look_back或放宽验证集比例。边界切分虽然简单但要注意验证集的第一条记录可能紧挨着训练集的最后一条这在时间序列里是可接受的——我们验证的是模型的泛化能力而不是严谨的防止信息泄漏。4. 搭建LSTM模型并完成训练与可视化预测4.1 用Keras定义LSTM网络层标准做法是使用Keras的Sequential模型堆叠12层LSTM最后接一个Dense层输出单值。第一层LSTM需要指定return_sequencesTrue这样能输出每个时间步的隐藏状态给第二层最后一层LSTM才默认返回最后一个时间步的输出。为了缓解过拟合两层之间和LSTM内部都可以加Dropout。不过Dropout在RNN里的用法比较特殊Keras会在时间步之间共享dropout mask因此不用自己写变体。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout model Sequential([ LSTM(64, return_sequencesTrue, input_shape(X_train.shape[1], X_train.shape[2])), Dropout(0.2), LSTM(32, return_sequencesFalse), Dropout(0.2), Dense(1) ]) model.summary()这里的参数含义第一层LSTM有64个单元输出每个时间步的隐藏状态第二层32个单元只输出最后一个时间步的结果input_shape的第一个维度是时间步数24第二个维度是特征数5。Dense(1)输出PM2.5的预测值。如果数据量很少少于5000样本建议只保留一层LSTMDropout减少参数量。4.2 编译参数与EarlyStopping模型用Adam优化器是默认选项它对学习率不那么敏感适合课程项目。损失函数用mean_squared_error因为它对较大的预测误差惩罚更重符合污染物浓度的物理意义。训练时设置batch_size64或128epochs先设50100配合EarlyStopping在验证集loss不再下降时提前停止避免白白跑很久。from tensorflow.keras.callbacks import EarlyStopping model.compile(optimizeradam, lossmse, metrics[mae]) early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) history model.fit( X_train, y_train, validation_data(X_val, y_val), epochs100, batch_size128, callbacks[early_stop], verbose1 )patience10表示连续10个epoch验证集loss没有变好就停止。restore_best_weightsTrue很重要——停止后会恢复验证集loss最小的那组权重而不是用最后一轮的权重。不设这个参数模型可能停在过拟合后的状态。训练过程中观察loss和val_loss两条曲线正常情况下降都下降最终val_loss略高于loss如果训练集loss继续下降但验证集loss反弹说明过拟合应该调大Dropout或减少LSTM单元数。4.3 预测结果的RMSE/MAE评估与绘图对比预测完成后最关键的步骤是反归一化。模型输出的是[0,1]区间的值必须用之前的scaler把PM2.5还原为真实浓度。这里有个细节如果你的scaler是对整个5维特征矩阵做的那么预测结果只有一列不能直接inverse_transform单列。常见做法是构造一个临时数组长度等于预测样本数列数等于特征数5把预测值放进目标列其余列用0填充因为反归一化只关系目标列对应的min/max其余位置不影响目标列的还原。pred_scaled model.predict(X_test) def inverse_target(pred_scaled, scaler, target_idx0): temp np.zeros((len(pred_scaled), scaler.scale_.shape[0])) temp[:, target_idx] pred_scaled[:, 0] inv scaler.inverse_transform(temp) return inv[:, target_idx] y_pred inverse_target(pred_scaled, scaler, target_idx0) y_true scaler.inverse_transform( np.concatenate([y_test.reshape(-1,1), np.zeros((len(y_test), features.shape[1]-1))], axis1) )[:, target_idx]这里scaler.scale_是每个特征的缩放比例数组长度等于特征数5。inverse_transform需要输入列数一致才能工作所以用临时数组补零。下面计算RMSE和MAEfrom sklearn.metrics import mean_squared_error, mean_absolute_error import math rmse math.sqrt(mean_squared_error(y_true, y_pred)) mae mean_absolute_error(y_true, y_pred) print(fRMSE: {rmse:.2f}, MAE: {mae:.2f})RMSE的单位与PM2.5浓度一样是μg/m³比如RMSE18意味着平均预测误差在18左右。空气质量等级里PM2.5浓度分档35/75/115如果RMSE在20以内对课程作业来说已经够用。最后画一张测试集对比图把真实值和预测值画在同一个坐标系上为了显示清晰通常只展示最近200个点。plt.figure(figsize(12, 5)) plt.plot(y_true[:200], labelTrue PM2.5, linewidth2) plt.plot(y_pred[:200], labelPredicted PM2.5, linewidth2, linestyle--) plt.legend() plt.title(LSTM prediction on test set (first 200 points)) plt.show()图形上如果预测曲线比真实曲线滞后一拍说明模型倾向于用上一步的值去做平滑这在时序预测里很常见原因是损失函数MSE对“略晚但接近”的预测惩罚不大。出现滞后时可以尝试增加look_back、增加LSTM单元数或者改用一步差分序列作为训练目标。如果预测曲线几乎是斜率为1的直线说明模型已经退化成复制上一个值此时需要检查数据预处理是否泄漏了未来信息。5. 验收技巧让期末大作业从“跑通”到“能答辩”5.1 训练曲线与残差检查期末答辩时老师最常问的是“你的模型有没有过拟合”。所以训练时要把history.history[loss]和val_loss画出来保存在代码里。另外残差分析比看RMSE更能暴露问题——将预测误差(y_true - y_pred)画成时间序列如果残差有明显的周期性模式说明LSTM还没学到某个周期性成分这时候应该增加look_back到48或72。如果残差在某个时段特别大去看那个时间段的原始数据是不是有传感器异常值——有些课程数据里会有负浓度或突然跳零这类毛刺会严重拉高RMSE。5.2 超参数快速调试表把超参数的影响整理成表格答辩时能直接引用来解释你的选择理由超参数常见取值范围偏大时的现象偏小时的现象look_back12, 24, 48, 72训练样本减少训练变慢可能学到过旧模式捕捉不到日周期预测滞后明显LSTM单元数32, 64, 128过拟合训练时间变长欠拟合预测曲线偏平滑Dropout0.1, 0.2, 0.3模型收敛慢可能欠拟合几乎不起正则作用batch_size32, 64, 128内存占用大收敛慢梯度震荡loss不稳定学习率默认Adam的0.001loss震荡收敛极慢几乎不下降实际调试时先固定batch_size128和Dropout0.2只调look_back和LSTM单元数不要同时改多个参数。每次改完记录RMSE和训练耗时形成对比表这就是答辩时的实验数据。5.3 多步预测的差异化选择很多期末大作业要求“预测未来几小时”而不是仅仅测试集拟合。如果预测未来1小时用前面的单步模型直接滑动即可如果必须预测未来24小时直接多步预测会有误差累积。一种折中做法是训练一个多输出模型让最后一层Dense(24)输出接下来24个时刻的PM2.5损失函数用MSE。这种方法误差在短中期6小时内表现尚可超过12小时就会明显偏离。答辩时如果被问“能不能预测未来一天”你可以回答“短期内可用长期需要引入外部气象预报数据”这个回答比硬着头皮说能预测更专业。5.4 代码结构整理的几个提示源代码提交时务必把训练和测试逻辑分离不要在同一个文件里既定义模型又反复做数据可视化。推荐按三个模块组织data_preprocess.py负责读数据、清缺失、做滑窗model_train.py负责构建模型、训练、保存模型权重model.save(lstm_air.h5)visualize.py负责画趋势图、相关性图、预测对比图。另外在requirements.txt里固定主要依赖版本比如tensorflow2.10,2.16、pandas1.5、matplotlib3.6因为Keras API在2.x和3.x有小差别固定大版本能避免运行环境不一致的问题。最后在代码注释里写明每个参数为什么这么设比如look_back24是因为“空气中污染物浓度在一天内受交通和气象影响呈现明显周期变化”这种注释就是答辩时的加分项。本文还有配套的精品资源点击获取
返回列表