简介这份资源是面向环境科学、数据挖掘与机器学习方向学习者及开发者的城市空气质量分析与预测系统完整项目包适合课程设计、毕业设计或技术练手场景。项目围绕网络爬虫抓取AQI等监测数据串联数据清洗与标准化、关联规则与聚类分析、时间序列特征工程、多模型预测对比、交叉验证与误差评估并配套可视化图表与前后端分离的系统架构覆盖从数据获取到实时更新反馈的完整链路。压缩包共416个文件约11.78MB以161个svg图表、44个vue前端组件、43个py脚本、40个pyc编译文件为主辅以jpg与png图片、js与css样式资源以及bat启动脚本、sql建表语句和md说明文档目录结构清晰便于按模块查阅与二次开发。目前已有189人学习下载可帮助读者快速理解空气质量预测系统的设计思路、模型选型与工程落地方式。1. 空气质量预测系统从数据挖掘到落地预测的完整路径很多做环境数据分析的同行都遇到过这样的尴尬手头攒了三五年的监测站点数据PM2.5、PM10、SO2、NO2、CO、O3 六项指标加气象五参数一应俱全可真要建一个能用的预测系统往往卡在“数据清洗完不知道下一步该干嘛”上。基于数据挖掘的城市空气质量分析与预测系统设计核心要解决的就是这条链路把原始监测数据变成可建模的特征矩阵再选合适的算法训练出能滚动预测未来 24 小时 AQI 的模型最后封装成可调用的服务。这套东西适合环境监测站的技术人员、做智慧城市数据中台的开发者以及想用真实时序数据练手的数据挖掘学习者。它不追求发论文级别的精度追求的是从 CSV 到预测接口这条路径能跑通、能复现、能解释。2. 数据层监测数据怎么清洗才能喂给模型2.1 空气质量数据的四类脏数据与处理策略城市空气质量监测数据看着规整实际拿到手会发现至少四类问题。第一类是缺失值常见于设备校准或断电时段表现为某小时六项指标全为 NULL 或 -999 占位符。第二类是异常值比如 PM2.5 突然出现 9999 这种量级多半是传感器故障而非真实污染。第三类是时间戳不连续整点数据缺了几段直接按行拼接会导致时序错位。第四类是量纲不统一不同站点上报的 CO 单位可能是 mg/m³ 也可能是 μg/m³。处理策略上我一般按这个顺序走先把 -999 和明显超物理极限的值PM2.5 1000、CO 50置为 NaN再用时间序列插值补缺。插值方法选线性插值还是样条插值取决于缺失跨度——连续缺 1 到 3 小时用线性插值足够缺半天以上建议用前后同星期同时刻的均值填充因为污染物浓度有强周期性。import pandas as pd import numpy as np # 读取原始监测数据假设列包含 datetime, PM2.5, PM10, SO2, NO2, CO, O3 df pd.read_csv(air_quality_raw.csv, parse_dates[datetime]) df df.set_index(datetime).sort_index() # 第一步把 -999 和超物理极限值替换为 NaN pollutants [PM2.5, PM10, SO2, NO2, CO, O3] limits {PM2.5: 1000, PM10: 1500, SO2: 2000, NO2: 1000, CO: 50, O3: 1000} for col in pollutants: df[col] df[col].replace(-999, np.nan) df.loc[df[col] limits[col], col] np.nan # 第二步短缺口线性插值长缺口用同星期同时刻均值 df[pollutants] df[pollutants].interpolate(methodlinear, limit3) for col in pollutants: weekly_mean df[col].groupby([df.index.dayofweek, df.index.hour]).transform(mean) df[col] df[col].fillna(weekly_mean) # 第三步检查时间连续性重采样到整点 df df.resample(1H).mean() print(df[pollutants].isna().sum())这段代码的逻辑分三层先做异常值屏蔽再做分级插值最后统一时间频率。参数上limit3控制线性插值最多补 3 个连续缺失点超过就交给周期性均值。resample(1H)保证后续建模时时间步长一致避免因为原始数据有半小时粒度导致特征错位。跑完看isna().sum()如果还有剩余缺失说明该站点数据质量太差建议直接剔除该站点而不是硬填。2.2 特征工程从六项污染物到可建模矩阵原始六项污染物直接扔给模型也能跑但效果通常差一截。原因在于空气质量的核心影响因素不只是污染物自身浓度还包括气象条件、时间周期和滞后效应。我一般会构造四组特征。第一组是气象特征温度、湿度、风速、气压、风向。风向要做正弦余弦分解否则 350° 和 10° 在数值上差 340 但实际方向接近。第二组是时间特征小时、星期、月份以及是否节假日。第三组是滞后特征当前时刻前 1 小时、前 3 小时、前 24 小时的 AQI 值。第四组是滚动统计过去 6 小时和 24 小时的均值、最大值、标准差。# 风向正弦余弦分解 df[wind_sin] np.sin(np.radians(df[wind_dir])) df[wind_cos] np.cos(np.radians(df[wind_dir])) # 时间特征 df[hour] df.index.hour df[dayofweek] df.index.dayofweek df[month] df.index.month # 滞后特征前1h、前3h、前24h的AQI for lag in [1, 3, 24]: df[fAQI_lag_{lag}] df[AQI].shift(lag) # 滚动统计过去6h和24h的均值、最大值、标准差 for window in [6, 24]: df[fAQI_roll_mean_{window}] df[AQI].rolling(window).mean() df[fAQI_roll_max_{window}] df[AQI].rolling(window).max() df[fAQI_roll_std_{window}] df[AQI].rolling(window).std() # 删除因滞后和滚动产生的空值行 df df.dropna() print(df.shape)这里的关键参数是滞后步长和滚动窗口。滞后 1 小时捕捉短期趋势滞后 24 小时捕捉日周期滚动 6 小时反映半天内的累积效应。注意shift和rolling都会在序列头部产生 NaN必须dropna后再建模否则训练集里混入空值会导致模型直接报错。特征矩阵的列数一般在 25 到 35 之间具体取决于气象参数有多少项。3. 模型层选什么算法做空气质量预测3.1 传统时序模型与树模型的适用边界空气质量预测的算法选型绕不开两个方向传统时序模型ARIMA、SARIMA和机器学习模型随机森林、XGBoost、LightGBM。我自己的经验是如果只有单站点、单指标、数据量在两年以内SARIMA 够用且可解释性强但如果是多站点、多指标、还要做特征重要性分析树模型明显更合适。SARIMA 的优势在于它显式建模了趋势和季节性参数 (p,d,q)(P,D,Q,s) 有明确的统计含义。缺点是它很难融入外部变量比如气象数据硬塞进去效果不稳定。树模型则天然支持多特征输入能自动捕捉非线性关系和特征交互而且训练完可以输出特征重要性方便解释“为什么今天预测 AQI 偏高”。我一般会两个都跑一遍做 baseline 对比。如果树模型的 RMSE 比 SARIMA 低 15% 以上就选树模型如果差距在 5% 以内考虑到 SARIMA 的部署更轻量我会倾向 SARIMA。这个阈值不是绝对的取决于你对可解释性和部署成本的要求。3.2 用 LightGBM 训练 AQI 预测模型的完整流程LightGBM 在空气质量预测这个场景下是我最常用的选择原因是训练速度快、内存占用低、对缺失值有一定容忍度而且支持自定义损失函数。下面是从数据划分到模型训练再到评估的完整代码。import lightgbm as lgb from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import mean_squared_error, mean_absolute_error import numpy as np # 特征列和目标列 feature_cols [c for c in df.columns if c ! AQI] X df[feature_cols] y df[AQI] # 时序交叉验证不能用随机划分否则会数据泄露 tscv TimeSeriesSplit(n_splits5) scores [] for train_idx, val_idx in tscv.split(X): X_train, X_val X.iloc[train_idx], X.iloc[val_idx] y_train, y_val y.iloc[train_idx], y.iloc[val_idx] model lgb.LGBMRegressor( n_estimators800, learning_rate0.05, max_depth7, num_leaves63, subsample0.8, colsample_bytree0.8, min_child_samples20, reg_alpha0.1, reg_lambda0.1, random_state42 ) model.fit(X_train, y_train, eval_set[(X_val, y_val)], eval_metricrmse, callbacks[lgb.early_stopping(50), lgb.log_evaluation(100)]) pred model.predict(X_val) rmse np.sqrt(mean_squared_error(y_val, pred)) mae mean_absolute_error(y_val, pred) scores.append((rmse, mae)) print(fFold RMSE: {rmse:.2f}, MAE: {mae:.2f}) print(fAverage RMSE: {np.mean([s[0] for s in scores]):.2f})这段代码有几个关键点。第一TimeSeriesSplit是必须的空气质量数据有时序依赖用train_test_split随机划分会把未来信息泄露到训练集导致评估结果虚高。第二early_stopping(50)表示验证集 RMSE 连续 50 轮不下降就停止训练防止过拟合。第三num_leaves63和max_depth7是控制模型复杂度的核心参数叶子数太多容易记住噪声太少则欠拟合。第四subsample0.8和colsample_bytree0.8引入随机性提升泛化能力。训练完之后用model.feature_importances_看哪些特征贡献最大。通常 AQI_lag_1、AQI_roll_mean_6、温度、风速会排在前列。如果发现某个特征重要性异常高但业务上说不通要回头检查是不是构造特征时引入了未来信息。3.3 模型评估RMSE 之外还要看什么RMSE 和 MAE 是基础指标但只看这两个容易漏掉关键问题。我还会看三个东西分位数误差、污染等级命中率、以及残差的自相关性。分位数误差是指模型在真实 AQI 高值段和低值段的误差分布。如果低值段误差小、高值段误差大说明模型对重污染事件的预测能力不足这在空气质量预测里是致命伤因为重污染天才是管控重点。污染等级命中率是把连续 AQI 离散化成优、良、轻度污染、中度污染、重度污染五档看模型预测的档位和真实档位一致的比例。残差自相关用 Durbin-Watson 检验如果残差存在自相关说明模型没捕捉到某些时序结构需要加滞后特征或换模型。from statsmodels.stats.stattools import durbin_watson # 假设 pred 是验证集预测值y_val 是真实值 residuals y_val - pred dw durbin_watson(residuals) print(fDurbin-Watson: {dw:.3f}) # 接近2表示无自相关 # 污染等级命中率 def aqi_level(aqi): if aqi 50: return 0 elif aqi 100: return 1 elif aqi 150: return 2 elif aqi 200: return 3 else: return 4 true_level y_val.apply(aqi_level) pred_level pd.Series(pred, indexy_val.index).apply(aqi_level) hit_rate (true_level pred_level).mean() print(f污染等级命中率: {hit_rate:.2%})Durbin-Watson 值在 1.5 到 2.5 之间算正常低于 1.5 说明残差正相关模型欠拟合。污染等级命中率一般能做到 75% 到 85%如果低于 70%要么特征不够要么模型复杂度不对。4. 避坑与排查空气质量预测系统常见的五个翻车点4.1 数据泄露滞后特征用错了时间窗口现象模型在验证集上 RMSE 低到 5 以下上线后预测误差直接翻三倍。原因构造滞后特征时用了shift(-1)而不是shift(1)把未来一小时的 AQI 当成了当前特征。解决所有滞后特征必须用正数 shift滚动统计必须用rolling后接shift(1)确保当前时刻只能看到历史数据。检查方法是打印特征矩阵的前几行人工核对每个特征的时间戳是否早于目标时间戳。4.2 站点混用不同城市的数据直接合并训练现象模型在 A 城市表现尚可在 B 城市完全失效。原因不同城市的污染源结构、气象条件、甚至监测设备型号都不同直接把多个城市的数据混在一起训练模型学到的是平均模式对单个城市都不够准。解决要么按城市分别建模要么在特征里加入城市编码作为类别特征让模型自己学城市间的差异。我一般倾向后者因为数据量更充足但前提是城市数量不超过 10 个否则类别特征维度太高。4.3 缺失值填充过度把噪声当信号现象填充完缺失值后模型在缺失段附近的预测误差明显偏高。原因用了过于复杂的插值方法比如样条插值把缺失段填得过于平滑引入了虚假的规律。解决缺失值填充要保守短缺口用线性插值长缺口用周期性均值填充后加一个“是否填充”的布尔特征让模型知道哪些点是补出来的。这个布尔特征在特征重要性里通常排不进前十但能帮助模型在填充段降低置信度。4.4 评估指标单一RMSE 低但重污染漏报现象整体 RMSE 看起来不错但重污染天的预测值普遍偏低。原因AQI 分布是右偏的大部分样本集中在 50 到 150 之间模型为了降低整体误差会倾向于预测中间值对高值段的拟合不足。解决训练时对高 AQI 样本加权或者用分位数损失函数替代 MSE。LightGBM 支持objectivequantile和alpha0.9专门优化 90 分位数的预测精度。4.5 部署后漂移模型半年后精度下降现象模型刚上线时 RMSE 在 20 左右半年后涨到 35。原因空气质量的影响因素在变化比如新增了工业排放源、道路施工、或者季节性气象模式偏移导致训练数据的分布和线上数据不一致。解决建立定期重训练机制每月用最近一年的数据重新训练一次同时监控线上预测残差的分布如果连续一周残差均值偏离 0 超过阈值触发告警并人工介入检查。5. 从模型到服务把预测封装成可调用的接口5.1 用 FastAPI 包装模型并暴露预测端点模型训练完只是半成品要让它产生价值得封装成接口。我一般用 FastAPI原因是它自带 OpenAPI 文档、支持异步、部署轻量。下面是一个最小可用的预测服务代码。from fastapi import FastAPI from pydantic import BaseModel import joblib import numpy as np import pandas as pd app FastAPI() model joblib.load(lgbm_aqi_model.pkl) feature_cols joblib.load(feature_cols.pkl) class PredictRequest(BaseModel): features: dict # 键为特征名值为数值 app.post(/predict) def predict(req: PredictRequest): # 按训练时的特征顺序组装输入 input_df pd.DataFrame([req.features])[feature_cols] pred model.predict(input_df)[0] return {aqi_prediction: round(float(pred), 2)} app.get(/health) def health(): return {status: ok}这段代码的关键在于feature_cols的保存和加载。训练时特征列的顺序必须和预测时一致否则 LightGBM 会按位置匹配特征导致预测结果完全错误。我一般用joblib把特征列名和模型一起保存预测时先按列名重排再输入。PredictRequest用字典接收特征方便调用方灵活传参但要在文档里写清楚哪些特征是必填的。5.2 滚动预测用预测值喂给下一时刻的滞后特征实际业务中往往需要预测未来 24 小时的 AQI而不是只预测下一小时。这就涉及滚动预测用当前时刻的预测值作为下一时刻的滞后特征输入。做法是维护一个滑动窗口每次预测完把结果追加到窗口末尾再构造下一时刻的特征。def rolling_forecast(model, initial_features, steps24): initial_features: 包含当前时刻所有特征的字典 history initial_features.copy() predictions [] for _ in range(steps): input_df pd.DataFrame([history])[feature_cols] pred model.predict(input_df)[0] predictions.append(pred) # 更新滞后特征把预测值填入下一时刻的 lag_1 history[AQI_lag_1] pred # 更新滚动统计简化处理实际需维护完整窗口 history[AQI_roll_mean_6] np.mean( [history.get(fAQI_lag_{i}, pred) for i in range(1, 7)] ) return predictions滚动预测的误差会逐步累积24 小时后的预测值参考价值有限。我的经验是前 6 小时预测比较可靠6 到 12 小时看趋势12 小时以上只看大致范围。如果业务方要求 24 小时精确预测建议用多模型策略短时用 LightGBM长时用 SARIMA 或简单的周期性均值兜底。5.3 模型监控上线后怎么知道它还准不准模型上线不是终点得持续监控。我一般会记录每次预测的输入特征、预测值和时间戳等真实 AQI 出来后计算残差按天聚合看残差均值和标准差的变化趋势。如果残差均值连续三天偏离 0 超过 10 个 AQI 单位或者残差标准差突然翻倍就触发重训练。监控指标不用多三个就够日均残差、残差标准差、污染等级命中率。前两个反映整体偏差和波动第三个反映对重污染事件的捕捉能力。这三个指标按周出报表比看单次预测误差有意义得多。这套系统从数据清洗到模型服务核心链路大概两周能跑通但调参和特征迭代会持续更久。我自己的习惯是先把 baseline 跑出来哪怕 RMSE 只有 30先上线看真实反馈再根据监控数据逐步优化特征和模型。最怕的是一直在离线调参调了三个月还没上线最后业务方失去耐心。希望帮到你。本文还有配套的精品资源点击获取