简介:面向环保数据分析初学者及相关从业者的一份机器学习项目资料,以空气质量指数(AQI)为对象,从环境监测数据出发,完整演示了数据分析与预测建模的流程,包括数据清洗、缺失值与异常值处理、特征标准化、基于pandas的探索性数据分析,以及多种可视化图表绘制。压缩包共33个文件,大小约4.23MB,内含1个CSV原始数据文件、2个Jupyter Notebook分析代码、24张反映分析过程与结果的PNG图表、2个HTML交互式地图页面和Markdown说明文档,整体目录结构清晰,便于对照学习。项目采用Python及scikit-learn等工具,应用线性回归、随机森林等算法构建模型,结合交叉验证进行参数调优,并对比不同算法的预测性能;同时利用matplotlib等库输出可视化结果,通过散点图、相关系数图等形式展示变量关系,帮助直观理解数据分布、特征关系与模型误差。目前已有196人学习/下载,适合需要完整案例参考、快速上手AQI预测项目的读者,也可作为课程设计或入门实践的辅助资料。 选择这个“机器学习-03-AQI分析与预测”项目来写,主要是因为后台收到不少读者私信,说前面两篇偏理论,希望看到一个完整的、从数据到模型的实战案例。正好手头有整理好的空气质量数据集和一套完整的建模代码,就借这篇博文把整个流程走一遍。先说明一下,这个项目是我自己实际建模过程的重现,代码和数据都会完整放出来,你可以直接运行复现,更适合刚学完机器学习基础、想动手做第一个完整项目的读者。
1. 项目背景与数据认知
1.1 预测任务的本质
先想清楚一个问题:AQI预测到底是一个什么类型的机器学习任务?
答案是典型的回归问题。我们手里有一批历史污染物浓度数据和对应的气象条件,要预测的标签是AQI数值或者某一种污染物浓度。这种问题在环境监测领域非常常见,也是很多高校机器学习课程的标准作业题。但作业归作业,真把一个预测系统做成能用的东西,要考虑的点就多了。
我先说一下数据集的基本情况。这份数据采集自某个城市国控监测站点,时间跨度差不多一年,包含了PM2.5、PM10、SO2、NO2、O3、CO六项常规污染物的小时浓度值,以及温度、湿度、气压、风速等气象参数。原始数据大概有一万条左右,每条记录都带时间戳。文件格式是CSV,处理起来很方便。
1.2 数据特征的时间属性
这里有个容易被新手忽略的关键点:这组数据自带时间属性,所以在处理时不能像处理普通表格数据那样随便打乱。
空气污染物浓度有很强的时序依赖,今天下午的臭氧浓度高,往往意味着前一天中午的光化学反应已经开始了。如果不考虑时间结构,直接随机切分训练集和测试集,会造成数据泄露——同一个时间段的内容既出现在训练集又出现在测试集里,模型评估结果会虚高,真正上线部署时立刻现原形。
我当时采用的策略是:按时间顺序做切分,前80%作为训练集,后20%作为测试集。这样模拟的是“用过去预测未来”的真实场景,评估结果才有参考价值。
2. 数据探索与预处理细节
2.1 缺失值处理与异常值识别
先把数据读进来,用Pandas快速看了一眼概况。原始数据中,SO2和CO这两列存在少量缺失,占比大概在5%以下,原因是部分监测仪器在特定时段进行了校准维护。对于时序数据,我们不能简单地把缺失行删掉,因为时间序列的连续性很重要。
我选择的方法是前向填充(ffill),用缺失时刻之前最近的一条有效值来补。这个方法对这个数据集是合理的,因为污染物浓度在短时间内变化幅度有限,前向填充不会引入太大的误差。不过要注意一点:如果缺失区间太长(比如超过12小时),用它补出来的数据可信度就比较低,这种情况建议直接删除这部分记录。
异常值方面的处理我相对克制。AQI数据里有一些看起来“异常”的高浓度值,比如某天某个站点PM2.5一度冲到400以上,看起来像异常值,但这在冬季污染过程中可能完全真实。如果一刀切用3σ法则把它们清掉,等于把数据里最有信息量的极端污染事件给抹掉了,模型学到的东西反而失真。
2.2 相关性分析与特征选择
预处理完成后,我先做了特征选择。计算所有特征与AQI的Pearson相关系数,排序后结果很有意思:
| 特征 | 与AQI的相关系数 | 初步判断 |
|---|---|---|
| PM2.5 | 0.92 | 强相关,核心特征 |
| PM10 | 0.87 | 强相关,核心特征 |
| 湿度 | -0.43 | 中等负相关,保留 |
| 温度 | -0.28 | 弱负相关,保留 |
| 风速 | -0.31 | 弱负相关,保留 |
| SO2 | 0.56 | 中等正相关,保留 |
| CO | 0.48 | 中等正相关,保留 |
从这个相关系数表格能看到两个信息。第一,PM2.5和PM10是AQI的最主要贡献因子,这符合我国《环境空气质量指数(AQI)技术规定》中分指数计算的基本逻辑。第二,气象条件里湿度对AQI影响最大,湿度高往往伴随静稳天气,污染物不容易扩散,这个在后面的特征工程里可以进一步利用。
我把相关系数绝对值低于0.1的特征去掉,保留了浓度和气象相关的核心字段,总共8个特征进入模型。
3. 建模过程与参数实验记录
3.1 特征工程:不只是把数据丢给模型
很多人做机器学习项目,拿到数据就直接标准化,然后开始跑模型,忽略了特征本身的信息价值。在这个AQI项目里,我做了两组额外的特征工程,对最终模型效果提升非常明显。
第一是构造温度湿度交互特征。空气污染和气象条件不是简单线性关系,湿度高但温度低时,污染物累积效应更强,这实际上是气象学里的“逆温层”现象。我新增了一个特征,用温度乘以湿度的值来捕捉这种交互影响。第二是构造滞后特征,把前1小时PM2.5浓度作为特征放入当前时刻的样本中。污染物浓度有很强的自相关性,这体现的是大气扩散的物理惯性,滞后特征能把这种惯性传递给模型。
3.2 多模型对比与调参记录
整个项目我测试了三个模型:线性回归、随机森林、XGBoost。分别说一下实测表现和调参重点。
线性回归是最基础的对比基线。从结果看训练集R2在0.81左右,测试集掉到0.75,有明显的欠拟合。AQI和污染物浓度之间的关系里包含了很多非线性因素,线性模型的上限就在那里,它适合作为基准线,但不适合直接上线使用。
随机森林的效果比线性回归好了一截。测试集R2能够稳定在0.87左右。我把随机森林的参数网格搜索范围设定为:n_estimators从50到300,max_depth从5到20,min_samples_split从2到10。实测最优参数是n_estimators=200,max_depth=12,min_samples_split=4。这里有个很实用的经验:随机森林对参数不敏感,你只要把n_estimators调到足够大(200以上),max_depth不要超过20避免过拟合,其他参数用默认值就能拿到还不错的分数。
XGBoost是最终采用的主力模型。经过五折交叉验证,最优参数组合为:
params = { 'learning_rate': 0.05, # 学习率,调低以减轻过拟合 'max_depth': 6, # 树深,6-8之间比较安全 'n_estimators': 800, # 树的数量,配合早停机制使用 'subsample': 0.8, # 每棵树的样本采样比例 'colsample_bytree': 0.8, # 每棵树的特征采样比例 'reg_alpha': 0.1, # L1正则,防止特征过多导致过拟合 'reg_lambda': 1.0 # L2正则,默认值就可以 }这些参数不是一次性试出来的,我调的路径是:先定学习率(0.05),再配合早停找n_estimators的最优值(在验证集上连续50轮不提升就停),最后再微调max_depth和subsample。记住一个经验:调参永远优先调对结果影响最大的那个参数,也就是学习率,不要一上来就动所有旋钮。
3.3 训练集与测试集的切分逻辑
我在前面已经提过,这个项目的切分逻辑是严格按照时间顺序。但训练时还要考虑一个具体操作:因为时间相关性存在,如果你用随机打乱的K折交叉验证来调参,会让模型看到“未来的信息”,导致交叉验证的分数虚高。
所以我做交叉验证时,使用的是按时间顺序切分的TimeSeriesSplit而不是默认的KFold。别小看这个细节,很多项目线上效果差,不是模型不行,而是数据切分的时候作弊了。训练的时候偷了懒,最后测试的时候还是要还的。
4. 模型评估与结果解读
4.1 评估指标的选择
回归任务的标准评估指标我这里用了三个:R2决定系数、均方根误差RMSE,以及平均绝对误差MAE。在空气质量预测场景里,我最关注的是RMSE,因为它对“大幅偏离真实值”的预测会给更重的惩罚,AQI预测的灾难性错误往往就是预测值和真实值差距特别大的情况。
最终模型的测试集表现如下:
| 模型 | R2(测试集) | RMSE(测试集) | MAE(测试集) |
|---|---|---|---|
| 线性回归 | 0.75 | 24.3 | 16.8 |
| 随机森林 | 0.87 | 18.1 | 11.2 |
| XGBoost | 0.91 | 15.2 | 9.4 |
RMSE为15.2意味着平均而言,模型预测的AQI与真实AQI相差15左右。对AQI这种0到500区间的指数来说,15的误差基本可以满足日常参考需求。不过这里我要坦白一点:RMSE在AQI比较低(50以下)和比较高(300以上)的两个区间表现是不均匀的,这个在残差分析里能看得很清楚。
4.2 残差分析与模型失效场景
我画了预测误差随时间变化的残差图,发现一个规律:在重污染过程(AQI大于200)的上升期和下降期,模型预测值系统性偏低,特别是在污染快速累积的头几个小时,预测值跟真实值能相差30到50个点。原因也好理解,模型学到的更多是“稳态”条件下污染物浓度和气象要素的关系,对突发性的污染积累过程响应偏慢,这本质上是因为模型输入里没有包含污染源排放变化的实时信息。
另外还注意到一个跨季节的问题:模型对夏天的预测误差整体小于冬天。这提示我,如果能拿到更长时间跨度的数据(比如两年以上),按季节训练多个子模型或者把季节编码作为一个特征喂进去,效果应该能更好。这也是后续迭代的方向。
5. 完整代码实现与数据可用性说明
5.1 项目目录结构
完整项目文件我整理了一下,包含以下文件:
AQI_Prediction/ ├── data/ │ └── air_quality.csv # 原始数据 ├── notebooks/ │ └── EDA.ipynb # 探索性分析 ├── src/ │ ├── preprocess.py # 数据预处理 │ ├── train.py # 模型训练与评估 │ └── config.yaml # 配置文件 └── results/ ├── predictions.csv # 测试集预测结果 └── model_metrics.json # 模型指标记录5.2 核心代码逻辑
模型的训练部分核心代码我用的是scikit-learn和xgboost两个库,代码量不大,核心逻辑就几行:
import pandas as pd from sklearn.model_selection import TimeSeriesSplit from sklearn.preprocessing import StandardScaler from xgboost import XGBRegressor from sklearn.metrics import r2_score, mean_squared_error # 读取数据并做预处理(略) # ... # 按时间顺序切分,前80%训练,后20%测试 train_size = int(len(df) * 0.8) train, test = df.iloc[:train_size], df.iloc[train_size:] # 分离特征和标签 feature_cols = ['PM2.5', 'PM10', 'SO2', 'NO2', 'CO', '湿度', '温度', '交互特征', '前1小时PM2.5'] target_col = 'AQI' X_train, y_train = train[feature_cols], train[target_col] X_test, y_test = test[feature_cols], test[target_col] # 标准化特征 scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) # XGBoost模型 model = XGBRegressor( learning_rate=0.05, max_depth=6, n_estimators=800, subsample=0.8, colsample_bytree=0.8 ) model.fit(X_train_scaled, y_train) # 预测与评估 y_pred = model.predict(X_test_scaled) test_r2 = r2_score(y_test, y_pred) test_rmse = mean_squared_error(y_test, y_pred, squared=False) print(f"Test R2: {test_r2:.3f}, RMSE: {test_rmse:.2f}")这段代码基本上可以直接运行,前提是环境里装好了pandas、scikit-learn、xgboost这几个库。数据文件air_quality.csv是公开可获取的空气质量监测历史数据,如果你需要实际执行,可以从公开的环境监测数据平台下载。代码中训练和预测的全过程大约在30分钟内就能跑完,不会让你等太长时间。
6. 常见问题与排查技巧
6.1 预测结果始终偏低或偏高
模型预测的AQI整体系统性偏差,最常见的原因有两个。第一是数据标准化时用了错误的scaler,在训练集上fit后忘了在测试集上transform,直接对测试集重新fit了。这是sklearn使用中最高频的错误,度量衡都变了,预测结果自然歪掉。第二个原因是目标变量没做处理,AQI数值分布在低值区域非常密集,中高值很稀疏,模型天然偏向学习出现频率高的样本区间,所以你可以尝试对目标变量取对数,让分布更均匀。
6.2 交叉验证分数很高但测试集一塌糊涂
如果训练集和验证集效果都好,测试集立刻翻车,九成是时间泄露问题。我在3.3节已经强调过,一定要用时间顺序切分的TimeSeriesSplit,同时特征工程里不能使用未来信息。比如有些人不小心用整段数据的均值和标准差去做标准化,这等于让训练过程偷看了测试集的统计信息,在真实场景中这种数据时不可能预先得到的。
6.3 模型训练速度慢到无法忍受
如果数据量上来了(比如多站点多年小时级数据,超过几十万条),用默认参数的XGBoost会跑得非常痛苦。实际处理时可以采用hist作为tree_method,这种分箱加速算法在sklearn的GBDT里叫histogram-based gradient boosting,在XGBoost里就是hist,训练速度可以快5到8倍,精度损失非常小。如果仍然慢,建议先用10%的数据做一次冒烟测试,确认流程没问题,再全量训练。
注意:当你刚开始跑通整个人工智能项目流程之后,想深入做AQI预测这个方向,很大概率会遇到“未知城市、未知时间段的预测”问题。本质上这种跨域预测在环境科学领域是公认难题。我的建议是优先收集目标城市至少一年以上的监测数据再训练,不要指望用一个城市的模型直接迁移到另一个城市。
7. 改进方向与个人心得
7.1 从单站点到区域预测的扩展思路
现在这个项目只用了单个站点的数据做预测,实际业务中一个城市里有十几个国控站点,如果把站点ID作为类别特征、把站点经纬度也加进去一起建模,预测的对象就可以从“这一个站点未来几小时AQI”变成“整个城市未来几小时AQI空间分布”。这个扩展思路在环境监测平台、智慧城市建设中很有价值。
从方法角度看,空间相关性建模可以尝试图神经网络,不过现阶段不是必须的。先用最简单的one-hot编码加线性模型把多站点扩展起来,你就已经能做出远远超过演示水平的成果了,图神经网络那些重武器留到业务确实有需要的时候再上,不要为了炫技而过度设计。
7.2 踩坑后的几个重要收获
这个项目做完,有几个实操层面的心得体会值得单独拎出来讲。
第一,特征工程的作用被很多人低估。最终模型从随机森林的0.87到XGBoost的0.91,这4个百分点的提升里,至少有一半功劳来自新构造的滞后特征和温湿度交互特征,而不是模型换成了XGBoost。你去调模型参数、换模型架构,不如先仔细审视一下特征里是否真的包括了物理世界的关键信息。污染物有物理惯性、有积累效应,模型不会自己学到这些,你要想办法把规律喂给它。
第二,不要只盯着R2和RMSE,把预测误差按AQI等级分段统计分析。我后来把这个项目的结果做了一次拆分评估,发现在空气质量等级“优”(AQI 0到50)时模型表现很好,但“轻度污染”到“中度污染”这一段的误差最大。原因也很简单,这个区间正好是累积过程的中间态,大气物理化学反应最复杂,边界特征不清晰。如果你能把“分段误差”当作默认评估方式来看待问题,你对模型能力的认知会准确非常多,而不是只看到一个漂亮的总分就觉得万事大吉。
第三,环境监测数据一旦碰上雨雪天气,仪表读数会受影响,这些记录在原始数据里看起来很异常,但它们不是设备故障,而是真实的自然现象。处理这些数据时,一定要结合气象记录一起判断再决定去留,别一看到异常值就从数据里把它们删掉。真实业务里,保守处理异常值比激进清洗要可靠得多。
这个项目做完,我个人的体会是,AQI预测这类环境数据挖掘问题,技术门槛并不高,难的是把气象知识、污染物化机理和数据建模真正融合到同一个框架里思考。后续如果有时间,我打算用这个数据继续做一个多步预测,把未来24小时逐小时的AQI变化趋势预测出来,那会更贴近预警产品的真实需求。
本文还有配套的精品资源,点击获取