ARTICLE DETAIL

资讯详情

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

空气质量预测实战:数据挖掘全链路与模型调优指南

空气质量预测实战:数据挖掘全链路与模型调优指南 简介这份资源面向数据挖掘与机器学习入门及进阶学习者围绕空气质量预测这一典型回归/分类场景提供从数据到建模的完整实战方案。包内共3个文件以csv数据集、ipynb代码笔记本和html分析报告为主压缩包约1.39MB体积轻便便于本地快速运行与复现。数据集包含空气质量相关特征字段代码部分完整呈现数据清洗、特征工程、模型训练与评估流程html报告则直观展示分析结果与可视化图表方便对照理解每一步建模思路。目前已有129人学习下载适合课程设计、竞赛练手或自学机器学习算法的读者参考。通过这份资料读者可掌握空气质量预测的完整建模链路理解特征处理与模型调参要点并借助现成代码快速迁移到其他环境数据预测任务中。1. 空气质量预测这件事为什么值得用数据挖掘重做一遍空气质量预测模型听起来像是环保部门的专属课题但真正做过一轮你会发现它其实是数据挖掘和机器学习里少有的「全链路练手场」数据脏、特征多、时序强、评价指标还特别讲究。很多做机器学习入门的朋友拿鸢尾花、泰坦尼克号练完手转头就想找一个既有真实业务价值、又能把特征工程到模型调参全流程跑通的项目空气质量预测正好卡在这个位置上。它要处理缺失值、异常值、时间滑窗、气象耦合还要面对「预测明天下午三点的 PM2.5」这种带时间约束的回归任务比单纯分类任务更接近工业场景。这套「数据集代码」的组合核心价值不在于模型本身有多深而在于它把数据挖掘的完整链条摊开给你看原始监测数据怎么清洗、气象因子怎么拼接、滞后特征怎么构造、树模型和神经网络怎么选、评估为什么不能只看 R²。适合已经会写 Python、跑过 sklearn 基础流程但没独立做过端到端时序回归的从业者。如果你正卡在「模型能跑但结果不可信」的阶段这个方向能帮你把每个环节的坑踩明白。2. 先搞清楚预测目标空气质量预测到底在预测什么2.1 预测对象与时间粒度的选择动手之前必须先定死一件事你预测的是哪个污染物、提前多久、按什么粒度。常见做法是预测未来 1 小时、未来 24 小时或未来 72 小时的 PM2.5 浓度粒度分小时级和日级。这三者对应的建模思路完全不同。小时级预测更依赖近期自相关滞后 1 到 6 小时的特征权重极高日级预测则更吃气象条件风速、湿度、气压的影响会被放大。我一般建议新手从「小时级、提前 1 小时、预测 PM2.5」起步。原因很实际这个设定下前一小时的浓度本身就是极强特征模型容易收敛你能快速拿到一个看起来不错的基线再逐步加难度。如果一上来就做 72 小时预测误差会大到让你怀疑代码写错了其实是任务本身难度决定的。数据集里通常包含时间戳、监测站点、PM2.5、PM10、SO2、NO2、CO、O3 这些污染物列外加温度、湿度、风速、风向、气压等气象列。你要做的第一件事不是建模而是确认时间戳是否连续、站点是否唯一、缺失比例有多高。2.2 回归还是分类指标怎么定空气质量预测可以是回归也可以是分类。回归直接输出浓度值分类则把 AQI 分成优、良、轻度污染等档位。两者没有优劣取决于业务要什么。如果下游要做预警分类更直接如果要做趋势展示回归更细腻。指标上有个血泪经验不要只报 R²。空气质量数据存在明显的季节性和突发峰值R² 很容易被大量平稳样本拉高掩盖模型在污染峰值上的糟糕表现。我一般同时看三个指标MAE 看平均偏差RMSE 对大误差更敏感再单独统计「真实值超过阈值时模型的命中率」。最后这个指标才是预警场景真正关心的。提示如果数据集里 PM2.5 缺失率超过 20%先别急着插值先看缺失是不是集中在某些时段或站点集中缺失往往意味着设备故障盲目填充会引入系统性偏差。3. 数据清洗与特征工程决定模型上限的一步3.1 缺失值、异常值与时间对齐拿到数据后第一步是统一时间索引。常见做法是把时间戳转成 pandas 的 datetime 并设为索引然后按小时重采样确认没有重复时间点。缺失值处理分两种短缺口连续少于 3 小时用线性插值长缺口直接标记为缺失段训练时跳过而不是硬填。异常值检测用物理范围加统计方法双保险。物理范围就是 PM2.5 不可能为负、不可能超过 1000统计方法用 IQR 或 3σ但要注意污染峰值本身可能就是真实极值别一刀切把峰值当异常删了。我的习惯是先把超物理范围的值置为 NaN再用前后均值填补统计异常只做标记不做删除留给模型自己判断。import pandas as pd import numpy as np # 读取原始数据时间列解析为 datetime df pd.read_csv(air_quality.csv, parse_dates[timestamp]) df df.sort_values(timestamp).set_index(timestamp) # 按小时重采样避免时间戳不连续 df df.resample(1H).mean() # 物理范围过滤负值和超上限置为缺失 for col in [PM2.5, PM10, SO2, NO2, CO, O3]: df.loc[(df[col] 0) | (df[col] 1000), col] np.nan # 短缺口线性插值限制最多填 3 个连续缺失 df df.interpolate(methodlinear, limit3, limit_directionboth) # 打印各列缺失比例决定后续处理 print(df.isna().mean().sort_values(ascendingFalse))这段代码的逻辑是先保证时间轴连续再做物理过滤最后只对短缺口插值。limit3是关键参数它防止把长时间故障段用线性插值糊过去。limit_directionboth保证序列首尾的缺失也能被处理。跑完看缺失比例如果某列还剩大量 NaN说明该列要么弃用要么单独做缺失指示特征。3.2 滞后特征与滑动窗口构造时序预测的核心特征来自历史。对 PM2.5 这类强自相关变量滞后 1、2、3、6、12、24 小时的值几乎必加。构造方式是用 shift但要注意 shift 之后会产生新的缺失需要在建模前统一 drop 或填充。滑动窗口统计量同样重要过去 6 小时均值、过去 24 小时最大值、过去 24 小时标准差。这些特征能帮模型捕捉累积效应和波动剧烈程度。风向这种周期性变量不能直接当数值用常见做法是拆成 sin 和 cos 两个分量否则 359 度和 1 度在数值上差很远实际却几乎同向。# 构造滞后特征 for lag in [1, 2, 3, 6, 12, 24]: df[fPM25_lag_{lag}] df[PM2.5].shift(lag) # 滑动窗口统计 df[PM25_roll_mean_6] df[PM2.5].shift(1).rolling(6).mean() df[PM25_roll_max_24] df[PM2.5].shift(1).rolling(24).max() df[PM25_roll_std_24] df[PM2.5].shift(1).rolling(24).std() # 风向拆成 sin/cos避免周期性断裂 df[wind_sin] np.sin(np.deg2rad(df[wind_dir])) df[wind_cos] np.cos(np.deg2rad(df[wind_dir])) # 构造预测目标下一小时 PM2.5 df[target] df[PM2.5].shift(-1) # 去掉因 shift 和 rolling 产生的缺失行 df df.dropna()这里有个容易翻车的地方滑动窗口必须先 shift(1) 再 rolling否则当前时刻的值会混进特征里造成标签泄漏。shift(-1)构造目标时方向是负的表示取下一时刻。风向的 sin/cos 转换是处理角度类特征的通用手法气象数据里很常见。3.3 特征筛选与共线性处理特征不是越多越好。PM2.5 的多个滞后项之间相关性极高全塞进去会让树模型的特征重要性分散也让线性模型系数不稳定。我一般先算相关矩阵把相关系数超过 0.95 的特征对里保留业务含义更清晰的那个。再用树模型跑一遍特征重要性砍掉重要性接近零的列。注意特征筛选必须在训练集上做不能拿全量数据算相关性再切分否则验证集信息会泄漏进特征选择过程评估结果虚高。4. 模型选型与训练从基线到调参的完整路径4.1 基线模型与树模型的取舍任何预测任务都该先有一个基线。空气质量预测最朴素的基线是「用上一小时的值直接当预测值」学术上叫 persistence model。如果复杂模型的 MAE 打不过这个基线说明特征工程或建模有问题别急着调参。基线之后树模型是首选。XGBoost、LightGBM 这类梯度提升树在表格型时序特征上表现稳定对缺失值和特征缩放不敏感训练快还能直接输出特征重要性。相比之下线性回归对滞后特征的多重共线性很敏感神经网络则需要更多数据和调参耐心。我一般先用 LightGBM 跑通全流程拿到一个可信的 MAE再考虑要不要上 LSTM 或 Transformer。from sklearn.model_selection import TimeSeriesSplit from lightgbm import LGBMRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error # 时序数据必须按时间切分不能随机打乱 split int(len(df) * 0.8) train, test df.iloc[:split], df.iloc[split:] features [c for c in df.columns if c not in [target, PM2.5]] X_train, y_train train[features], train[target] X_test, y_test test[features], test[target] model LGBMRegressor( n_estimators500, learning_rate0.05, max_depth6, num_leaves31, subsample0.8, colsample_bytree0.8, random_state42 ) model.fit(X_train, y_train) pred model.predict(X_test) print(MAE:, mean_absolute_error(y_test, pred)) print(RMSE:, mean_squared_error(y_test, pred) ** 0.5)切分用TimeSeriesSplit或直接按时间点切绝不能随机切。随机切会让未来数据进入训练集评估结果好看但上线就崩。learning_rate0.05配n_estimators500是稳妥组合max_depth6和num_leaves31控制模型复杂度防止过拟合。subsample和colsample_bytree做行和列采样提升泛化。4.2 时序交叉验证与超参数调整单次切分的评估波动大尤其是空气质量数据有季节性。更稳的做法是滚动窗口验证用前 N 个月训练预测下一个月然后窗口前移。这样能看出模型在不同季节的表现差异。超参数调整用 Optuna 或 GridSearch 都行但搜索空间别开太大。LightGBM 重点调learning_rate、num_leaves、min_child_samples和正则项lambda_l1、lambda_l2。学习率越低需要的树越多训练越慢但通常更稳。我一般先固定学习率 0.05 粗调树的数量和叶子数再降到 0.01 精调。import optuna def objective(trial): params { n_estimators: trial.suggest_int(n_estimators, 200, 1000), learning_rate: trial.suggest_float(learning_rate, 0.01, 0.1), num_leaves: trial.suggest_int(num_leaves, 15, 63), min_child_samples: trial.suggest_int(min_child_samples, 5, 50), lambda_l1: trial.suggest_float(lambda_l1, 1e-3, 10, logTrue), lambda_l2: trial.suggest_float(lambda_l2, 1e-3, 10, logTrue), } model LGBMRegressor(**params, random_state42) model.fit(X_train, y_train) return mean_absolute_error(y_test, model.predict(X_test)) study optuna.create_study(directionminimize) study.optimize(objective, n_trials50) print(study.best_params)n_trials50是性价比比较高的起点再多收益递减。logTrue用于正则项这种跨数量级的参数。注意这里为了演示简洁用了单次切分实际项目里 objective 内部应该换成滚动验证的平均 MAE否则调出来的参数只对某一段数据好。4.3 神经网络方案的适用边界如果数据量足够几年以上的小时级数据、且你愿意花时间调参LSTM 或 Temporal Fusion Transformer 这类时序模型能捕捉更复杂的长期依赖。但多数空气质量数据集只有一两年数据树模型往往更划算。神经网络的坑在于对缺失值敏感、需要归一化、训练不稳定、调参空间大。我见过不少人一上来就搭 LSTM结果 MAE 还不如 LightGBM问题多半出在数据量不够和特征没对齐。提示如果一定要上神经网络先把树模型的特征工程结果直接喂进去保证输入一致再对比两者。这样你才能确定差距来自模型本身而不是特征处理。5. 避坑与排查那些让模型结果不可信的细节5.1 标签泄漏现象是评估极好上线极差现象验证集 MAE 低到离谱比如个位数但换一段时间预测就崩。原因特征里混入了未来信息最常见的是滑动窗口没 shift、或者用全量数据做了标准化和插值。解决所有涉及时间聚合的操作先 shift 再 rolling标准化参数只用训练集拟合插值只在训练集内部做测试集的缺失用训练集统计量填。5.2 时间切分错误现象是随机切分指标虚高现象用 train_test_split 随机切分R² 到 0.95但按时间切分只有 0.7。原因相邻小时的样本高度相似随机切分让训练集和测试集共享了几乎相同的时间片段。解决一律按时间顺序切分或用 TimeSeriesSplit确保测试集时间晚于训练集。5.3 缺失值填充引入偏差现象是模型在故障时段预测异常平稳现象某段时间真实值波动大模型预测却一条直线。原因长缺口被线性插值填成了平滑序列模型学到了假的平稳模式。解决长缺口不插值改为添加缺失指示列或直接在这些时段不计算损失。5.4 评价指标单一现象是 MAE 好看但峰值全错现象整体 MAE 很低但污染峰值时段预测值远低于真实值。原因平稳样本占多数模型偏向预测均值。解决对峰值样本加权或单独统计阈值以上样本的召回率和 MAE把它作为模型选择依据。5.5 特征重要性误读现象是删了「重要」特征模型反而变好现象按特征重要性删掉低分特征模型效果没提升甚至下降。原因树模型的特征重要性会被高基数特征和相关性特征分散不代表因果贡献。解决结合业务含义和相关性分析做筛选别只信重要性排序。6. 让预测真正可用的两个进阶技巧第一个技巧是残差建模。空气质量数据里气象突变和节假日效应很难被常规特征完全捕捉。我的做法是先让主模型预测再对残差单独建一个轻量模型输入是节假日标记、天气突变指标如风速变化率、温度骤降幅度。两个模型叠加后峰值时段的 MAE 通常能降一截。这个思路本质是让主模型学平稳规律残差模型学突发事件分工明确。第二个技巧是预测区间而不是单点。业务方真正关心的往往不是「明天 PM2.5 是 85」而是「大概率在 70 到 110 之间」。用分位数回归或对树模型做 bootstrap 采样能给出预测区间。LightGBM 支持分位数损失把 objective 设成 quantile分别训练 0.1 和 0.9 分位就得到 80% 预测区间。这个区间比单点值有用得多也更容易让业务方接受模型的不确定性。# 分位数回归给出预测区间 lower LGBMRegressor(objectivequantile, alpha0.1, n_estimators500, learning_rate0.05) upper LGBMRegressor(objectivequantile, alpha0.9, n_estimators500, learning_rate0.05) lower.fit(X_train, y_train) upper.fit(X_train, y_train) pred_low lower.predict(X_test) pred_high upper.predict(X_test) # 检查真实值落在区间内的比例理想接近 80% coverage ((y_test pred_low) (y_test pred_high)).mean() print(区间覆盖率:, coverage)alpha控制分位点0.1 和 0.9 对应 80% 区间。覆盖率是验证区间质量的核心指标如果算出来只有 50%说明区间太窄需要调整 alpha 或增加模型复杂度。这个技巧我在实际项目里用得最多因为它直接回答了业务方「你这个预测靠不靠谱」的追问。最后说个我自己的习惯每次跑完模型我都会把预测值和真实值按时间画出来肉眼扫一遍。指标再好看图上如果出现系统性偏移或峰值全错那模型就是不能用的。这个动作花不了几分钟但帮我躲过了好几次「指标漂亮、上线翻车」的尴尬。希望帮到你。本文还有配套的精品资源点击获取
返回列表