拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

基于RNN与LSTM的航班延误预测:从论文到工程落地

基于RNN与LSTM的航班延误预测:从论文到工程落地

简介:这份PDF面向民航从业者、空管技术人员及机器学习方向的学习者,聚焦航班延误预测这一实际难题,系统讲解如何用循环神经网络挖掘延误在时间维度上的潜在关联。资源共1个PDF文件,压缩包约1MB,内容为期刊论文全文,含摘要、引言、算法原理、模型设计与基础数据说明等章节,便于按模块研读。文中详细拆解RNN的隐藏层状态更新与BPTT梯度问题,并引入LSTM的输入门、遗忘门、输出门及细胞单元四步更新机制,说明其如何缓解长期依赖与梯度消失,进而构建RNN与LSTM单元相混合的延误预测模型,并基于民航空管历史真实数据展开验证。目前已有194人学习。读者可借此理解深度学习在空管场景的落地思路,掌握特征自动提取、时序建模与并行计算集成的关键方法,为延误趋势预判、地面保障资源调配等应用提供参考。

1. 航班延误预测为什么选 RNN 而不是 XGBoost:一份 2019 年的空管实战论文拆解

如果你在民航、空管或者交通数据岗位待过,大概率遇到过这个场景:领导拿着前一天大面积延误的复盘报告问,能不能提前一天告诉哪天会延误、哪个机场会堵。传统做法是上贝叶斯、决策树、XGBoost,把能见度、流量、航班计划一股脑塞进去做分类。但这类模型有个硬伤——它把每条样本当成独立的,今天延误和昨天延误之间的时序关联被直接抹掉了。而航班延误恰恰是个强时序问题:前一时段的状态会顺着时间轴往后传导,昨天傍晚的流控很可能就是今天上午延误的根子。

这份《基于循环神经网络的航班延误预测模型》就是冲着这个痛点去的。作者刘亮来自青岛空中交通管理站,2019 年发表在《信息通信》上,用的是华东地区 9 个大型机场的真实空管历史数据,把 RNN 和 LSTM 单元混搭起来搭了一个延误预测模型。它不是纯理论推演,而是从空管自动化系统、航空气象网、管制运行品质系统里真刀真枪抽数据做出来的。适合谁看?做交通时序预测的算法工程师、民航信息化方向的研究生、以及想把深度学习落到行业数据上的从业者。下面我按「模型怎么立住 → 数据怎么进 → 代码怎么跑 → 坑在哪」的顺序,把这份论文拆成能复现的步骤。

2. RNN 与 LSTM 混合结构:从状态公式到细胞单元的四步更新

2.1 RNN 的状态传递机制与梯度消失的由来

RNN 的核心思想不复杂:网络在处理当前时刻数据时,会把上一时刻的隐藏层状态一起拿进来算。论文里给了两个状态公式,我把它翻译成能看懂的话。公式一是隐藏层状态更新:St = σs(U·Xt + W·S(t-1)),其中Xt是 t 时刻的输入,S(t-1)是上一时刻的隐藏状态,U是输入层到隐藏层的权重矩阵,W是上一时刻状态到当前状态的转移矩阵。公式二是输出:Ot = σo(V·St),V是隐藏层到输出层的权重矩阵。两个激活函数σs和σo通常取 tanh 或 ReLU。

关键点在于,RNN 在所有时刻共享同一组参数 U、W、V。这意味着每个输出的梯度不仅依赖当前时刻的计算,还要沿着时间轴往回传。训练用的是 BPTT(时间反向传播)算法。问题就出在这:当序列拉长,梯度在反复相乘中要么趋近于零(梯度消失),要么爆炸式增长(梯度发散)。论文明确点出了这个缺陷——用 BPTT 训出来的 RNN 很难学到长期依赖。放到航班延误场景里,如果只看前两三个时段,RNN 够用;但延误往往有跨天、跨时段的累积效应,这时候纯 RNN 就力不从心了。

2.2 LSTM 的三个门与细胞状态更新四步

LSTM 的改进思路是在 RNN 的隐藏层里塞进一个「存储处理器」,论文里叫细胞单元(cell)。一个 cell 里有三个门:输入门 i、遗忘门 f、输出门 o。数据进来后,先判断哪些信息有用,符合规则的留下,不符合的通过遗忘门丢掉。这种「一进二出」的传递机制,在反复更新中解决了 RNN 的梯度问题和长期依赖问题。

论文把 LSTM 的细胞单元更新拆成四步,我按自己的理解重新讲一遍,方便你对着代码看:

第一步,遗忘门决定从旧细胞状态C(t-1)里舍弃什么。它同时接收当前输入Xt和上一时刻隐藏状态h(t-1),经过一个 Sigmoid 层,对C(t-1)中每个元素输出一个 0 到 1 之间的值。1 表示完全保留,0 表示完全遗忘。

第二步,确定要往细胞状态里存什么新信息。先用输入门的 Sigmoid 层决定哪些值需要更新,再用 tanh 层生成一个候选向量C~t,准备加进细胞状态。

第三步,更新旧细胞状态。把旧状态C(t-1)乘以遗忘门输出ft,丢掉该丢的;再加上it * C~t,得到新状态Ct。

第四步,确定隐藏层输出。细胞状态Ct先过 tanh 函数过滤,再和输出门 Sigmoid 层的输出相乘,得到最终输出ht。

这四步就是 LSTM 能扛住长期依赖的根本原因——细胞状态像一条传送带,信息在上面流动时只经过少量线性交互,梯度不容易被反复稀释。论文选择 RNN 全连接层加 LSTM 单元层作为隐藏层,正是看中了 LSTM 在时间维度上挖掘多层依赖关系的能力。

2.3 混合模型的层结构设计

论文的模型结构不是纯 LSTM 堆叠,而是「全连接隐藏层 → LSTM 单元层 → 全连接隐藏层」的混合结构。输入层之后先接一个全连接隐藏层,作用是搭建数据之间的关联性结构,把航班计划、天气、流控这些异构特征先做一轮融合。然后接 LSTM 细胞单元层,建立深度反馈网络,分析各项数据对延误的影响,挖掘时间维度上的多层依赖。最后再接一个全连接隐藏层,对中间数据做处理,输出预测结果。

为什么这么设计?我个人的理解是:纯 LSTM 直接吃原始特征,容易在特征维度高、量纲差异大的时候训练不稳定。前面加一层全连接做特征压缩和融合,能让 LSTM 专注于时序关系的建模。后面加一层全连接做输出映射,是因为 LSTM 的输出维度未必和预测目标维度对齐。这种「FC-LSTM-FC」的骨架,在工业时序预测里是很常见的做法,不算花哨但稳。

训练优化方面,论文用的是 SGD(随机梯度下降)。相比传统梯度下降每次迭代用全部样本求最优解,SGD 每个迭代步骤只用一个样本,计算时间和存储空间都明显减少。论文还提到一个细节:单个样本的噪声不确定性会让算法不收敛到局部最优的直接下降方向,但当样本量足够大时,反而能用更少的时间和计算资源找到最优路径。为了防止样本过少和噪声过小带来的过拟合,研究里用了随机抽样程序,每个迭代步骤随机选样本。这个做法本质上是给训练过程加噪声,提升模型适应性。

3. 数据管道搭建:从空管自动化系统到模型输入张量

3.1 三类数据源的字段结构与抽取逻辑

论文的数据来源分三块,每块的字段结构不一样,需要分别处理后再对齐。

第一块是空管自动化系统的航班数据,分两类。航班计划数据:航班号、起飞机场、落地机场、预计起飞时间、预计落地时间。航班执行情况数据:航班号、航班实际起飞时间、航班实际落地时间。这两类数据按航班号关联,就能算出每个航班的实际延误时长。

第二块是青岛航空气象网的天气数据,包括起飞机场和落地机场的 METAR 报文和 TAF 报文。METAR 是实时气象观测,TAF 是机场预报。论文从报文里提取能见度、天气现象、雨雪量,并按每 3 小时取平均值。这里注意,METAR 报文是文本格式,需要写解析器把关键字段抠出来。

第三块是青岛管制运行品质系统的流控数据,包括近 2 年青岛管制区域每日外部限制情况,以及区域内三条重要航线的每日受限情况。论文坦承,华东整个区域的流控数据拿不到,所以只在青岛的模型里用了这块数据。这是个很实在的边界说明——做行业模型,数据可得性往往比算法选择更决定成败。

数据筛选范围是华东地区 9 个大型机场,这些机场在 2018 年全国吞吐量排名前 30。所有历史航班中,只保留在这 9 座机场起降的航班。

3.2 按落地机场分组与时间序列构造

论文的一个关键处理是:将历史数据按落地机场分组。为什么要按落地机场分?因为不同机场的延误模式差异很大。虹桥的延误可能主要受华东区域流控影响,而厦门高崎可能更多受天气和跑道容量制约。混在一起训,模型学到的是一锅粥。按落地机场分组后,每个机场的进离港航班每日计划序列可以独立输入模型。

构造时间序列时,需要把每日的航班计划、执行情况、天气、流控数据按时间轴对齐,形成「天」粒度的序列样本。输入是连续若干天的多维特征,输出是后续天数的延误状态。论文里说的「预测后续天数的延迟状态」,我理解是一个多步预测或者单步滚动预测的设定。具体窗口长度论文没给,这在实际复现时需要自己调。

下面给一段数据预处理的骨架代码,用 pandas 做分组和序列构造。注意这不是论文原代码,是我按论文描述补的常见做法:

import pandas as pd import numpy as np # 假设 flight_df 包含: flight_no, dep_airport, arr_airport, # sched_dep, sched_arr, actual_dep, actual_arr # weather_df 包含: airport, date, visibility, weather_phenom, precip # flow_df 包含: date, restriction_level def build_sequence(flight_df, weather_df, flow_df, arr_airport, window=7): # 1. 筛选落地机场 df = flight_df[flight_df['arr_airport'] == arr_airport].copy() # 2. 计算每个航班的延误时长(分钟) df['delay_min'] = (df['actual_dep'] - df['sched_dep']).dt.total_seconds() / 60 df['delay_min'] = df['delay_min'].clip(lower=0) # 负值归零 # 3. 按天聚合:每日平均延误、延误航班占比 df['date'] = df['sched_dep'].dt.date daily = df.groupby('date').agg( avg_delay=('delay_min', 'mean'), delay_ratio=('delay_min', lambda x: (x > 15).mean()) # 15分钟以上算延误 ).reset_index() # 4. 合并天气和流控 daily = daily.merge(weather_df, on='date', how='left') daily = daily.merge(flow_df, on='date', how='left') daily = daily.fillna(method='ffill').fillna(0) # 5. 构造滑动窗口序列 feature_cols = ['avg_delay', 'delay_ratio', 'visibility', 'precip', 'restriction_level'] data = daily[feature_cols].values X, y = [], [] for i in range(len(data) - window): X.append(data[i:i+window]) y.append(data[i+window, 0]) # 预测下一天的平均延误 return np.array(X), np.array(y) X, y = build_sequence(flight_df, weather_df, flow_df, arr_airport='ZSSS', window=7) print(X.shape, y.shape) # 例如 (300, 7, 5) (300,)

这段代码的逻辑说明:第一步按落地机场筛选,对应论文的「按落地机场分组」。第二步算延误时长,用实际起飞减预计起飞,负值归零是因为提前起飞不算延误。第三步按天聚合,把航班级数据压成日级特征,delay_ratio用 15 分钟阈值是民航常见的延误判定标准。第四步合并天气和流控,用前向填充处理缺失值。第五步构造滑动窗口,window=7表示用过去 7 天预测第 8 天,特征列选了 5 个,实际复现时可以根据数据可得性增减。

参数说明:window是时间窗口长度,论文没给具体值,7 天是一个保守起点,可以试 3、5、14。delay_ratio的阈值 15 分钟可以改成 30 或 60,取决于你对「延误」的定义。fillna(method='ffill')对天气数据是合理的,因为气象条件有持续性;但对流控数据,如果缺失可能意味着当天没有限制,填 0 更合适,这里需要按业务判断。

3.3 特征归一化与样本划分的注意事项

论文没有展开讲归一化,但这是实操里绕不过去的。延误时长、能见度、降水量、流控等级,量纲差异巨大。延误时长可能几百分钟,能见度是几千米,降水量是毫米级,流控等级可能是 0 到 3 的离散值。直接喂给 LSTM,梯度会被大量纲特征主导。

常见做法是对连续特征做 Min-Max 归一化或 Z-Score 标准化。Min-Max 把值压到 [0,1],适合有明确边界的特征如能见度;Z-Score 适合分布接近正态的特征。流控等级这种离散有序变量,可以做 embedding 或者 one-hot,但论文里数据量不大,直接当连续值归一化也能跑。

样本划分要注意时序性。不能随机打乱后划分训练集和测试集,否则未来信息会泄露到训练中。正确做法是按时间切分:前 80% 的时间段做训练,后 20% 做测试。如果要做交叉验证,用时间序列交叉验证(TimeSeriesSplit),不要用 KFold。

提示:航班延误数据有很强的季节性和节假日效应。春运、暑运、黄金周的延误模式和平日完全不同。如果训练集里没有覆盖这些时段,模型在对应时段的预测会明显偏弱。建议在特征里加入月份、星期、是否节假日等时间标识。

4. 模型训练与调参:SGD、过拟合与序列长度怎么定

4.1 SGD 在延误预测中的参数设置

论文明确用了 SGD 做优化。SGD 的核心参数是学习率。学习率太大,损失震荡不收敛;太小,收敛慢且容易卡在局部最优。论文没给具体学习率,我一般会从 0.01 或 0.001 起步,配合学习率衰减策略。比如每 10 个 epoch 乘以 0.9,或者用 ReduceLROnPlateau 在验证损失不降时减半。

动量(momentum)是 SGD 的常用搭档,取 0.9 是默认值。动量帮助 SGD 在相关方向上加速,在震荡方向上抑制,对时序数据这种梯度方向变化频繁的场景很有用。权重衰减(weight decay)可以加一点,比如 1e-4 或 1e-5,相当于 L2 正则,防止权重过大。

batch size 的选择和 SGD 的随机抽样有关。论文说每个迭代步骤随机选样本,这其实是 batch size 等于 1 的纯 SGD。但纯 SGD 训练太慢且不稳定,实际复现时用 mini-batch 更常见,batch size 取 32 或 64。论文提到的「随机抽样程序」可以理解为每个 epoch 前 shuffle 训练样本,或者用 DataLoader 的 shuffle=True。

下面给一段 PyTorch 的训练骨架,展示 FC-LSTM-FC 结构和 SGD 配置:

import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset class DelayPredictor(nn.Module): def __init__(self, input_dim, hidden_dim, fc_dim, output_dim=1): super().__init__() # 前置全连接层:融合异构特征 self.fc1 = nn.Sequential( nn.Linear(input_dim, fc_dim), nn.ReLU() ) # LSTM 层:建模时序依赖 self.lstm = nn.LSTM(fc_dim, hidden_dim, batch_first=True) # 后置全连接层:输出映射 self.fc2 = nn.Linear(hidden_dim, output_dim) def forward(self, x): # x: (batch, seq_len, input_dim) batch, seq_len, _ = x.shape x = x.view(batch * seq_len, -1) # 展平做全连接 x = self.fc1(x) x = x.view(batch, seq_len, -1) # 还原序列维度 lstm_out, _ = self.lstm(x) # LSTM 前向 out = self.fc2(lstm_out[:, -1, :]) # 取最后时刻输出 return out # 超参数 input_dim = 5 # 特征数 hidden_dim = 64 # LSTM 隐藏单元数 fc_dim = 32 # 全连接层维度 lr = 0.001 epochs = 100 batch_size = 32 model = DelayPredictor(input_dim, hidden_dim, fc_dim) criterion = nn.MSELoss() optimizer = torch.optim.SGD(model.parameters(), lr=lr, momentum=0.9, weight_decay=1e-5) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=20, gamma=0.5) # 假设 X_train, y_train 已归一化 train_ds = TensorDataset(torch.FloatTensor(X_train), torch.FloatTensor(y_train)) train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True) for epoch in range(epochs): model.train() total_loss = 0 for xb, yb in train_loader: optimizer.zero_grad() pred = model(xb).squeeze() loss = criterion(pred, yb) loss.backward() optimizer.step() total_loss += loss.item() scheduler.step() if (epoch + 1) % 10 == 0: print(f"Epoch {epoch+1}, Loss: {total_loss/len(train_loader):.4f}")

逻辑说明:fc1对应论文的前置全连接隐藏层,把 5 维特征压到 32 维。lstm对应 LSTM 单元层,隐藏维度 64。fc2对应后置全连接层,输出 1 个预测值。前向传播里先把序列展平做全连接,再还原维度送 LSTM,最后取最后一个时刻的隐藏状态做预测。这是「FC-LSTM-FC」的标准写法。

参数说明:hidden_dim=64是起点,数据量大可以加到 128 或 256,但论文的数据规模不大,64 够用。lr=0.001配合 StepLR 每 20 个 epoch 减半,是保守配置。momentum=0.9和weight_decay=1e-5是常规值。batch_size=32比论文的纯 SGD 更稳,如果显存够可以加到 64。

4.2 过拟合的识别与应对

论文专门提到「防止样本过少以及样本噪声过小带来的过度拟合问题」。航班延误数据按天聚合后,一个机场一年也就 365 条样本,9 个机场加起来 3000 多条。对 LSTM 来说,这个量级偏小,过拟合风险很高。

识别过拟合的信号:训练损失持续下降,但验证损失在某个 epoch 后开始上升;或者训练集 R² 很高,测试集 R² 明显低。应对手段有几个。第一,Dropout。在 LSTM 层后加nn.Dropout(0.2)或0.3,训练时随机丢弃部分神经元。第二,早停(Early Stopping)。验证损失连续 10 个 epoch 不降就停,保存验证损失最低的模型。第三,减小模型容量。把hidden_dim从 64 降到 32,或者减少 LSTM 层数。第四,数据增强。对时间序列可以做加噪声、时间扭曲、窗口切片等增强,但民航数据增强要谨慎,别把物理规律破坏了。

论文提到的「随机抽样程序」也可以理解为一种正则化——每个迭代步骤随机选样本,相当于给梯度加噪声,让模型不会死记硬背训练样本。这个思路和 Dropout 异曲同工。

4.3 序列长度与预测步长的权衡

序列长度(window)和预测步长(horizon)是一对需要权衡的参数。window 太短,模型看不到足够的时序上下文,学不到长期依赖;window 太长,样本数减少,训练变慢,而且 LSTM 对超长序列的记忆能力也有限。论文没给具体值,我一般会试 3、7、14、30 天几档。

预测步长方面,论文说的是「预测后续天数的延迟状态」,可以是一步预测(预测明天),也可以是多步预测(预测未来 3 天)。一步预测准确率高但实用价值有限,多步预测实用但误差会累积。常见做法是训练一个一步预测模型,然后滚动预测:用预测出的明天去构造后天的输入,逐步往后推。但滚动预测的误差会放大,需要监控。

注意:如果做多步预测,损失函数可以改成对多步误差加权求和,让近期的预测误差权重更高。比如预测未来 3 天,权重设为 [0.5, 0.3, 0.2],这样模型优先保证明天的准确率。

5. 避坑与排查:数据对齐、梯度异常与评估陷阱

5.1 现象:模型在训练集上表现很好,测试集一塌糊涂

原因:最常见的是数据泄露。比如归一化时用了全量数据的均值和方差,而不是只用训练集的统计量。或者随机打乱了时序数据做训练测试划分,导致未来信息泄露。另一个原因是过拟合,样本量太小而模型容量太大。

解决:归一化参数必须从训练集计算,然后应用到测试集。时序数据必须按时间切分,不能随机打乱。如果确认是过拟合,加 Dropout、减层数、加早停。我一般会先画训练损失和验证损失的曲线,如果两条线在某个点后分叉,就是过拟合;如果验证损失从一开始就高于训练损失很多,可能是数据分布不一致。

5.2 现象:损失变成 NaN 或者突然爆炸

原因:梯度爆炸。RNN 和 LSTM 在长序列上训练时,梯度可能指数级增长。学习率太大也会导致参数更新过猛,损失发散。另外,如果输入特征没有归一化,大量纲特征会让梯度计算溢出。

解决:加梯度裁剪(gradient clipping),torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0),把梯度范数限制在 1.0 以内。降低学习率,从 0.001 降到 0.0001 试试。检查输入数据有没有 NaN 或 Inf,归一化是否到位。如果用了 log 变换,注意零值和负值。

5.3 现象:预测值总是接近均值,没有波动

原因:模型学成了「均值预测器」。在 MSE 损失下,如果模型无法捕捉时序模式,最优策略就是输出训练集均值,这样损失最小。这通常意味着输入特征没有提供有效信息,或者序列长度太短,模型看不到有意义的模式。

解决:检查特征和延误的相关性,如果所有特征和目标的相关系数都接近零,换特征比调模型更有效。增加序列长度,让模型看到更长的上下文。换损失函数,比如用 MAE 或者 Huber Loss,对异常值不那么敏感,可能逼模型学更有结构的输出。另外,检查延误数据本身是否有足够的方差,如果某个机场常年准点,预测均值反而是对的。

5.4 现象:不同机场的预测效果差异巨大

原因:论文按落地机场分组建模,但不同机场的数据量、延误模式、影响因素差异很大。大机场数据多、模式复杂,小机场数据少、模式简单。如果统一用一套超参数,效果必然参差。

解决:按机场分别调参。数据量大的机场可以用更大的模型和更长的序列,数据量小的机场用简单模型甚至退回到统计方法。论文选了 9 个吞吐量前 30 的机场,数据量相对有保障,但如果你要扩展到中小机场,需要单独评估。另一个做法是多任务学习,共享底层 LSTM,每个机场有自己的输出头,这样小机场可以借大机场的数据提升泛化。

5.5 现象:METAR 报文解析出错导致特征缺失

原因:METAR 报文是固定格式的文本,但不同气象站、不同时段的报文格式可能有细微差异。比如能见度单位有时是米有时是英里,天气现象代码有几十种组合。用简单的字符串分割很容易漏字段或解析错。

解决:用成熟的气象报文解析库,比如 python-metar,而不是自己写正则。解析后做合理性检查:能见度不应该为负,降水量不应该超过物理上限,温度露点差应该在合理范围。对解析失败的报文,标记为缺失而不是填零,因为零能见度和缺失能见度是完全不同的含义。论文里按每 3 小时取平均,这个聚合窗口也要注意,如果某个时段报文缺失,平均值的代表性会下降。

6. 从论文到落地:滚动预测的工程化技巧与验证方法

论文的模型输出是「后续天数的延迟状态」,但真正要落地到空管运行,一步预测不够用。运行单位需要的是未来 3 天甚至 7 天的延误趋势,好提前调配地面保障资源、安排备降方案。这就涉及滚动预测的工程化。

滚动预测的基本思路是:用训练好的模型预测明天,把预测值当作已知值拼接到输入序列末尾,再预测后天,如此往复。但这里有个坑——预测误差会逐步累积,到第三天可能已经偏得离谱。我的做法是给滚动预测加一个误差修正项:每次预测后,用最近几天的实际值和预测值的偏差来校正下一步的输入。具体来说,维护一个偏差滑动窗口,预测时把当前偏差加回去。这个技巧在时间序列预测里叫 bias correction,实现简单但效果明显。

验证方法上,除了常规的 RMSE、MAE、R²,我建议加两个业务指标。一个是延误等级的分类准确率:把连续延误值离散成「准点、轻微延误、中度延误、严重延误」四档,看模型分对档的比例。空管人员更关心「明天是不是严重延误」,而不是具体延误 47 分钟还是 52 分钟。另一个是趋势方向准确率:预测的延误是上升还是下降,方向对了,即使数值有偏差,运行决策也能用。

下面给一段滚动预测加偏差修正的代码:

def rolling_predict(model, last_sequence, steps=3, bias_window=3): """ model: 训练好的 DelayPredictor last_sequence: 最近 window 天的特征序列, shape (1, window, input_dim) steps: 预测未来几天 bias_window: 用最近几天的偏差做修正 """ model.eval() preds = [] seq = last_sequence.clone() recent_bias = 0.0 with torch.no_grad(): for step in range(steps): pred = model(seq).item() pred_corrected = pred + recent_bias # 偏差修正 preds.append(pred_corrected) # 构造下一步输入:把预测值当作延误特征拼到序列末尾 # 这里假设延误特征是第 0 维,其他特征用最后一天的值填充 next_feat = seq[:, -1, :].clone() next_feat[:, 0] = pred_corrected seq = torch.cat([seq[:, 1:, :], next_feat.unsqueeze(1)], dim=1) return preds # 假设 last_seq 是最近 7 天的归一化特征 preds = rolling_predict(model, last_seq, steps=3) print("未来 3 天延误预测:", preds)

逻辑说明:rolling_predict每次预测一步,把预测值填回序列的延误特征位,其他特征保持不变(实际中天气和流控需要外部预报输入)。recent_bias是偏差修正项,初始为 0,实际使用时可以用最近几天的预测偏差均值来更新。seq[:, 1:, :]是滑动窗口,丢掉最旧的一天,拼上最新预测的一天。

参数说明:steps=3是预测天数,可以改成 7。bias_window=3是偏差窗口,代码里没展开,实际可以维护一个实际值队列,每次有新实际值时计算偏差。注意预测值填回序列时,如果延误特征做了归一化,要记得反归一化后再填,或者保持归一化空间一致。

验证的时候,我会把滚动预测的结果和实际值按天对齐,画一张时间序列对比图。重点看三个地方:延误高峰有没有抓到、趋势转折有没有提前预警、平峰时段的误报多不多。如果高峰抓不到,说明模型对极端值不敏感,可以给损失函数加权,让大延误样本的权重更高。如果平峰误报多,说明模型太敏感,可以加一个阈值,只有预测延误超过某个值才触发预警。

从那以后我每次做时序预测项目,都会强制走一遍「按时间切分 → 训练集统计量归一化 → 滚动预测验证 → 业务指标评估」这个流程,不再只看 RMSE。这套流程帮我避开了很多次「离线指标漂亮、上线就翻车」的坑。希望帮到你。

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

返回列表