☰
非合作博弈分层调度模型与双层鲸鱼算法在居民需求响应中的应用
2026/10/14 21:37:30 网站建设 项目流程

这些年做负荷调度和需求响应相关的仿真项目,我见过不少让人头疼的场景:居民侧负荷随意用、峰谷差越拉越大、电网侧被迫频繁调节机组出力,而简单的固定峰谷电价又很难真正改变用户行为。后来在某个模拟项目里试了基于非合作博弈的分层调度思路,配合双层鲸鱼算法在Matlab里求解,效果比我想象中扎实。这里把整套模型的拆解思路、算法实现细节和调试经验整理出来,给正在做类似方向的朋友一份可以直接参考的实践记录。

这个方向适合谁看?如果你是做电力系统优化调度、能源互联网策略研究,或者在写居民需求响应方向论文的开发者,这篇内容应该能帮你省下不少绕路时间。文章会按“为什么用博弈论—分层模型怎么搭—双层鲸鱼算法怎么落地—仿真中出现过的坑”这条线索展开,代码逻辑部分我尽量讲得能直接照搬。

1. 居民负荷调度的现实困境:从何下手比怎么优化更难

1.1 负荷不是铁板一块,先要把类型分明白

居民家里的用电设备五花八门,但从调度角度看,它们响应能力完全不一样。我在建模时习惯把所有负荷分成三类,这样后面定决策变量和约束条件才有抓手。

  • 可转移负荷:洗衣机、洗碗机、电动汽车充电桩这类设备,工作时段可以平移,但启动后一般不能中断。它们的特点是“总用电量固定、用电时段灵活”,这是调度优化的主力对象。
  • 可削减负荷:空调、热水器、部分供暖设备,运行功率可以在一定范围内连续调节,短时间内降低功率不会造成灾难性影响,但削减太多会影响舒适度。这类负荷适合做功率调节,不适合做时段平移。
  • 刚性负荷:照明、电视、冰箱这类设备,用电时段和功率基本不可控,在模型里一般作为固定基线处理。

三类负荷对电价信号的敏感度差异非常大。可转移负荷的响应速度最快——只要峰谷电价差足够大,用户愿意把充电时间挪到夜间;可削减负荷需要配合温度舒适度区间来约束;刚性负荷基本不参与调度。如果把三类负荷混在一起建一个黑箱模型,上层优化器根本不知道应该向谁发力,结果往往会失真。

1.2 供电商和用户的目标天然冲突

在传统“电网定电价、用户被动接受”的模式下,双方目标其实是对着干的。供电商希望削峰填谷、降低系统峰谷差、减少购电成本,最好用户都在谷段用电;但用户在乎的是电费便宜、生活舒适,不会因为你电网需要削峰就牺牲自己的用电习惯。

我见过不少只优化供给侧的策略,结果并不理想——电价定得太激进,用户直接无视,响应率低得可怜;电价定得太柔和,峰谷差又压不下去。问题就出在把用户当成“被动执行者”,而没有考虑用户会在给定电价下做出对自己最有利的选择。

非合作博弈在这个场景里的价值,恰好就是正视这种冲突:上层的供电商制定电价策略,下层的居民用户根据电价优化自己的用电计划,双方都在追求自身利益最大化,最终通过迭代收敛到一个互相接受的均衡状态。这不是一个“压制用户”的过程,而是把双方博弈关系显式建模,让电价信号本身去引导行为。

1.3 非合作博弈的基础认知:每个人都为自己选最优

第一次接触非合作博弈的开发者可能会被“博弈论”三个字吓住,但实际在负荷调度里用到的核心概念并不复杂。

先说纳什均衡。简单理解就是:给定其他参与者的策略,任何单个参与者都找不到更好的单方面改进行为。把这个映射到居民负荷调度里——给定供电商公布的一组分时电价,每个用户都会找到自己的最优用电计划;所有用户都按最优计划执行后,供电商再基于这些用电行为评估电价策略是否合适,如果某一方觉得自己吃亏且有能力调整策略,就继续迭代。最终达到的状态,就是没有任何一个参与者愿意在别人不动的情况下独自改变策略。

这里容易被误解的点是“非合作”。非合作不等于对抗和扯皮,而是说每个决策主体只考虑自己的目标和约束,不指望通过行政命令或强制手段让其他主体配合。在居民负荷场景里,供电商和用户之间、用户和用户之间都天然是非合作的——“别人家怎么用电是你控制不了的,你能做的只是在给定信息下选好自己的策略”。这种假设比合作博弈更贴近现实。

2. 分层调度模型怎么搭:上层定电价,下层调负荷

2.1 上层模型:供电商的定价策略

上层决策者设定分时电价策略,目标是兼顾自己的运营收益和系统运行效益。实际建模时,我习惯用“调度周期内总负荷峰谷差最小化 + 供电收益不低于基准”作为双目标,再做加权处理。

上层的决策变量是24个时段的电价向量,或者简化成峰、平、谷三档电价。如果走三档电价路线,决策变量会少很多,但精度有限。我做过对比,直接以24时段电价为变量虽然求解压力更大,但收敛后电价曲线更平滑,削峰效果也更明显。

约束条件一般包括:

  • 电价上下限,避免电价过高或过低引发公平性问题
  • 平均电价保持在一定水平,防止运营商通过整体抬高电价获利
  • 相邻时段电价变化幅度不能过大,防止价格突变给用户带来不可预期的账单冲击

这里要特别提一下“用户响应度”的问题。如果上层模型假设用户100%严格按照电价优化用电计划,就偏离现实了。比较稳的办法是引入一个响应系数,表示实际参与负荷调整的比例。比如某时段电价激励很强时,理论上可转移负荷应该全部移到谷段,但实际上只有80%的用户会响应。这个系数可以让模型更贴近真实场景,后面调参时也方便做灵敏度分析。

2.2 下层模型:居民用户的经济用电计划

下层每个用户或每类典型用户在接收到电价信号后,构建自己的用电成本最小化问题。

决策变量包括:可转移负荷的启停时间段、可削减负荷在各时段的功率。目标函数是电费支出最小,同时要兼顾舒适度惩罚项——如果为了让电费便宜而把空调全关掉,模型得给出一个惩罚项来阻止这种不合理的极端行为。

我在实际模型中用到的用户目标函数形式大致是:

  • 电费支出:各时段电价乘以对应时段的用电功率之和
  • 舒适度惩罚:可削减负荷实际功率与期望功率的偏差,超过一定阈值后产生惩罚成本
  • 可转移负荷的启停约束:连续运行特性不能被破坏

这个模型看起来思路清晰,但落地时有个细节很关键:所有用户的决策彼此独立,互不通信。每个用户只知道电价和自己家的情况,不知道自己邻居怎么用电。这正是非合作博弈的体现——没有中央协调单元指导每个用户必须怎么做,而是靠价格信号实现间接协调。

2.3 上下层的耦合:电价到负荷,负荷又回到电价

上下层不是孤立的,而是通过一条循环链路耦合在一起:

  1. 供电商发布一组初始电价
  2. 每个用户针对这个电价独立求解自己的负荷计划
  3. 所有用户的计划汇总起来,形成系统总负荷曲线
  4. 供电商观察总负荷曲线,评估削峰填谷效果和收益,调整电价
  5. 进入下一轮迭代,直到电价和负荷不再发生显著变化

这个过程中,上层电价影响下层负荷,下层负荷反过来又影响上层电价的调整方向。如果不显式分层,把运营商和所有用户的决策变量放进一个大优化问题里求解,那么每个用户的具体隐私信息都必须暴露给上层。在真实场景里没有几个用户愿意做这种事。分层模型的最大工程价值,就是通过“迭代协调”替代“集中统一求解”,保护了用户侧的决策隐私,同时把问题规模控制在可求解范围内。

2.4 为什么不行做一个整体大优化?算力和隐私是两座山

这是我被问过很多次的问题:“既然目标函数都有,约束条件也不复杂,为什么不能把所有用户的决策变量放到一个大优化器里直接求全局最优?”

技术上确实可以,但有两个现实问题。

第一个是计算复杂度。假设有50个用户,每个用户有20个可转移负荷的时段决策,再加上功率调节决策,联合决策变量就有上千维。上层再加24个电价变量,这个混合整数非线性问题用常规求解器跑一次,时间成本很高。如果把用户规模扩大到几百户,问题规模会直接失控。

第二个是信息和隐私壁垒。集中式求解要求上层拿到所有用户的用电偏好、负荷参数和内部约束,这在现实里几乎无法实现。居民不会愿意为了“系统最优”而交出这些信息。分层模型通过迭代的方式让每一步只暴露“在当前电价下你打算怎么用电”,而不是把所有偏好参数全交出去,工程上更容易被接受。

3. 双层鲸鱼算法的核心原理与Matlab代码实现

3.1 鲸鱼优化算法到底在模仿什么

鲸鱼优化算法(Whale Optimization Algorithm, WOA)是模拟座头鲸捕猎行为的一种群智能优化算法。座头鲸捕食时有一种非常特别的行为——气泡网攻击:先潜入水下,围绕猎物喷出螺旋状的气泡网,把猎物困住,然后从下方张大口向上冲,一次性吞掉聚集的猎物。

算法把这种行为抽象成几个核心的数学更新机制:

  • 包围猎物:种群中的鲸鱼个体根据当前最优位置来更新自己的位置,对应算法里的“包围”阶段。
  • 螺旋气泡网攻击:以螺旋轨迹逼近最优位置,对应算法的局部搜索阶段,保证收敛精度。
  • 随机搜索:当参数满足一定条件时,个体放弃跟随当前最优位置,转而随机在搜索空间探索,对应全局搜索阶段,防止陷入局部最优。

这几条机制的数学公式在很多基础资料里都有,篇幅原因我不逐条推导,直接给出Matlab核心更新逻辑的写法。

function [newPos, fitness] = whaleUpdate(pos, bestPos, lb, ub, iter, maxIter, b, fitnessFunc) % pos : 当前个体位置向量 % bestPos : 当前全局最优位置向量 % lb, ub : 变量下界和上界向量 % b : 螺旋形状常数,通常取1 % fitnessFunc : 适应度函数句柄 dim = length(pos); newPos = zeros(1, dim); % 收敛因子a,随迭代线性递减 a = 2 - iter * (2 / maxIter); % 随机系数 r1 = rand(); r2 = rand(); A = 2 * a * r1 - a; C = 2 * r2; p = rand(); if p < 0.5 % 收缩包围机制 if abs(A) < 1 D = abs(C * bestPos - pos); newPos = bestPos - A * D; else % 随机搜索 randIdx = randi([1, size(pos, 1)]); randPos = lb + (ub - lb) .* rand(1, dim); D = abs(C * randPos - pos); newPos = randPos - A * D; end else % 螺旋更新 l = -1 + 2 * rand(); D = abs(bestPos - pos); newPos = D * exp(b * l) .* cos(2 * pi * l) + bestPos; end % 边界约束 newPos = max(newPos, lb); newPos = min(newPos, ub); fitness = fitnessFunc(newPos); end

这段代码是鲸鱼算法最常用的基础更新框架。注意最关键的参数A,它的绝对值反映个体当前是“开发”还是“探索”。a从2线性下降到0,意味着前期搜索空间大、后期收窄精度高,这是算法能在“全局搜索”和“局部开发”之间取得平衡的核心。

3.2 为什么选鲸鱼算法而不是粒子群或遗传算法

做负荷调度,很多人第一反应是粒子群算法(PSO)或者遗传算法(GA),我在早期项目里也主要用这两种。后来切换到鲸鱼算法,主要看中三点。

第一,参数少,调参成本低。粒子群要调惯性权重、个体学习因子、社会学习因子;遗传算法要调交叉概率、变异概率、种群规模、选择策略——每个参数都直接影响收敛质量,调起来非常费时间。鲸鱼算法核心参数只有种群规模、迭代次数和螺旋常数b,b通常固定取1,真正需要人工干预的实际只有前两个。

第二,探索与开发的平衡做得好。鲸鱼算法用p这个随机值在螺旋开发和包围收缩之间切换,再用A的绝对值决定是包围还是随机搜索。这种多层随机机制使得算法前期不容易过早收敛,后期又具备较强的局部精细搜索能力。我在多个测试函数上做过对比,鲸鱼算法的收敛曲线通常比PSO更平滑,震荡更少。

第三,双层嵌套时结构非常自然。后面马上要讲的双层模型,外层要反复调用内层优化结果来评估适应度,这就意味着外层算法每评估一次适应度,都要完整跑一遍内层优化。如果内外层都选择参数少、收敛快的算法,总计算时间才可控。鲸鱼算法在这方面比遗传算法有明显优势——遗传算法的交叉变异操作在嵌套调用时流程太重了。

3.3 双层嵌套怎么组织和实现:内外层各干什么

这个模型的双层结构需要理解成“外层套内层”的关系:

  • 外层鲸鱼算法:负责优化24时段电价。每个鲸鱼个体就是一组电价向量,它的适应度评估方式是——把这一组电价传给内层,等内层用户算完自己的最优负荷计划,返回各时段总负荷,然后外层根据总负荷计算目标函数(峰谷差、供电商收益),加上电价约束的罚函数,得到最终适应度值。
  • 内层鲸鱼算法:负责优化单个用户在给定电价下的用电计划。把用户的负荷决策变量编码成一个位置向量,目标函数就是电费支出加舒适度惩罚。每个内层个体跑完自己的迭代后,返回最优用电计划给外层作为“用户对电价的响应”。

内外层为什么必须用循环而不是一次性求解?因为这里存在一个“逆向递归”的关系——外层的适应度函数内部就要调用一次完整的内层优化,如果不把内层计算封装成函数,外层每次迭代都要手动写一遍全流程,代码会乱到没法维护。

在Matlab里实现时,我建议用几个独立的函数文件来组织:

  1. outer_woa.m—— 外层主程序,负责电价种群初始化和外层迭代
  2. inner_woa.m—— 内层主程序,负责用户在给定电价下的负荷计划求解
  3. get_load_response.m—— 将电价传给内层并返回总负荷曲线的接口函数
  4. objective_outer.m—— 外层适应度计算
  5. objective_inner.m—— 内层适应度计算

这个分层结构有个明显的工程优势:如果你想换一种内层求解算法,比如把内层鲸鱼算法换成二次规划求解器,只需要修改inner_woa.m的return格式保持统一,其他函数完全不用动。我在后续实验中就试过内层直接用商业求解器,整体框架没有做任何重构。

3.4 适应度函数与约束处理:罚函数比硬限制靠谱

约束处理是建模最容易翻车的地方。有的开发者倾向于在位置更新后直接强制截断变量范围,比如电价不在允许范围内就直接拉回边界值。这在简单约束下问题不大,但在这个双层模型里容易出问题。

我推荐在内外层适应度函数中都采用罚函数机制。比如电价约束可以这样处理:如果某个时段的电价超出允许范围,不直接截断,而是把超出的量乘上一个罚系数加到目标函数里,让算法在搜索过程中“意识到”这个解不可接受,并且能根据超出的程度平滑地调整搜索方向。

这种做法的好处在于,鲸鱼算法的个体更新依赖适应度值的相对大小。如果你把所有违反约束的个体适应度一律设成同一个很大的常数,算法就丢失了“哪些个体违反得更轻”的信息,搜索效率会大幅下降。而罚函数保留了这种梯度信息,算法能知道往哪个方向调整能更快回到可行域。

function fitness = objective_outer(price, lambda1, lambda2, priceBase, loadResult) % price : 当前电价向量 % lambda1 : 峰谷差目标权重 % lambda2 : 收益惩罚权重 % priceBase : 基准电价,用于收益约束 % loadResult : 传入内层计算得到的各时段总负荷向量 peakValleyDiff = max(loadResult) - min(loadResult); revenue = sum(price .* loadResult); revenuePenalty = max(0, priceBase - revenue) * 1000; priceBoundPenalty = sum(max(0, price - priceMax).^2) + sum(max(0, priceMin - price).^2); fitness = lambda1 * peakValleyDiff + lambda2 * revenuePenalty + priceBoundPenalty; end

罚系数取多少合适?这个没有统一答案,但我的经验是先让罚项的量级和目标项量级接近,再逐步增大罚系数。如果罚系数太小,算法会钻空子找违反约束的解;如果太大,罚项会淹没目标项,优化变成单纯找可行解。我通常用10倍步长去试,找到一个在目标值和约束违反度之间平衡的值。

4. 仿真实验设计与参数调优的实战经验

4.1 基础场景怎么设定:用典型日数据跑通闭环

仿真场景我建议先不要铺得太大。最开始我拿某小区的典型日负荷数据做过一版实验,后来发现用公开的、标准化的算例数据跑通逻辑闭环,比用复杂但来源不明的数据靠谱得多。

我常用的基础参数设定如下表所示,供第一次搭模型的朋友参考:

参数项设定值说明
调度周期24小时,1小时为1个时段数据粒度适中,计算量可接受
用户类别数3类每类代表一种典型用电模式
可转移负荷每类2~3个设备洗衣机、洗碗机、电动汽车
可削减负荷每类1~2个设备空调、热水器
电价范围0.3 ~ 1.2 元/kWh参照常见居民阶梯电价上下限
外层鲸鱼种群20电价维度不高,20足够
外层迭代数50先跑通,再提高
内层鲸鱼种群15单用户决策变量规模不大
内层迭代数30调参阶段从低开始

这个规模下,Matlab跑一次完整双层优化大约几分钟到十几分钟。如果第一次仿真就跑上百迭代、大规模用户,调试成本和等待时间会让你怀疑模型写错了。

4.2 平衡点收敛怎么看:不能只看目标函数下降

双层优化的收敛判断是新手最容易误解的地方。外层目标函数下降了,不代表上下层已经达到均衡——因为目标函数和用户响应行为是两回事。

我在跑仿真时判断收敛有三个标准,缺一不可:

第一,外层目标函数值稳定。连续若干代之间,适应度变化幅度小于一个很小的阈值。

第二,电价曲线稳定。看两个连续迭代轮次之间,24个时段电价的最大变化量。如果电价还在明显波动,说明上层策略没有定下来。

第三,负荷响应曲线稳定。电价定了,用户响应结果也要稳定。如果电价差不多,但用户侧负荷曲线还在跳来跳去,说明内层算法没有收敛,外层基于这个不稳定结果做的调整自然不可信。

有一个非常典型的现象值得注意:外层算法已经收敛到了一个不错的解,但内层因为迭代次数不够,实际上返回的是一个次优响应,导致外层目标函数“看起来”更好或更差,从而误导了后续搜索。解决思路是先确保内层在任意给定电价下都能给出一致、稳定的响应结果,再做外层收敛。简单说就是先调内层参数,后调外层参数。

4.3 双层优化跑不出理想结果时的排查顺序

这事我踩过不少坑,总结出一套排查顺序,基本能涵盖绝大多数情况。

第一步,查内层单点优化是否正确。拿一组固定的电价,单独跑内层算法,手动检查最优负荷计划是否符合直觉。比如峰时电价高,可转移负荷应该尽量避开;谷时电价低,负荷应该往那边靠。如果这一步结果不符合常识,说明内层建模或代码有bug,不要急着去调外层。

第二步,查内外层接口的数据传递。外层传给内层的电价向量有没有被正确地转换单位?内层返回的负荷曲线时段顺序有没有对齐?这听起来低级,但我实际遇到过两次这种问题——一次是索引偏移了一位,另一次是单位从MW写成了kWh,导致负荷量级差了一千倍,外层目标函数被一个错误的负荷数据带偏。

第三步,查约束是否有可行域为空的情况。有时候电价范围设得太窄,加上收益约束太紧,可行解根本不存在。算法在不可行区域里转圈,适应度曲线一直是平的或者乱跳。这时候放宽约束条件重新跑,再逐步收紧回来。

第四步,查权重系数间的量级匹配。峰谷差本身可能是几千kW的量级,收益惩罚可能是几十万元的量级,如果不做归一化处理,小目标会被大目标彻底淹没。务必将不同目标项归一化到同一个量级再配权重,否则表面上在调权重,实际上是在做量纲换算。

4.4 收敛震荡的处理:双层博弈的一个顽固难点

双层优化的震荡问题在某些参数组合下特别明显——外层调整电价,内层跟着改负荷,负荷又反过来推动电价调整,结果就形成持续振荡,数值始终无法稳定。

我在几次实验里总结出三个有效对策。

第一,限制每轮电价的调整步长。不要让外层算法在相邻两代之间大幅跳变电价。给电价更新加一个最大变化幅度,就像给PID控制加限幅一样,可以显著缓解响应链路的振荡。

第二,引入历史解的记忆机制。简单说就是两次迭代之间做松弛——例如取当前解和历史解的加权平均作为下一轮使用的解,让系统对变化更平滑。这在处理博弈迭代收敛问题时是很经典的手法。

第三,检查负荷模型的连续性。很多负荷参数如果设置不当,会让内层目标函数出现强烈的非凸性,比如可转移负荷的启动时间选择在相邻时段之间跳来跳去,导致负荷曲线剧烈波动。这种情况下,可以考虑把时间粒度细化,或者增加负荷启停的约束平滑项。

5. 从实验台到真实场景的限制与扩展方向

5.1 模型理想化假设带来的边界

基于非合作博弈的分层调度模型在仿真里表现很好,但真要走向实际部署,有几个假设必须重新审视。

第一个是响应度的确定性假设。模型默认用户会严格按照优化结果行动,现实里用户可能看了账单也不愿意动,或者因为意外情况临时改变用电计划。想让模型更落地,可以把用户响应建模成概率性的——在优化目标里加入响应不确定性项,或者跑多个随机场景做期望值优化。

第二个是信息交互的现实约束。分层模型保护了用户隐私,但上层仍然需要知道每个用户的响应结果才能迭代。真实场景里这些数据从哪里来、多久更新一次、可能存在哪些误差,都会影响最终的调度精度。工程化的做法是用历史响应数据训练一个用户行为预测器,替代直接调用优化器获取响应结果。

第三个是电价公平性的公众感受。模型里的电价可以为一个目标函数而自由波动,但现实中的电价调整要面对用户接受度和社会舆情。价格波动幅度如果过大,即使模型计算出来是“最优”的,也难以落地执行。

5.2 可以继续深挖的几个方向

这套模型框架的价值在于“分层+博弈”的思路可以复用到不少邻近问题上,我自己已经在尝试的几个方向供你参考。

  • 电动汽车聚合商博弈:把若干电动汽车车主聚合起来,用类似的分层模型和电网互动,内层目标从电费最小改充放电计划的最优。
  • 价格型与激励型需求响应的融合:在电价引导的基础上,再叠加需求响应补贴策略,用户的决策空间更丰富,博弈行为也更复杂。
  • 多时间尺度协同:目前是24小时尺度,实际运行中还需要日内滚动修正,用超短期预测更新负荷基线,外层电价也需要分阶段固定,避免用户无所适从。

5.3 给初次上手者的最后一个提醒

关于代码调试速度,我个人的体会是先把一切都调小、调少、调简单。用3个时段、2个设备、1个用户跑通整个链路,确认博弈逻辑闭环无误,再逐步扩展时段和设备数量。很多人一上来就建24时段、50个用户、十几类设备的大模型,结果一个接口错误都看不出来,最后被变量传递问题白白耗掉好几天——这种事我经历过太多次了。

如果这套方法对你的项目有帮助,我建议重点啃透两个地方:一是内外层数据传递的一致性,二是约束罚函数与目标函数之间的量级匹配。这两个地方做扎实了,整个仿真一般不会出现太大问题。后面如果你做了电动汽车聚合或者多用户博弈的扩展版本,也欢迎多交流参数调优的心得。

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

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

立即咨询