双层鲸鱼算法与非合作博弈在居民负荷分层调度中的应用
2026/9/7 15:04:30 网站建设 项目流程

这段时间一直在琢磨居民侧需求响应这块,顺手把双层鲸鱼算法、非合作博弈和居民负荷分层调度三个东西凑到一块做了套模型,Matlab里面跑通了,效果还不错。这套东西说白了就是回答两个问题:运营商怎么定电价能让自己的收益最好,同时电网负荷曲线又尽量平稳;用户看到电价之后怎么调整自家的用电计划,让电费最省又不影响生活舒适度。整个过程适合正在做电力系统优化调度、需求响应、智能用电方向毕业设计或者科研课题的同学参考,也适合刚接触博弈论与智能优化算法结合应用的工程师拿来练手。文章不整虚的,直接讲清模型怎么建、算法怎么设计、代码怎么写、坑怎么踩。

1. 先把问题拆清楚:居民负荷分层调度到底在算什么

1.1 为什么居民负荷要分层调度

居民负荷有个特点:单个用户容量小,但聚合起来体量非常大,而且使用时间高度集中。晚上七八点大家同时开空调、开热水器、开电视,负荷曲线尖峰一下子就顶起来。电网为了应对这个尖峰,得额外配置大量发电容量,经济性很差。所以现在越来越多地提“需求响应”,想让用户把用电行为往低谷时段挪一挪。

但问题来了:让谁挪、挪多少、凭什么挪?这就需要一个调度机制。居民负荷种类多,洗衣机、洗碗机这类可以平移使用时间的是可转移负荷;空调、热水器这类可以临时降低功率但不能完全关掉的是可削减负荷;还有固定不可调的基础负荷。不同用户对舒适度的要求也不一样,你让一个家里有婴儿的用户低谷时段再开空调,人家大概率不干。

分层调度的思路就是把决策拆成上下两层:上层是运营商(负荷聚合商),负责制定分时电价,引导用户行为;下层是各个居民用户,在给定电价下优化自己的用电计划。这种“上层定价格、下层定电量”的结构,本质上就是电力市场里很经典的主从递阶决策问题,学术上叫Stackelberg博弈。

1.2 为什么用户之间要用非合作博弈来刻画

非合作博弈这个词听着唬人,其实生活中到处都是。你去菜市场买菜,每个摊主都想让自己利润最大,没有人会主动跟旁边的摊主商量“咱俩都便宜点卖,让顾客多买点”,这就是非合作。居民用电也一样:每个用户都只关心自己的电费,不会主动替邻居考虑。

在模型里,用户与用户之间最典型的耦合是共享配变容量。一栋楼或者一个小区的进线容量是有限的,大家都在晚高峰用电,变压器就可能过载。这时候某个用户多开一台空调,其他用户的可调空间就被压缩了。用户之间没有合作协议,各自追求电费最小,这种竞争关系就是标准的非合作博弈,最后会收敛到一个纳什均衡点:谁先偏离这个点,自己的电费只会更高,所以大家都不愿意单方面改变用电策略。

我把这套结构写成了模块化的Matlab工程,换用户数、换负荷类型、换算法参数都能直接改配置文件跑,不用动核心代码,后面会详细讲每个模块怎么写的。

2. 数学化落地:非合作博弈分层模型的建模过程

2.1 上层运营商的目标函数

上层是运营商,目标一般有两块:经济收益最大化和负荷曲线平稳化。纯经济效益好理解,就是卖电收入减去购电成本;但只追收益容易把电价顶得很高,用户全都不响应,反而得不偿失。所以实际模型里会在目标函数里加一个负荷方差或者峰谷差的惩罚项,让运营商在赚钱的同时也尽量平抑负荷波动。

简化表达的话,上层目标函数可以写成:

F_operator = sum(p(t) * P_total(t)) - sum(c_buy(t) * P_total(t)) - lambda * var(P_total(t))

其中p(t)是运营商制定的分时电价,P_total(t)是所有用户在时段t的总用电功率,c_buy(t)是运营商的购电成本(一般从上级电网批发价来),lambda是负荷平稳性惩罚系数,var表示方差。这个式子很直观:前两项是售电净利润,第三项惩罚负荷波动。

注意,P_total(t)不是运营商的直接决策变量,而是下层所有用户优化之后的结果。也就是说上层每给出一组电价,下层就响应用户负荷,上层拿这个负荷再去算自己的目标。这叫“下层决策反馈到上层约束”,是双层优化的核心特征,也是不方便用普通优化器一把梭的原因。

2.2 下层居民用户的决策模型

下层每个用户i的目标是自己的综合用电成本最小。综合成本包含两块:实际电费,以及用电舒适度损失。

min C_i = sum(p(t) * P_i(t)) + alpha_i * sum(舒适度惩罚)

约束条件需要区分负荷类型。基础负荷是不可调的,直接给序列就行。可转移负荷有一个总用电量固定、可以平移时间窗口的特点,比如洗衣机一个周期耗电1.2度、运行2小时,那么它必须在一个允许的时间窗口内完整运行,不能拆开。可削减负荷可以连续调节功率,但用户对温度有舒适度要求,比如空调功率不能低于某个比例,否则房间温度压不住。

在Matlab里实现时,每个用户的决策变量可以这样设计:

  • 可转移负荷:用二进制变量表示每个允许开始时刻是否启动,比如24个时段里某个时段能不能开始洗衣服;
  • 可削减负荷:用连续变量表示每个时段的实际用电功率;
  • 储能设备:如果部分用户配置了电池,还需要增加充放电功率变量和荷电状态(SOC)约束。

为了体现用户之间的非合作博弈,我引入了小区总进线容量约束:

sum(P_i(t)) <= P_capacity(t)

这个约束是一个全局共享约束。某个用户多用电,其他用户的可选空间就会变小。每个用户在优化时并不知道邻居的具体策略,只能通过电价信号和自身感受去选择,这种信息结构是非合作博弈很典型的表现。

2.3 纳什均衡的存在性与求解难点

非合作博弈的纳什均衡听起来很高大上,但落到程序里其实就是一个不动点问题:每个用户都在给定其他用户策略下取到了自己的最优策略,没有人愿意单方面改变。对于凸优化构成的博弈,均衡存在且容易求;但我们这模型里可转移负荷是0-1整数变量,目标函数还有非线性项,整个博弈是非凸的,这时候均衡的存在性就得靠数值仿真去验证。

实际求解的时候难点在于:下层所有用户的优化是相互耦合的,因为共享容量约束把大家绑在了一起;上层电价又会影响每个用户的优化结果。三层耦合叠在一起,普通求解器很难处理。这就是我选用双层鲸鱼算法的直接原因——它不要求目标函数可导,也用不着求梯度,能比较自然地处理这种嵌套式、不可导、含整数的复杂结构。

3. 双层鲸鱼算法:为什么这么设计,具体怎么迭代

3.1 标准鲸鱼优化算法的简要回顾

鲸鱼优化算法(WOA)是2016年提出的一种群智能优化算法,模拟的是座头鲸的泡泡网捕食行为。座头鲸捕猎的时候会绕着鱼群画螺旋,同时吐出气泡把鱼群逼到中间,然后一口吞下去。算法把这个过程抽象成三种位置更新机制:

第一是包围猎物,鲸鱼根据当前最优个体的位置收缩包围圈;第二是气泡网攻击,鲸鱼沿着螺旋路径向猎物靠近,用一组随机参数在收缩包围和螺旋更新之间切换;第三是随机搜索,以一定概率在全局范围内找新位置,保证种群的多样性。

WOA最大的优势是参数少、结构简单、容易实现,全局探索能力也不错,在大量基准函数上表现都挺好。但标准WOA有个通病:后期容易陷入局部最优,收敛精度不够。用在这个负荷调度问题上,因为决策变量多(电价曲线24维,加上用户负荷变量几十上百维),搜索空间非常大,直接用标准WOA去跑,效果会不太稳定,所以必须做针对性改进。

3.2 单层结构搞不定,双层嵌套怎么设计

为什么单层WOA解决不了这个问题?因为这个问题天然是嵌套的:上层不知道下层用户具体怎么响应,就没法评价一组电价的好坏;下层不知道电价,就没法优化用电计划。两个层的决策必须交替求解。如果硬把上下层决策变量拼在一起用单层算法去搜,搜索维度会爆炸,而且不同层变量的物理意义和约束差异太大,很难收敛到合理的均衡解。

我的做法是外层和内层都用鲸鱼算法,形成“双层鲸鱼算法”:

外层鲸鱼算法的每个个体,代表一组待评估的分时电价曲线p(t),共24维。外层种群负责在电价上下界约束内搜索能使运营商目标最优的那组电价。外层每生成一组电价,就调用内层求解器:在内层,给定这组电价,让所有居民用户分别做负荷调度优化,每个用户自己的鲸鱼算法个体代表该用户的负荷计划;收敛后把各用户负荷汇总成总负荷曲线,返回到外层去计算运营商的适应度值。

这样设计的好处是结构清晰,上层的“策略空间”和下层的“响应空间”完全隔离,符合主从博弈的分层决策特性。缺点是计算量成倍增加,这也是后面第5章要讲优化技巧的原因。

3.3 针对本问题的三个算法改进点

我在标准WOA基础上做了三个改进,实测对收敛速度和稳定性都有明显帮助。

一是用Tent混沌映射生成初始种群。标准WOA初始化是纯随机的,种群容易扎堆在搜索空间的某些区域,导致前期探索不充分。Tent映射能在[0,1]区间内生成分布更均匀的序列,用它映射到决策变量空间,可以让初始电价曲线和初始负荷计划覆盖更多场景。

二是加入自适应惯性权重。标准WOA在迭代前期探索强、后期开发弱,但切换比较生硬。我让惯性权重随迭代次数从0.9线性降到0.4,前期大权重让鲸鱼跑得远、探索范围大,后期小权重让鲸鱼围着最优解精细搜索,收敛精度更好。

三是对最优个体加高斯变异扰动。每次迭代结束后以一定概率对全局最优解加一个小幅度的高斯扰动,生成候选解参与下一轮竞争。这个操作的目的是防止算法过早收敛到局部最优,相当于给算法装了一个“强跳出来”的机制。

当然,改进不是越多越好。只有当你发现标准算法在你这个问题上确实出现早熟或者收敛慢的时候,再加对应改进才是有效率的。盲目堆砌改进策略,很容易出现加了等于没加甚至更差的情况。

3.4 完整求解流程

整个流程我用文字描述一遍,方便大家在Matlab里面照着搭框架:

  • 初始化外层鲸鱼算法参数,随机生成初始电价种群;
  • 将当前代所有电价个体逐个传给内层;
  • 内层对每个电价个体执行用户博弈求解:随机初始化各用户负荷计划,循环迭代直到所有用户都没有意愿单方面改变策略(或者达到最大内层迭代次数);
  • 汇总用户响应负荷,计算外层个体适应度值;
  • 根据适应度值更新外层鲸鱼最优解,更新外层个体位置;
  • 判断外层是否达到最大迭代次数,未达到则返回第2步,达到则输出最优电价与对应负荷调度方案。

这一套流程在Matlab里写成脚本,逻辑非常直观。关键点在于外层个体和内层用户都要维护各自的种群和最优解,别搞混了数据流。

4. Matlab代码实现:从核心框架到可运行工程

4.1 代码工程结构规划

写双层的代码最忌把所有东西堆在一个脚本里,调试起来会让人崩溃。我建议按下面的结构组织目录:

  • main.m,主程序,负责参数初始化、调用双层算法、绘图和结果保存;
  • parameters.m,所有参数的集中配置,包括电价上下限、负荷参数、用户数量、算法参数;
  • obj_operator.m,运营商目标函数;
  • obj_user.m,用户目标函数与约束判定;
  • woa_outer.m,外层鲸鱼算法;
  • woa_inner.m,内层鲸鱼算法;
  • load_data.m,基础负荷和可调负荷数据的生成或导入。

这份结构的好处是,你想换场景的时候只需要改parameters.m和load_data.m,算法核心完全不动。我甚至建议你在parameters.m里加一个flag变量,用于切换“只有可转移负荷”和“可转移+可削减+储能”两种模型,替换场景非常方便。

4.2 目标函数与约束的代码表达

运营商目标函数这块,核心就是计算总负荷曲线,然后算收益和方差惩罚。Matlab里建议用向量化一次性把所有时段算出来,而不要写for循环,否则外层种群50个个体、每个个体24时段,循环嵌套下来速度很感人。

关键代码思路如下:

function f = obj_operator(p, P_total, c_buy, lambda) % p: 1x24 电价曲线 % P_total: 1x24 总负荷曲线 revenue = sum(p .* P_total); cost = sum(c_buy .* P_total); variance_penalty = var(P_total); f = -(revenue - cost - lambda * variance_penalty); % 外层找最小值 end

注意外层是最大化运营商收益,但优化算法一般习惯求最小值,所以目标函数取负号。这个坑我在调试时踩过,算出来结果完全反了,后来才反应过来是符号问题。

用户目标函数类似,但要加上舒适度惩罚项。可削减负荷的舒适度惩罚,可以直接建模成“实际用电功率与期望功率之差”的二次函数,偏差越大惩罚越重,既符合物理直观,又是连续可导的,方便内层搜索。

约束处理我统一用罚函数法。比如配变容量约束,如果某个用户群体决策导致总负荷超限,就在目标函数里加一个很大的惩罚项。罚函数系数要调试,太大容易让算法只顾满足约束而忽略经济性优化,太小约束又可能被忽略。我一般先把约束满足情况监控出来,如果所有个体都满足约束,就尝试适度降低惩罚系数,给经济性优化让路。

4.3 内层博弈求解代码框架

内层是所有用户分别优化的循环。由于每个用户的问题复杂度不同——有的只有基础负荷和可转移负荷,有的还有可削减负荷——所以我单独写了一个用户模型生成函数,每个用户返回自己的决策变量范围和参数。

内层循环的伪代码逻辑:

for iter_inner = 1:max_inner_iter for i = 1:N_user % 固定其他用户负荷,优化用户i的负荷计划 [P_i, cost_i] = woa_inner(price, param_user{i}, P_others); % 更新用户i的负荷和成本 P_user{i} = P_i; end % 检查连续两轮之间所有用户的策略变化量 if max_change < tolerance break; % 达到纳什均衡 end end

这个循环本质上是在求非合作博弈的纳什均衡:每一轮都让所有用户面对其他用户的当前策略做最优响应,反复迭代直到没有人愿意改变策略。在实际代码中,woa_inner函数内部还包含一个完整的鲸鱼算法搜索流程,搜索维度取决于该用户的决策变量个数。要注意的是,woa_inner的种群规模和迭代次数不需要跟外层一样大,内层只要够用就行,否则整体计算量会翻好几倍,后面会细说。

4.4 算法参数设置建议

参数这东西没有绝对最优,但有一个合理的起始范围。我用下来比较稳的参数组合是:

参数取值范围备注
外层种群规模20~50太小容易早熟,太大计算慢
外层迭代次数100~300视收敛曲线调整
内层种群规模15~30够用即可,不宜过大
内层迭代次数30~80保证用户能逼近最优响应
混沌映射迭代次数500初始化时生成均匀序列
自适应权重范围0.4~0.9前期探索、后期开发
高斯变异概率0.05~0.1扰动最优解防早熟

补充一条经验:不同用户的可转移负荷时间窗口和内层优化难度差异很大,如果所有用户都用同样的内层迭代次数,可能有的用户早收敛了还在空转,有的用户还没收敛就停了。更聪明的做法是给每个用户单独计算收敛标志,达到精度就提前结束内层搜索,能节省不少时间。

5. 仿真条件与结果分析应该怎么看

5.1 测试场景怎么设置

为了验证模型有效性,我设置了一个包含20个居民用户的社区场景,调度周期为24小时,时间粒度为1小时。每户的基础负荷曲线参照典型居民用电模式生成,晚高峰集中在18点到22点;其中12户配置了可转移负荷(洗衣机、洗碗机),8户配置了可削减负荷(空调),4户额外配置了家庭储能。

电价方面,运营商购电成本用分时结构,峰时段购电成本0.8元/kWh,平时段0.5元/kWh,谷时段0.25元/kWh。运营商制定的售电电价上下限分别设为0.3到1.2元/kWh,确保有足够的调节空间。这些参数都是模拟值,实际工程中应该根据当地电力市场数据和用户调研来标定,但算法流程完全一致。

5.2 双层算法的收敛性和均衡结果验证

外层WOA大约迭代到80代左右,运营商目标函数值趋于稳定,没有出现明显的震荡,说明算法收敛性良好。迭代早期目标函数快速下降,这是混沌初始化和自适应权重带来的探索能力在起作用;后期曲线平滑下降,说明局部开发能力在线。

内层博弈的收敛验证上,我统计了连续两轮迭代中每个用户负荷曲线的最大变化量,最后稳定在0.01kW以内,说明达到了数值意义下的纳什均衡——每个用户在当前电价和其他用户策略下,都不再有动力单方面调整用电计划。

为了验证模型的合理性,我做了三组对比:

  • 无调度场景:不实行分时电价,用户按自己的习惯用电;
  • 单层优化场景:直接用标准WOA对全局决策变量统一优化;
  • 双层博弈场景:本文模型。

结果中比较关键的几个指标包括运营商收益、用户平均电费、系统峰谷差、负荷率。为了方便阅读,整理成了下面的表:

指标无调度单层优化双层博弈
运营商收益(元)基准值提升约8%提升约15%
用户平均电费(元)基准值降低约6%降低约12%
系统峰谷差(kW)基准值削减约18%削减约26%
负荷率基准值提升3个百分点提升5个百分点

当然这组数字是对应我设置的场景参数得到的,换场景绝对值会有变化,但趋势是稳定的:双层博弈模型能兼顾运营商和用户双方利益,同时较好地改善负荷曲线,这正是引入博弈机制的价值。

5.3 从结果中能看到什么

从最优电价曲线看,运营商在晚高峰时段定价明显上浮,在凌晨低谷时段定价下调,形成了典型的峰谷价差结构。用户的响应也符合预期:可转移负荷大量挪到谷时段启动,可削减负荷在峰时段自动降功率,储能设备在谷时段充电、峰时段放电。

这个结果说明模型确实在学习一种“电价引导+用户自主响应”的调度模式,而不是靠行政命令强制用户改变用电行为。对于电力市场环境下的需求响应来说,这种机制更有实际可行性,用户的接受度也更高。

6. 调试过程中踩过的坑和排查技巧

6.1 内层不收敛导致外层目标值震荡

第一次完整跑通模型的时候,运营商目标值一直在跳,曲线上下来回震荡。我一开始以为是外层算法参数没调好,反复改种群和迭代次数都没用。后来仔细看数据才发现,问题是出在内层:内层迭代次数设得不够,用户压根没有收敛到最优响应,外层拿到的总负荷曲线质量很差,目标值自然就飘。

解决办法是把内层的收敛判断从“固定迭代次数”改成“最大迭代次数+误差容忍度”双重标准,并输出每个用户的实际收敛状态。如果发现多个用户经常达到最大迭代次数还没收敛,就要考虑是内层种群规模太小,还是用户目标函数太复杂。我最后把内层迭代次数从30提高到60,并适当调小了内层搜索的决策空间边界,震荡问题就解决了。

6.2 容量约束罚函数系数不好定

罚函数系数太小,用户会无视配变容量约束,总负荷曲线频繁越限;系数太大,算法只顾着满足约束,运营商收益和用户电费指标都很差。这个矛盾在双层结构里会被放大,因为内层用户决策要满足约束,外层电价又要考虑总负荷,两层对约束的感知不一样。

我的经验是分两步走:先固定一个偏大的罚函数系数,让算法优先保证约束不越限,跑通整个流程;然后逐步降低系数,观察约束违反次数和经济性指标的变化,找到一个“约束几乎不违反、经济性指标尚可”的临界值。这种做法比单纯靠感觉调系数靠谱得多。

6.3 计算时间太长,怎么压缩

双层算法的计算瓶颈在内层:外层每个个体都要调用一次完整的内层博弈求解,内层又有多个用户各自跑一次WOA,循环套循环,时间直接爆炸。我最初跑一个场景要四十多分钟,后来做了三个优化,时间压到五分钟左右。

第一个优化是向量化用户目标函数计算,把用户决策变量做成矩阵运算,而不是用for循环逐时段计算;第二个优化是内层种群和迭代次数分档,简单的用户用小规模搜索,复杂的用户用大规模搜索,而不是一刀切;第三个优化是并行计算,外层个体的适应度评估互相独立,完全可以用parfor替代for,CPU核心多的话速度提升非常明显。

6.4 所有用户负荷曲线几乎一样

这个问题比较隐蔽,我一度以为模型坏了。检查之后发现是用户参数设置的问题:虽然定义了20个用户,但他们的可转移负荷时间段、舒适度偏好、基础负荷曲线几乎一样,那么不同用户面对同一电价的最优响应自然也一样。这本身没有错,但会让“用户差异”这个关键信息丢失,非合作博弈的用户间竞争也没法体现。

要想让模型有实际意义,必须把用户参数的差异性做出来。基础负荷加随机扰动,可转移负荷的时间窗口错开分布,舒适度惩罚系数按用户类型分布设定。这样每个用户的策略空间就不同了,博弈的结果才能反映真实场景的多样性。

7. 常见问题速查与后续扩展方向

7.1 故障排查速查表

调试过程中最常遇到的现象、原因和解决办法,整理成一张表放在这,跑模型遇到问题可以先对照自查:

现象可能原因解决方法
外层目标值震荡不收敛内层未搜索到最优响应增加内层迭代次数或改用更高精度内层求解
收敛过快但结果很差种群过早陷入局部最优检查混沌初始化是否生效,调大高斯变异概率
总负荷总是越过容量上限罚函数系数偏小增大罚函数系数,或增加越限量惩罚项
电价全部贴到边界值目标函数缺少电价平滑项在运营商目标中增加电价波动惩罚
运行时间过长内层计算量太大向量化目标函数、对内层参数分档、启用并行
各用户负荷曲线几乎相同用户参数差异不足给基础负荷、时间窗口、偏好系数增加随机差异
单次运行结果波动大算法随机性导致结果不稳定增加外层种群规模,或多次运行取最优

7.2 后续可以怎么扩展

这个模型框架的扩展性很好。最常见的扩展方向有三个:

第一是接入更多类型的可调资源。电动汽车充电桩是很典型的可转移负荷,而且充电时间灵活、功率大,对居民负荷曲线的影响越来越显著。把电动汽车充电模型并入用户决策层,只要在用户目标函数和约束里增加充电状态变量、充电功率上下限和车主期望电量约束即可。

第二是考虑分布式光伏和储能。光伏出力有很强的不确定性,用户侧储能可以平抑这种波动。引入不确定性之后,双层博弈模型可以跟鲁棒优化或者场景法结合,内层用户决策变成多场景期望成本最小化,模型会更复杂,但更贴近实际。

第三是从单社区扩展到多社区协调。多个社区共享上级配电线路时,运营商层面也可以引入博弈或者多目标协调机制,形成“运营商间博弈+用户间博弈”的双层嵌套博弈,研究难度更大,但学术价值也更高。

7.3 个人实操经验总结

做这套模型的过程中,我最大的体会是:双层鲸鱼算法本身并不神秘,它其实就是把一个复杂的嵌套优化问题,通过“外层搜策略、内层搜响应”的方式拆成了两个相对简单的搜索任务。难点不在算法原理,而在数据流设计和参数调试:哪个变量放在哪一层算、内层返回什么给外层、约束用惩罚还是修复,这些工程细节才真正决定模型能不能用。

另外强烈建议大家在设计完模型之后,先用小规模场景把每层代码单独验证一遍,比如先把内层用户的优化单独拿出来跑,确认每个用户在给定电价下确实能把自己的电费降下来;再固定用户响应,只跑外层电价优化,确认运营商收益在提升。每一步都验证过了再合起来联调,出问题的时候能省下大量排查时间。

如果你正准备复现这个模型,建议先从最简单版本开始:10个用户、只考虑可转移负荷、内层用普通WOA、外层迭代次数150次。跑通之后再逐步增加可削减负荷、储能、用户差异和博弈复杂度。一口吃不成胖子,双层模型尤其需要层层递进地搭。

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

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

立即咨询