前阵子做居民负荷调度方向的仿真时,我第一版用的是最省事的单层集中式优化:把整个小区的用户当作一个整体,以系统总成本最小为目标去分配负荷曲线。结果曲线非常漂亮,峰谷差下降明显。可后来我做了一个反向实验——假设每个用户独立决策,完全按自己的电费账单和舒适度偏好来用电,结果几乎没有人愿意跟着那条"全局最优曲线"走。问题一下就暴露了:居民负荷调度真正的难点不在优化,而在处理多个决策者之间的利益博弈。
后面我重构了整个方案,做了基于非合作博弈的居民负荷分层调度模型,并用双层鲸鱼算法求解,整套用Matlab实现。这篇文章会把建模思路、博弈模型设计、双层算法原理、代码实现细节和踩过的坑一次性讲透,适合正在做智能用电、需求响应或负荷调度仿真,又不想停留在单层优化套路的读者。
1. 为什么居民负荷调度要绕开"单层集中式"这条路
1.1 集中式调度的三个天然局限
单层集中式调度有一个隐含假设:所有用户都是"可指挥"的,调度中心给每个用户下发负荷指令,用户无条件执行。这个假设在工业负荷场景里勉强成立,因为工厂有合同约束和惩罚机制;但放到居民用户身上,基本上不现实。
第一个问题是目标不一致。调度中心关心的是峰谷差、系统运行成本、线路负载率,而居民用户关心的是电费便宜不便宜、屋里热不热、洗衣机能不能在下班前洗完衣服。两个目标本来就不对齐,强行把用户塞进系统级目标函数里,算出来的结果只会让用户在仿真里"听话"、在现实中"出走"。
第二个问题是信息不完全。集中的调度器需要知道每一户的用电偏好、负荷类型、可转移容量,这些信息收集起来成本极高,而且用户没动力如实上报——报了真实的可转移容量,反而可能被要求多削峰,对自己没好处。这就进入了第三个问题,激励不相容:越配合调度的用户,承担的不舒适成本越高;越不配合的用户,反而什么都不用牺牲。长期运行下去,系统必然退化。
所以,单层集中式调度在理论上有漂亮的数学性质,但在居民场景下,它解决的是"如果一个上帝来分配用电,怎么分最合理",而不是"一群真实用户凑在一起,最终会形成什么局面"。
1.2 居民负荷的天然分层结构
居民负荷调度不需要硬造分层,它本身就有明显的层级特征。
最底层是单个用户。每个用户家里有一堆电器,大致可以分成三类:固定负荷、可转移负荷、可中断负荷。固定负荷(照明、冰箱、路由器)基本不具备调度价值,全天刚性运行。可转移负荷(洗衣机、洗碗机、电动汽车充电)的特点是总用电量不变,但可以从一个时段挪到另一个时段。可中断负荷(空调、热水器、电采暖)的特点是可以在一定范围内调节功率,完全关掉也行,但会影响舒适度。
中间层是负荷聚合商或物业小区控制器。它的角色是整合一大群用户的弹性资源,形成一条可以被电网识别的聚合负荷曲线,同时负责向用户传递价格信号或激励信息。有没有这一层,不影响模型主体的博弈关系,但从工程角度讲,有了聚合层,电网才不会面对成千上万个独立节点。
最高层是电网调度机构或零售商。它掌握全局负荷信息,能够制定分时电价、确定削峰目标。这个层级不直接指挥每一台洗衣机,而是通过价格信号影响所有用户的决策环境。
所以,"分层调度"本质上是顺着这套天然结构搭了一套决策框架:上层定规则,下层做响应。
1.3 把"被动执行"改成"博弈决策"之后发生了什么
改成博弈思路之后,整个问题发生了质的变化:
- 每个用户不再是调度指令的接受者,而是拥有独立目标和策略空间的参与者。
- 用户与用户之间通过电价相互耦合——你多用电,价格可能上涨,别人就得多付钱;反过来也一样。
- 调度的目标从"让每个用户执行指定曲线"变成"设计一个规则,让用户在追求自己利益的过程中,自发形成对系统有利的均衡状态"。
这其实是一种很务实的思路:你没法控制一群人的行为,但你可以改变他们做决策的环境,让每个人在"对自己好"的同时,客观上帮了系统。这正是需求响应追求的"双赢"状态,也是非合作博弈模型在这个题目里真正值钱的地方。
2. 非合作博弈建模:用户之间的"互相牵制"是如何写进数学式的
2.1 参与者、策略空间与收益函数
把非合作博弈框架落到居民负荷调度上,需要明确四个基本要素。
参与者:假设有N个居民用户,记作i = 1, 2, ..., N。每个用户都是一个独立的理性决策者。为了保证模型可解,通常会假设用户之间信息对称但策略独立,即每个人都知道电价规则,但不联合密谋。
策略空间:用户i的策略向量是它在24个时段内的可调度负荷功率,记作x_i = [x_i1, x_i2, ..., x_i24]^T。如果是可转移负荷,x_it表示t时段转移后的用电量,需要满足全天总用电量守恒;如果是可中断负荷,x_it必须在舒适度允许的功率区间内取值。
收益函数:用户i的收益由三部分组成——用能效用、电费支出、不舒适惩罚。
f_i(x_i, λ) = Σ_t [U_i(x_it) - λ_t · x_it - θ_i · (x_it - x_it^0)^2]
其中λ_t是t时段电价,x_it^0是用户i在无调度状态下的基准用电量,θ_i是舒适度敏感系数。最后一项的意思是:如果调度方案让用户偏离了他的习惯用电量,他要付出"不舒适代价"。
博弈规则:所有用户同时(或近似同时)选择自己的策略x_i,电价λ_t由调度规则决定(见2.3),最终所有用户达到纳什均衡状态。
2.2 效用函数为什么要用二次函数
收益函数里最关键的是U_i(x_it)这个效用函数。我见过不少初学者随便填一个线性函数进去,结果用户会疯狂把所有负荷堆到电价最低的时段,因为线性效用下多用电永远有正收益,没有饱腹感。
实际建模通常用二次效用函数:
U_i(x_it) = α_i · x_it - β_i · x_it²
其中α_i > 0,β_i > 0。这个函数的经济学含义很直观:用电量从0往上涨的时候,用户获得的满足感是增加的,但边际效用递减——第一度电带来的满足感很大(比如给手机充电、烧壶水),后面的电带来的额外满足感越来越小。
这个性质直接决定了博弈模型的合理性。如果没有边际效用递减,用户面对低电价会无限增加用电,负荷曲线会出现新的尖峰;有了二次函数,用户会在"多用点电的爽感"和"多付点钱的痛苦"之间自动寻找平衡点,调度问题才真正有解。
2.3 电价反馈:把独立用户"耦合"在一起的关键机制
非合作博弈居民负荷调度的精髓在一个看似简单却极其关键的设定:电价不是固定常数,而是随系统总负荷动态变化的。
最常用的线性价格响应模型是:
λ_t = λ_base_t + k_t · (Σ_i x_it - P_ref_t)
λ_base_t是基础分时电价,P_ref_t是t时段的基准负荷(比如无调度时的历史平均负荷),k_t是价格弹性系数。这个公式的物理含义很直白:当在t时段所有用户的负荷之和超过基准值时,电价上涨;低于基准值时,电价下降。
这个电价反馈机制把原本完全独立的所有用户耦合在了一起。用户在制定自己的用电策略时,不仅要看自己的效用和基础电价,还必须在心里"猜"其他用户会怎么用电——因为别人用多了,价格被推高,自己的成本也会增加。这就是典型的非合作博弈场景:每个人的最优决策取决于其他人的决策。
从代码实现的角度看,这个耦合也意味着下层的用户优化不能完全并行独立计算——虽然每个用户的变量是分开的,但在评估某个用户策略的收益时,必须使用包含所有用户最新决策的总负荷计算出来的电价。
2.4 纳什均衡:模型到底在求什么
在这个博弈模型里,一个策略组合x* = (x_1*, ..., x_N*)被称为纳什均衡,如果对任意一个用户i,在其他人策略保持x*_(-i)不变的情况下,用户i单方面改变策略都不能让收益f_i变得更大。
换句话说,均衡状态下的情况是:每个用户都觉得自己已经尽力了,换任何其他策略都不划算,于是所有人都不再变动策略,整个系统达到稳定。这个状态下的负荷曲线和电价,就是模型要输出的调度结果。
纳什均衡的存在性依赖于目标函数的凸性和策略空间的紧性,在这些条件满足时,可以采用迭代最优反应(iterative best response)的方式逼近均衡点——具体到算法实现上,就是给一组初始电价和用户策略,让每个用户轮流优化自己的负荷,更新总负荷,更新电价,再重复,直到连续两轮的策略变化小于某个阈值。
需要提醒的是:现实博弈场景可能存在多个纳什均衡,不同初始点收敛到的均衡可能不同。实践中一般采取多次随机初始化取最优均衡,或者在收益函数中加入足够强的扰动项来引导系统收敛到对电网更友好的均衡。
3. 双层鲸鱼算法:为什么标准鲸鱼算法在这里不够用
3.1 标准鲸鱼优化算法的三步核心机制
鲸鱼优化算法(WOA)是2016年提出的群体智能优化算法,灵感来自座头鲸的泡泡网捕食行为。选择它作为求解器,核心原因是算法结构简单、控制参数少、全局搜索能力在线性递减参数的控制下表现得比较稳定,而且在Matlab里实现只要几十行核心代码。
标准WOA包含三种位置更新机制,每次迭代以随机概率p和系数|A|决定采用哪一种:
收缩包围:当p < 0.5且|A| ≤ 1时,鲸鱼向当前最优个体包围收缩。
D = |C · X_best - X(t)|
X(t+1) = X_best - A · D
其中A = 2a·r1 - a,C = 2·r2,a从2线性递减到0,r1、r2是[0,1]随机数。
螺旋气泡网攻击:当p ≥ 0.5时,鲸鱼以螺旋轨迹靠近最优个体。
D' = |X_best - X(t)|
X(t+1) = D' · e^(b·l) · cos(2πl) + X_best
其中b是螺旋形状常数,l是[-1,1]随机数。
随机搜索:当p < 0.5且|A| > 1时,鲸鱼在群体中随机选一个个体作为参考,实现大范围探索。
X_rand = X(rand_index)
X(t+1) = X_rand - A · |C · X_rand - X(t)|
这三条规则让WOA在迭代前期保持较强的探索能力,后期随着a递减逐渐转入局部精调。对于24维或更高维的优化问题,标准WOA已经能用,但直接套到双层博弈问题上会出问题,原因见下节。
3.2 双层嵌套如何对接"定价—响应"主从博弈
回到我们的分层调度模型。上层调度机构要制定分时电价策略,下层每个用户在给定电价下优化自己的负荷。这是一个典型的"领导—跟随"结构,上层决策会影响下层决策环境,下层决策又反过来通过总负荷影响上层的目标,标准单层WOA无法表达这种嵌套关系。
所以需要双层鲸鱼算法,结构如下:
外层鲸鱼——负责上层调度机构的价格策略优化。决策变量是24时段的分时电价向量λ = [λ_1, ..., λ_24],外层适应度函数是网侧目标:峰谷差最小、电网运行成本最低、用户平均满意度损失最小,多目标通过加权合成单目标。外层的每条鲸鱼个体,都代表一组完整的分时电价方案。
内层鲸鱼——负责下层每个用户的负荷策略优化。给定外层传来的电价λ,每个用户i的优化问题是:在不违反约束的前提下最大化自己的收益f_i(x_i, λ)。内层可以有N条并行的鲸鱼进程,分别对应N个用户。所有内层鲸鱼求解完毕后,把各自的最优负荷汇总成系统总负荷曲线。
反馈闭环——外层根据内层返回的总负荷曲线,用价格响应模型更新实际电价(或直接评估外层目标函数),计算适应度,然后通过鲸鱼的位置更新机制生成新一代电价方案。如此循环,直到外层适应度稳定收敛。
这个嵌套关系在逻辑上对应了"分层调度"的定义:上层定规则,下层做最优响应,响应结果反过来影响上层决策,最终收敛到整个系统的稳定状态。
3.3 我在工程实现里加的三个实用改进
标准双层WOA能跑通,但直接投到工程里会碰到两个问题:收敛慢、不稳定。我在实现里做了三处针对性改进。
第一,自适应惯性权重。标准WOA在后期a递减到接近0时,种群多样性快速下降,容易陷入局部最优。我给外层鲸鱼的位置更新叠加了一个随迭代次数递减的惯性权重w = w_max - (w_max - w_min)·(t/T),前期w大,保留更多全局探索能力;后期w小,加速局部精调。这样24维电价向量的搜索稳定很多。
第二,下层并行化。内层N个用户的优化是天然独立的,只是评估收益时需要共享总负荷信息。我采用"每轮迭代先并行求解所有用户,再用本轮最新策略汇总总负荷、更新电价"的方式,避免用户在迭代过程中读取到过期的价格信息,既保持了并行效率,也维持了博弈的同步性。
第三,约束修复而非罚函数硬惩罚。上层电价必须满足上下限约束和平均电价约束,直接用罚函数会导致有效搜索空间被大幅压缩,罚函数系数也难调。我的做法是:鲸鱼生成新位置后,如果越界,用边界映射或投影法直接把个体拉回可行域,保证种群中每个个体都是可行解。这样寻优效率高不少。
4. Matlab代码实现的关键细节
4.1 工程文件的划分
我建议把工程拆成六个文件,职责分明,调试起来不用在几百行的脚本里翻来翻去。
| 文件 | 职责 |
|---|---|
| main.m | 参数初始化、调用双层求解、输出结果 |
| WOA_outer.m | 外层鲸鱼:优化分时电价向量 |
| WOA_inner.m | 内层鲸鱼:单用户在给定电价下的负荷优化 |
| cal_user_fitness.m | 计算用户收益(效用-电费-不舒适度) |
| cal_system_fitness.m | 计算上层目标(峰谷差+电网成本+满意度损失) |
| load_model.m | 配置三类负荷参数、基准曲线、约束 |
| plot_results.m | 绘制负荷曲线、电价曲线、收敛曲线 |
main.m 里最核心的部分是双层调用的骨架:
%% 参数初始化 num_users = 100; T = 24; price_lb = 0.3; % 电价下限(元/kWh) price_ub = 1.2; % 电价上限 max_iter_outer = 50; max_iter_inner = 30; pop_outer = 30; % 外层种群规模 pop_inner = 20; % 内层种群规模 %% 外层鲸鱼初始化 prices = price_lb + (price_ub - price_lb) * rand(pop_outer, T); for iter = 1 : max_iter_outer for k = 1 : pop_outer % 内层:每个用户在该电价下做最优响应 load_profile = zeros(num_users, T); for i = 1 : num_users load_profile(i, :) = WOA_inner(prices(k, :), user_params(i), max_iter_inner, pop_inner); end % 汇总总负荷,评估外层适应度 fitness(k) = cal_system_fitness(prices(k, :), load_profile, net_params); end % 更新外层鲸鱼位置,得到新的电价种群 prices = update_whale_position(prices, fitness, iter, max_iter_outer); end这个骨架把两层关系表达得很清楚:外层给价格,内层做响应,响应结果回传,外层更新价格。
4.2 上层电价染色体的编码与约束修复
上层决策变量是24个时段的电价,编码方式最简单也最有效:直接用一个1×24的向量表示一组电价方案。之所以不建议用二进制编码或更复杂的编码方式,是因为连续数值编码配合边界映射修约束,在Matlab里的实现效率和搜索平滑性都远好于离散编码。
电价必须满足的两个硬约束是:每个时段电价在允许区间内,且全天平均电价不能过高——否则用户总电费暴涨,满意度指标崩掉,上层目标函数完全失去意义。约束修复在代码里是这样做的:
function prices = repair_prices(prices, price_lb, price_ub, avg_price_max) % 先做边界映射,保证每个时段电价在上下限内 prices = max(price_lb, min(price_ub, prices)); % 再检查平均电价约束,超了就把所有时段按比例压缩 avg_price = mean(prices); if avg_price > avg_price_max scale = avg_price_max / avg_price; prices = prices * scale; end end这里有个逻辑要特别注意:边界映射用的是截断而非重新随机。截断可以保证新个体和原个体"离得不远",搜索连续性不会被破坏。如果用随机重置,会因为跳变太大破坏已经找到的较好区域。
4.3 下层用户最优负荷的并行求解
下层是所有计算量的大头,因为N个用户每个都要做一轮小优化。在实际工程里,我一般用parfor替代for并行计算,前提是用户参数已预加载,每个用户之间的数据互不依赖。
total_load = zeros(T, 1); user_load = zeros(num_users, T); parfor i = 1 : num_users % 并行求解每位用户的最优负荷 user_load(i, :) = WOA_inner(prices_current, user_params(i), max_iter_inner, pop_inner); end total_load = sum(user_load, 1) + fixed_load; % 加上固定负荷需要注意:博弈迭代过程中,电价是用上一轮的总负荷算出来的,所以在parfor体内千万不能动态更新价格或读取其他用户的实时决策结果。所有同步都放在parfor结束之后统一做,一致性才有保障。
内层用户适应度函数是这么写的:
function fitness = cal_user_fitness(x, price_vector, user_param, lambda_total_load) % x: 1x24决策向量(该用户各时段可调度负荷) % price_vector: 1x24电价向量 alpha = user_param.alpha; beta = user_param.beta; theta = user_param.theta; x_base = user_param.x_base; utility = alpha * x - beta * x.^2; % 用能效用 cost = price_vector .* x; % 电费支出 discomfort = theta * (x - x_base).^2; % 不舒适惩罚 fitness = sum(utility - cost - discomfort); % 总收益,内层求最大 end在用户可转移负荷的子问题里,需要额外加一个等式约束(全天总用电量守恒),处理方式是在WOA_inner的初始种群生成时采用整数随机划分,或者在迭代结束后做负荷总量归一化,把偏差按比例修正回去。
4.4 主循环收敛判断与参数表
双层模型的收敛有个常见假象:内层先收敛了,外层还在缓慢变化,如果只盯内层适应度会误判。我的做法是:主循环记录外层最优适应度序列,连续5代的变化率小于1e-4就认为已经收敛;同时还要记录价格变化的幅度,因为有时候适应度没变但价格在漂移,这种漂移会导致最终电价方案不稳定。
参数表我通常这样设置,可以直接作为起点参考:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 时段数T | 24 | 粒度1小时,兼顾计算量和精度 |
| 用户数N | 100 | 规模适中,方便观察博弈效果 |
| 外层种群 | 30 | 电价维度24,30个体足够探索 |
| 外层迭代 | 50 | 经验值,配合早停条件使用 |
| 内层种群 | 20 | 单个用户维度不高,20够用 |
| 内层迭代 | 30 | 内层优化不能太深,否则整体计算量爆炸 |
| 价格弹性系数k | 0.02 | 太大会导致价格振荡,太小博弈力不足 |
| 可转移负荷占比 | 20% | 模型对结果敏感的重要参数 |
运行时间方面,100个用户、内外层各30次迭代左右,在一个中档台机上约需要几分钟。如果要跑参数敏感性分析,建议先把内层迭代次数降下来,用快速粗糙结果选参,再回归精细参数。
5. 仿真结果应该怎么设计、怎么解读
5.1 对照方案设计
判断这个模型有没有价值,不能只看它自己的结果曲线多好看,必须有对照。我建议至少跑三组:
- 方案A:无调度基线。所有用户按原始习惯用电,电价固定不分时,作为性能比较的基准线。
- 方案B:单层集中式优化。把所有用户捆成一个整体,以系统成本最小为目标直接优化总负荷曲线,不考虑用户个体收益。
- 方案C:本文的双层鲸鱼博弈调度。上层动态定价,下层用户博弈响应。
三组方案在相同负荷数据、相同用户数量下运行,才能看清"博弈调度相对集中优化到底损失了多少最优性、换来了多少可行性"。
5.2 结果指标定义
评价方案不能只看一个指标,我至少盯四个维度:
- 峰谷差率和峰谷差。峰谷差率 = (峰值负荷-谷值负荷)/峰值负荷,衡量削峰填谷效果。这是电网侧最关心的指标。
- 用户平均电费变化率。相对基线电费的节省比例,衡量用户侧收益。如果用户电费没有明显下降,用户凭什么参与调度?
- 用户不满意度。Σ用户不舒适惩罚 / Σ用户基准用电量,衡量调度方案的"人性化程度"。
- 算法稳定性。同样参数跑10次,最优/最差/平均结果之间的离散程度。群体智能算法没有这个指标,很难判断结果是偶然还是必然。
5.3 典型结果长什么样
下面是一组有代表性的结果(基于某模拟项目的100户典型日负荷数据进行多次运行的均值),用来告诉你合格的结果大致是什么量级:
| 方案 | 峰谷差(kW) | 峰谷差率(%) | 用户平均电费节省(%) | 用户不满意度指数 |
|---|---|---|---|---|
| 无调度基线 | 820 | 41.2 | — | 0 |
| 单层集中优化 | 590 | 30.8 | 6.2 | 12.5 |
| 双层鲸鱼博弈调度 | 615 | 31.9 | 9.8 | 4.7 |
从结果能读出两层信息:集中式优化的削峰效果确实最好,因为它完全没有用户意愿约束,可以任意把高峰负荷压下来,但代价是用户不满意度高达12.5——这在实际中意味着用户会拒绝执行。双层博弈调度削峰的绝对值略逊一筹,但用户电费节省更明显、不满意度低很多,整体上是一个"用户愿意执行"的方案。
这里有个经验:不要一味追求峰谷差最小。调度方案最后要落地,用户的接受度和实际执行率比纸面上的削峰效果重要得多。博弈模型的价值恰好体现在这里。
6. 实操中绕不开的坑和三个可扩展方向
6.1 罚函数系数的两难,以及我的替代方案
很多人在用户约束处理上喜欢用罚函数:把违反约束的部分乘以一个大罚数加进目标函数。这样实现简单,但坑很深。罚数太小,约束形同虚设;罚数太大,目标函数的数值量级被罚数主导,效用函数和电费部分的作用被稀释,好不容易算出来的解看起来"满足约束"但物理上不合理。
我的替代方案是用"可行性优先修复策略":生成新位置后,先按约束逻辑直接修正决策变量。比如可转移负荷总量必须守恒,就把每个用户的全天负荷向量按比例缩放,使总和等于基准总量;功率越界的部分直接截断。修正后再计算适应度,所有个体天然满足硬约束,罚函数系数就被彻底绕开了。计算量增加很少,但调参痛苦大幅减少。
6.2 时段划分粒度与数据对齐
另一个容易翻车的细节是时段粒度。很多论文案例把一天分成24个时段,1小时一步,跑起来很舒服。但实际负荷数据往往不是整点的,空调功率随温度分钟级波动,洗衣机的运行周期也可能是45分钟。如果不做数据对齐,把分钟级数据粗暴聚合到小时级,会出现:原峰值被平均掉,削峰效果被高估;转移负荷的时段边界错位,导致可转移量失真。
我的建议是:原始数据先按15分钟粒度进行预处理和可视化,模型计算再聚合到1小时。不要直接从原始分钟数据算模型,否则运行时间膨胀,而且电网侧的峰谷特性会被分钟级随机波动干扰,不利于结果对比。
6.3 三个更深入的方向
- 主从博弈(Stackelberg)框架:本文的上下层关系其实已经在向主从博弈靠拢。如果想更严谨,可以把上层电网塑造成领导者,明确其先动优势,下层的用户非合作博弈作为跟随者的均衡问题,整个模型可以用逆向归纳的思路去求解,理论性质更清晰。
- 多目标帕累托优化:上层要同时照顾削峰、电网成本、用户满意度,加权求和只是一个妥协方案。如果三个目标量纲差异太大,权重很难定。更进阶的做法是改用多目标鲸鱼算法,直接求帕累托前沿,给决策者一个三维权衡面。
- 强化学习替代内层求解器:内层用户的最优响应问题在博弈重复进行时,可以用Q-learning或策略梯度方法学习用户的决策规律,避免每次博弈都重新跑一遍鲸鱼优化。这对大规模用户场景很有吸引力,只是稳定性和训练时间又是一笔新账。
最后再分享一个我在这个项目里收获最大的体会:调度模型的价值不在于把某个指标算得多漂亮,而在于它能不能描述真实世界里各方参与者"会怎么动"。非合作博弈加分层调度这个组合,本质上是放弃了"上帝视角"的幻想,老老实实承认用户是活生生的人,然后用机制设计去引导他们。后面再做类似题目,我都习惯先在纸上把所有参与者的利益画出来,再开始写代码——这一步省下来的调参时间,远远超过我最初写模型用的时间。