☰
基于机器学习和时空网络图的航班分配优化实战
2026/10/22 4:05:37 网站建设 项目流程

简介:本资源面向航空公司并购扩展后的业务重组优化场景,提供一套基于机器学习与时空网络图的航班机型分配完整方案,适合运筹优化、机器学习方向的学习者及毕业设计参考。包内共12个文件,以6个ipynb代码文件为核心,配合2份docx论文、2份md说明文档、1个py脚本及license,压缩包约5.73MB,结构清晰便于按模块查阅。方案先用随机森林与概率统计方法分析历史产品的主要影响因素,并借助时间序列预测短期销售数量,为机型分配模型提供输入;再基于时空网络图构建含十万级变量与约束的大型混合优化模型,兼顾航班飞行与维护的时间、空间约束,分别以最大化用户需求和最大化销售总量为目标求解,输出若干可行分配组合并对比优劣。已有66人学习,读者可获取完整建模思路、Gurobi优化代码与论文文档,用于复现实验或拓展研究。

1. 航班分配为什么不能只靠人工排班:从一张延误传播图说起

某枢纽机场的早高峰,一个航班因为前序晚到被推迟 40 分钟。如果只是这一班晚点,调度员改一下机位、调一下机组就完事。但真实情况是:这架飞机后面还挂着 5 段航程,机组值勤时间逼近上限,衔接旅客里有 60 多人要转国际航班。一个 40 分钟的延误,会在时空网络里像水波一样扩散到十几个航班。这就是航班分配优化要解决的核心问题——它不是排一张静态时刻表,而是在「飞机、机组、旅客、时刻、机场容量」五重约束下,找一组让总延误和总成本最小的分配方案。

标题里的「基于机器学习和时空网络图」,说的正是当下主流的两条腿走路:用时空网络图把航班、机场、时间片建成图结构,把分配问题转成图上的路径与匹配问题;再用机器学习去预测延误传播、估计边权、给求解器提供更好的初始解或剪枝依据。这套东西适合谁?适合做运控、排班、运筹优化、图神经网络方向的工程师,也适合想拿一个「有真实约束、有图结构、有预测模块」的完整项目练手的人。下面我按「先立模型、再跑通、再避坑」的顺序,把这条路讲透。

2. 时空网络图怎么建:节点、边和三类权重的定义

2.1 为什么用时空网络而不是普通二分图

普通航班分配常被建模成「航班—机位」或「航班—机组」的二分匹配,问题是它丢掉了时间维度。同一架飞机在 8:00 和 10:00 能飞的航班,在二分图里长得一模一样,但实际约束完全不同。时空网络图的做法是:把每个「航班在某时刻的到达/出发事件」当成一个节点,节点带时间戳和机场标识;边分三类——飞行边(航班本身)、地面等待边(同一机场相邻时间片)、衔接边(同一飞机/机组可接续的两个航班)。这样一条从早到晚的路径,就代表一架飞机的完整排班,路径上的时间单调递增天然满足时序约束。

我一般会把时间离散成 15 分钟一个片,一天 96 个片。片太长会丢精度,片太短会让图规模爆炸。这个粒度是血泪经验换来的:5 分钟粒度下,一个中型枢纽一天的节点数能到几十万,求解器直接跪。

2.2 节点和边的字段设计

落地第一步是把原始航班数据转成图。下面这段是把航班记录构造成节点和边的核心逻辑,字段设计直接决定后面模型能不能跑。

import pandas as pd import networkx as nx # flights: 每行一个航班,含 dep_airport, arr_airport, dep_time, arr_time, tail_no def build_spatiotemporal_graph(flights: pd.DataFrame, slot_minutes: int = 15): G = nx.DiGraph() # 时间片函数:把时间戳对齐到离散片 def to_slot(ts): return int(ts.timestamp() // (slot_minutes * 60)) for _, f in flights.iterrows(): dep_node = (f["dep_airport"], to_slot(f["dep_time"]), "dep") arr_node = (f["arr_airport"], to_slot(f["arr_time"]), "arr") # 飞行边:代表航班本身,权重含飞行成本 G.add_node(dep_node, kind="dep", airport=f["dep_airport"]) G.add_node(arr_node, kind="arr", airport=f["arr_airport"]) G.add_edge(dep_node, arr_node, type="flight", flight_id=f["flight_id"], tail_no=f["tail_no"], cost=f["flight_cost"]) # 地面等待边:同机场、同机尾、时间相邻的到达->出发 for airport in flights["dep_airport"].unique(): nodes = [n for n in G.nodes if n[0] == airport] nodes.sort(key=lambda x: x[1]) for a, b in zip(nodes, nodes[1:]): if a[2] == "arr" and b[2] == "dep": G.add_edge(a, b, type="ground", cost=0.0) return G

逻辑说明:to_slot把连续时间压成离散片,是控制图规模的关键旋钮。飞行边携带tail_no,是为了后面按机尾聚合路径;地面等待边只在「到达节点 → 出发节点」之间连,保证时序不乱。参数上,slot_minutes建议 10~20,太小图爆炸,太大丢衔接可行性。cost先留空位,后面用预测模型填。

2.3 三类权重:成本、延误传播、容量惩罚

边权不是拍脑袋定的,通常拆成三块。第一块是硬成本,燃油、机组、起降费,直接来自运营数据。第二块是延误传播权重,这是机器学习要介入的地方——用历史数据训练一个模型,预测「如果这条衔接边被选中,下游平均会多晚多久」。第三块是容量惩罚,同一时间片同一机场的边数超过容量时给一个软惩罚,避免解出物理上不可能的方案。

权重类型数据来源更新频率作用
硬成本运营账单月度目标函数主项
延误传播历史延误日志 + 模型预测每日/每周让解偏向抗延误方案
容量惩罚机场时刻容量表季度软约束,防超卖

提示:容量惩罚系数不要设太大,否则求解器会为了躲惩罚把航班排到凌晨,解虽然可行但运营上没人接受。

3. 机器学习模块怎么接:延误预测与边权估计

3.1 预测目标的选择:别一上来就预测具体分钟数

很多人第一反应是回归预测「延误多少分钟」,然后发现 MAE 怎么调都下不去。原因是延误分布长尾极重,少数大面积延误把误差拉爆。更稳的做法是改成分类:把延误分成「准点/轻度/中度/重度」四档,模型输出概率,再把概率加权成期望延误当作边权。这样既保留了不确定性信息,又避免了回归被极端值带偏。特征上,我一般用前序航班延误、机场同时段流量、天气编码、机尾历史准点率、时段(早高峰权重高)这几类。

3.2 用梯度提升树做延误分档预测

下面是一个可复现的最小训练脚本,用梯度提升树做四分类,输出每档概率再折算期望延误。

import numpy as np import pandas as pd from sklearn.ensemble import GradientBoostingClassifier from sklearn.model_selection import train_test_split # df: 每行一个航班,label 为 0/1/2/3 四档延误 FEATURES = ["prev_delay", "airport_flow", "weather_code", "tail_on_time_rate", "hour_of_day", "is_peak"] def train_delay_model(df: pd.DataFrame): X = df[FEATURES].values y = df["label"].values X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, random_state=42) clf = GradientBoostingClassifier( n_estimators=200, # 树数量,200 在多数数据集上够用 learning_rate=0.05, # 学习率,配 200 棵树比较稳 max_depth=4, # 控制过拟合,别超过 5 subsample=0.8 # 行采样,抗噪 ) clf.fit(X_tr, y_tr) print("验证集准确率:", clf.score(X_te, y_te)) return clf # 把概率折算成期望延误(分钟),作为边权 DELAY_CENTER = np.array([0, 20, 60, 150]) # 四档的代表延误 def expected_delay(clf, x_row): proba = clf.predict_proba(x_row.reshape(1, -1))[0] return float(np.dot(proba, DELAY_CENTER))

逻辑说明:DELAY_CENTER是每档的代表值,用概率加权得到期望延误,比直接回归稳得多。参数上,max_depth超过 5 在小样本上必过拟合;learning_rate和n_estimators是一对,降学习率就要加树。失败时先看验证集准确率是否接近多数类占比——如果四档里重度只占 5%,模型全预测「准点」也有 95%,这时候要看混淆矩阵而不是准确率。

3.3 把预测边权写回图

训练完不是终点,要把expected_delay写回时空图的衔接边,让求解器用上。

def attach_predicted_weight(G, clf, feature_lookup): for u, v, data in G.edges(data=True): if data["type"] == "ground": feats = feature_lookup[(u, v)] # 预先按边索引好的特征 data["cost"] = expected_delay(clf, np.array(feats)) return G

这里feature_lookup要提前按边建好,别在循环里现查数据库,否则几十万条边能跑到天亮。这一步做完,图上的边权就同时含硬成本和预测延误,求解器优化的就是「总成本 + 期望延误」。

4. 求解与验证:从整数规划到滚动时域

4.1 建模成最小费用流还是整数规划

时空网络上的航班分配,最自然的建模是最小费用流:源点连早班出发、汇点连晚班到达,每条路径代表一架飞机的排班,费用就是边权和。最小费用流有多项式算法,规模大也能跑。但真实问题里有「机组值勤上限」「机场容量」这类耦合约束,纯流模型表达不了,得升级成带附加约束的整数规划(MIP)。我的经验是:先跑最小费用流拿到一个可行下界,再用 MIP 加约束逼近,两者对比能快速判断约束是不是写狠了。

4.2 用滚动时域应对实时性

全天一次性求解在实时场景里不现实。常见做法是滚动时域:每次只看未来 4~6 小时,求解后只执行最近 1 小时,然后往前滚。这样每轮问题规模可控,也能吸收实时延误。

def rolling_horizon_solve(G, solver, horizon_slots=24, step_slots=4): results = [] total_slots = max(n[1] for n in G.nodes) t = 0 while t < total_slots: window = [n for n in G.nodes if t <= n[1] < t + horizon_slots] sub = G.subgraph(window).copy() plan = solver(sub) # 调用你的 MIP/流求解器 results.append((t, plan)) t += step_slots # 只推进一小步,重叠部分做平滑 return results

参数说明:horizon_slots是预测窗口,step_slots是推进步长,两者比值决定重叠程度。比值太小(步长接近窗口)会导致相邻两轮解跳变,运营上表现为航班被反复改派;比值 1:4 到 1:6 比较稳。失败时先看窗口边界处的航班是不是总被改,那通常是边界效应,把窗口拉长或加边界惩罚项。

4.3 验证指标:别只看总延误

验证要分三层。第一层是可行性,检查有没有违反机组值勤、容量、衔接最小转场时间。第二层是运营指标,总延误、延误航班占比、最大单机延误。第三层是鲁棒性,用历史延误场景回放,看方案在扰动下的退化幅度。我一般会做一个对照:人工排班方案 vs 模型方案,在同一批历史场景下比。如果模型只在「平均延误」上赢,但在「最坏单机延误」上输,那这个方案在真实运控里会被调度员骂——因为他们最怕的是某一架飞机彻底崩掉。

指标人工基线模型方案关注点
总延误(分钟)基准应下降主目标
延误航班占比基准应下降覆盖面
最大单机延误基准不应变差鲁棒性红线
求解耗时分钟级秒级实时可用性

5. 避坑与排查:五个真实翻车点

5.1 图规模爆炸导致求解器内存溢出

现象:节点数到几十万后,MIP 求解直接 OOM 或跑几小时不出解。原因:时间片太细 + 把所有机场所有航班都塞进一张图。解决:按枢纽分区建子图,或先做航班聚类,把低频支线航班聚合;时间片从 5 分钟放宽到 15 分钟。我一般会先统计节点数,超过 20 万就先降粒度再谈建模。

5.2 预测边权分布偏移导致解偏向凌晨

现象:模型上线后,方案总把航班往凌晨排。原因:训练数据里凌晨样本少,模型对凌晨延误估计偏低,边权被系统性低估。解决:对时段做分层采样,或给凌晨边加一个固定惩罚;同时监控各时段被选中的边占比,偏离历史分布就告警。

5.3 滚动时域边界反复改派

现象:相邻两轮解差异大,同一航班被改来改去。原因:窗口边界处约束不完整,求解器在边界上「甩锅」。解决:加边界平滑惩罚,让上一轮已执行的分配在下一轮有惯性;或把步长调小、窗口调大。这个坑很玄学,调参时要有耐心。

5.4 机组约束漏写导致解不可执行

现象:模型解出来看着漂亮,但一排班就发现有机组连续值勤超时。原因:建模时只写了飞机衔接,忘了机组是独立资源。解决:把机组当成第二类流,或者加显式的值勤时长约束。别指望求解器自己懂法规,约束得你写。

5.5 验证集泄漏导致指标虚高

现象:离线指标很好,上线就崩。原因:延误预测的特征里混入了未来信息,比如用了「本次航班实际到达时间」去预测本次延误。解决:严格按时间切分训练/验证集,特征只允许用预测时点之前可得的信息。这个坑最隐蔽,建议做一次特征时间戳审计。

6. 进阶技巧:把预测不确定性真正用起来

前面把预测概率折成了期望延误,其实丢掉了不确定性。更进阶的做法是保留分布,做分布鲁棒优化:不优化期望,而是优化「最坏情况下的分位数」,比如 90 分位延误。这样解出来的方案在极端天气下退化更小。实现上,可以把每档概率当成一个场景,用场景法求解,或者直接在目标里加一个 CVaR 项。

另一个技巧是让预测模型和求解器联合迭代:先用当前边权求解,把解出来的路径对应的真实延误回灌到训练集,再重训预测模型。这个闭环跑两三轮回合,边权会越来越贴合实际运营。注意别迭代太多次,容易过拟合到最近几天的数据。

还有一个我常用的验证小技巧:把模型方案和人工方案混在一起,让有经验的调度员盲评,看他们能不能挑出哪个是模型出的。如果挑不出来,说明方案至少在「像人排的」这一点上过关了;如果一眼被认出,通常是某些约束写得太理想化。这个盲评比任何离线指标都直接。

最后说个我自己的习惯:每次调完参,我都会把「最坏单机延误」单独拉出来看一遍,而不是只看总延误。因为总延误可以靠牺牲个别航班来优化,但真实运营里,一架飞机崩掉会连带机组、旅客、后续航段一起崩。宁可总延误多几分钟,也不要出现一个失控的极端值。这个取舍没有标准答案,但心里得有这根弦。希望帮到你。

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

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

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

立即咨询