☰
AQI预测实战:从数据清洗到模型部署的完整链路
2026/9/29 17:49:13 网站建设 项目流程

简介:这是一份面向机器学习与数据分析初学者的《AQI分析与预测》实战资源,聚焦空气质量指数回归建模任务,覆盖从数据清洗、探索性分析到模型训练与评估的完整流程,适合想用Python pandas及scikit-learn完成环境数据项目的学习者。压缩包共33个文件、约4.23MB,核心包括两个可运行的ipynb代码、data.csv原始数据集、HTML可视化页面和Markdown说明文档,另有24张png结果图,目录结构清晰,方便对照代码与图表理解每一步分析结论。数据以PM2.5、PM10、NO2等污染物浓度及温度、湿度、风速等气象因素为主,详细演示缺失值处理、标准化、特征相关性分析等预处理动作,并构建线性回归、随机森林等预测模型,使用MSE、RMSE、R²等指标评估效果。已有197人学习下载,对刚接触机器学习项目流程、想参考规范数据分析范式的读者具有直接参考价值。

1. 重度污染天为什么总是预测不准:AQI分析与预测到底在做什么

城市空气质量监控大屏上,AQI(空气质量指数)预测值在“良”到“轻度污染”区间看着还挺准,可一到重污染过程就集体翻车,预报值和实测值能差出两三个等级。这个现象在环境数据相关的机器学习项目里非常典型。原因大多不在模型本身,而在数据处理口径和训练集分布上——重污染样本本来就少,模型天然偏向预测“安全”的值。这篇笔记围绕 AQI 分析与预测的完整链路展开:AQI 分指数怎么按国标算出来、监测数据怎么清洗和构造特征、线性回归和树模型各自的落地表现、以及把模型变成定时批量预测任务时一定会遇到的坑。整篇的代码和数据组织方式都按 Python + pandas + scikit-learn + XGBoost 这一套最常见的机器学习技术栈来写,适合刚接触机器学习预测项目的工程师,也适合做环境数据、气象数据或智慧城市相关业务的人照着复现。

2. 从监测数据到AQI分指数:先复现计算口径,再整理数据管道

2.1 AQI不是实测值而是分段函数:IAQI的计算口径

很多新手拿到监测数据后,第一反应是“直接用 PM2.5 浓度做回归,然后再把预测浓度换成 AQI”。这个做法不是不行,但会引入一层不必要的误差,因为 AQI 对浓度做了分段线性变换,同样的浓度误差落在不同区间,AQI 误差完全不同。更稳的做法是先把 AQI 序列完整算出来,再决定预测目标是 AQI 本身还是等级。

AQI 的定义是 6 项污染物分指数(IAQI)的最大值。单污染物分指数的计算公式是分段线性插值:

import pandas as pd import numpy as np # IAQI分段界限表,格式: (浓度下限, 浓度上限, IAQI下限, IAQI上限) # 这里只列出PM2.5和PM10,完整实现需要补全SO2/NO2/CO/O3共6项 pm25_table = [ (0, 35, 0, 50), (35, 75, 50, 100), (75, 115, 100, 150), (115, 150, 150, 200), (150, 250, 200, 300), (250, 350, 300, 400), (350, 500, 400, 500), ] pm10_table = [ (0, 50, 0, 50), (50, 150, 50, 100), (150, 250, 100, 150), (250, 350, 150, 200), (350, 420, 200, 300), (420, 500, 300, 400), (500, 600, 400, 500), ] def calc_iaqi(conc, table): """按国标HJ 633-2012的分段线性插值公式计算单一污染物IAQI""" for c_lo, c_hi, i_lo, i_hi in table: if c_lo <= conc < c_hi: return (i_hi - i_lo) / (c_hi - c_lo) * (conc - c_lo) + i_lo if conc >= table[-1][0]: return table[-1][3] return 0 # 示例:PM2.5浓度为82.5 ug/m3时,IAQI约为109(对应轻度污染) print(calc_iaqi(82.5, pm25_table))

这段代码的核心逻辑是:浓度落在哪个区间,就用该区间上下限做线性插值。这里有两个必须注意的口径问题。第一个是区间边界,当浓度恰好等于 35 时,应该落入第二个区间,所以循环判断用的是c_lo <= conc < c_hi,下闭上开。第二个是 O3 的处理,O3 的 AQI 要同时计算 1 小时平均和 8 小时滑动平均两种分指数,取更高的那个参与 AQI 计算,日数据里很容易漏掉这一点。CO 的单位是 mg/m³,其他 5 项是 μg/m³,把单位统一了再套表,否则结果差出几十倍。

完整算 AQI 时,把 6 项污染物的 IAQI 都算出来后取 max,就是该小时的 AQI。我一般会在特征工程阶段把每个小时的 AQI、以及每个小时的主要污染物(也就是 IAQI 最大的那一项)都存下来,主要污染物本身就是一个有用的特征。

2.2 数据集怎么组织:一张能直接读进 pandas 的监测表

这个项目里“数据”指的不只是 AQI 历史值,还包括气象数据,因为温度、湿度、风速、气压对污染物累积和扩散影响很大,没有气象特征,AQI 预测的上限会很矮。常见的做法是申请公开监测站点的逐小时数据,再把气象站数据按时间戳对齐,合成一张宽表。

# 原始数据长这样,列名统一成小写,时间列放到第一列 import pandas as pd df = pd.read_csv("aqi_hourly.csv", parse_dates=["datetime"]) df = df.set_index("datetime").sort_index() print(df.shape) print(df.columns.tolist()) print(df.head(3))
datetime,PM25,PM10,SO2,NO2,CO,O3_8h,temp,humidity,wind_speed,pressure 2023-01-01 00:00:00,82.5,116.2,18.3,62.7,1.2,48.9,-2.4,61.3,2.1,1023.5 2023-01-01 01:00:00,88.1,120.4,17.9,63.5,1.3,49.1,-3.1,63.0,1.8,1024.1 2023-01-01 02:00:00,91.3,125.6,17.5,64.2,1.4,49.0,-3.6,65.2,1.5,1024.8

这张表已经是重采样成整点小时后的数据了。原始监测数据不一定正好落在整点,有的站是每 5 分钟一条,有的站偶发缺测,所以第一件事永远是统一时间分辨率。我一般取“小时”作为预测粒度,因为 AQI 的发布口径就是整点小时值,用更高频的数据只会增加对齐成本,不会带来明显的精度收益。

数据的时间范围也有讲究。如果要做“预测明天的 AQI”,拿到的数据就必须至少包含一整年,因为空气质量有很强的季节性,冬天和夏天的污染成因完全不同。只有两三个月的数据,模型学到的只是当前季节的规律,换季就失效。数据量级上一年的逐小时数据大约是 8760 行,这个规模对 pandas 来说是小数据,不需要上 Spark 或数据库,单机内存完全能跑。

2.3 清洗和拼接:时间对齐、缺失值、异常值

监测数据最常见的脏数据有三种:时间戳错位、浓度负值、长时间缺失。时间戳错位通常出现在手工拼接的 Excel 表里,表现为某一天的数据整体偏移了几个小时,肉眼难发现,但会直接破坏滞后特征。浓度负值是仪器零点漂移导致,应该置空而不是保留。长时间缺失则发生在设备维护期间,动辄一整天的空档,这时线性插值已经不合适了。

import numpy as np # 1) 统一到整点小时,多个站点数据同时存在时取均值聚合 df = df.resample("h").mean() # 2) 浓度负值属于仪器异常,直接置空 conc_cols = ["PM25", "PM10", "SO2", "NO2", "CO", "O3_8h"] for col in conc_cols: df.loc[df[col] < 0, col] = np.nan # 3) 短缺失(<=6小时)用线性插值,长缺失先用线性插值再回填 df = df.interpolate(limit=6, limit_direction="both") df = df.fillna(method="bfill").fillna(method="ffill") # 4) 丢弃仍然存在缺失的行,这些行做特征时会产生脏滞后值 df = df.dropna() print(f"清洗后剩余 {len(df)} 条记录,缺失率 {df.isna().sum().sum()}")

参数limit=6表示只对连续缺失不超过 6 个小时的窗口做插值,超过 6 小时的完整空缺交给 bfill 和 ffill 处理。这个阈值不是拍脑袋定的,6 小时以内的小缺口做线性插值不会引入明显偏差,但一整天的缺测如果也用插值,会把一段完全虚构的曲线塞进序列里,后续构造滞后特征时这堆假数据会被模型当真。

清洗完后建议顺手做一次可视化检查,把 PM2.5 和 AQI 的时序曲线画出来,重点看有没有“平台期”——连续多个小时完全一样的值通常是仪器卡死,不是真实大气状态。平台期在插值后依然会保留,需要额外检测:df["PM25"].diff().eq(0)连续出现 6 次以上就标记为异常。

3. 特征工程和时间切分:用线性回归把第一个模型跑起来

3.1 滞后特征和滚动统计量:AQI预测的核心就是吃惯性

AQI 序列有很强的自相关性,今天的 PM2.5 浓度很大程度上由过去几小时的累积决定,所以滞后特征是这套方案里最重要的特征组。除了污染物浓度本身的滞后值,气象特征同样要构造滞后和滚动统计量,尤其是风速和湿度,它们对污染物的稀释和二次生成起作用。

# 污染物浓度滞后特征:过去1、2、3、6、24小时 for lag in [1, 2, 3, 6, 24]: df[f"PM25_lag{lag}"] = df["PM25"].shift(lag) df[f"PM10_lag{lag}"] = df["PM10"].shift(lag) # 滚动统计量:3小时均值反映短时累积,24小时均值反映日循环背景 df["PM25_roll3_mean"] = df["PM25"].rolling(3).mean() df["PM25_roll24_mean"] = df["PM25"].rolling(24).mean() # 气象特征同样做滞后,风速滞后24小时可以捕捉前一天的大风扩散效果 df["wind_speed_lag3"] = df["wind_speed"].shift(3) df["wind_speed_lag24"] = df["wind_speed"].shift(24) # 时间特征:小时和星期,用来表达交通排放的日循环和周末效应 df["hour"] = df.index.hour df["weekday"] = df.index.weekday # 生成目标列:预测未来1小时的AQI,shift(-1) 是标签,不允许参与训练 df["target"] = df["AQI"].shift(-1) df = df.dropna()

shift(1)和shift(-1)的方向必须想清楚:shift(1)拿到的是上一小时的值,属于历史信息;shift(-1)拿到的是下一小时的值,这就是要预测的标签。建模时只能把shift(1)及更早的信息放进特征矩阵,shift(-1)放进目标列。代码里我把 target 和其他特征放在同一个 DataFrame 里,后期切分时再严格按特征列筛选,这是一个容易查半天才发现问题的隐藏坑。

rolling(24)会引入未来问题吗?不会,滚动窗口只往后看。但要注意:.rolling(24).mean()在计算当前小时时用的是“过去 24 小时”,得到的值本质上还是滞后信息,建模时不会泄漏。滞后 24 小时会产生 24 个 NaN,dropna()会丢掉序列最开始的一天数据,这是可接受的代价。

3.2 时间序列切分:训练、验证、测试不能随机打乱

常规机器学习项目习惯用train_test_split按比例随机切分,这在 AQI 预测里是严重错误。大气过程是连续演化的,昨天和今天的样本高度相关,随机打乱后训练集里会出现“用明天的信息预测昨天”的幻觉,测试集分数会虚高到不真实。正确的做法是严格按时间顺序切三段。

# 按时间切分,不用 train_test_split train = df.loc["2023-01-01":"2023-10-31"] val = df.loc["2023-11-01":"2023-11-30"] test = df.loc["2023-12-01":"2023-12-31"] # 特征列:排除掉索引列、ID列、目标列 exclude_cols = ["target", "AQI"] feats = [c for c in df.columns if c not in exclude_cols] print(f"特征数量: {len(feats)}") X_train, y_train = train[feats], train["target"] X_val, y_val = val[feats], val["target"] X_test, y_test = test[feats], test["target"]

三段切在整月边界,是刻意为之——让验证集独立覆盖一个完整月的天气过程,而不是随机抽零散的天。训练集用 10 个月,验证集和测试集各 1 个月,比例大约 8:1:1,对小时级数据来说训练量已经足够。如果你手里的数据只有 3 个月,可以把比例放宽到 7:2:1,但一旦切出来的测试集时长少于两周,评估指标会随着个别重污染过程的出现而剧烈波动,说服力不足。

验证集和测试集的作用要分开:验证集用来在模型训练时做早停和参数筛选,测试集只在最终确定方案后跑一次。如果针对测试集反复调参数,测试集就变成了验证集,分数就失去了客观性。

3.3 线性回归基线:先接受一个不完美的结果

在推 XGBoost 之前,先用线性回归跑一版基线,目的不是追求精度,而是验证特征管道和数据切分没有低级错误。如果线性回归在验证集上的 R² 都达不到 0.6,大概率是特征或清洗有问题,这时候换再复杂的模型也是白费。

from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score model = LinearRegression() model.fit(X_train, y_train) # 验证集评估 y_pred_val = model.predict(X_val) rmse_val = mean_squared_error(y_val, y_pred_val, squared=False) mae_val = mean_absolute_error(y_val, y_pred_val) r2_val = r2_score(y_val, y_pred_val) print(f"验证集 RMSE: {rmse_val:.2f}") print(f"验证集 MAE: {mae_val:.2f}") print(f"验证集 R²: {r2_val:.4f}")

mean_squared_error(..., squared=False)在 scikit-learn 1.4 之后被标记为废弃,建议改用root_mean_squared_error。如果你跑代码时遇到 DeprecationWarning,直接换成新写法即可,不影响数值结果。这三个指标配合使用:RMSE 对大误差敏感,能暴露极端预测失误;MAE 反映平均偏移水平;R² 衡量相对基线(预测全部均值)的改进程度。

一个健康的 AQI 小时级预测基线是 R² 在 0.85 到 0.92 之间,听起来高得离谱,但这正是滞后特征带来的“便宜分数”——AQI 本来就是一个强自相关序列,能预测准很大程度上是因为知道过去几小时的值。反过来,如果线性回归在没有任何滞后特征的条件下还能有高 R²,那才要怀疑数据泄漏了。

4. 换上随机森林和XGBoost:参数与特征重要性的落地经验

4.1 树模型在这个问题上的优势:非线性、特征交互、缺失值容忍度

线性回归在 AQI 预测上能打,是因为滞后特征已经抓住了线性主效应。但实际场景里 AQI 和气象、排放之间有明显非线性交互:高温低风速时 O₃ 生成加速,湿度高于 80% 时 PM2.5 吸湿增长,这些规律线性模型表达不了。树模型能自动拆出这些交互,而且不需要做特征归一化,省掉 StandardScaler 这一步。

模型对非线性的表达特征归一化缺失值处理可解释性训练速度
线性回归差,需人工构造交互项需要需提前填充强(系数直接可读)极快
随机森林中,可表达分段交互不需要原生支持中(特征重要性)快
XGBoost强,可表达复杂交互不需要原生支持中偏弱较快

随机森林和 XGBoost 之间的取舍不是精度问题,而是稳定性和调参成本。随机森林参数少,默认参数跑出来的结果就接近可用;XGBoost 上限更高,但需要处理学习率、树深度、正则化系数之间的配合,新手容易在验证集上反复横跳。我的习惯是:第一版树模型用随机森林,把特征管道跑顺,再上 XGBoost 看增量。

4.2 一组能直接用的初始参数和早停训练

XGBoost 在这个规模的数据集上训练时间以秒计,所以参数可以放心地往偏大的方向设,配合早停来避免过拟合。下面这组初始参数我在多个城市的气象数据上都用过,效果稳定,适合作为第一版起点。

import xgboost as xgb model = xgb.XGBRegressor( n_estimators=2000, # 先给足树的数量,靠早停截断 learning_rate=0.05, # 学习率太低很慢,太高早停前学不完 max_depth=5, # 深度控制单棵树的复杂度 subsample=0.8, # 行采样,每棵树用80%样本 colsample_bytree=0.8, # 列采样,每棵树用80%特征 reg_lambda=1.5, # L2正则,压制树对单一特征的过度依赖 random_state=42, verbosity=0, # 控制台不输出训练日志 ) model.fit( X_train, y_train, eval_set=[(X_val, y_val)], verbose=False, )

n_estimators给到 2000 看起来夸张,但因为有early_stopping_rounds,训练会在验证集指标连续多轮不再提升时自动停下,最终实际树数可能只有几百棵。这里的早停参数要注意版本差异:新版 XGBoost 建议把early_stopping_rounds放进fit(),旧版本放在构造函数里,否则会报警告。max_depth=5对小时级表格数据够用了,再深容易把噪声也背下来;colsample_bytree=0.8能保证每棵树只看 80% 的特征,让重要性分布更均匀,也有一点对抗特征间共线性的作用。

随机森林对照版同样值得跑一遍,主要是为了比较训练速度和精度曲线。随机森林没有早停机制,树的棵数直接决定模型大小,512 棵已经是计算量和精度的平衡点。

4.3 特征重要性:看看模型学的是规律还是记忆

训练完之后,第一件事不是看测试集分数,而是看特征重要性排序。这个步骤能暴露两类问题:一是模型重度依赖某一个时间特征(说明序列里有隐藏周期性被模型记住了);二是某些物理上不太可能起作用的特征排得异常靠前(说明有泄漏)。

# 特征重要性:XGBoost 自带的 gain 表示特征平均增益,比默认的 weight 更可靠 imp = pd.Series( model.feature_importances_, index=feats ).sort_values(ascending=False) print(imp.head(15))

feature_importances_默认的weight统计的是特征被分裂的次数,容易被取值多的高基数特征虚高;手动指定importance_type="gain"按平均信息增益排序更有参考意义。看结果时记住一个规律:在加入滞后特征的场景里,“AQI 滞后 1 小时”“PM2.5 滞后 1 小时”占据前两名是合理的,说明自回归在起作用。但如果排第一的是“hour”(小时),就要检查是不是目标列和特征列错位了。

另一个值得做的检查是把时间特征(hour、weekday)单独从特征列表里剔除,重训一次对比验证集分数。如果分数几乎不变,说明模型的预测能力全部来自污染物和气象的物理过程;如果分数暴跌,说明模型的“高精度”有一部分来自记住一周里哪天污染重——这在长期预测里是虚假的记忆,换个季节就失效。

5. AQI预测的5个高频踩坑点:现象、原因、解决

5.1 重污染样本占比太低,预测结果永远偏向“良”

  • 现象:整体 RMSE 看着不错,每季度的重污染天全部没报出来,等级命中率一统计只有 30%。
  • 原因:全年数据里“优”和“良”占了大头,模型优化 RMSE 时天然倾向把预测值压向样本分布密集的区间,重污染样本少,错得再多对整体损失贡献也小。
  • 解决:一是评估时按 AQI 分级分别看 RMSE,重污染段的误差单列;二是训练时给重污染样本加权,XGBoost 里可以用sample_weight参数;三是采样时保证训练集里重污染时段不过度下采样。

5.2 把“未来”的数据混进了特征,测试集分数好得反常

  • 现象:验证集 R² 到了 0.97 以上,比正常发挥高出太多,而且特征重要性里某个气象特征排名异常靠前。
  • 原因:最常见的泄漏是直接把当天的实测气象“预报值”当成特征,或者做滚动均值时把窗口开到了未来。某些数据集里预先算好的“当日均值”列也是泄漏源,因为它在整日数据齐全之前根本不存在。
  • 解决:检查所有特征列,只保留shift(k)且k >= 1的列;滚动统计统一用closed="left"和shift(1)落在过去窗口上。写特征工程代码时把“特征可获取时间”和“标签时间”的差显式注释在代码里,比事后排查高效得多。

5.3 模型换了一个城市就失效,精度掉一半

  • 现象:同一份代码、同一套参数,在另一个城市的数据上 R² 从 0.90 跌到 0.65。
  • 原因:不同城市的污染物构成差异大。有的城市 PM2.5 主导,有的城市 O₃ 主导,O₃ 的季节曲线和日曲线与 PM2.5 完全不同,用 A 城市训练出的特征交互关系在 B 城市不成立。
  • 解决:不要跨城市直接迁移模型。常见做法是每个城市单独训练,至少采用该城市一年的数据;如果数据量不足一年,至少用迁移学习的方式,把原城市的模型作为初始版本,用新城市数据微调最后几轮迭代。

5.4 RMSE被个别重污染日拉爆,单日误差解释了整个误差

  • 现象:RMSE 远大于 MAE 的合理比例,去掉最严重的一天测试样本,RMSE 骤降 30%。
  • 原因:MSE 对大误差做平方惩罚,一次“预报重度污染结果实际爆表”的极端失败,相当于几十个普通样本的损失总和。
  • 解决:报告多看 MAE 和中位数误差;如果业务上不希望为极端情况付出平方惩罚,把 XGBoost 的损失函数从默认的回归改成reg:pseudohubererror,Huber 损失对大残差的惩罚是线性的,预测会更稳。

5.5 同一份代码在不同机器上跑出的分数对不上

  • 现象:同事用同样数据跑出 R²=0.91,你这边只有 0.87,特征数量一模一样。
  • 原因:scikit-learn 和 XGBoost 的版本差异会导致默认参数行为变化,另一个高发原因是随机数种子没固定,树模型的分裂点选择带有随机性。
  • 解决:在项目根目录放 requirements.txt,锁住关键库版本;所有带随机性的模型参数都显式传入random_state;训练前用numpy.random.seed()固定全局种子。这套动作做完,同一份代码在不同机器上的分数差应该能控制在 0.01 以内。

6. 把模型变成定时批量预测任务:滚动预测明天的AQI并监控漂移

6.1 用今天已有的监测值滚动预测未来24小时

训练时我们的目标列是“下一小时 AQI”,但业务上更常用的口径是“预测明天一整天的 AQI”,也就是从当前时刻出发,滚动地用“预测值”反哺“滞后特征”。这需要写一个滚动预测脚本:先加载最新的监测数据,构造特征,预测第一个小时,然后把预测值当作滞后特征喂给下一个小时的预测。

import pandas as pd import numpy as np # 已有:清洗好的最新数据 latest_df,训练好的模型 model # 特征函数复用训练时的管道,只是 shift 的方向要调整 def build_features_for_forecast(df, horizon=24): # 先构造原始特征(含shift),再循环外推 out = df.copy() for h in range(horizon): if h == 0: # 第一个预测步:所有滞后特征都来自真实观测 pred = model.predict(out[feats].iloc[[-1]])[0] else: # 后续步:把上一小时预测值填回去 out.loc[new_time, "PM25"] = pred # 重建该小时的滞后/滚动特征 out = append_features_for_single_row(out, new_time) pred = model.predict(out[feats].iloc[[-1]])[0] new_time = out.index[-1] + pd.Timedelta(hours=1) preds.append(pred) return preds

这段代码隐藏了一个绕不开的误差累积问题:预测步数每多一步,模型的输入里“预测值”的比例就越高,误差会逐渐放大。所以滚动预测结果要比单步预测结果更保守,尤其在重污染过程中。业务上如果只需要“明天平均 AQI 等级”,建议把 24 小时的逐小时预测值再取平均,而不是直接用某个小时的预测值下结论。

6.2 模型漂移监控:每周重训还是每日增量更新

AQI 预测系统上线后,模型漂移来得非常快——污染源变化、气象模式变化、甚至监测仪器更换都会让特征分布悄悄偏移。常见做法是每天把最新数据追加进训练集,每周全量重训一次,而不是每天重训,避免模型被近期噪声带偏。监控指标上用滚动 7 天的 RMSE,和历史值比较,超过历史 P90 就触发告警。

# 伪代码:每周重训任务 latest_df = load_clean_up_to_yesterday() df_all = pd.concat([historical_df, latest_df]).drop_duplicates() train_data, val_data = time_split(df_all) model = train_xgboost(train_data, val_data)

重训频率不是越勤越好。AQI 数据是强自相关的,最近几周的噪声在全局样本里占比很低,每日重训耗时不会很短,精度提升却有限。每周重训加上月度归档,是时间和效果之间比较平衡的选择。

6.3 业务认的是等级命中率而不是RMSE

AQI 预测做出来之后,业务同事最关心的不是 R² 或 RMSE,而是“预报的等级准不准”。环境管理部门发布的是“优、良、轻度、中度、重度、严重”6 个等级,一个预测误差哪怕 RMSE 很小,只要跨了等级边界,就是一次预报失误。所以最终验证至少要把预测值映射回等级,算一次等级命中率。

def aqi_to_level(aqi): """AQI等级映射,边界值和国标保持一致""" if aqi <= 50: return "优" elif aqi <= 100: return "良" elif aqi <= 150: return "轻度" elif aqi <= 200: return "中度" elif aqi <= 300: return "重度" else: return "严重" y_pred_level = y_pred.apply(aqi_to_level) y_true_level = y_test.apply(aqi_to_level) level_acc = (y_pred_level == y_true_level).mean() print(f"等级命中率: {level_acc:.2%}")

等级命中率里有个“宽恕”现象:相邻等级的误判通常被业务接受,差两个等级才是真正的失败。所以除了整体命中率,再算一个“误差不超过 1 级”的容忍命中率,整体命中率到 60% 以上、容忍命中率到 90% 以上,基本满足业务发布要求。

我自己的习惯是每个季度重新看一次特征重要性和重污染日误差分布,换模型之前先确认是物理过程变了还是数据质量下降了。预测这种东西,翻一次车就知道哪几个环节不能偷懒了。这套链路做完,剩下就是持续维护数据管道和监控漂移的事情,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询