在实际的空气质量预测项目中,模型性能往往不是只由网络层数决定,而是由数据的时间尺度、上下文信息和预测目标之间的匹配程度决定。AirFlow 这个模型方向的核心也在这里:它不追求用一个固定分辨率处理所有输入,而是把“上下文保留”和“多速率状态建模”作为设计主线,专门解决污染物浓度在小时级、日级、周级不同频率上的演化规律。本文从工程落地视角拆解这个模型思路,给出数据准备、模型结构、训练评估、常见排错和生产部署建议,适合正在做时序预测、环境数据分析或城市计算相关项目的开发者参考。
1. 从同名歧义开始:AirFlow 模型解决的不是调度问题
1.1 为什么空气质量预测需要专门的建模方法
空气质量预测在形式上是一个多变量时序预测任务:输入历史一段时间内的污染物浓度和气象数据,输出未来若干小时的 PM2.5、PM10、NO2、O3 等浓度值。但它比普通时序预测更复杂的地方在于,污染物浓度受到多个物理过程的共同作用。
局地机动车排放、工业活动、建筑扬尘会在小时尺度上造成浓度突变;气象系统(风速、风向、边界层高度)会在数小时到数天内改变污染物的扩散条件;季节性供暖、降水模式和大气环流则带来更缓慢的长期趋势。如果只用固定采样间隔的数据,比如统一重采样到小时,模型很难同时捕捉分钟级的峰值突变和天气系统带来的日际变化。AirFlow 模型的出发点正是保留多个时间分辨率的输入,让不同频率的状态信息各司其职。
1.2 上下文保留和多速率状态建模分别指什么
上下文保留(Context Preserving)关注的是模型在处理长序列时,如何让历史信息不丢失。常见的 RNN/LSTM 模型虽然能记住过去,但记忆会随着时间步增长而衰减,遇到极端污染事件或者天气转折时,模型容易把重点放在最近的输入上,忽略几天前已经出现的累积趋势。上下文保留机制通常通过门控、注意力或额外记忆单元实现,让模型在生成当前状态时,有选择地保留长期上下文。
多速率状态建模(Multi-Rate State Modeling)关注的是输入序列本身的采样频率。传统做法把所有特征重采样到同一频率,例如全部转成小时数据,这样实现简单,但会牺牲高频细节。AirFlow 的做法是并行维护多条时间分支:分钟级分支捕捉局地突变,小时级分支建模扩散和气象过程,日级或周级分支刻画季节背景,最后在设计好的融合模块中合并这些状态。
1.3 不要把方法名和工作流调度平台搞混
搜索“AirFlow”时,大量结果会指向 Apache AirFlow,那是开源的工作流调度平台,用于编排数据处理任务、定时触发 ETL 流程,和空气质量预测没有直接关系。AirFlow 作为学术模型名称,主要出现在论文、技术报告和时序预测项目复现里。如果目标是学习调度平台,需要查找 Apache AirFlow 官方文档,并重点确认版本差异;如果目标是做空气质量预测,本文后续内容围绕模型方法展开,不涉及调度平台安装部署。
理解这一点很重要,因为后续的代码和配置都基于 PyTorch 和数据处理工具,不是基于任务调度平台。
2. 问题定义与数据准备:先想清楚要预测什么
2.1 任务形式化和预测目标
在动手写模型之前,先把任务写成数学形式。假设某个监测站点在时刻 t 的观测向量为 X_t,包含 D 个特征,例如 PM2.5、PM10、SO2、NO2、CO、O3、温度、湿度、风速、风向、气压。给定过去 L 个时间步的历史观测:
X_{t-L+1}, X_{t-L+2}, ..., X_t
目标是预测未来 H 个时间步的污染物浓度:
Y_{t+1}, Y_{t+2}, ..., Y_{t+H}
最常见的设定是:输入过去 72 小时数据,预测未来 24 小时 PM2.5 或 AQI。这里的 H 决定模型是单步预测还是多步预测。多步预测比单步更难,因为误差会逐步累积,尤其在边界层变化剧烈的清晨和傍晚时段。
如果把任务扩展到多个站点,则 X_t 变成站点集合上的图信号矩阵,需要额外引入站点间距离、风向路径等信息。AirFlow 的思路在单站点上先适用,多站点场景可以看作其在空间维度的扩展。
2.2 数据来源与字段设计
空气质量预测常用的公开数据包括中国环境监测总站发布的全国城市空气质量实时数据、一些高校和科研机构整理的城市污染物数据集,以及 Kaggle、UCI 等平台上经过清洗的历史数据。实际使用前要确认数据的时间范围、更新频率、授权方式和字段缺失情况。
常见字段可以整理成表,便于后续编写预处理代码时对照:
| 字段 | 类型 | 示例 | 说明 |
|---|---|---|---|
| timestamp | datetime | 2024-01-15 08:00:00 | 观测时间,注意时区统一 |
| station_id | string | 1001A | 站点编码,多站点场景关键 |
| pm25 | float | 78.5 | PM2.5 浓度,单位微克/立方米 |
| pm10 | float | 120.3 | PM10 浓度 |
| no2 | float | 56.2 | 二氧化氮浓度 |
| o3 | float | 30.1 | 臭氧浓度 |
| temperature | float | 12.6 | 气温,单位摄氏度 |
| humidity | float | 45.0 | 相对湿度,单位百分比 |
| wind_speed | float | 2.3 | 风速,单位米/秒 |
| wind_direction | float | 180.0 | 风向角度,0-360 度 |
| pressure | float | 1023.5 | 气压,单位 hPa |
需要注意的是,风向是环形变量,直接作为数值特征会让 0 度和 360 度看起来差异很大,实际工程中建议拆成 sin 和 cos 两个分量,或者按 16 方位分桶编码。
2.3 多速率数据对齐:不要把高频信息直接磨平
多速率建模的第一步不是把所有数据塞进同一个 DataFrame,而是按业务需求生成多个频率的序列。比如原始监测数据是 5 分钟一条,气象数据是 1 小时一条,想同时保留小时级波动和日级趋势,可以生成三条分支:5 分钟分支、1 小时分支、1 天分支。
这里推荐用 pandas 的 resample 完成:
import pandas as pd def build_multirate_series(df, value_col="pm25", rules=None): if rules is None: rules = { "high": "5min", "mid": "1h", "low": "1D", } df = df.copy() df["timestamp"] = pd.to_datetime(df["timestamp"]) df = df.set_index("timestamp").sort_index() series = {} for name, rule in rules.items(): # mean 用于整体浓度,max 可以单独保留峰值分支 series[name] = df[value_col].resample(rule).mean() return series代码块的思路是:同一列污染物按不同窗口聚合,得到高频、中频、低频三条曲线。实际项目中,高频分支建议额外保留一个“最大值”序列,因为空气质量预测对污染峰值更敏感,均值会把短时高浓度事件磨平。
注意:重采样前必须检查原始时间戳是否连续、是否存在重复时间。重复时间戳会直接导致 resample 报错或者统计出错误结果。
高频和低频分支的长度可能相差很大,例如 5 分钟分支一天 288 个点,日分支一天只有 1 个点。模型不能直接把这些序列拼接起来,需要按窗口切分后再输入不同的编码器。
3. 核心模型设计:多分支编码与上下文门控
3.1 多速率时间编码器结构
AirFlow 的模型结构可以理解为一个“多分支编码器 + 上下文融合模块 + 输出层”。每个频率分支独立处理对应分辨率的序列,然后把各自得到的隐状态融合起来,输出未来预测值。
下面是一个简化的 PyTorch 实现,只用于说明思路,不力求复现论文中的全部细节:
import torch import torch.nn as nn class RateEncoder(nn.Module): def __init__(self, input_size, hidden_size, num_layers=1): super().__init__() self.gru = nn.GRU( input_size=input_size, hidden_size=hidden_size, num_layers=num_layers, batch_first=True, ) def forward(self, x): # x: (batch, time, feature) output, hidden = self.gru(x) return output, hidden # hidden: (layers, batch, hidden) class AirFlowStyleModel(nn.Module): def __init__(self, input_sizes, hidden_size, horizon): super().__init__() # input_sizes 是各频率分支的特征维度,例如 [6, 6, 6] self.encoders = nn.ModuleList([ RateEncoder(size, hidden_size) for size in input_sizes ]) self.context_fc = nn.Linear(hidden_size * len(input_sizes), hidden_size) self.predictor = nn.Linear(hidden_size, horizon) def forward(self, multi_rate_inputs): hiddens = [] for x, encoder in zip(multi_rate_inputs, self.encoders): _, hidden = encoder(x) # 取最后一层最后一刻的隐状态 hiddens.append(hidden[-1]) context = torch.cat(hiddens, dim=-1) context = torch.tanh(self.context_fc(context)) out = self.predictor(context) return out这段代码里每个分支使用独立的 GRU,特征维度由 input_sizes 配置。高、中、低频率分支不共享参数,因为 5 分钟分辨率上的波动模式和 1 天分辨率上的季节模式本质不同,共享参数反而会引入尺度冲突。
3.2 上下文保留机制:让长期记忆参与融合
仅仅把每条分支最后一个隐状态拼接在一起,还不足以体现“上下文保留”。GRU 的最后一个隐状态仍然可能把几天前的重要信息压缩掉。更合理的方式是在融合前引入一个上下文门控模块,让历史记忆和当前窗口信息做加权组合。
class ContextGate(nn.Module): def __init__(self, hidden_size): super().__init__() self.gate = nn.Linear(hidden_size * 2, hidden_size) def forward(self, current_state, long_memory): # current_state: 近期模式编码 # long_memory: 长期上下文向量 g = torch.sigmoid(self.gate(torch.cat([current_state, long_memory], dim=-1))) return g * current_state + (1 - g) * long_memory这里的 long_memory 可以来自一个额外的慢速编码器,比如对过去 30 天日序列做注意力池化得到。门控的值越接近 1,表示模型越信任当前窗口信息;越接近 0,表示越依赖长期上下文。这种机制在天气状态发生转折时尤其重要:当污染物浓度已经明显升高时,模型需要判断这是短期局地排放还是长期静稳天气累积造成的,不能单纯根据最近几小时数据做决策。
除了门控,注意力机制也是非常直接的上下文保留方式。对 encoder 输出的整条序列做加权求和,模型可以自动学习哪段时间最重要。实际项目中可以两者结合:门控用来控制旧记忆和新信息的比例,注意力用来挑选历史时间步中的关键片段。
3.3 损失函数与训练逻辑
空气质量预测本质是回归任务,最常见损失函数是 MSE 和 MAE。MSE 对大误差惩罚更重,会推动模型更关注浓度峰值,但也容易被个别异常观测主导;MAE 更稳定,但在梯度更新时对误差正负方向处理不同。很多项目会选择 Huber Loss,它在小误差时接近 MSE,在大误差时接近 MAE,兼顾收敛速度与鲁棒性:
criterion = nn.SmoothL1Loss()如果业务更关注高污染时段,可以对预测误差按真实浓度加权。例如真实浓度超过 150 微克/立方米时损失权重放大 1.5 到 2 倍,让模型把更多能力放在高风险区间。
训练循环没有特殊之处,但时间序列预测要注意数据划分方式。不能用随机打乱训练集和测试集,必须按时间顺序切分,否则未来信息会泄漏到训练集中,验证指标会虚高。
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, mode="min", factor=0.5, patience=5 ) for epoch in range(max_epochs): model.train() for batch in train_loader: optimizer.zero_grad() pred = model(batch["inputs"]) loss = criterion(pred, batch["targets"]) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step() val_loss = evaluate(model, val_loader) scheduler.step(val_loss) if early_stopping.should_stop(val_loss): break训练中比较关键的细节是梯度裁剪。多分支模型合并时,不同频率分支的梯度尺度可能差异很大,如果不加 clipping,低频分支偶尔会把高频分支的权重更新方向带偏。
4. 从零复现最小案例:数据预处理、训练与验证
4.1 环境依赖与项目结构
复现这个方向不需要太重的依赖,建议使用以下环境:
| 依赖 | 版本建议 | 用途 |
|---|---|---|
| Python | 3.9 及以上 | 基础运行环境 |
| PyTorch | 2.0 及以上 | 模型搭建与训练 |
| pandas | 1.5 及以上 | 数据处理与重采样 |
| numpy | 1.24 及以上 | 数值计算 |
| scikit-learn | 1.2 及以上 | 数据切分与评估指标 |
| matplotlib | 3.6 及以上 | 预测曲线可视化 |
安装时使用 pip 即可,实际项目要以自己的系统环境为准:
pip install torch pandas numpy scikit-learn matplotlib项目结构建议按下面这种方式组织,方便后续扩展:
airflow_forecast/ ├── data/ │ ├── raw/ # 原始监测数据 │ └── processed/ # 预处理后的多速率序列 ├── src/ │ ├── data_preprocess.py # 多速率重采样与缺失值处理 │ ├── model.py # 模型结构定义 │ ├── train.py # 训练与验证脚本 │ └── evaluate.py # 指标计算与可视化 ├── configs/ │ └── default.yaml # 超参数配置 └── notebooks/ └── explore.ipynb # 数据探索4.2 数据预处理:归一化、缺失值与时间窗口切分
预处理最容易犯的错误是“先整体归一化再划分数据集”。正确做法是只用训练集拟合归一化参数,再用它转换验证集和测试集。下面的代码演示了最小处理流程:
import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler def preprocess_for_training(df, feature_cols, target_col="pm25"): # 1. 保证时间排序 df = df.sort_values("timestamp").reset_index(drop=True) # 2. 数值特征填充 df[feature_cols] = df[feature_cols].fillna(method="ffill") # 3. 只在训练数据上拟合归一化参数 train_size = int(len(df) * 0.7) train_df = df.iloc[:train_size] test_df = df.iloc[train_size:] scaler = StandardScaler() train_x = scaler.fit_transform(train_df[feature_cols]) test_x = scaler.transform(test_df[feature_cols]) train_y = train_df[target_col].values test_y = test_df[target_col].values return train_x, test_x, train_y, test_y, scaler这段代码的意图是:先用前向填充处理缺失值,再按时间切分,最后在训练集上 fit scaler。实际项目中“前向填充”不一定是最好的策略,如果缺失时间较长,还要结合线性插值或基于相似日的填充,但要避免直接引入未来数据。
窗口切分时,要按固定步长滑动生成样本,同时保证目标值不会越过时间边界造成泄漏:
def make_windows(x, y, input_len, horizon, step=1): xs, ys = [], [] for start in range(0, len(x) - input_len - horizon + 1, step): end = start + input_len xs.append(x[start:end]) ys.append(y[end:end + horizon]) return np.array(xs), np.array(ys)这里 input_len 是历史窗口长度,horizon 是预测长度,step 是滑动窗口步长。step 越大,生成的样本越少,训练越快,但数据利用率越低。
4.3 训练脚本与验证流程
多速率输入在 DataLoader 中需要同时返回多个频率的数组。如果同时使用 5 分钟、1 小时、1 天三条分支,输入窗口对应的时间长度必须一致,比如都以“过去 3 天”为准,那么三条分支的形状分别是 (batch, 864, feature)、(batch, 72, feature)、(batch, 3, feature)。
模型训练和验证的伪代码可以这样组织:
def train_one_epoch(model, loader, optimizer, criterion): model.train() total_loss = 0.0 for batch in loader: high = batch["high"] mid = batch["mid"] low = batch["low"] target = batch["target"] optimizer.zero_grad() pred = model([high, mid, low]) loss = criterion(pred, target) loss.backward() optimizer.step() total_loss += loss.item() return total_loss / len(loader)验证阶段除了计算损失,还要保存可视化结果,把测试集上的预测序列和真实序列画在一起。这一步很关键,因为 RMSE 只能看到一个数字,而曲线图能直观展示模型是否存在系统性滞后,比如预测曲线比真实曲线晚 6 小时,这种问题在数值指标上很难察觉。
5. 参数选型与评估设计
5.1 核心参数速查表
多速率模型的控制参数比单模型多,下面是几个最重要的参数及其影响:
| 参数 | 推荐初始值 | 调大影响 | 调小影响 | 注意事项 |
|---|---|---|---|---|
| 高频窗口长度 | 72 小时 | 上下文更长,训练成本上升 | 只保留近期信息,可能丢失缓变过程 | 要保证各频率时间范围一致 |
| 分支数量 | 3 | 信息维度更多,但融合变难 | 实现简单,但频率信息不完整 | 不是越多越好,先做 2 到 3 条 |
| hidden_size | 64 | 拟合能力更强,容易过拟合 | 模型容量不足,长期趋势学不住 | 结合样本量选择 |
| 学习率 | 1e-3 | 收敛快,可能震荡 | 训练慢,适合精细调参 | 使用 ReduceLROnPlateau 自动调节 |
| 批大小 | 64 | 梯度更稳定,内存占用更大 | 更新频繁,容易抖动 | 序列较长时应减小批大小 |
| dropout | 0.2 | 正则更强,训练变慢 | 过拟合风险高 | 多分支融合层尤其建议加 dropout |
| 预测步长 | 24 小时 | 业务价值高,误差累积明显 | 误差更小,但短期预测实用性有限 | 建议同时训练多个 horizon 头 |
参数调节不能只盯训练损失。多分支模型容易在训练集上表现很好,验证集却出现大的偏差,所以要同时观察训练和验证曲线,差距过大说明过拟合,应该增加 dropout、减少分支数量或增大数据量。
5.2 评估指标选择
常用的回归指标都能用,但要结合业务场景解读:
- RMSE:对大幅误差敏感,适合评估模型在高浓度事件上的表现。单位与浓度一致,便于业务理解。
- MAE:反映平均绝对误差,比 RMSE 更稳健,不受个别离群点主导。
- MAPE:百分数误差,直观,但 PM2.5 接近 0 时会变得极大且不稳定,冬春清洁天气时慎用。
- R²:反映模型对目标方差的解释程度,但不能反映预测是否系统偏小。
如果预测目标是 AQI 等级或者是否超标,还需要计算分类准确率、召回率、误报率。例如预测未来 24 小时是否会出现 PM2.5 超过 150 微克/立方米的高污染事件,即使浓度数值有误差,只要等级判断正确,对预警也有价值。
评估时建议按浓度区间分组统计,而不是只看全量指标。把测试集按照真实 PM2.5 浓度分成“低于 75、75 到 150、高于 150”三组,分别计算 RMSE,可以快速发现模型是否只在低浓度区间表现好,高浓度区间失效。
5.3 对照实验设计
要验证 AirFlow 的多速率和上下文保留模块确实有效,不能只跑一个模型就说效果不错。建议设计以下对照实验:
| 实验 | 输入 | 模型 | 目的 |
|---|---|---|---|
| 基线 | 小时级单分支 | LSTM/GRU | 验证多速率的增益 |
| 单速率强化 | 小时级单分支,更大隐藏层 | 相同结构 | 排除参数规模导致的效果差异 |
| 多速率无上下文门控 | 多分支 | 无 ContextGate | 验证上下文保留的作用 |
| AirFlow 风格 | 多分支 | 完整结构 | 综合结果 |
实验过程中必须固定随机种子、数据划分和归一化参数,否则无法判断效果差异来自模型还是数据。每一组实验至少跑 3 个随机种子,取平均值和标准差,结论才可靠。
6. 常见问题排查:数据缺失、粒度错位与预测漂移
6.1 高频问题对照表
实际复现中经常出现问题,可以先按下面表格快速定位:
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 训练 loss 出现 NaN | 数据缺失值未处理或归一化参数为 0 | 打印输入数据的 min/max,检查是否包含 NaN | 删除或插值缺失时间戳,归一化前检查方差 |
| 预测曲线整体滞后数小时 | 输入窗口和预测窗口的时间对应关系写错 | 画出一次具体样本的输入、真实目标、预测目标 | 检查数据切分,确认预测目标从 t+1 开始 |
| 多分支融合后效果不如单分支 | 频率分支特征没有对齐,或融合层参数过多 | 分别单独训练每个分支并记录指标 | 先跑单分支,再逐步叠加分支,观察增益 |
| 高浓度时段预测偏低 | MSE 把常规时段主导,峰值样本占比低 | 按浓度区间统计 RMSE | 使用峰值加权损失或 Huber Loss |
| 模型在验证集上波动大 | 数据划分随机化或样本量太小 | 固定随机种子重跑 | 改用时间顺序切分,增加交叉验证 |
6.2 三个典型问题排查链路
第一类是数据缺失。现象是训练时某些高频分支的长度不固定,或者测试时出现 NaN。先检查原始数据的每分钟记录数量分布,确认是否存在停站、断网等长时间缺失。处理方式不是直接删除整段时间,而是对缺失段打标记,并在预处理阶段用插值或“前值”填充。更稳妥的方式是把“缺失时长”作为一个额外特征传入模型,让模型自己学习缺失时间对预测的影响。
第二类是粒度错位。多速率模型要保证每个分支覆盖的物理时间范围一致。比如想用过去 3 天预测未来 24 小时,高频 5 分钟分支应截取最近 864 个点,小时分支应截取最近 72 个点,日分支应截取最近 3 个点。如果小时分支错取成最近 72 个点,但实际是 24 小时前的数据,模型会从错误的起点开始学习,结果通常表现为预测滞后或周期性抖动。
第三类是长期预测漂移。多步预测误差累积是常见现象,尤其在 horizon 较长时。可以尝试三种策略:一是直接多步输出,让模型一次输出未来 24 小时;二是自回归迭代,把上一时刻预测值当作下一步输入,但误差累积明显;三是先训练单步模型,推理时采用教师强制和预测输入混合的策略,降低漂移。实践中经常发现直接多步输出反而更适合长期预测,因为模型不需要反复把噪声预测值当成真实输入。
注意:验证指标只显示“平均 RMSE 下降”是不够的,还要看预测曲线是否出现系统性时间滞后。如果预测曲线形状正确但整体向右平移,说明模型学到的是延迟复制,而不是真正的趋势预测。
7. 生产环境落地要点与扩展方向
7.1 从离线预测到在线推理的改造
离线实验跑通后,生产环境会引入新的问题:数据实时性、推理延迟和模型更新频率。在线预测场景下,模型一般以小时或分钟频率被调用,输入是最近一段时间的监测数据,输出是未来 24 小时的浓度曲线。
在线推理需要把数据预处理和模型加载做成一个服务。建议在服务启动时一次性加载模型权重和归一化参数,并把已经计算好的特征缓存到内存中,避免每次请求都从数据库重新拉取并重采样全部历史数据。重采样计算量虽然不大,但频繁执行会造成不必要的延迟和 IO 压力。
接口输出还需要设定降级策略。如果上游数据中断超过一定时间,模型给出的输入窗口不完整,此时返回缓存的上一次预测结果,或直接返回“数据不可用”,而不是输出一个看似正常但没有依据的预测值。
7.2 模型维护与监控
生产模型不是训练完就结束,需要持续监控预测偏差。每天把真实浓度和模型预测值对比,计算滚动偏差;如果发现系统偏差持续超过阈值,就要触发重训流程。重训前先排查数据质量,因为站点仪器更换、传感器漂移、时间字段格式变化都会让模型输入分布发生偏移。
模型版本管理也很重要。每次重训后要记录数据版本、特征版本、模型版本和评估指标,并保留上一个版本的权重,便于回滚。归一化参数一旦重新拟合,必须和对应模型打包发布,不能单独替换。
空气质量预测模型上线前还要考虑业务指标和模型指标的关系。例如预警阈值是否依赖预测曲线的最大值,而不是平均值。如果业务上最关心是否出现重度污染,模型评估就要重点关注高浓度时段的误差和高污染事件的召回率。
7.3 扩展方向
AirFlow 的多速率和上下文保留思路,可以继续向几个方向扩展。
第一,引入气象预报数据。历史气象数据用于训练,未来气象预报数据可以加入 decoder,让模型知道未来风速、降水和风向的变化,这是提升中长期预测效果的重要路径。
第二,加入站点间空间关系。多个监测站点之间存在空间相关性,下游站点的污染往往来自上游城市传输。可以引入图神经网络,把站点邻接关系和风向路径建模成图结构,让每个站点的状态更新时考虑邻近站点的信息。
第三,结合物理约束。纯数据驱动模型容易预测出不符合物理规律的浓度值,比如在持续强降雨时段预测 PM2.5 大幅上升。可以在损失函数中加入物理正则项,或者在模型结构中加入排放、扩散等物理过程的简化模拟层,提升预测合理性。
第四,不确定性量化。空气质量预测对决策非常重要,仅输出一个点预测不够。可以使用深度集成、MC Dropout 或分位数回归,输出预测区间,给决策层提供风险信息。
实际复现时,不建议一开始就追求完整论文结构。先从单一站点、两个频率分支入手,把数据预处理、模型训练、评估和可视化链路跑通,再逐步加入上下文门控、更多频率分支和站点空间关系。这个过程中,数据分布、时间对齐和评估粒度的问题往往比模型结构更影响最终效果,值得优先投入精力。