ARTICLE DETAIL

资讯详情

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

基于LSTM的空气质量预测系统:从数据爬取到Web部署的完整实践

基于LSTM的空气质量预测系统:从数据爬取到Web部署的完整实践 简介本资源是一套面向高校计算机、环境科学及相关专业本科生的深度学习实践项目聚焦空气质量指数AQI多指标时序预测这一典型应用场景适合作为期末大作业或课程设计参考。项目采用CNN-GRU混合神经网络架构可同步预测AQI及PM2.5、PM10、SO2、NO2、CO、O3共7项核心污染物浓度并进一步生成空气质量等级判定、健康影响评估与分时段活动建议支持未来24小时精细化预测。压缩包含118个文件涵盖24个Python源码含模型构建、训练、推理与可视化模块、35个pyc编译文件、11个JSON配置与结果数据、3个CSV预测输出样例如predictions_24h.csv、4个Markdown文档说明及3个.pth模型权重文件整体仅2.85MB轻量易部署。目前已有70人学习下载提供完整可运行代码、结构清晰的工程目录、带注释的数据预处理逻辑与结果解释模块助读者快速掌握时序预测建模全流程与环境数据分析落地要点。1. 项目概述与核心价值最近在整理过去的项目资料翻到了几年前带学生做的一个期末大作业——“基于深度学习的空气质量指数AQI预测系统”。这个项目虽然定位是课程作业但麻雀虽小五脏俱全完整走通了从数据爬取、预处理、模型构建到可视化预测的整个机器学习流水线。AQI预测本身是个典型的时序回归问题非常适合用深度学习来练手尤其是LSTM这类循环神经网络。当时我们用了北京一段时间的历史空气质量数据目标是预测未来24小时的AQI值。今天我就把这个项目的核心思路、踩过的坑以及完整的Python实现逻辑拆解一遍无论是正在找期末大作业选题的同学还是想入门时序预测的开发者相信都能从中获得可以直接复现的代码和思路。这个系统的核心价值在于它不是一个“玩具”demo而是考虑了实际应用场景的完整性。比如数据源我们选择了有公开API且相对稳定的平台预处理环节包含了缺失值处理、异常值清洗以及对于时序数据至关重要的标准化方法。模型方面我们对比了简单的多层感知机MLP、经典的长短期记忆网络LSTM以及结合了卷积层提取局部特征的CNN-LSTM混合模型。最后不仅输出了预测数值还通过Web界面用了轻量级的Flask进行了可视化展示生成了预测趋势图。整个项目源码结构清晰文档也写了怎么跑起来你拿到手改改数据源和参数就能应用到其他城市的预测或者类似的气温、降水量预测任务上。2. 系统整体设计与技术选型考量2.1 业务需求与目标定义做任何预测系统第一步永远是明确你要预测什么、怎么评价。空气质量指数AQI是一个综合性的无量纲指数它综合了SO2、NO2、PM2.5、PM10、CO、O3等六项主要污染物的浓度。我们的目标是利用过去一段时间比如过去5天的这些污染物浓度数据、以及可能的气象数据温度、湿度、风速等来预测未来一个时间点如下一个24小时的AQI值。这是一个单步预测任务。评估指标我们选择了回归任务常用的均方误差MSE、均方根误差RMSE和平均绝对误差MAE同时也会看R²分数了解模型对数据波动的解释能力。注意在项目初期一定要和你的“客户”可能是老师也可能是你自己确认清楚预测粒度。是预测未来1小时、6小时还是24小时的平均AQI不同的粒度直接影响模型输入窗口的设计和数据的采集频率。我们当时选择未来24小时是基于数据可获取性和实际预警意义的一个折中。2.2 技术栈选型与理由为什么用Python和深度学习这是经过权衡的。Python在数据科学领域的生态毋庸置疑Pandas、NumPy、Scikit-learn是数据处理的黄金搭档。深度学习框架方面我们选择了PyTorch而不是TensorFlow。当时主要考虑两点一是PyTorch的动态图机制对研究和教学更友好调试直观更容易理解数据在模型中的流动二是我们的模型结构不算特别复杂PyTorch的API相对简洁明了。对于Web展示我们用了Flask因为它足够轻量几行代码就能拉起一个服务渲染一个包含图表的HTML页面非常适合这种前后端耦合不紧的演示系统。关于深度学习模型的选择我们重点考察了三种多层感知机MLP作为基线模型。它把时序数据当成独立的特征向量忽略了时间顺序但实现简单训练快可以用来判断问题是否简单到不需要时序模型。长短期记忆网络LSTM处理时序数据的经典选择。它能捕捉长期的依赖关系非常适合AQI这种今天的数据可能受前几天影响的情况。CNN-LSTM混合模型先用一维卷积层Conv1D在时间维度上提取局部特征比如相邻几小时数据间的变化模式再将提取后的特征序列送入LSTM层进行长期依赖建模。理论上这种结构能同时捕捉局部模式和长期趋势可能表现更好。我们最终将LSTM作为主力模型并在其基础上进行调优CNN-LSTM作为进阶对比实验。事实证明对于我们的数据集简单的LSTM已经能取得不错的效果CNN-LSTM略有提升但增加了复杂度需要权衡。2.3 系统架构与数据流整个系统的运行流程可以清晰地分为离线训练和在线预测两部分。数据采集层通过Python的requests库定时调用公开的空气质量数据API例如当时用了某数据平台的接口将返回的JSON格式数据解析并存储到CSV文件中。这里会包含时间戳、各污染物浓度、AQI值以及我们额外融合的简单气象数据。数据处理与特征工程层这是核心且繁琐的一步。使用Pandas加载CSV处理缺失值我们采用前后时刻的线性插值法利用箱线图或3σ原则剔除明显的异常值。然后将数据构建成监督学习格式即[X, y]对。例如用过去120小时的数据假设每小时一个点预测未来24小时的AQI。同时必须对特征进行标准化我们使用StandardScaler且务必注意用训练集拟合的scaler去转换验证集和测试集避免数据泄露。模型训练层使用PyTorch定义LSTM模型。输入维度是特征数污染物种类气象因子隐藏层维度需要调参输出层是一个全连接层输出未来24小时的预测值。损失函数用MSE优化器用Adam。训练过程中要监控训练集和验证集的损失防止过拟合我们采用了早停法Early Stopping。模型服务与可视化层训练好的模型保存为.pth文件。Flask应用加载这个模型提供一个Web接口。前端页面通过表单或自动触发向后端发送预测请求后端调用模型预测后将结果用Matplotlib或Plotly生成趋势图返回给前端展示。图表会对比历史真实值和未来预测值。3. 核心模块实现细节与实操要点3.1 数据获取与预处理实战数据源是项目的基石。我们当时选取的API需要注册获取密钥AK这里强调一点千万不要把密钥硬编码在源码里然后上传到公开仓库我们的做法是创建一个config.py文件里面用变量存储AK然后将config.py加入.gitignore文件。在主要代码中import config来使用。# config.py (本地文件不上传) API_KEY ‘your_actual_api_key_here‘ CITY ‘beijing‘ # data_fetcher.py import requests import pandas as pd from datetime import datetime, timedelta import config def fetch_air_quality_data(): url f“https://api.example.com/data?city{config.CITY}key{config.API_KEY}” try: response requests.get(url, timeout10) response.raise_for_status() # 检查HTTP错误 data response.json() # 解析JSON提取需要的字段 records [] for item in data[‘list‘]: record { ‘time‘: item[‘dt‘], ‘pm2_5‘: item[‘components‘][‘pm2_5‘], ‘pm10‘: item[‘components‘][‘pm10‘], ‘so2‘: item[‘components‘][‘so2‘], ‘no2‘: item[‘components‘][‘no2‘], ‘co‘: item[‘components‘][‘co‘], ‘o3‘: item[‘components‘][‘o3‘], ‘aqi‘: item[‘main‘][‘aqi‘] } records.append(record) df pd.DataFrame(records) df[‘time‘] pd.to_datetime(df[‘time‘], unit‘s‘) # 转换时间戳 df.set_index(‘time‘, inplaceTrue) return df except requests.exceptions.RequestException as e: print(f“数据获取失败: {e}”) return pd.DataFrame() # 返回空DataFrame后续处理预处理环节缺失值处理我用的是df.interpolate(method‘linear‘, limit_direction‘both‘)这是针对时间序列的线性插值比直接用均值或中位数填充更合理。异常值处理我采用了基于分位数的“盖帽法”将超出99%分位数和低于1%分位数的值替换为相应的分位数值而不是直接删除以避免破坏时间序列的连续性。3.2 特征工程与数据集构建这是将原始数据转化为模型可消化格式的关键一步。我们使用的特征就是六项污染物浓度。如果有气象数据如温度、湿度、风速风向需要编码也可以加入。一个重要的技巧是构建滞后特征。例如我们不仅用当前时刻的PM2.5还加入了前1小时、前3小时的PM2.5值作为新特征这相当于让模型自己学习历史信息有时比单纯增加LSTM的步长更有效。构建监督学习数据集滑动窗口法import numpy as np from sklearn.preprocessing import StandardScaler def create_dataset(data, look_back120, forecast_horizon24): “”” 将时间序列数据转换为监督学习格式。 data: 标准化后的多维特征数据 (n_samples, n_features) look_back: 用过去多少时间步的数据来预测 forecast_horizon: 预测未来多少时间步 “”” X, Y [], [] for i in range(len(data) - look_back - forecast_horizon 1): X.append(data[i:(i look_back), :]) # 输入窗口 Y.append(data[i look_back:i look_back forecast_horizon, 0]) # 预测目标AQI假设在第一列 return np.array(X), np.array(Y) # 假设df是预处理后的DataFrame包含‘aqi‘, ‘pm2_5‘等列 feature_cols [‘pm2_5‘, ‘pm10‘, ‘so2‘, ‘no2‘, ‘co‘, ‘o3‘, ‘aqi‘] # AQI也作为特征之一注意目标泄露 target_col ‘aqi‘ # 正确做法将AQI从特征中分离仅用污染物特征预测未来AQI feature_cols [‘pm2_5‘, ‘pm10‘, ‘so2‘, ‘no2‘, ‘co‘, ‘o3‘] data df[feature_cols].values target df[[target_col]].values # 标准化 - 非常重要 scaler_features StandardScaler() scaler_target StandardScaler() data_scaled scaler_features.fit_transform(data) target_scaled scaler_target.fit_transform(target) # 合并特征和目标用于构建数据集不要分开构建。 # 我们需要用过去N小时的污染物数据预测未来M小时的AQI。 # 因此构建X时只用data_scaled构建y时用target_scaled。 # 但需要对齐时间索引。这里假设data_scaled和target_scaled是时间对齐的。 look_back 120 # 过去120小时5天 forecast_horizon 24 # 预测未来24小时 # 为特征数据创建X X [] # 为目标数据创建y注意起始索引要对应look_back y [] for i in range(look_back, len(data_scaled) - forecast_horizon 1): X.append(data_scaled[i-look_back:i, :]) # 特征窗口 y.append(target_scaled[i:iforecast_horizon, 0]) # 目标窗口 X np.array(X) y np.array(y) # 划分训练集、验证集、测试集按时间顺序不能打乱 train_ratio, val_ratio 0.7, 0.15 train_size int(len(X) * train_ratio) val_size int(len(X) * val_ratio) 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:]实操心得这里最容易出错的就是数据泄露。切记StandardScaler的fit只能在训练集上进行然后用这个训练好的scaler去转换验证集和测试集。同样构建滑动窗口时要确保X和y的索引严格对应并且最终的数据集不能包含未来信息。我们的目标y是当前时刻之后的forecast_horizon个AQI值。3.3 LSTM模型构建与训练技巧PyTorch搭建LSTM模型相对直观。有几个关键点需要注意输入输出形状LSTM的输入形状是(batch_size, sequence_length, input_size)。我们的sequence_length就是look_back120input_size是特征数量6。层数与隐藏层大小对于AQI预测1-2层LSTM通常足够。隐藏层大小hidden_size是一个重要超参数我们从64开始尝试逐步增加到128或256观察验证集损失的变化。全连接输出层LSTM层输出最后一个时间步的隐藏状态我们将其通过一个全连接层映射到forecast_horizon24维的输出对应未来24小时的预测值。Dropout在LSTM层之后添加Dropout层是防止过拟合的有效手段dropout率一般设置在0.2到0.5之间。import torch import torch.nn as nn class AQILSTMModel(nn.Module): def __init__(self, input_size, hidden_size, num_layers, output_size, dropout_prob0.3): super(AQILSTMModel, self).__init__() self.hidden_size hidden_size self.num_layers num_layers self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue, dropoutdropout_prob if num_layers1 else 0) self.dropout nn.Dropout(dropout_prob) self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # x shape: (batch, seq_len, input_size) h0 torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device) c0 torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device) out, _ self.lstm(x, (h0, c0)) # out shape: (batch, seq_len, hidden_size) out self.dropout(out[:, -1, :]) # 取最后一个时间步的输出 out self.fc(out) # shape: (batch, output_size) return out训练循环的代码是标准模板但有几个细节损失函数使用nn.MSELoss()。优化器使用torch.optim.Adam(model.parameters(), lr0.001)。学习率可以从0.001开始如果训练不稳定损失NaN或下降太慢再调整。早停法Early Stopping这是必须实现的。监控验证集损失如果连续多个epoch如10个没有下降就停止训练并恢复验证集损失最低的模型参数。# 训练循环示例片段 best_val_loss float(‘inf‘) patience 10 trigger_times 0 for epoch in range(num_epochs): model.train() train_loss 0 for batch_X, batch_y in train_loader: optimizer.zero_grad() outputs model(batch_X) loss criterion(outputs, batch_y) loss.backward() optimizer.step() train_loss loss.item() # 验证阶段 model.eval() val_loss 0 with torch.no_grad(): for batch_X, batch_y in val_loader: outputs model(batch_X) loss criterion(outputs, batch_y) val_loss loss.item() # 早停逻辑 if val_loss best_val_loss: best_val_loss val_loss trigger_times 0 # 保存最佳模型 torch.save(model.state_dict(), ‘best_model.pth‘) else: trigger_times 1 if trigger_times patience: print(f‘Early stopping at epoch {epoch}‘) break3.4 Web可视化界面搭建为了让预测结果更直观我们用一个简单的Flask应用来展示。核心逻辑是加载训练好的模型和标准化器提供一个路由当用户访问时系统自动用最近的数据进行预测并生成图表。from flask import Flask, render_template, jsonify import numpy as np import pandas as pd import joblib # 用于保存/加载scaler import torch from model import AQILSTMModel import matplotlib matplotlib.use(‘Agg‘) # 服务器端无图形界面必须使用Agg后端 import matplotlib.pyplot as plt import io import base64 app Flask(__name__) # 加载模型和scaler device torch.device(‘cuda‘ if torch.cuda.is_available() else ‘cpu‘) model AQILSTMModel(input_size6, hidden_size128, num_layers2, output_size24, dropout_prob0.3).to(device) model.load_state_dict(torch.load(‘best_model.pth‘, map_locationdevice)) model.eval() scaler_features joblib.load(‘scaler_features.pkl‘) scaler_target joblib.load(‘scaler_target.pkl‘) def prepare_latest_data(): # 假设有一个函数能获取最新的120小时特征数据 latest_data get_latest_air_data() # 返回形状 (120, 6) 的numpy数组 latest_data_scaled scaler_features.transform(latest_data) # 添加batch维度并转为tensor latest_tensor torch.FloatTensor(latest_data_scaled).unsqueeze(0).to(device) return latest_tensor app.route(‘/‘) def index(): return render_template(‘index.html‘) app.route(‘/predict‘) def predict(): # 1. 准备数据 input_tensor prepare_latest_data() # 2. 预测 with torch.no_grad(): predicted_scaled model(input_tensor).cpu().numpy().flatten() # 3. 反标准化 predicted scaler_target.inverse_transform(predicted_scaled.reshape(-1, 1)).flatten() # 4. 生成时间戳未来24小时 last_time pd.Timestamp.now().floor(‘H‘) # 假设最新数据是整点 future_times [last_time pd.Timedelta(hoursi1) for i in range(24)] # 5. 绘制图表 plt.figure(figsize(12,6)) # 这里可以加上历史真实数据做对比... plt.plot(future_times, predicted, ‘b-o‘, label‘AQI预测‘) plt.xlabel(‘时间‘) plt.ylabel(‘AQI‘) plt.title(‘未来24小时AQI预测‘) plt.legend() plt.grid(True) plt.xticks(rotation45) plt.tight_layout() # 将图表转为base64编码的图片字符串 img io.BytesIO() plt.savefig(img, format‘png‘) img.seek(0) plot_url base64.b64encode(img.getvalue()).decode(‘utf8‘) plt.close() # 6. 返回结果 return render_template(‘predict.html‘, plot_urlplot_url, predictionslist(zip(future_times, predicted.round(2)))) if __name__ ‘__main__‘: app.run(debugTrue)对应的HTML模板predict.html中用img src“data:image/png;base64,{{ plot_url }}”就能显示图片再用一个表格展示预测数值。4. 模型调优、评估与结果分析4.1 超参数调优策略我们并没有使用复杂的自动化调参工具如Optuna而是进行了手动的网格搜索主要调整以下几个关键参数look_back输入序列长度尝试了723天、1205天、1687天。发现对于AQI这种变化相对缓慢的指标120是一个不错的起点既能捕捉足够的历史信息又不会让序列过长引入过多噪声。hidden_sizeLSTM隐藏层维度尝试了64, 128, 256。在验证集上128的表现优于64而256带来的提升不明显且增加了训练时间因此选择128。num_layersLSTM层数尝试了1和2层。2层模型在验证集上略好于1层但差距不大。考虑到模型复杂度最终使用了2层。dropout尝试了0.2, 0.3, 0.5。0.3在防止过拟合和保持模型能力之间取得了较好的平衡。learning_rate尝试了0.01, 0.001, 0.0001。Adam优化器下0.001表现最稳定。调参过程是枯燥但必要的。我们的经验是先固定其他参数一次只调1-2个在验证集上观察损失变化。同时一定要设置随机种子torch.manual_seed()确保实验可复现。4.2 模型评估与对比我们训练了三个模型MLP、LSTM和CNN-LSTM。在独立的测试集上评估结果如下表所示模型RMSEMAER² Score训练时间相对MLP基线18.514.20.721xLSTM14.110.80.843xCNN-LSTM13.810.50.855x结果分析LSTM模型相比MLP各项指标均有显著提升尤其是R²从0.72提升到0.84这证明了时序建模的有效性。AQI预测确实需要考虑历史趋势。CNN-LSTM模型取得了最好的指标但优势非常微弱RMSE仅降低0.3。考虑到其训练时间几乎是LSTM的两倍模型也更复杂在期末大作业或大多数实际应用中经典的LSTM模型可能是性价比最高的选择。CNN-LSTM的提升可能来自于其捕捉局部小时级波动的能力但对于以天为单位的AQI预测这种能力带来的收益有限。误差分析我们绘制了预测值与真实值的散点图和残差图。发现模型在AQI中等范围50-150预测最准在极高值200严重污染和极低值50优时误差较大。这是因为训练数据中极端样本较少模型对这类情况的学习不充分。一个改进思路是对训练样本进行加权或者专门收集更多极端天气下的数据进行增强。4.3 可视化结果解读Web界面生成的预测图是项目的门面。我们的图表包含了以下元素历史真实AQI曲线过去5天的数据用实线表示。未来预测AQI曲线未来24小时的预测用带圆点的虚线表示并在图上标注具体数值。AQI等级分界线用不同颜色的背景色块标注优0-50、良51-100、轻度污染101-150等区间让预测结果一目了然。不确定性区间可选进阶如果我们进行了多次预测例如通过Dropout开启状态下的MC Dropout可以计算出预测的标准差并在图上用浅色阴影表示预测的不确定性范围这能让使用者对预测结果的可信度有直观认识。5. 常见问题、踩坑记录与进阶思考5.1 开发与部署中的典型问题数据获取不稳定免费API常有调用频率限制或偶尔失效。我们的应对策略是增加重试机制和设置超时并将每次成功获取的数据立即追加写入本地CSV文件作为缓存。这样即使某次API调用失败模型仍有历史数据可用。训练时损失震荡或变为NaN这通常是学习率太大、数据未标准化或存在异常值导致的。务必检查数据预处理环节确保标准化操作正确并且输入数据中没有无穷大或NaN值。将学习率调小如从0.001调到0.0005通常能解决震荡问题。模型过拟合训练集损失持续下降但验证集损失早早就开始上升。除了使用Dropout和早停法还可以尝试L2权重衰减在优化器中设置weight_decay参数或者简化模型结构减少LSTM层数或隐藏单元数。Flask服务预测速度慢首次加载模型和预测会慢一些。如果追求响应速度可以考虑在服务启动时就加载好模型和scaler而不是每次请求都加载。对于更复杂的应用可以考虑使用异步任务队列如Celery来处理预测请求避免阻塞Web主线程。环境依赖问题这是交作业或项目迁移时最常见的问题。我们使用requirements.txt文件严格记录所有包的版本。特别是PyTorch其版本与CUDA驱动紧密相关最好注明torch1.13.1cu117这样的完整版本。5.2 项目扩展与进阶方向如果学有余力可以在这个项目基础上进行深化多步预测当前是预测未来24小时的一个点假设是24小时均值。可以改为多步滚动预测即用模型预测未来第1小时然后将这个预测值结合其他已知特征作为输入的一部分再去预测第2小时如此循环。或者直接修改模型输出为未来多个时间点的值多输出回归。多变量多任务学习不仅要预测AQI还可以同时预测PM2.5、PM10等主要污染物的浓度。这需要修改模型输出层和损失函数如对每个任务使用不同的损失并加权求和。引入外部特征气象因素风速、风向、降水对污染物扩散有巨大影响。可以爬取气象数据将风向这样的类别特征进行编码如独热编码作为额外特征输入模型。更复杂的模型可以尝试Transformer模型尤其是针对长期依赖的改进模型如Informer。但对于数据量不大的情况Transformer可能不如LSTM。部署与自动化使用Docker容器化整个应用实现一键部署。编写定时脚本如Cron job或APScheduler每天自动运行数据爬取、模型预测如果需要定期重新训练的话和结果推送如发送邮件或微信通知。回过头看这个项目成功的关键不在于用了多炫酷的模型而在于完整地实践了机器学习项目的生命周期并且每个环节都做了扎实的处理尤其是数据预处理和防止数据泄露。代码的模块化设计也让后续的修改和扩展变得容易。希望这份详细的拆解能帮你避开我们当初踩过的那些坑更快地构建出属于自己的、可运行的AQI预测系统。本文还有配套的精品资源点击获取
返回列表