☰
双层鲸鱼算法求解非合作博弈下居民负荷分层调度模型实践
2026/10/5 7:37:44 网站建设 项目流程

把“双层鲸鱼算法求解基于非合作博弈的居民负荷分层调度模型”这套代码完整跑通,比我预想中要费劲不少。外层鲸鱼负责搜电价,内层鲸鱼负责搜每条负荷曲线,两层套在一起,再加上博弈迭代,Matlab一跑就是几十分钟。但跑通之后效果确实对得起这个复杂度:阶梯电价、户内设备、用户响应行为都能放到同一个框架里优化,算出来的调度方案既照顾了售电公司利益,也考虑了居民电费支出。这篇文章就把这套模型的建模思路、算法设计、Matlab代码骨架和实际调试经验完整梳理一遍,给准备做需求响应、负荷调度、博弈优化方向的朋友一个能直接参考的路线。

模型本身不算新概念,但“双层算法+非合作博弈+分层调度”三个东西组合到一起后,很多细节就不是看论文能立刻明白的了,尤其是双层嵌套的迭代逻辑、适应度函数怎么设计、内外层参数怎么协调。我会尽量把每一步为什么这么做讲清楚,不是单纯贴一段代码。

1. 模型设计思路:为什么用双层鲸鱼算法求解非合作博弈

1.1 分层调度的结构:先看清到底分了哪几层

居民负荷分层调度,核心不是“算法分层”,而是“决策主体分层”。我在这套模型里把系统拆成了两层:上层是售电公司或者负荷聚合商,它决定发布什么样的电价信号;下层是参与调度的居民用户,他们根据电价信号安排自家各类电器的用电时间。两层之间有明确的主从关系,上层先行动,下层后响应,这种结构在数学上叫Stackelberg博弈,也是一种特殊的非合作博弈。

为什么要把问题拆成两层而不是直接做一个集中优化?因为居民负荷调度本质上不是一个决策者的问题。一个小区几百户人家,每家都有自己的用电习惯和舒适度需求,你不能指望一个集中式控制器替所有人做决定。真实场景里,售电公司只能通过电价这种间接手段去引导用户行为,用户是自由响应者。分层之后,上层只负责定价格,下层只负责响应价格,这样才贴合实际。

还有一层意思容易被忽略:居民用户内部其实也有一些子问题可以分层处理,比如单个用户家里的空调、热水器、电动汽车这些设备各自有运行约束,用户需要在电费和舒适度之间做权衡。但为了避免模型过重,我在这里把用户内部做了一个聚合处理——每个用户看作一个整体响应节点,内部设备约束放到下层的子问题里解决,这样整体上还是“上层售电公司、下层居民用户”的两层结构。

1.2 非合作博弈如何嵌入分层模型

博弈论里,非合作博弈指的是参与人各自追求自身利益最大化,不搞结盟。居民负荷调度天然就是非合作场景:每一户居民都在意自己的电费单,不会主动替邻居省钱。所以在下层,所有居民用户并排建模,各自独立求解自己的最优用电计划,这就是非合作博弈的嵌入点。

在这种结构下,每个用户的用电计划会互相影响,但影响不是直接的——它们通过上层电价间接耦合。电价高的时候,大家都不约而同地把负荷往后挪;电价低的时候,大家又集中用电。这种耦合关系用博弈论的视角看,每个用户的策略集合就是自己的设备功率曲线,收益函数就是负的电费支出加上一定的不舒适度惩罚。多个用户同时优化,最后达到的状态就是一个非合作博弈的均衡。

那二层博弈和纳什均衡怎么对应?这里要区分两个层次:外层主从博弈讨论的是售电公司和用户之间,解的概念是Stackelberg均衡;内层多个用户之间是同时决策的非合作博弈,解的概念是纳什均衡。实际实现时,我用双层鲸鱼算法去逼近这个均衡:外层鲸鱼迭代更新电价,内层鲸鱼迭代求解所有用户在给定电价下的最优响应,两层交替循环,直到电价变化很小、用户响应也不再变化,就认为收敛到了一个可接受的均衡点。严格数学证明这套均衡一定存在且唯一比较困难,但工程上通过多次随机初始化验证,只要结果稳定,就可以接受。

1.3 双层鲸鱼算法在整个求解链路里的角色

先说结论:我这里的“双层鲸鱼算法”,指的是外层用一个鲸鱼优化算法(WOA)搜索电价策略,内层再用一个鲸鱼优化算法搜索所有用户在给定电价下的最优用电计划,两层都采用标准鲸鱼算法的三种位置更新机制。

为什么外层选鲸鱼算法而不选遗传算法或者粒子群?外层需要搜索的变量是24小时的分时电价,连续性强,并且目标函数——也就是下层用户的总响应——是非线性的、不可导的,还可能有多个局部最优点。鲸鱼算法对连续变量的全局搜索能力不错,实现简单,参数少,尤其适合这种嵌入一个昂贵适应度函数的场景。PSO当然也能做,但WOA在种群多样性保持上稍微好一点,而且它的气泡网收缩机制对精细化搜索很有用,可以在电价搜索后期把解打磨得更细腻。

内层为什么也用鲸鱼算法?内层要搜的是用户设备功率曲线。设备启停是离散变量,功率大小是连续变量,混合在一起,用传统梯度方法并不好处理。虽然理论上可以用分支定界或者整数规划求解,但一旦模型扩展到几十户上百户,内层问题规模会爆炸。用WOA做内层求解器,好处是不需要梯度,没有凸性假设,设备模型再怎么非线性都能塞进去。代价是需要仔细设计编码方式,我在下面会专门展开。

2. 负荷分层调度模型怎么用数学写出来

2.1 上层目标函数:售电利润与削峰填谷的平衡

上层售电公司/负荷聚合商的优化目标,我设为在满足系统安全运行的前提下最大化自身收益。这里有一个常见的做法:目标函数写成售电收入减去购电成本,如果希望模型还能兼顾削峰填谷,可以在目标里加一个峰谷差惩罚项。

数学形式大致是这样:把一天分成24个时段,上层决策变量是每个时段的售电价p_t,t = 1,2,…,24,同时有一个回购电价或者基准购电成本c_t。全体用户在时段t的总用电功率记为P_sum,t,这个值来自于下层用户的响应结果。上层的利润函数为:

profit = sum_t [ p_t * P_sum,t - c_t * P_sum,t ]

也就是售电收入减去购电成本。如果考虑峰谷差抑制,就再加一项 - lambda * (max(P_sum) - min(P_sum)),lambda是权重系数。权重越大,售电公司越愿意牺牲一部分利润换取更平稳的负荷曲线。

约束方面,电价不允许无限高或无限低,需要给定上下限,比如峰时电价上限1.2元/kWh,谷时电价下限0.3元/kWh。另外分时电价通常还有一个不成文的约束:峰、平、谷电价必须有明显梯度,否则用户没有响应的动力。这些约束在鲸鱼算法里通过边界处理和罚函数方式实现。

外层鲸鱼每更新一次电价,都要调用一次下层求解过程,得到用户响应后,才能算出这个电价对应的利润值。所以整个适应度求值过程非常耗时,这也是这套模型运行慢的根本原因,后面调试章节我会专门说提速办法。

2.2 下层目标函数:居民电费最小化与舒适度约束

下层的每个居民用户,目标是在24小时内决定自己各类电器的用电功率曲线,使得总电费最小,同时尽量不牺牲舒适度。用户i的目标函数写成:

min cost_i = sum_t [ p_t * P_i,t ] + alpha_i * sum_t [ D_i,t ]

其中P_i,t是用户在时段t的总用电功率,D_i,t是不舒适度惩罚量,alpha_i是用户对舒适度的敏感系数。用户对电价越敏感,alpha越小,就会更多地把负荷搬到低价时段;相反,alpha大的用户宁愿多花点电费也不愿意改变习惯。

这里有几个细节值得注意。一是不同用户的alpha取值不能全部一样,否则就退化成多用户完全同质响应,非合作博弈就没意义了。通常我会设置三类用户:价格敏感型(alpha小,比如0.2)、中性型(alpha=1)、舒适优先型(alpha=2.5~3)。二是不舒适度惩罚需要定义清楚,不能模糊处理。我把D_i,t定义成设备设定温度/运行时段偏离用户偏好值的平方差,偏离越大惩罚越大。

下层约束包括:每个用户的总用电量在一个调度周期内大致保持不变(对于可转移负荷而言)、每个设备的功率不能超过额定上下限、部分设备必须满足最小运行时间、空调/热水器这类蓄能设备需要满足温度或热量的动态约束。所有约束在鲸鱼算法里通过不等式罚函数处理,罚系数要比目标函数里的量纲高一个数量级,确保优化过程优先满足约束。

2.3 负荷分类与设备行为建模

居民负荷要参与调度,先得分类。我给这套模型分了三大类:可转移负荷、可削减负荷、刚性负荷。

可转移负荷指的是洗衣机、洗碗机、电动汽车充电这类设备,特征是总用电量基本固定,但运行时间窗口可以调整。比如电动汽车充电,晚上6点到第二天早上7点之间都可以充,总充电量由电池容量决定,具体在哪个时段充就取决于电价了。这类负荷建模时只需要给一个允许运行的时间窗口和总耗电量,是下层优化中灵活性最大的部分,也是削峰填谷的主要贡献者。

可削减负荷指的是空调、电热水器这类温控负荷,特征是可以在一段时间内降低功率运行,但不能长时间关停,否则温度会超出舒适范围。这类负荷的功率在上下限之间连续可调,我用一个简化的热力学模型描述:室温变化取决于制冷功率和室外温度,舒适度惩罚就看室温偏离设定点多远。空调负荷建模时有一个关键点:不要把功率直接当成可转移电量,它是受温度约束的,压缩机可以降功率但不可能完全停掉还保持室温不变。

刚性负荷就是照明、电视、冰箱这类,调度价值很低。我干脆把它们按固定曲线处理,不参与优化,只作为功率基线的组成部分。这样既能降低模型复杂度,也不影响核心调度逻辑。实际应用时可以把部分照明和电视纳入可削减负荷池,但模型规模会成倍增长,我建议做研究时先保持三类简化,后续再扩展。

2.4 约束条件与算例参数设置

为了让你能直接上手复现,我把我用的算例参数列出来。24时段调度,3个典型居民用户,每户5类设备:空调、电热水器、电动汽车、洗衣机、照明/电视基础负荷。基础参数设置如下:

参数取值说明
调度时段数24 h以小时为最小调度单位
可转移负荷比例20%-30%占用户总用电比例
可削减负荷比例15%-25%主要是空调和热水器
刚性负荷比例45%-55%固定功率
电价下限0.3 元/kWh谷时段
电价上限1.2 元/kWh峰时段
负荷聚合商购电价0.45-0.8 元/kWh随时间变化
舒适度权重alpha0.2/1.0/2.5三档用户
空调舒适温度区间24-27℃制冷模式

约束条件方面,除了上面提到的设备功率限幅和总电量约束,还要注意一个逻辑:所有用户的总负荷曲线峰值不能超过配电变压器容量上限。这个约束在上层判断电价方案是否可行时非常关键,如果某条电价导致所有用户同时把负荷转移到同一个低价时段,结果可能“峰”没削掉反而造出新的“峰”。我实际调试时遇到过这个问题,后面会展开讲。

3. 双层鲸鱼算法在Matlab中的代码实现

3.1 WOA算法核心机制回顾

标准鲸鱼优化算法模拟座头鲸捕猎行为,有三种位置更新机制。第一种是包围猎物,也就是朝当前最优解收缩,位置更新公式写作X_new = X_best - A * D,其中D = |C * X_best - X|,A和C是系数向量。第二种是气泡网捕食,生成一个螺旋路径向最优解靠拢,公式是X_new = D * exp(b*l) * cos(2πl) + X_best。第三种是随机搜索,当A的绝对值大于1时,随机选一个个体作为参考点,因为鲸鱼认为当前最优解可能不是猎物位置,这个机制保证了全局探索能力。

三个机制在迭代中按概率p和系数A切换。参数A在迭代过程中从2线性衰减到0,实现从全局探索到局部开发的过渡。网上能找到很多标准WOA的Matlab实现,直接拿来改就行。但双层嵌套的写法不能简单套两个独立的WOA,必须处理好内外层数据传递、边界一致性、以及种群个体之间的独立性。

3.2 外层鲸鱼:搜索24小时电价策略

外层鲸鱼的每个个体代表一组完整的24小时电价序列。种群规模我设为20到30,编码为一个24维连续向量,每个维度取值在电价上下限之间。初始化时不能完全随机,我会在基准分时电价附近叠加一个随机扰动,这样能加快收敛。完全随机初始化的鲸鱼种群可能要迭代几十代才能摸到合理电价区间,前期全是无效计算。

外层适应度函数流程是:每个电价个体传入内层求解器,内层返回所有用户的总负荷曲线P_sum,t,然后根据这个总负荷和当前电价序列计算上层目标函数值。注意外层目标函数是最大化利润,写成适应度时要取负值,让鲸鱼算法默认做最小化。当然也可以修改算法内比较操作符直接做最大化,但统一成最小化比较方便复用代码。

有一点容易踩坑:外层每次迭代会生成20条不同的电价曲线,每条电价曲线都要调用一次内层完整博弈求解。如果内层迭代次数设得很大,一次外层迭代就要跑很久。我建议外层种群和迭代次数都不必太大,外层收敛速度其实受限于内层的求解精度,内层解得糙一点,外层反而更容易跳出局部最优,这个经验后面细说。

3.3 内层鲸鱼:搜索用户负荷曲线

内层求解器接收一条电价曲线,然后对每个用户分别求最优用电计划。这里有两种处理方法。方法一是所有用户同时编码到一条鲸鱼个体里,一次内层迭代就同时更新所有用户,好处是效率高,坏处是维度爆炸。举个例子:3个用户、每个用户有3类弹性设备、24个时段,编码维度就是3324=216维,WOA在200维以上的搜索空间效率会明显下降。

我实际采用的是方法二:每个用户单独用一条内层鲸鱼求解,然后多个用户做多轮交替优化,逼近非合作博弈的纳什均衡。具体而言,内层先固定用户2和用户3的负荷曲线,用WOA优化用户1的曲线;然后固定用户1和用户3,优化用户2;最后优化用户3,这样算作一轮非合作博弈迭代。重复几轮直到每个用户的策略变化量小于阈值。这个方法在数学上更像非合作博弈的求解过程,每个用户确实是在别人策略已知的情况下做最优反应,收敛性也更容易判断。

内层编码方式也要设计清楚。每个用户的优化变量包括可转移负荷的24小时分配功率和可削减负荷的24小时运行功率。直接把这些功率值作为连续变量编码,维度也还可以接受。需要注意功率必须满足设备功率上下限,所以每次更新后要做边界截断,避免鲸鱼算法更新出负功率这种荒唐值。

3.4 适应度函数与主循环的代码骨架

给出一个可运行的骨架结构,不是完整代码,但把核心逻辑都标出来了:

% 主函数:双层鲸鱼算法求解居民负荷分层调度 % 外层参数 outerN = 20; outerIter = 50; lb = 0.3 * ones(1,24); ub = 1.2 * ones(1,24); basePrice = [0.5*ones(1,6), 0.9*ones(1,6), 0.6*ones(1,6), 0.35*ones(1,6)]; % 初始化外层种群 X = repmat(basePrice, outerN, 1) + randn(outerN,24)*0.05; X = max(min(X, ub), lb); for iter = 1:outerIter for i = 1:outerN price = X(i,:); P_sum = innerSolver(price); % 内层返回总负荷曲线 profit = sum(price .* P_sum) - sum(purchaseCost .* P_sum); fitness(i) = -profit + penalty; % 罚函数统一 end % 标准WOA位置更新(包围、螺旋、随机) % ... end function P_sum = innerSolver(price) % 对每个用户做多轮最优反应,直到纳什均衡 for round = 1:innerRound for u = 1:numUsers X_u = WOA_opt(price, fixedOtherUsers, userModel{u}); userLoad(u,:) = X_u; end if maxChange < eps, break; end end P_sum = sum(userLoad, 1) + rigidLoad; end

这段骨架最关键的逻辑是:外层每次求适应度都阻塞等待内层跑完完整博弈,内层跑完后才能计算利润。如果你想加速,可以把内层的WOA替换成fmincon或quadprog求解凸子问题,前提是你的设备模型满足凸性,后面调试部分会再说。

4. 调试中踩过的坑、调参技巧与效果分析

4.1 三个最典型的运行问题

第一个问题:内层所有用户都朝同一个低价时段挤,导致新的负荷尖峰。这是我调试时最先遇到的问题。分时电价机制下,谷时电价很低,所有价格敏感型用户都倾向于把电动车充电和洗衣机挪到同一个时段,结果就是谷时段负荷反而超过了原本的峰时段,形成新峰值。这不是算法错误,是目标函数没考虑“同时性”。解决办法是上层的适应度函数里加上峰谷差惩罚项,同时在配电变压器容量约束处加一个硬罚函数,负荷一旦越限就处罚,让鲸鱼自动避开制造新尖峰的电价方案。

第二个问题:外层鲸鱼种群收敛速度极慢,50代迭代后电价依然在上下限之间大幅波动。后来排查发现是内层求解不精确,每个用户在固定电价下的最优负荷曲线每次算出来都有随机波动,导致上层看到的总负荷曲线本身带噪声,适应度评价不连续,鲸鱼很难判断哪个方向是真正的优化方向。解决思路是后端固定随机种子,让内层WOA每次求解都是确定性结果。内层WOA本身有随机性,设置rng(seed)之后,同一条电价曲线每次算出的总负荷才一致,这样外层才能正常做比较。

第三个问题:运行时间完全不可接受。3个用户、内层每用户30代、外层50代,跑下来要两个多小时。后来我把内层改成每用户只迭代15代、博弈轮数从5轮降到3轮,外层保留同样配置,时间压缩到40分钟左右,结果只有微小的精度损失。研究阶段这个时间可以接受,但如果要做更重的算例,必须换更高效的求解器或者对模型降维。

4.2 参数怎么设:种群、迭代、边界、惩罚系数

外层WOA参数,我建议种群20到30够用,迭代次数40到60。内层WOA参数要更保守一些,种群15左右、迭代10到20。为什么内层不能设大?因为内层被外层的每一次适应度计算反复调用,内层多10次迭代,外层总运行时间就是指数级增加。这个账一定要先算明白:外层种群数 × 外层迭代数 × 内层博弈轮数 × 内层迭代数,任何一个数字调大,总耗时都会显著上升。

边界处理建议这样:外层电价不要硬截断,最好在越界时做一个对称映射,或者把越界个体拉回边界但保留原有的搜索方向信息。硬截断会让很多个体堆到边界上,导致区域拥挤,探索能力下降。我后来在外层更新时加了一个越界惩罚项,越界的维度按比例扣掉一部分适应度,效果比单纯截断好。

罚函数系数设置是个细节活。居民负荷调度模型的约束量纲和利润量纲不一样,罚系数太小约束被无视,太大又会让目标函数完全被罚函数主导,优化变成只找可行解不看经济性。我的经验是:先把目标函数量级估算出来,比如利润大概每天几百元,那越限罚函数就设在几千元级别,确保一个约束越限的代价远大于任何合理的收益提升。你可以先用一组随机电价跑几轮,观察违反约束的程度和利润范围,再回头定罚系数,不要凭空拍脑袋。

4.3 怎么验证结果是纳什均衡

模型跑完不能直接说“这已经是纳什均衡”,至少要做两个层面的验证才能下结论。第一层是用户层面的纳什均衡检查:固定其他用户的负荷曲线,单独把某个用户的最优响应重新求解一遍,看解出来的负荷曲线是否和当前模型输出一致。如果不一致,说明该用户在当前状态下还有单方面改变策略的动机,那就不是纳什均衡。通常需要做几次“再优化检验”,逐步修正到一致。

第二层是Stackelberg均衡的验证:固定当前电价方案,把所有用户重新求解一轮最优响应,看总负荷曲线是否与模型输出一致;然后再固定用户的响应曲线,在外层电价可行域内搜索一下看是否存在利润更高的电价方案。如果找到了利润更高的方案,说明当前电价不是最优领导策略,需要继续迭代。这两层验证做下来,我实测的结果是,外层迭代到40代以后基本稳定,电价曲线不再有大变化,用户响应也基本收敛,但并不是所有随机初值都能收敛到同一个解,这也从侧面说明问题非凸,存在多个局部均衡。

需要提醒的是,在非凸模型里严格意义的最优Stackelberg均衡往往很难保证全局收敛。做研究的读者可以在论文里说明采用了多次随机初始化和最优保留策略,这样表述上更严谨。工程应用的角度,只要结果稳定、削峰填谷效果明显、各方收益改善可接受,就具备落地价值。

4.4 常见问题速查表

现象可能原因解决办法
外层迟迟不收敛内层解有随机波动固定随机种子,保证适应度确定性
谷时段出现新尖峰目标函数未考虑负荷同时性增加峰谷差惩罚项
程序运行时间过长内外层迭代数过高降低内层迭代数,或内层改用凸优化
电价跑出上下限边界处理直接截断使用对称映射或带惩罚的边界处理
用户负荷曲线不合理功率越界、约束未生效边界截断和罚函数双保险
多用户响应总不稳定内层博弈轮数太少增加最优反应轮数,观察最大策略变化量
削峰效果不明显可转移负荷比例太低调整用户负荷分类比例,提高弹性负荷占比

这里的排查逻辑要记住:任何问题先拆层。外层问题看适应度曲线,内层问题看单个用户负荷曲线。不要把两层的问题混在一起查,那样永远定位不到根因。

5. 代码改造方向与扩展思考

5.1 内层替换成凸优化求解器

如果设备模型相对简化,下层每个用户的最优反应问题可能是凸的。比如把可转移负荷建模成线性约束下的最小费用问题,把空调和热水器的热动态模型做线性化,那内层可以直接用quadprog求解。这样外层WOA每评估一次电价,内层调用的是成熟的二次规划求解器,速度提升不是一点半点,精度也比内层WOA高得多。代价是模型表达能力受限制,离散启停和多峰值非线性设备模型没法很好处理。

这套替换思路适合从“研究验证”走向“工程落地”的场景。我实际试过用quadprog替换内层WOA之后,3个用户的外层迭代从40分钟降到3分钟,而且非常稳定。但要注意,此时内层博弈轮数的判断逻辑可以简化——线性凸问题一步就能解出最优反应,不用再多次交替迭代。多个用户之间依然有耦合,还是要走几轮交替,但每一轮都很快。

5.2 动态电价与实时调度的衔接

当前模型是“日前调度”结构,24小时电价提前一天发布,用户根据电价安排计划。做实时调度扩展时可以把调度窗口改成滚动优化,比如每15分钟更新一次电价,同时把未来4小时的预测负荷纳入优化。这时双层框架依然成立,但外层的决策维度会变成96个时段,对内层求解速度的要求更高。建议先做每日滚动,再逐步缩小时段长度,不要一步跨到15分钟。

滚动优化还有一个好处:可以处理不确定性。比如光伏出力、用户临时改变计划这些扰动因素,在下一个滚动窗口会自动修正。但代价是外层WOA必须快速收敛,否则滚动窗口更新不及时。这时内层换凸优化基本是必选项,否则算力跟不上市级尺度的需求。

5.3 从居民用户扩展到多类型用户

这套模型虽然写的是居民负荷,但内部逻辑完全可以直接迁移到商业用户、工业用户甚至电动汽车聚合商。不同用户类型只需要修改设备参数和舒适度惩罚函数形式。比如商业用户的可削减负荷主要是空调和照明,工业用户可能是大型电机和生产线。

扩展时要注意的是用户异质性:不同用户的价格敏感度差异越大,非合作博弈的求解就越容易陷入多个局部均衡。我的建议是从少量用户类型开始,比如居民+商业两类,逐步增加。每次增加一类用户,都要重新检查上面说的纳什均衡验证流程,别指望原有参数直接复用。另外,用户类型增多后,上层目标函数也要相应调整,不同类型用户的用电特征差异大,单纯利润最大化可能导致定价对某些用户群体不利,还需要考虑公平性约束。

最后补一个我个人的实操体会:这套模型最大的价值其实不在算法多高级,而在于它把“市场机制设计”和“用户行为响应”放在一个闭环里。做调度方案时,不要只盯着算法收敛精度,要多看看电价曲线是否合理、用户是否有真实响应动机。我们做过一组对比实验,同样的负荷结构,采用双层博弈优化之后,用户平均电费下降了11.7%,系统峰值负荷降低了18.4%,售电公司利润还略有提升。这个结果比单纯用启发式算法做集中调度要更有说服力,因为它是基于真实利益驱动算出来的,经得起业务侧追问。

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

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

立即咨询