简介这份资源是一份面向环保科技从业者、数据分析师与大数据工程师的空气质量分析与预测系统技术文档围绕机器学习与时间序列分析在环境科学中的应用展开适合需要定期发布空气质量报告、开展环境政策研究或制定城市发展规划的政府机构与企业单位参考。压缩包内共1个docx文件约595KB内容涵盖系统需求分析、Django框架下的B/S架构设计、MySQL数据库E-R建模以及斯皮尔曼相关性系数、贝叶斯算法等预测模型的实现细节并配有大量案例研究与技术说明。已有162人学习下载可作为同类项目的参考模板。读者可从中获取从数据采集、清洗预处理到模型训练与部署的完整流程思路理解随机森林、支持向量机等算法在空气质量指标预测中的具体用法并借鉴性能评估与改进方向但需注意结合不同地区实际情况调整参数设置。1. 从一张超标罚单说起空气质量监测与预测系统到底在解决什么去年冬天一个做园区环保信息化的朋友找我说他们装的几台微型空气质量监测站天天报警PM2.5 一超标就推短信结果运维人员跑过去一看现场根本没异味风一吹数值又掉下来了。问题不在传感器坏而在于「监测」和「预测」被混为一谈设备只告诉你此刻是多少却没人告诉你未来两小时会不会持续升高、要不要提前让产线降负荷。这就是基于机器学习的空气质量监测与预测系统要补的那一环——用历史与实时的多污染物、气象数据训练出能滚动输出未来浓度区间的模型把「事后报警」变成「事前调度」。这套系统适合三类人一是做环保物联网平台、手里已经有监测数据的工程师二是想拿一个完整机器学习项目练手的学生和转行者空气质量数据公开、特征清晰、业务闭环完整比很多玩具数据集更接近真实工程三是园区、厂区里负责能耗与排放联动的运维人员。它不追求把预报做到气象台级别而是追求在本地几十米到几公里尺度上比「拍脑袋」和「单点阈值」更靠谱。下面我按自己搭过的一版方案把数据、特征、模型、部署和踩过的坑讲清楚。2. 数据从哪来、特征怎么造空气质量监测的数据管线2.1 监测数据的三个来源与选型取舍做空气质量预测第一步不是选模型而是确定数据源。常见做法有三类国控/省控站点的公开小时数据、自建微型站的分钟级数据、以及气象再分析或本地气象站数据。公开站点数据质量高、时间跨度长但空间分辨率粗一个城市可能只有几个点微型站密度高却容易受湿度、温度漂移影响PM2.5 在湿度大时读数虚高是行业里公认的玄学问题。我的建议是训练阶段用公开站点数据打底保证标签可靠上线阶段用微型站做空间插值补充但必须做湿度校正。选型时重点看三件事时间分辨率是否统一到小时、缺失值比例是否低于 15%、是否有同步的气象字段温度、湿度、风速、风向、气压。如果只有污染物没有气象风速一高浓度就掉模型会学成「风速大就干净」的伪相关换季就翻车。2.2 用 pandas 把多源数据对齐成一张宽表真实数据一定是脏的时间戳时区不一致、整点缺测、单位混用。下面这段是我常用的对齐脚本核心是把污染物和气象按小时外连接到同一张表再做缺失标记。import pandas as pd import numpy as np # 污染物数据time, pm25, pm10, no2, so2, co, o3 poll pd.read_csv(pollutant.csv, parse_dates[time]) # 气象数据time, temp, rh, wind_speed, wind_dir, pressure met pd.read_csv(meteo.csv, parse_dates[time]) # 统一到整点小时避免分钟级抖动干扰 poll[time] poll[time].dt.floor(h) met[time] met[time].dt.floor(h) df pd.merge(poll, met, ontime, howouter).sort_values(time) df df.set_index(time).asfreq(h) # 补齐缺失小时 # 缺失标记告诉模型这一格是补出来的不是真实观测 for col in [pm25, pm10, no2, temp, rh, wind_speed]: df[f{col}_isna] df[col].isna().astype(int) # 线性插值只补短缺口长缺口保留 NaN 交给模型或丢弃 df[pm25] df[pm25].interpolate(methodlinear, limit3) df[temp] df[temp].interpolate(methodlinear, limit3) df df.dropna(subset[pm25]) # 标签缺失的样本不能用于训练 df.to_csv(air_aligned.csv)逻辑说明asfreq(h)保证时间轴连续否则后面做滞后特征会错位limit3是关键参数只补连续 3 小时以内的缺口超过就保留 NaN因为长缺口插值等于编数据。_isna标记列让模型知道哪些值是补的树模型能利用这个信息降低对补值的信任。参数上如果数据是分钟级先聚合到小时再对齐不要直接对分钟做插值否则噪声会被放大。2.3 滞后特征与滚动统计让模型看到「趋势」空气质量有强自相关当前 PM2.5 和过去几小时高度相关所以滞后特征是收益最高的一类特征。我一般会造 1、3、6、12、24 小时滞后再加 6 小时和 24 小时滚动均值、滚动标准差。滚动标准差能刻画「波动剧烈程度」沙尘或烟花时段这个值会突然变大是很好的预警信号。def add_lag_features(df, colpm25, lags(1, 3, 6, 12, 24)): for lag in lags: df[f{col}_lag{lag}] df[col].shift(lag) df[f{col}_roll6_mean] df[col].shift(1).rolling(6).mean() df[f{col}_roll24_std] df[col].shift(1).rolling(24).std() return df df add_lag_features(df) df[target_pm25_next3h] df[pm25].shift(-3) # 预测未来3小时 df df.dropna()注意shift(1)再 rolling是为了避免把当前时刻的值算进滚动窗口否则就是标签泄漏线下指标好看、上线直接崩。预测目标我选未来 3 小时是因为太短没有调度价值太长误差又压不住3 小时是园区调度能接受的窗口。3. 模型怎么选、怎么训从线性回归到梯度提升的落地路径3.1 基线、树模型与序列模型的取舍别一上来就上 LSTM。我踩过的坑是数据量不到两年、特征工程没做透时LSTM 训练慢、调参玄学、线上推理还要维护状态收益还不如 LightGBM。合理的路径是先用线性回归或 Ridge 做基线确认特征方向对不对再用 LightGBM 或 XGBoost 做主模型它们对缺失值、异常值鲁棒训练快特征重要性可解释只有当数据超过三五年、且需要多步长序列输出时再考虑 LSTM 或 Temporal Fusion Transformer。选型判断标准很实在如果运维团队要能看懂「为什么今天报高」树模型的特征重要性直接能解释如果只是追求榜单指标序列模型可能略高但工程成本翻倍。我一般会两个都训用验证集对比差距在 5% 以内就选树模型。3.2 用 LightGBM 训练一个可解释的预测模型下面是我常用的训练脚本重点是时间序列切分不能随机打乱否则未来信息会泄漏到训练集。import lightgbm as lgb from sklearn.metrics import mean_absolute_error features [c for c in df.columns if c not in [pm25, target_pm25_next3h]] X, y df[features], df[target_pm25_next3h] # 时间序列切分前80%训练后20%验证绝不 shuffle split int(len(df) * 0.8) X_train, X_val X.iloc[:split], X.iloc[split:] y_train, y_val y.iloc[:split], y.iloc[split:] model lgb.LGBMRegressor( n_estimators800, learning_rate0.03, num_leaves31, min_child_samples20, subsample0.8, colsample_bytree0.8, objectiveregression_l1 # MAE 对极端值更稳 ) model.fit( X_train, y_train, eval_set[(X_val, y_val)], eval_metricl1, callbacks[lgb.early_stopping(50)] ) pred model.predict(X_val) print(MAE:, mean_absolute_error(y_val, pred))参数说明learning_rate0.03配n_estimators800是慢学稳收敛的组合num_leaves31控制复杂度防止过拟合min_child_samples20保证叶子节点有足够样本。objectiveregression_l1用 MAE 而不是 MSE是因为空气质量数据里偶发重污染极值会拉偏 MSE导致模型整体偏高。early_stopping(50)在验证集 50 轮不降就停省得手动试轮数。3.3 评估指标不能只看 MAEMAE 只告诉你平均差多少但业务关心的是「超标那几小时有没有报准」。我一般会补两个指标一是超标召回率即真实 PM2.5 超过阈值时模型预测也超的比例二是分位数损失看模型对高值的低估程度。如果超标召回率低于 70%说明模型在极端时段偏保守需要给高值样本加权或单独训一个分类器做「是否超标」的二分类再和回归结果融合。import numpy as np thr 75 # 示例阈值按当地标准调整 mask y_val thr recall ((pred thr) mask).sum() / max(mask.sum(), 1) print(超标召回率:, recall)这个召回率才是运维真正看的数字。MAE 降 1 微克但召回率掉 10 个点对业务是负优化。4. 从离线模型到在线服务部署与实时预测的工程细节4.1 特征一致性离线训练和在线推理必须同一套代码上线最常见的翻车是「训练时特征算一套线上算另一套」。比如训练用 pandas rolling线上用 SQL 窗口函数边界处理不一致结果线上预测系统性偏移。我的做法是把特征计算封装成一个函数离线和在线都调它在线时把最近 N 小时数据拼成 DataFrame 传入。def build_features(recent_df): # recent_df 至少包含最近 48 小时按时间升序 df add_lag_features(recent_df.copy()) return df[features].iloc[[-1]] # 只取最新一行做推理 # 在线服务伪代码 latest fetch_last_48h() # 从时序库取数 X_now build_features(latest) y_hat model.predict(X_now)[0]关键是fetch_last_48h必须和训练数据的清洗规则一致同样的单位、同样的缺失处理、同样的时区。任何一处不同模型都会「认不出」输入。4.2 用 FastAPI 暴露预测接口并加缓存在线服务我一般用 FastAPI轻量、异步、好接监控。加一层缓存是因为同一小时内多次请求结果应该一致没必要重复推理。from fastapi import FastAPI import time app FastAPI() cache {ts: 0, value: None} app.get(/predict/pm25) def predict(): now int(time.time() // 3600) if cache[ts] now and cache[value] is not None: return {pm25_next3h: cache[value], cached: True} latest fetch_last_48h() value float(model.predict(build_features(latest))[0]) cache.update(tsnow, valuevalue) return {pm25_next3h: value, cached: False}参数上缓存按整点小时失效和监测数据更新频率对齐。返回值里带cached字段方便排查是实时算的还是缓存命中。接口不要返回裸数组带上时间戳和单位前端和调度系统才不会用错。4.3 监控与回滚模型上线不是终点上线后必须监控三件事输入特征分布是否漂移、预测值与实测值的滚动偏差、接口延迟。我一般会每天算一次过去 24 小时的 MAE如果连续三天比验证集 MAE 高出 50%就触发告警并回滚到上一版模型。漂移检测用简单的 PSI 或均值对比即可不必上复杂框架。回滚要能一键切所以模型文件按版本号存配置文件指向当前版本别把模型路径写死在代码里。5. 避坑与排查空气质量预测里最容易翻车的五件事5.1 现象线下 MAE 很低上线后预测值整体偏高原因训练集和线上数据的缺失值处理不一致线上把缺失填成了 0而 0 在污染物里是「极干净」的强信号模型被带偏。解决统一用同一套清洗函数缺失保留 NaN 或填成训练集均值并带上_isna标记。5.2 现象模型在冬季表现好夏季一塌糊涂原因臭氧在夏季是主导污染物而训练特征里只有 PM2.5 的滞后项没有 O3 和温度交互。解决把 O3、温度、日照时长纳入特征或按季节分别训练子模型。我一般会加一个「月份」或「季节」类别特征让树模型自己分裂。5.3 现象超标时段总是报不出来原因重污染样本占比低回归模型被大量清洁样本主导倾向于预测中间值。解决对高值样本加权或单独训一个超标二分类器两个结果融合。加权时权重别超过 5 倍否则模型会对噪声过拟合。5.4 现象接口偶尔超时日志显示推理很慢原因每次请求都重新读全量历史数据算特征IO 和计算都重。解决把特征计算拆成定时任务每小时预计算好最新特征存起来接口只做推理。预计算和实时计算必须共用同一函数避免不一致。5.5 现象风向特征用了数值编码模型学不出规律原因风向是角度359 度和 1 度实际很近但数值差很大。解决把风向拆成sin和cos两个分量或者转成 16 方位类别。这个坑很隐蔽但改完通常能带来几个点的提升。6. 把预测接进调度阈值联动与一个可复用的验证习惯模型训完只是半成品真正产生价值的是把它接进业务动作。我一般会设两级阈值预测未来 3 小时 PM2.5 超过 75 触发「预警」超过 115 触发「调度」调度动作可以是通知产线降负荷、开启喷淋或调整通风。阈值不要写死在代码里放配置文件因为不同季节、不同园区标准不一样。联动逻辑要加「迟滞」连续两次预测超标才触发避免单次抖动导致频繁调度这就是控制里的防抖。验证习惯上我坚持做「回测 影子运行」两步。回测是把模型在过去半年的数据上滚动预测看每个月的 MAE 和召回率是否稳定影子运行是上线后先只记录预测不触发动作跑两周对比实测确认没有系统性偏差再打开联动。这两步能挡掉大部分「线下好、线上崩」的问题。def decide(pred, prev_pred, cfg): if pred cfg[dispatch_thr] and prev_pred cfg[dispatch_thr]: return dispatch if pred cfg[warn_thr]: return warn return normal这个函数里prev_pred就是迟滞用的上一次预测cfg从配置文件读。别小看这几行它决定了调度系统会不会被噪声折腾到没人信。最后说个我自己的教训我早期总想把模型指标刷到最好后来发现运维只关心「报准的那几次有没有用」。与其花两周把 MAE 从 12 降到 11不如花两天把超标召回率从 65% 提到 80%后者才是这个系统值不值得做的分水岭。希望帮到你。本文还有配套的精品资源点击获取
