
简介基于Python与神经网络实现的共享单车调度系统源码以求解最优单车调度路径为核心面向计算机、人工智能、数据科学等相关专业的在校学生与开发者可支撑毕业设计、课程设计、大作业等任务。压缩包共16个文件、548KB以11个Python脚本为主体并配有4个npy数据文件及1个说明文档脚本涵盖Geohash解码、区域划分、POI关联、需求统计、BP神经网络训练、误差计算和蚁群算法调度等完整环节npy文件存储训练与测试数据txt说明便于快速上手。已有160人学习/下载。通过该源码可系统了解共享单车调度从数据预处理、神经网络建模到路径寻优的落地流程项目模块边界清晰既适合入门者对照学习也便于二次开发和扩展作为毕设或课设演示具有不错的参考价值。1. 神经网络调度系统解决的是哪一类共享单车问题早高峰的地铁出站口共享单车堆到人行道上同一时间的小区门口一辆车都找不到。这是每天都会发生的潮汐调度问题。标题里这套 Python 开发基于神经网络开发的共享单车调度系统源码(最优单车调度路径)解决的就是“预测哪里会缺车或爆仓并按最优路径派调度车”这一件事。它用神经网络预测各站点未来一段时间的借还量再用路径优化算法算出调度车跑哪些站点、先跑谁后跑谁。适合两类人一类是做课程设计或毕设的计算机、交通方向学生想跑通并看懂源码另一类是负责单车运营调度的团队想把手动派单替换成数据驱动方案。下面按“建模—预测—路径—排错—验证”的顺序拆开讲。2. 调度问题为什么需要神经网络先想清楚这是两道题一套调度系统让人看不懂通常不是因为代码难而是因为问题没拆开。共享单车调度看上去是“哪缺送哪”实际上要回答两个完全不同的问题未来哪些站点会缺车或爆仓调度车按什么顺序跑才能又快又省前者是时间序列预测后者是组合优化。神经网络负责第一问路径算法负责第二问。2.1 调度本质是“预测 优化”两段式问题潮汐效应是共享单车调度的根因早高峰住宅区向外流出、办公区流入晚高峰反向流动下雨天、周末、大型活动还会改变流向。站点库存不是“一个静态数”而是“一条随骑行需求起伏的曲线”。如果只按当前库存调度决策永远慢半拍看到缺车时车早已被骑走看到堆满时高峰期已经结束。把决策链条完整展开应该是这样的先预测未来 1 到 2 小时每个站点的借出量和归还量用“归还 — 借出”算出站点的净变化量结合当前库存判断哪些站点会越界把越界站点连同调度量打包成任务清单最后给调度车排一条访问顺序。任何一个环节出错后台的人工调度员就得自己拿对讲机补位。人工作业的常见做法是看监控大屏和网格员上报哪缺补哪。这种做法的局限很直接网格员看到的只有当下预测不了半小时后的需求多个站点同时缺车时“先跑近的”也不等于“先跑对的”。神经网络在这里的价值是少做无用功——提前知道哪些站点会越界调度车出发时就有了确定性。拿到原始骑行数据后第一件事不是建模而是先把潮汐方向算出来判断哪些站点是净流出、哪些是净流入import pandas as pd # 原始订单表每一行是一次骑行含起点、终点、起止时间 df pd.read_csv(trips.csv, parse_dates[start_time, end_time]) # 按小时统计每个站点的借出量和归还量 rent df.groupby([df[start_station], df[start_time].dt.hour])[bike_id].count() ret df.groupby([df[end_station], df[end_time].dt.hour])[bike_id].count() # 净流量 归还量 - 借出量正数为净流入负数为净流出 net rent.sub(ret, fill_value0) net.groupby(start_station).mean()逻辑说明这段代码用分组聚合把潮汐方向量化。如果某个站点在早高峰时段的净流量长期为负说明它是“供血站”早高峰需要优先补车反过来净流量为正的站点是“蓄血池”需要及时把车调走否则堆满后用户还不了车。参数说明这里小时粒度只是粗判方向后面做预测要降到 15 分钟粒度否则高峰拐点会被抹平。2.2 神经网络在系统里的角色预测器不是决策器第一次看这个标题的人多少会以为源码里有一张巨大的神经网络输入全城站点状态输出完整的调度方案。端到端方案在论文里不少见但在真实运营里很难落地动作空间是“站点排序”属于离散组合空间神经网络输出很难保证合法性而且模型完全不解释为什么这么调度一线师傅不敢执行。常见做法是把系统拆成两个可替换的模块神经网络只做需求预测器输出各站点的借出量、归还量路径规划交给遗传算法这类启发式优化算法来求“最优单车调度路径”。这样做的好处是两块可以分别验证、分别升级。预测错了路径算法再强也白搭路径不对预测再准也到不了站。拆开以后问题定位容易得多。有人会问路径规划为什么不直接用强化学习可以但在这个场景里性价比不高。强化学习做组合优化要精心设计状态、动作和奖励还要给每个城市规模重新训练遗传算法不用训练输入距离矩阵和调度量就能跑改容量、改车辆数都只是改参数。对源码学习者和一线团队来说遗传算法是更可靠的第一步等摸清数据规律再考虑换强化学习不迟。2.3 同为神经网络为什么时序预测不选BP而选LSTM在这个系统里选哪种神经网络取决于数据类型。骑行量是按 15 分钟采样的时间序列有早晚高峰周期、有天气扰动。有些人照着 BP 神经网络结构图把全连接层搭起来把过去 7 个时段摊平当特征也能拟合训练集。问题是前馈网络对输入顺序不敏感把“今天 20 点”和“昨天 20 点”的特征交换输出几乎不变周期信号的信息就丢了。CNN 的强项是空间特征提取适合图像、网格这类有局部相关结构的数据站点时序只有一维卷积核扫过去意义有限。LSTM 这类循环网络按时间步展开门控机制让信息沿着时间方向传递天然适合早高峰这种“前几个时段逐步爬升、破峰后回落”的形态。训练 LSTM 时正向、反向传播和残差计算都在时间步之间传递和 BP 在全连接层逐层回传梯度的方式不是一回事这也是两类网络行为差异的根源。网络类型擅长处理在调度系统中的定位BP / 前馈网络静态特征映射能做但丢时间顺序预测上限低CNN图像、网格类空间数据不适合站点一维时序LSTM / GRU时间序列、周期信号作为预测主力捕捉早晚高峰周期性还有一个工程经验值得说全城站点如果上百个逐个站点训练模型代价不小。我一般会先对站点做聚类地铁口、小区、写字楼、校园各成一类同一类站点共享一个模型。这样模型数量从“站点数”降到“类别数”训练数据和参数规模都健康很多。3. 把需求预测做稳站点借还量预测与训练配置预测模块是整个调度的“眼睛”。眼睛看错后面路径算得再漂亮都是白跑。这一章按数据处理、模型训练、调度量换算三步走每一步都有可以直接改参数照跑的代码。3.1 从骑行记录到站点-时段特征矩阵的预处理原始订单表通常只有几列bike_id、start_station、end_station、start_time、end_time。建模前要把它重排成“站点 × 时间槽”的流量矩阵一个时间槽的宽度建议取 15 分钟。太细5 分钟会让大部分站点一个槽内只有 0 到 2 笔订单稀疏得没法学太粗1 小时又会把 7:30 到 8:30 这种跨高峰拐点的变化抹掉。import pandas as pd def build_sequence_dataset(trip_csv, slot_min15, history_len7): # 读取单笔骑行订单每行代表一次借出和一次归还 df pd.read_csv(trip_csv, parse_dates[start_time, end_time]) # 把时间对齐到 slot_min 分钟粒度再按站点聚合两个方向的流量 df[slot] df[start_time].dt.floor(f{slot_min}min) rent df.groupby([start_station, slot]).size().rename(rent_cnt) ret df.groupby([end_station, slot]).size().rename(return_cnt) ts pd.concat([rent, ret], axis1).fillna(0).reset_index() # 为每个站点生成滞后特征过去 history_len 个时段的借出/归还量 feature_cols [] for lag in range(1, history_len 1): shifted ts.groupby(start_station)[[rent_cnt, return_cnt]].shift(lag) shifted.columns [frent_lag{lag}, freturn_lag{lag}] feature_cols.append(shifted) ts pd.concat([ts] feature_cols, axis1) # 每个序列前 history_len 个时段没有滞后值丢掉这些不完整样本 ts ts.dropna() return ts逻辑说明借出量按 start_time 统计归还量按 end_time 统计各自用自己发生的时间槽归位不在建模阶段把“在途车辆”单独拆出来。滞后特征是把每个站点的历史序列往后平移让模型看到“过去 7 个时段这个站借了多少、还了多少”。dropna 删掉每个站点开头没有完整历史的部分避免模型学到一堆缺失值。参数说明history_len 取 7 意味着用过去约 105 分钟预测未来 15 分钟刚好覆盖一个完整的高峰前奏。想要更长上下文可以再拼一组 96 步一天前同一时段的周期间隔特征但特征维度翻倍训练时间变长建议先跑通再加深。如果手头有天气、温度、节假日数据按 slot 左连接进来即可不需要改模型结构只要输入特征维度对得上。3.2 搭建 LSTM 预测模型并完成训练闭环特征矩阵准备好后X 的形状是 [样本数, history_len, 特征数]y 的形状是 [样本数, 2]两个输出分别是未来一个时间槽的借出量和归还量。站点数量少的项目可以让所有站点共用一套模型把“站点类型”也拼进特征站点多、类型差异大的项目按上一章的聚类结果分组建模效果更稳。import torch import torch.nn as nn class StationDemandLSTM(nn.Module): def __init__(self, in_features, hidden64, num_layers2): super().__init__() self.lstm nn.LSTM(in_features, hidden, num_layers, batch_firstTrue) self.fc nn.Linear(hidden, 2) # 输出借出量、归还量两个标量 def forward(self, x): out, _ self.lstm(x) # out: [B, T, hidden] last out[:, -1, :] # 取最后一个时间步的隐状态 return self.fc(last)逻辑说明输入 x 保持 [批量, 时间步数, 特征数] 的三维形状batch_firstTrue 让第一个维度是批量。LSTM 每个时间步都会输出一个隐状态预测“未来”时只需要最后一个时间步的隐状态所以用 out[:, -1, :] 截取再经过一个全连接层压成两个数。def train_one_model(model, train_loader, val_loader, epochs30, lr1e-3): optimizer torch.optim.Adam(model.parameters(), lrlr) loss_fn nn.MSELoss() best_val float(inf) for epoch in range(epochs): model.train() total_loss 0.0 for xb, yb in train_loader: optimizer.zero_grad() pred model(xb) loss loss_fn(pred, yb) loss.backward() # 时间步上的残差在这里回传 optimizer.step() total_loss loss.item() * len(xb) # 每轮结束评估验证集连续变差就提前停 val_loss evaluate(model, val_loader) if val_loss best_val: best_val val_loss torch.save(model.state_dict(), best_model.pt)逻辑说明训练循环就是常规监督学习流程但要提醒一点——LSTM 的 loss.backward() 会把梯度沿时间步展开回传和 BP 全连接层逐层回传的路径不一样学习率太大会导致梯度爆炸。evaluate 函数自行实现即可关闭梯度后跑一遍 val_loader 返回平均 MSE连续 3 轮验证集不下降就停止。参数说明lr1e-3 是 Adam 的稳妥起点hidden64 对单站点序列足够两层 LSTM 已经能刻画早晚高峰周期堆到三层以上容易过拟合。epochs30 配合早停用别真跑满 30 轮。数据切分务必按时间顺序前 60% 训练、20% 验证、20% 测试归一化只用训练集的 min/max 去转换验证集和测试集这块没做好后面全是“验证集很漂亮、上线就翻车”的戏码。3.3 把预测值换算成调度量库存区间与净需求模型输出的是借出量和归还量还不是调度量。调度量的核心是“站点未来的库存会不会越界”。把预测归还量减预测借出量再加上当前库存得到不做调度时的预测期末库存只有库存低于下限或高于上限的站点才需要进入调度名单。import numpy as np def build_dispatch_demand(rent_pred, return_pred, stock_now, cap100, lower0.2, upper0.8): # 预测期末库存 当前库存 净流入 stock_future stock_now (return_pred - rent_pred) # 目标库存回到安全区间边界而不是填满或清空 target np.full_like(stock_future, np.nan) target[stock_future lower * cap] lower * cap target[stock_future upper * cap] upper * cap # 调度量 目标库存 - 预测期末库存正为放车负为收车 demand np.where(np.isnan(target), 0.0, target - stock_future) return demand逻辑说明这段代码输出带符号的调度量数组。demand 是正数说明预测期末库存低于下限调度车要往这个站点放车demand 是负数说明预测期末库存高于上限调度车要从这个站点收走车。关键在目标库存不是 100% 也不是 0而是回到安全区间——这能避免调度车反复空跑。参数说明lower0.2、upper0.8 是常见的初始值意思是库存保持在容量的 20% 到 80% 之间。容量 cap 要按站点实际桩位数填不是随便写 100。如果站点在高峰时段的借出量波动很大可以把 lower 抬高到 0.3给突发需求留余量。这里的“安全边际”需要根据历史最大净流出量来标定一线运营通常按“早高峰最大需求量 30% 余量”来改这两个数。举个例子一个容量 100 的站点当前库存 80模型预测未来 15 分钟借出 40、归还 10那么未来库存是 50没低于 20 的下限不需要调度。如果预测借出 60、归还 5未来库存只有 25已经低于下限这时需要补 20 - 25 的差值也就是放 5 辆车过去。这个例子说明不能只看“有没有车”还得看“将来够不够用”。4. 从预测结果到最优调度路径容量约束下的路径求解预测模块给出了每个站点带符号的调度量接下来要回答“调度车往哪跑”。这一问的数学本质是车辆路径问题VRP若干辆车从车场出发访问一批站点满足站点需求后回到车场要求总路程最短。共享单车版本的特殊之处在于需求有正有负——有的站点要放车有的站点要收车同一辆车可以在中途先收后放车上的存量随时不能超过容量。4.1 调度问题建模站点需求带正负号的车辆路径问题把问题参数化候选站点集合 S每个站点 i 有一个带符号需求 d_i正数表示需要放车负数表示需要收车调度车从车场出发容量为 C站点之间的行驶距离由距离矩阵 D 给出。目标是在容量约束下把一次出车任务划分成若干条路线使得车队总行驶距离最小同时尽量优先满足越界量大的站点。常见误区是直接把“缺车站点”按缺车数量从大到小排个序一辆车一路跑过去。这样顺序看着合理实际忽略了道路拓扑和容量可能前一个站点把车装满后面三个站点要放货却装不下司机只能在现场临时调整路线全乱。遗传算法搜索的是“访问顺序”解码时按容量切分成多辆车正好把这个约束装进去。站点间距离用经纬度算 haversine 距离就够了。地图 API 能给真实路网距离但离线场景和课程设计里经纬度球面距离已经能反映“谁离谁近”。实际运营如果要用可以先跑通再替换距离矩阵路径求解代码不用动。import numpy as np def haversine(a, b): # 用经纬度近似站点间距单位公里 lat1, lon1, lat2, lon2 map(np.radians, [a[0], a[1], b[0], b[1]]) dlat, dlon lat2 - lat1, lon2 - lon1 h np.sin(dlat / 2) ** 2 np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2) ** 2 return 6371 * 2 * np.arcsin(np.sqrt(h)) def route_distance(route, dist): # route 是站点编号列表车辆从车场(0)出发并回到车场 total dist[0][route[0]] for i in range(len(route) - 1): total dist[route[i]][route[i 1]] return total dist[route[-1]][0]逻辑说明haversine 计算的是球面最短距离6371 是地球半径公里数。route_distance 把一条路线的起止点都算进总距离符合调度车从车场出发、最终回场的真实动作。4.2 用遗传算法求解调度路径的代码骨架遗传算法的思路是用一条染色体表示“站点访问顺序”用适应度函数评价这条顺序的好坏然后反复选择、交叉、变异让种群整体往短距离方向进化。def fitness(seq, demand, dist, capacity): # 贪心切分模拟多辆车当前车辆装不下就新开一辆 routes, current, load [], [], 0.0 for idx in seq: load demand[idx] if abs(load) capacity: # 超容量当前车结束从下一辆重新开始 routes.append(current) current, load [], demand[idx] current.append(idx) routes.append(current) total sum(route_distance(r, dist) for r in routes) return 1.0 / (total 1e-9) def swap_mutate(seq, prob0.08): seq seq.copy() for i in range(len(seq)): if np.random.rand() prob: j np.random.randint(len(seq)) seq[i], seq[j] seq[j], seq[i] return seq def order_crossover(p1, p2): # 顺序交叉保留父本1的一段其余按父本2顺序填入 n len(p1) a, b sorted(np.random.choice(n, 2, replaceFalse)) child [None] * n child[a:b] p1[a:b] rest [x for x in p2 if x not in child[a:b]] pos 0 for i in range(n): if child[i] is None: child[i] rest[pos] pos 1 return child逻辑说明fitness 里用贪心切分模拟车队一辆车装不下就从下一个站点开新车。这个方法不保证切分本身最优但计算快而且能保证染色体合法GA 搜索过程中只需要关注“站点访问顺序”。适应度取距离的倒数是因为遗传算法默认“适应度越大越好”距离越小适应度越高。swap_mutate 是两点交换变异order_crossover 保持父本一段不动其余位置按另一个父本的相对顺序填充这是排列编码里最常用的交叉方式。def ga_solve(demand, dist, capacity, pop_size80, generations200): n len(demand) pop [np.random.permutation(n) for _ in range(pop_size)] for gen in range(generations): scores np.array([fitness(s, demand, dist, capacity) for s in pop]) order scores.argsort()[::-1] elites [pop[i].copy() for i in order[:5]] # 精英保留 new_pop elites[:] while len(new_pop) pop_size: i1, i2 np.random.choice(order[:20], 2, replaceFalse) child order_crossover(pop[i1], pop[i2]) child swap_mutate(child) new_pop.append(child) pop new_pop best_seq max(pop, keylambda s: fitness(s, demand, dist, capacity)) if gen % 50 0: print(fgen {gen}: {1 / fitness(best_seq, demand, dist, capacity):.2f} km) return best_seq逻辑说明每一代先算全体适应度并按分数排序前 5 个精英直接复制进下一代防止最优解在交叉变异中丢失。其余个体从排名前 20 的父本池里随机选两个交叉变异相当于简化版锦标赛选择。主循环每 50 代打印一次当前最优距离用来确认收敛情况。参数说明pop_size80、generations200 是起步值。站点规模 20 到 50 个时这个配置已经能看到明显收敛曲线。交叉概率在代码里默认总是执行交叉实际要调低到 0.8 到 0.9 之间可以加一个 if np.random.rand() 0.85 的判断变异概率保持 0.05 到 0.1超过 0.2 会退化成随机搜索。4.3 遗传算法参数怎么调种群、迭代、变异概率参数常见取值作用与翻车点种群大小 pop_size80-150太小容易早熟收敛陷在局部路线迭代次数 generations200-400看收敛曲线连续 50 代不降就停交叉概率0.8-0.9太高会破坏已经不错的路段顺序变异概率0.05-0.1过大退化为随机搜索精英保留数量5-10防止最优解被交叉破坏调参不要拍脑袋看收敛曲线。跑完 200 代后画一条“每代最优距离”曲线如果曲线在 80 代以后基本走平说明已经收敛加大迭代次数只会增加等待时间如果 200 代还在明显下降加到 400 代。如果多次运行结果波动超过 5%先加种群到 150再看变异概率有没有被设成 0.2 以上。还有两个工程细节值得注意。容量 C 必须按真实调度车标定常见三轮调度车一次装 20 到 40 辆不要写 100。demand 里如果存在绝对值特别大的站点一辆车单跑一个站点就会超容量说明该站点需要拆成两趟或改用更大车辆而不是让遗传算法硬解。跑完以后把 GA 的结果和简单贪心排序按需求绝对值降序对比如果优势不到 3%优先检查距离矩阵是不是算错了而不是继续调算法。5. 训练到部署必踩的5个坑现象、原因、排错顺序首次把整套调度系统跑通的人十个有八个会在下面五个地方翻车。每条都按“现象—原因—解决”整理后面再遇到直接对照排查。5.1 预测很准调度却不生效现象验证集 RMSE 很小模型预测曲线和真实曲线几乎贴合但跑完模拟调度后全城缺车率一点没降。原因模型预测的是自然状态下的骑行需求而调度动作本身会改变站点库存进而影响后续的借车和还车。把“预测值”直接当成“调度完的结果”忽略了反馈。调度车刚把车补进小区门口用户立刻骑走一批账面上看还是缺车但系统已经完成了该做的事。解决调度只针对越界站点做库存回归不追求“一步到位”评估指标不要只看预测误差要看“调度后不满足率”。把“预测—调度—再预测”放进历史回放里跑闭环才能看到真实效果。5.2 数据划分不当导致的预测“虚高”现象训练集和验证集的 loss 都好得反常一到测试集或者上线后误差立刻翻倍。原因两个典型错误。一是归一化时对全量数据集做 fit_transformscaler 偷看了验证集和测试集的均值方差二是随机切分样本把同一天的相邻时间槽同时分进训练集和测试集模型等于“提前知道了答案”。解决按时间顺序切分数据90 天数据用前 60 天训练、15 天验证、15 天测试归一化只用训练集 fit验证集和测试集只做 transform。构造滞后特征时每个站点序列开头的缺失值要丢干净不要让 NaN 填充值混进训练。5.3 新站点冷启动模型给出零预测现象新站点刚投放历史数据为空模型输出全部趋近 0调度系统完全忽略它。两周后这个站点的车堆成了山用户投诉才被发现。原因站点特征缺失时常见做法是 NaN 补 0但“没数据”被模型理解成“这个站点从来没人用”预测结果自然全是 0。调度量换算阶段又把 0 判定为“不需要调度”问题被放大。解决冷启动站点不单独预测。按上一章的聚类结果把它归入最近的热门站点组用组内均值做兜底预测或者用同类型站点地铁口、写字楼、小区的全城均值顶上。等地积累两周真实订单后再切回独立预测。5.4 调度车容量成了摆设现象算法生成的路线单车装载辆次远超调度三轮车容量。调度师傅拿到路线看都不看直接说没法执行。原因把容量约束做成适应度里的软惩罚项时惩罚系数设太小遗传算法为了压缩总距离宁可让路线超载也不愿意多绕路。软惩罚在这个场景下天然吃亏因为“超载”不会立即表现为距离变长。解决把容量检查前移到路线解码阶段上一章的贪心切分方式超容量直接切段生成新车从源头杜绝不可行解进入适应度计算。容量值按真实车辆标定不要拿系统总运力去填。提示遗传算法迭代完以后手动挑一条最优路线按顺序模拟一遍装载量变化。如果中途任何一段装载量超过容量说明解码有 bug先修代码再调参。5.5 时序粒度太粗导致高峰期调度迟滞现象用 1 小时粒度预测早上 7:30 发出调度指令调度车 8:00 才到站点此时用户已经把车骑走站点仍然空着。原因60 分钟粒度把“破峰”这个拐点抹平了模型以为整个 7 点到 8 点都在爬坡同时调度决策时刻和预测目标时刻之间隔着一段执行时间这条延迟没被考虑进预测目标。解决预测粒度降到 15 分钟预测目标从“未来 1 小时”改成“未来 30 到 45 分钟”给调度车留足在路上的时间。如果数据支持直接用“未来 3 个 slot 的累计净变化”作为训练标签等价于让模型学更短周期的窗口。5.6 排错顺序先查数据再查调度量最后查路径遇到调度效果差不要一上来就怀疑遗传算法。按这个顺序排查先看时间切分和归一化有没有泄漏再看预测结果按站点拆开的误差分布找出哪些站点系统性偏高或偏低然后检查调度量的正负号对不对——放车和收车方向反了是全系统最快翻车的写法最后才看路径算法因为预测错和调度量错都会让路径算法输出看似合理、实则无用的路线。6. 上线前怎么验证调度的有效性历史回放模拟与三个核心指标调度系统上线前别只看预测指标。常见做法是历史回放模拟和写 python 量化交易策略代码 时做回测是同一套思路拿过去 30 天的骑行订单和站点库存快照把后 5 天当成“假未来”按 15 分钟推进逐时段运行“预测 → 生成调度量 → 路径求解 → 移动车辆 → 更新库存 → 吃订单”的完整循环最后统计效果。核心指标看三个。第一是缺车不满足率某个 15 分钟内站点无车可借的订单量占该时段总需求的比例。第二是爆仓率站点库存超过容量 90% 的时长占比代表用户还不了车的概率。第三是单车日均周转率调度后车辆平均每天被租借次数这个数能判断调度是否过度干预、打扰了正常骑行。三个指标一起看缺车率降了但周转率暴跌说明车被调度到了没人骑的地方系统在空转。还要和基线对比。常见基线是贪心规则调度哪个站点库存低于阈值就派距离最近的车去补。如果神经网络调度方案只比贪心好 2%不值得上线因为维护模型、监控数据漂移都有长期成本。我习惯每次回放模拟后保留一份快照包括站点状态序列、预测结果、实际订单和调度路线这样出现问题时能回到具体某个时段逐字段复盘。这是这些年最值回票价的习惯。希望帮到你。本文还有配套的精品资源点击获取