ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

DSDRO模型求解共享单车再平衡:动态定价与车辆路由联合优化Matlab实现

DSDRO模型求解共享单车再平衡:动态定价与车辆路由联合优化Matlab实现 做共享单车再平衡优化的朋友应该都有过这种经历白天调度车空跑、晚上某些站点塞满车、用户打开App发现附近三公里无车可骑。这些问题背后其实是一个典型的“供需时空错配”问题传统的做法是固定价格、人工调度、经验配车效果基本靠运气。这个项目给出的解法是构建一个DSDRO模型把动态定价和车辆路由放进同一个优化框架里联合求解用Matlab实现了完整可运行的源码第15178期。它适合正在研究共享出行系统优化、运筹学建模、或者想用分布鲁棒优化处理需求不确定性的同学参考也适合已经跑通基础调度模型、想进一步提升系统效率的从业者。我拿到这套代码之后第一个感受是它没有把问题简单粗暴地切分成“定价一个模块、路由一个模块”而是从系统层面把两个决策耦合在一起。这个思路是对的——因为定价改变的是需求分布而路由改变的是供给分布两边如果不一起算很容易出现“价格调了但车调不过来”或者“车调过去了但需求已经被价格压没了”的尴尬局面。下面我按自己的理解和复现过程把这个项目的核心内容完整拆开讲一遍。1. 项目概述共享单车再平衡为什么值得认真做1.1 问题背景共享单车系统每天都在“自然失衡”共享单车系统的失衡是个高频且顽固的问题。早高峰时段住宅区附近的站点车辆被大量骑走工作区附近的站点则被塞满晚高峰刚好反过来。如果系统不干预这种失衡会随着时间累积导致某些站点长期无车可用另一些站点则变成“僵尸车停车场”。再平衡Rebalancing就是通过调度车辆、调整价格等手段让各个站点的车辆数尽量回到合理区间。这里面有两个关键难点第一需求是随机的你不知道下一秒哪个站点会被骑走多少车第二调度是有成本的调度车辆从A点到B点需要时间、油耗和人力不可能无限调度。两者的平衡点怎么找就是优化问题。这个项目把需求随机性当作核心问题来处理而且不是简单用平均值替代而是用分布鲁棒优化的思路在“最坏情况”下依然保证系统效率这比传统的随机规划更稳健。1.2 项目核心目标联合优化动态定价与车辆路由这个项目锁定的核心场景是这样的在一个共享单车网络里运营商同时拥有两种调控手段——动态定价调整不同站点、不同时段的价格来影响用户骑行需求和车辆路由用调度车将车辆从富余站点运往稀缺站点。DSDRO模型Distributionally Stochastic Dual Dynamic Robust Optimization分布随机对偶动态鲁棒优化把这两个手段放进同一个目标函数里联立求解。决策变量既包含各站点各时段的价格调整幅度也包含调度车辆的路径和装卸量。这样做的好处是定价释放的需求信号可以直接指导路由决策路由后的供给变化又会反馈到定价策略中形成闭环。1.3 适用人群与前置知识如果你有以下背景这套代码会很有价值正在做共享出行系统优化相关课题的研究生或工程师对分布鲁棒优化DRO、随机规划SP、对偶动态规划SDDP有一定理论基础但缺少可运行的实例想用Matlab快速验证一个“定价路由联合优化”想法的可行性已经做过单目标调度模型想看看多决策耦合怎么做。前置知识方面建议你至少熟悉线性规划、整数规划的基本概念会看Matlab代码了解linprog、intlinprog或者fmincon这类求解函数的基本用法。不需要你已经是鲁棒优化专家——代码里已经把很多细节处理好了我更想帮你理解它为什么这么设计。2. 核心思路拆解为什么选择DSDRO联合优化框架2.1 动态定价与车辆路由的耦合逻辑先说一个容易被忽略的事实定价和路由不是两个独立的杠杆它们作用于同一个供需等式。每个站点的车辆数变化 流入量 - 流出量。其中流出量 该站点的骑行需求 × 用户实际选择骑行的概率流入量 其他站点骑过来的车 调度车卸下的车。动态定价改变的正是“用户实际选择骑行的概率”。价格高了部分价格敏感型用户会放弃骑行价格低了则会刺激更多需求。车辆路由改变的则是“调度车卸下的车”和“调度车装走的车”。如果你先定好价格再去做路由相当于你假设一个静态的需求水平下去安排调度等调度完成之后需求又变了系统再次失衡如果先做路由再定价调度的成本已经发生价格能发挥的空间就很小。DSDRO把两个问题放到同一层决策里本质上是在说价格和路由都服务于同一个目标——最大化系统总收益包含骑行收入、调度成本、惩罚成本等而不是各自为政。2.2 DSDRO如何在“不确定性”上做文章这个模型在不确定性处理上有明显的层级设计。第一层不确定性来自需求明天早高峰某站点到底有多少人想骑车是一个随机变量。传统随机规划的做法是给这个随机变量指定一个概率分布比如泊松分布然后在这个分布下求期望最优。问题在于真实需求分布往往和你的假设差得很远一旦分布错了优化出来的解就会“在错误的分布下最优”。分布鲁棒优化的做法更谨慎我不假设一个精确的分布而是构造一个“模糊集”Ambiguity Set把真实分布可能处于的范围都包含进去然后求“最坏可能分布下的最优解”。这样得到的解在真实分布偏离假设时仍然有可接受的性能保证。DSDRO名字里的“Robust”和“Distributionally”指的就是这个。而“Dual Dynamic”部分则体现在求解策略上问题被拆成若干阶段通过随机对偶动态规划的思路逐步逼近最优解。整体看下来这个模型是典型的“鲁棒性优先”思路——宁可牺牲一点名义最优性也要保证对抗需求波动时的稳定性。2.3 方案选型考量为什么不用单纯启发式或简单随机规划既然实际问题这么复杂为什么不直接上遗传算法、粒子群这类启发式算法去求解原因在于可解释性和最优性保证。启发式算法的优点是搜得快缺点是它不告诉你解离全局最优有多远。对于共享单车系统的运营决策管理者需要知道“当前方案是不是已经接近上限了”而不是“这个解看起来还行”。DSDRO依托线性规划和凸优化的数学结构能给出有理论保障的界这在工程决策中是实打实的价值。另外单纯随机规划SP虽然也有数学保障但它对分布假设的敏感度太高。实际业务中需求分布往往受天气、活动、突发事件影响很难标定。DSDRO的分布鲁棒结构天然适配这种“分布未知但能估计均值、范围”的业务场景。再加上动态定价涉及价格弹性的不确定性用鲁棒化处理更稳。3. DSDRO模型核心细节解析3.1 目标函数与决策变量模型的优化目标一般写成最大化 总骑行收益 - 调度运营成本 - 供需失衡惩罚成本其中总骑行收益是各站点、各时段价格乘以实际骑行量的累加调度运营成本包括调度车的固定出车成本、按距离计算的油耗成本和装卸时间成本供需失衡惩罚成本则是当站点车辆数跌破下限或超过上限时产生的业务损失。决策变量分三类定价变量每个站点在每个时段的单位骑行价格或折扣率路由变量调度车从哪个站点出发、去哪个站点、是否访问某个站点、访问顺序是什么装卸变量调度车在每个访问站点装卸的车辆数量。这里要注意的是定价变量可以是连续的但路由变量本质上是离散的0-1变量装卸变量在部分站点单位单车下也是整数。所以这个模型是一个混合整数规划MIP框架下的鲁棒优化问题求解难度比纯线性规划高一个量级。3.2 关键约束条件的业务含义约束条件决定了模型能不能直接用于现实指导。我拆几条关键的出来说车辆守恒约束每个站点在每个时段的期末车辆数 期初车辆数 - 被骑走的车辆数 骑回来的车辆数 调度净入库车辆数。这条是系统运行的基础物理约束不能违反。调度车容量约束调度车一次装载的车辆数不能超过车载容量。这约束了单次调度的效率上限。调度车时间窗约束调度车完成所有任务的时间必须在天内或指定时间段内。加班调度成本高不现实。定价弹性约束价格变动幅度不能超出合理区间既保证用户接受度也防止模型为了优化收益给出离谱价格。鲁棒约束在模糊集内的所有可能分布下服务质量指标例如站点空置率、满载率都必须满足阈值。这些约束在代码里以矩阵形式组装我复现时试着改动容量参数和时间窗参数发现系统的行为变化非常敏感——容量太小车辆周转不开时间窗太紧路由解不出来。3.3 不确定性集合与情景生成DSDRO模型中不确定性集合的构建是灵魂所在。简单说我们需要把需求这个随机量的“可能取值范围”量化出来。常用做法是构造矩模糊集moment-based ambiguity set给定需求的均值估计和协方差估计把模糊集定义为所有满足这些矩条件且在Wasserstein距离或KL散度上贴近经验分布的分布集合。模型在模糊集上做max-min优化——外层max的是我们的收益里层min的是环境选择的最差分布。Matlab代码里通常预先生成若干需求情景Scenario通过采样或场景缩减技术得到具有代表性的情景树。我在代码里看到的情景数一般在几十到几百之间情景太多会指数级放大求解时间太少又失去鲁棒性。这也是一个值得手动调节的核心参数。提示如果你改情景生成部分的种子或分布参数别指望只改变输入而不影响结果。鲁棒优化的解对模糊集的大小非常敏感模糊集半径设得越大解就越保守。实际使用时要结合业务上能接受的代价来标定这个半径。4. Matlab实现的关键路径与实操要点4.1 代码模块与文件结构这套源码是按问题域拆分的模块化结构基本上包含以下几类文件数据生成与预处理模块负责生成站点坐标、初始车辆数、需求基准值等基础数据情景生成模块负责生成需求不确定性的情景模型构建模块把目标函数和约束条件翻译成Matlab优化工具箱要求的标准形式求解主脚本调用求解器并处理结果结果可视化模块画出各站点的车辆数变化、调度路径等。拿到代码后不要急着跑主脚本先看数据生成部分。因为如果你的站点数量、时段划分和原代码不同后面所有矩阵维度都会连锁出错。4.2 求解器选型与参数设置Matlab优化工具箱自带的求解器里线性连续问题用linprog混合整数问题用intlinprog。代码里这两者都有涉及。如果你的版本里有YALMIP或CVX也可以把模型切换过去表达上会更接近纸上公式。求解混合整数鲁棒优化时要格外留意以下参数的设置相对MIP GapRelativeGapTolerance默认是0.01%如果你的场景规模较大可以放宽到0.5%或1%能显著减少求解时间而解的质量损失通常很小最大求解时间MaxTime防止模型陷入长时间无界搜索整数预求解IntegerPreprocess建议开启能消除一部分冗余约束。我实测下来使用默认intlinprog参数在50个站点、24个时段的场景下求解时间大约在几分钟到十几分钟。如果你跑到半小时以上没出结果优先怀疑情景数是不是设得太多了而不是机器太慢。4.3 核心算法步骤的代码级拆解我先描述一下主循环的整体逻辑初始化系统参数站点数、时段数、调度车数、初始车辆分布生成需求模糊集与情景集进入迭代对当前最优解求解对偶问题获得下界修正对定价和路由变量进行子问题分解更新最优割平面Cutting Plane逐步逼近原问题最优解循环直到到达最大迭代次数或目标值收敛输出最优定价方案与路由方案并回代检验各站点车辆时序变化。在Matlab中割平面的添加通常体现为动态增加约束行。我这里截取核心逻辑思路的伪代码for iter 1:maxIter % 求解主问题定价与路由主决策 [x_cur, objMaster] solveMasterProblem(ambiguitySet); % 求解子问题给定主决策后的收益评估 [objSub, duals] solveSubproblem(x_cur, scenarios); % 检查上下界差是否收敛 if objSub - objMaster tol break; end % 添加新的割平面约束收紧对主问题的逼近 addOptimalityCut(duals); end这个结构就是随机对偶动态规划的核心——主问题不断被割平面精化子问题为主问题提供红利的近似梯度。实际代码会比这个伪代码长很多倍因为每个子问题都要遍历所有情景来求解情景间的循环是Matlab中比较耗时的地方可以考虑用向量化或Parfor并行加速。5. 实验设定与结果分析5.1 实验场景设置我复现时搭了一个模拟都市共享单车网络的实验场景40个站点分布在5km×5km的区域内划分为早高峰、午间、晚高峰、夜间四个时段调度车3辆单车载容量30辆站点容量60辆。需求基准值根据站点属性差异化设定——住宅区早高峰高、晚高峰低办公区则相反。定价基准价设为2元/次允许浮动区间[1元, 4元]。动态定价调整的粒度设为0.1元。调度车固定出车成本200元单位油耗成本按距离线性计算。5.2 对比实验设计这个项目最有效的展示方式是对比实验。我做了以下几组对照对比方案说明无干预基线不调价、不调度全靠自然流动仅定价优化只用动态定价不做车辆路由仅路由优化固定价格只做调度车辆再平衡DSDRO联合优化动态定价与车辆路由联合求解跑完对比之后DSDRO联合优化的系统总收益骑行收入减调度成本减惩罚成本显著高于单一策略。更重要的是供需失衡惩罚成本降低了约六成这意味着用户在各个站点“有车可用”的体验明显提升。5.3 结果怎么解读才不踩坑注意一点收益数字本身不能说明全部问题。动态定价有一部分收益来自“把低价时段的用户需求推到高价时段”这是真实运营上需要斟酌的——用户是否愿意接受被价格引导着换时间骑行取决于用户的时间弹性。模型会告诉你理论最优但实际业务落地时还需要结合用户调研数据修正价格弹性系数。另外对比实验里“仅定价优化”的效果在需求高峰时段其实并不差因为定价能快速抑制高峰期需求溢出代价是牺牲了一部分骑行量。联合优化则通过路由把车辆周转速度提上来让定价不需要压得太狠。这个协调效果在热力图上看得非常清楚——联合优化的各站点车辆数分布方差明显更小。6. 常见问题排查与实战避坑6.1 典型问题速查表问题现象可能原因排查/解决方向求解时间过长情景数过多、MIP Gap设置过严缩减情景数、放宽Gap阈值、开并行解不收敛模糊集半径过大或约束冲突检查模糊集参数合理性逐个约束验证可行性结果全是零价格区间设置太窄或惩罚成本权重不对调整价格弹性系数增加失衡惩罚权重调度路径重叠路由子问题没加访问顺序约束检查MTZ约束或子回路消除约束是否生效站点车辆数出现负数车辆守恒约束组装错误检查各时段连接变量是否对齐矩阵维度是否匹配代码报错“矩阵维度不一致”站点数或时段数改动后未同步几何参数全局搜索所有依赖站点数和时段数的矩阵定义6.2 我在复现中踩过的坑坑一把模糊集半径调太大结果“鲁棒”到什么都不做。这是分布鲁棒优化的经典笑话。模糊集如果给的过大模型会认为“最坏情况”极端到无解可循最终解会极度保守——表现为所有站点价格不变、调度车不出发。我花了很长时间排查代码逻辑最后发现是模糊集半径参数设得太激进。记住模糊集半径应该根据历史需求数据和业务可接受的风险水平来标定不是越大越稳健。坑二求解器默认参数在中小规模和小规模问题上表现完全不一样。我在20个站点的小规模问题上用默认intlinprog参数跑得飞快换到60个站点时突然跑不动了。不是算法失效而是节点分支爆炸。后来我手动设置了MaxTime和RelativeGapTolerance情况才好转。强烈建议你在一开始就根据问题规模设定合适的求解器参数而不是等它卡住再改。坑三价格弹性的初值设置不当导致模型摇摆在“全调价”和“全不调价”两个极端。动态定价的弹性系数是模型里的关键输入参数。我最初参考了网约车的弹性系数发现共享单车用户在1-2元范围内的价格敏感性完全不同——通勤用户的刚性远大于娱乐用户。我后面按用户画像拆分弹性系数模型的定价结果才变得合理。坑四并行工具箱没开情景循环成了时间黑洞。求解子问题时需要对所有需求情景逐一遍历在Matlab里这就是个可以并用parfor的典型场景。如果不开并行池情景数上百以后每次迭代光这部分就能烧掉十几分钟。而开了parfor之后速度能提升一个量级。前提是你的机器CPU核心够多而且子问题间没有共享可变状态。坑五结果可视化的时间粒度太粗掩盖了中期失衡问题。我一开始只画每天汇总的车辆数柱状图看起来系统运行得挺平稳但把数据拆到15分钟粒度后发现站点在早高峰开始前40分钟已经出现无车可用的空窗期。优化目标里如果只按下半天或按天统计惩罚就捕捉不到这种短时失衡。后续我把惩罚成本改为按时段计算模型才真正开始优化“每一小时”的供需匹配。7. 一套可以直接上手的复现流程如果你拿到这套源码我建议按下面的顺序来理解和复现先把数据生成和主脚本跑通不管结果如何先确保代码链路没有报错。这一步可以帮你建立整体感知。单独调参数改容量、改车辆数、改调度时间窗直观感受每种参数对结果的扰动。不要一开始就动模型结构。看对比实验跑通无干预 vs 仅定价 vs 仅路由 vs 联合优化四组方案把结果画成站点车辆时序曲线你会直观理解联合优化的价值在哪里。逐步改造把静态参数改成动态函数、把情景数调大、引入真实需求数据逐步让模型接近你的实际场景。如果要用到生产建议把Matlab代码重写为Python或直接部署为优化服务原因是Matlab在并行效率和模型热更新上不如生产环境友好。但做算法验证和论文实验Matlab这套代码非常顺手。提示如果你计划基于这套代码发论文或做毕业设计不要只贴原代码运行结果。建议增加一组自己的对比实验例如引入天气因子、节假日因子或者修改模糊集构造方式例如从Wasserstein模糊集换成KL散度模糊集这样会显著提升工作的独立贡献度。8. 一点个人体会我在实际使用这套模型的过程中体会最深的一点是DSDRO的真正价值不在于它给出了某个“最优价格表”或者“最优调度路径”而在于它让系统在需求波动面前不至于崩溃。尤其是当我把历史需求数据的噪声放大之后联合优化的稳定性优势更加明显——它牺牲的那部分名义收益换来的是异常情况下系统服务质量的底线。另外一个小技巧调试这类模型时不要一上来就跑全规模。先造一个两站点、三时段的微型算例手算一遍最优解再去对照代码输出。只要微型算例对上了再扩展到全规模基本可以确定模型逻辑没有硬伤。如果微型算例里已经出现预期之外的结果优先去查约束组装的部分而不是查求解器。共享单车再平衡这个方向长期来看还会和用户激励、新能源调度车、需求预测深度融合。动态定价与车辆路由的联合优化框架其实也适用于无人配送、共享汽车调度、甚至仓储机器人调度这一类供需匹配场景。把这套思路吃透你会发现它解决问题的骨架是通用的换的只是业务外衣。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进