ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

LSTM空气质量指数预测实战:从数据构造到模型调优的完整指南

2026/10/2 14:43:20 拓冰建站 浏览量
LSTM空气质量指数预测实战:从数据构造到模型调优的完整指南 简介这份资源是面向高校学生的Python期末大作业与课程设计参考项目核心任务是基于LSTM神经网络完成空气质量指数预测适合具备Python基础、正在准备毕设或课程设计的学习者使用。压缩包共收录2000个文件以1989个csv数据文件为主体涵盖北京地区多时段空气质量与气象记录另有4个py源码文件承载LSTM模型搭建、训练与预测逻辑并配有xml配置、md说明文档及pyc缓存文件整体约29.33MB结构清晰、便于按模块查阅。目前已有222人学习下载说明该方案在同类作业中具有一定参考热度。项目代码注释较为完整新手也能理解数据预处理、特征构造、模型训练与结果评估的完整链路下载后简单部署即可运行可直接作为期末大作业、课程设计或毕业设计的实现基础帮助读者快速搭建可演示、可讲解的预测系统。1. 从一份期末大作业说起LSTM 做空气质量指数预测到底难在哪每年到了期末周总有一批同学在群里问同一件事有没有一份能跑通的 LSTM 空气质量指数预测源码。表面上看这就是个时间序列回归任务输入历史污染物浓度和气象数据输出未来某天的 AQI 数值用 PyTorch 搭个 LSTM 层接全连接层就能出结果。但我见过太多人卡在同一个地方代码跑起来了loss 也降了预测曲线却像一条被压扁的直线完全跟不上真实 AQI 的波动。问题不在 LSTM 本身而在于大多数人把时间序列预测当成了普通的监督学习。AQI 数据有强周期性、突变性和滞后效应直接切窗口、归一化、丢进网络模型学到的往往只是均值。这份大作业真正要解决的是如何用 LSTM 捕捉污染物浓度的时序依赖让预测结果在趋势和峰值上都能对得上。适合正在做课程设计、想拿高分、或者第一次接触 LSTM 时间序列预测的读者跟着走能复现一套完整的训练和评估流程。2. 数据准备与特征工程AQI 预测的输入到底怎么构造2.1 空气质量数据集长什么样从哪来常见的公开数据源是各地环保部门发布的逐小时或逐日空气质量数据字段一般包括 PM2.5、PM10、SO2、NO2、CO、O3 六项污染物浓度外加温度、湿度、风速、气压等气象指标目标列是 AQI 或首要污染物。我一般会先用 pandas 把原始 CSV 读进来看一眼时间跨度、缺失比例和数值范围。如果数据里 AQI 列是空的需要根据污染物浓度按国标公式反推这一步很多源码包会直接跳过导致后面预测目标本身就是错的。拿到数据后第一件事不是急着建模而是画图。把 AQI 按时间轴画出来观察有没有明显的日周期、周周期和季节趋势。如果曲线在冬季明显抬升、夏季回落说明季节性特征很强后面构造特征时要把月份或季节编码进去。这一步用 matplotlib 几行就能完成但决定了你后续窗口切分的方向。import pandas as pd import matplotlib.pyplot as plt # 读取原始数据假设时间列名为 datetimeAQI 为目标列 df pd.read_csv(air_quality.csv, parse_dates[datetime]) df df.sort_values(datetime).reset_index(dropTrue) # 查看缺失情况 print(df.isnull().sum()) # 按时间画 AQI 曲线观察周期和突变 plt.figure(figsize(14, 4)) plt.plot(df[datetime], df[AQI], linewidth0.8) plt.xlabel(Time) plt.ylabel(AQI) plt.title(AQI Time Series) plt.tight_layout() plt.show()这段代码的作用是先建立对数据的直觉。parse_dates把时间列转成 datetime 类型sort_values保证时序不乱。缺失值统计出来后如果某一列缺失超过 30%我一般直接弃用低于 10% 的用前向填充或线性插值补上。注意不要用全局均值填充那会破坏时序连续性LSTM 对输入的顺序很敏感。2.2 滑动窗口构造与归一化三个参数决定模型上限LSTM 的输入是三维张量形状为(样本数, 时间步长, 特征数)。时间步长就是你要用过去多少个小时或多少天的数据来预测下一时刻。这个参数没有固定答案AQI 预测里常见取值是 24、48、72。步长太短模型看不到完整周期步长太长早期信息被遗忘训练也慢。我一般会先用 24 试一版再对比 48 和 72 的验证集误差。归一化是另一个翻车高发区。很多人对整个数据集做 MinMax 归一化然后切训练集和测试集这会造成数据泄漏。正确做法是先用训练集拟合归一化器再变换测试集。如果特征里有风速这种量纲差异大的列标准化比归一化更稳。import numpy as np from sklearn.preprocessing import MinMaxScaler def create_sequences(data, target_idx, window_size): 滑动窗口切分返回 X 和 y X, y [], [] for i in range(len(data) - window_size): X.append(data[i:i window_size]) y.append(data[i window_size, target_idx]) return np.array(X), np.array(y) # 选取特征列和目标列 feature_cols [PM2.5, PM10, SO2, NO2, CO, O3, TEMP, HUMI] target_col AQI data df[feature_cols [target_col]].values # 按 8:2 切分先切再归一化避免泄漏 split int(len(data) * 0.8) train_data, test_data data[:split], data[split:] scaler MinMaxScaler() train_scaled scaler.fit_transform(train_data) test_scaled scaler.transform(test_data) window_size 24 target_idx len(feature_cols) # AQI 在最后一列 X_train, y_train create_sequences(train_scaled, target_idx, window_size) X_test, y_test create_sequences(test_scaled, target_idx, window_size) print(X_train.shape, y_train.shape) # (样本数, 24, 9)create_sequences里window_size控制回看长度target_idx指定预测目标在特征矩阵中的位置。注意测试集的归一化必须用训练集的 scaler否则模型在测试阶段看到的数值分布和训练时不一致误差会虚高。这一步做完建议把X_train的 shape 打印出来确认常见错误是特征列选多了或少了导致输入维度和网络定义对不上。3. 搭一个能跑通的 LSTM 模型层数、隐藏单元和训练循环3.1 PyTorch LSTM 网络结构的最小实现PyTorch 的nn.LSTM接受(batch, seq_len, input_size)的输入输出包括每个时间步的隐藏状态和最后一个时间步的细胞状态。做回归预测时通常取最后一个时间步的输出接全连接层。网络结构不需要太深一层或两层 LSTM 加一个 Linear 层就够用。隐藏单元数从 32 到 128 都有人用我一般从 64 起步根据验证集 loss 再调。import torch import torch.nn as nn class AQILSTM(nn.Module): def __init__(self, input_size, hidden_size64, num_layers2, dropout0.2): super(AQILSTM, self).__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout ) self.fc nn.Linear(hidden_size, 1) def forward(self, x): # x: (batch, seq_len, input_size) out, _ self.lstm(x) # 取最后一个时间步的输出 out out[:, -1, :] return self.fc(out).squeeze(-1) model AQILSTM(input_sizeX_train.shape[2]) print(model)batch_firstTrue让输入的第一维是 batch符合大多数人的习惯。num_layers2时dropout才会生效单层 LSTM 加 dropout 没有意义。squeeze(-1)把输出从(batch, 1)压成(batch,)和标签维度对齐。如果训练时报维度错误先检查input_size是否等于特征数再检查标签有没有多一维。3.2 训练循环与损失函数选择MSE 不是唯一答案AQI 预测的损失函数常用 MSE 或 MAE。MSE 对大误差惩罚重适合希望模型抓住峰值的场景MAE 对异常值更鲁棒但梯度在零点附近不稳定。我一般先用 MSE 跑一版如果发现模型对高 AQI 样本预测偏低换成 HuberLoss 会好一些。优化器用 Adam学习率 1e-3 起步配合ReduceLROnPlateau在验证 loss 不降时衰减。from torch.utils.data import TensorDataset, DataLoader # 转成 Tensor X_train_t torch.tensor(X_train, dtypetorch.float32) y_train_t torch.tensor(y_train, dtypetorch.float32) X_test_t torch.tensor(X_test, dtypetorch.float32) y_test_t torch.tensor(y_test, dtypetorch.float32) train_loader DataLoader(TensorDataset(X_train_t, y_train_t), batch_size64, shuffleTrue) test_loader DataLoader(TensorDataset(X_test_t, y_test_t), batch_size64) criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience5, factor0.5) epochs 100 for epoch in range(epochs): model.train() train_loss 0 for xb, yb in train_loader: optimizer.zero_grad() pred model(xb) loss criterion(pred, yb) loss.backward() optimizer.step() train_loss loss.item() * xb.size(0) train_loss / len(train_loader.dataset) # 验证 model.eval() val_loss 0 with torch.no_grad(): for xb, yb in test_loader: pred model(xb) val_loss criterion(pred, yb).item() * xb.size(0) val_loss / len(test_loader.dataset) scheduler.step(val_loss) if (epoch 1) % 10 0: print(fEpoch {epoch1}, Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f})shuffleTrue只在训练集上用测试集保持顺序以便后续按时间对比预测曲线。ReduceLROnPlateau的patience5表示验证 loss 连续 5 个 epoch 不降就衰减学习率。如果训练 loss 一直降但验证 loss 反弹说明过拟合可以加 dropout 或减小隐藏单元数。注意model.eval()和torch.no_grad()要成对出现否则验证阶段会更新梯度显存也会爆。4. 预测结果反归一化与评估怎么判断模型是真的学到了4.1 反归一化与真实曲线对比模型输出的是归一化后的 AQI必须用训练集的 scaler 反变换回原始量纲才能和真实值比较。反归一化时要注意scaler 是对所有列拟合的AQI 在最后一列所以要用scaler.data_min_[-1]和scaler.data_max_[-1]手动还原或者构造一个只含 AQI 列的占位矩阵再逆变换。def inverse_aqi(scaled_values, scaler, target_idx): 只反归一化 AQI 列 dummy np.zeros((len(scaled_values), scaler.n_features_in_)) dummy[:, target_idx] scaled_values return scaler.inverse_transform(dummy)[:, target_idx] model.eval() with torch.no_grad(): pred_test model(X_test_t).numpy() pred_real inverse_aqi(pred_test, scaler, target_idx) y_real inverse_aqi(y_test, scaler, target_idx) # 画对比曲线 plt.figure(figsize(14, 4)) plt.plot(y_real[:200], labelTrue AQI) plt.plot(pred_real[:200], labelPredicted AQI) plt.legend() plt.title(AQI Prediction vs Ground Truth) plt.show()这段代码的关键是inverse_aqi函数它构造了一个和原始特征维度相同的零矩阵只把预测值填进 AQI 列再逆变换。如果直接对预测值做逆变换维度对不上会报错。画图时只取前 200 个点避免曲线太密看不清。如果预测曲线明显滞后于真实曲线说明窗口步长偏大或模型没学到突变特征可以尝试减小window_size或加入差分特征。4.2 评估指标RMSE、MAE 和 R² 各自说明什么单看 loss 不够要算 RMSE、MAE 和 R²。RMSE 对大误差敏感能反映模型在峰值处的表现MAE 是平均绝对误差更直观R² 表示模型解释了多少方差AQI 预测里能到 0.7 以上就算不错。如果 R² 很低但 RMSE 也不大说明数据本身波动小模型只学到了均值。from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score rmse np.sqrt(mean_squared_error(y_real, pred_real)) mae mean_absolute_error(y_real, pred_real) r2 r2_score(y_real, pred_real) print(fRMSE: {rmse:.2f}, MAE: {mae:.2f}, R2: {r2:.4f})这三个指标要一起看。RMSE 和 MAE 差距大说明存在个别大误差样本可能是极端污染事件没被模型捕捉到。R² 为负说明模型还不如直接预测均值这时候要回头检查数据泄漏、归一化或窗口构造是否有问题。我一般会把误差最大的几个时间点单独拎出来看往往对应节假日、沙尘或降雨突变这些场景需要在特征里加入天气预警或日期标记。5. 避坑与排查LSTM 空气质量预测最常见的五个翻车点5.1 现象loss 降到很低但预测曲线是直线原因通常是归一化方式不对或窗口内信息不足。如果对所有特征做了全局归一化AQI 的波动被压缩到很小范围模型输出接近均值就能让 MSE 很低。解决方法是改用训练集拟合 scaler并检查 AQI 列归一化后的方差是否过小。另一个可能是window_size太大模型只记住了长期均值把步长降到 12 或 24 再试。5.2 现象训练集 loss 正常测试集误差突然翻倍这是典型的数据泄漏或分布偏移。检查切分时有没有打乱顺序时间序列必须按时间切不能随机切。如果测试集包含训练集之后很久的数据污染物排放结构可能已经变化模型外推能力不足。解决办法是改用滚动预测每次用最近一段历史预测下一步而不是一次性预测整个测试集。5.3 现象模型对高 AQI 样本预测普遍偏低MSE 对大误差惩罚重但高 AQI 样本数量少模型倾向于牺牲这些样本降低整体 loss。可以换 HuberLoss 或对高 AQI 样本加权。另一个做法是在特征里加入 AQI 的滞后项和差分项让模型更容易捕捉上升趋势。如果数据里高 AQI 集中在冬季把月份做 one-hot 编码也能缓解。5.4 现象训练过程中 loss 变成 NaN常见原因是学习率太大或输入里有异常值。先检查数据里有没有 inf 或极大值用np.isfinite过滤。然后把学习率降到 1e-4 再跑。如果用了 LSTM 加 BatchNorm注意 BatchNorm 在时间序列上容易不稳定建议换成 LayerNorm 或直接不用。梯度裁剪也是常用手段torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)加在loss.backward()之后。5.5 现象换一台机器跑结果完全不一样随机种子没固定。PyTorch 的权重初始化、DataLoader 的 shuffle 都会引入随机性。在代码开头加上torch.manual_seed(42)、np.random.seed(42)如果用了 CUDA 再加torch.cuda.manual_seed_all(42)。另外DataLoader的shuffleTrue本身也有随机性复现时可以把 shuffle 关掉或固定 generator。注意即使固定了种子不同 PyTorch 版本之间也可能有细微差异记录版本号是好习惯。6. 让预测再准一点的三个进阶技巧第一个技巧是加入差分特征。AQI 序列往往不平稳直接预测原始值效果一般。我一般会额外构造一列AQI_diff AQI[t] - AQI[t-1]让模型同时学习原始值和变化量。预测时先把差分还原再叠加到上一时刻的 AQI 上。这个做法在峰值附近提升明显因为模型对突变更敏感了。第二个技巧是用双向 LSTM 做离线分析。如果任务不要求实时预测只是做期末报告里的历史拟合nn.LSTM换成nn.LSTM(bidirectionalTrue)能同时利用过去和未来信息R² 通常能涨几个点。但要注意双向 LSTM 不能用于实时滚动预测因为预测时刻的未来数据不可知。报告里要写清楚这个边界否则答辩时容易被问住。第三个技巧是集成多个窗口步长的预测结果。分别用 12、24、48 训练三个模型把预测值取平均或加权平均。不同步长的模型关注的时序尺度不同集成后对趋势和峰值的兼顾更好。加权系数可以用验证集上的 RMSE 倒数来定简单但有效。# 多窗口集成示例 preds [] for ws in [12, 24, 48]: X_tr, y_tr create_sequences(train_scaled, target_idx, ws) X_te, y_te create_sequences(test_scaled, target_idx, ws) # 此处省略模型训练假设已得到 pred_ws preds.append(pred_ws) final_pred np.mean(preds, axis0)集成时要注意不同窗口步长对应的测试集样本数不同需要对齐时间索引。我一般取最大步长对应的那段时间作为公共评估区间避免样本数不一致导致平均出错。最后说一个我自己的习惯每次跑完实验把超参数、随机种子、验证集指标和预测曲线截图存到一个单独文件夹里命名带上日期。期末大作业往往要反复调参没有记录的话三天前跑出的最好结果就找不回来了。这个习惯帮我省过很多后悔药。希望帮到你。本文还有配套的精品资源点击获取