☰
蒙特卡罗与同步回代:风光场景生成与约减实战解析
2026/10/6 8:52:12 网站建设 项目流程

搞过电力系统调度或者新能源并网分析的人,大概都体会过这种尴尬:风光出力曲线看着每天都是那么几条,可真拿一条典型日曲线去算储能容量、算备用需求、算机组组合,结果往往偏乐观,关键时刻调度员只能靠人工经验兜底。蒙特卡罗算法、启发式风光场景生成、同步回代场景约减这几个词,听起来像论文标题,实际上干的活很直白——把无限多种可能的风光出力过程,压缩成有限个有代表性的场景,并且保留概率信息。我最早接触这套东西是在做风光储联合优化的时候,被随机优化模型的求解规模卡到怀疑人生,后来把场景从几千个约到十个,计算时间从半小时降到几秒,结果精度几乎没有下降。这篇文章就按这个完整流程来拆,从蒙特卡罗生成、启发式改进到同步回代消除的每一步原理、Python实现和实际调参经验都会讲到,适合正在做随机优化、电力规划或储能配置的朋友参考。顺带说一句,最近很热的“上传一段视频生成对应的三维场景”,本质上也是在用少量关键信息表达复杂世界,和场景约减是同一个思路。

1. 风光场景生成到底在解决什么

1.1 为什么一条“典型出力曲线”不靠谱

很多初学者喜欢把历史数据平均一下,当作风光出力场景输入优化模型。表面上看省事,实际上有两个大问题。第一,平均曲线会抹掉时间相关性,尤其是风力光伏不同步的时候,系统实际需要的旋转备用会被严重低估。第二,光伏、风电的极端出力状态,比如连续阴天、大风的阵风过程,才是电网规划里最要命的场景,平均之后这些极端情况彻底消失,优化结果自然偏乐观。我在一个光伏加储能项目里试过用典型日曲线做容量配置,结果算出来的储能容量比实际需求少了将近30%。后来改成场景法,才把方案置信水平补回来。

所以场景生成要回答的不只是“平均长什么样”,而是“极端情况和中间状态各有多大可能出现”。对运行调度来说,你要知道最差工况出现在什么时候;对规划来说,你要知道概率分布的长尾到底有多厚。单条确定性的出力曲线给不了这些信息,必须用一批带概率的场景来逼近真实分布。

1.2 蒙特卡罗采样、启发式生成和同步回代约减如何串成一条线

整套流程其实是一条流水线。第一步,用蒙特卡罗算法按照风速、辐照度等概率模型生成成千上万条风光时序,这一步解决“样本够不够”的问题。第二步,用启发式策略对采样过程或初始场景做优化,解决“样本质量好不好”的问题。第三步,用同步回代消除法把场景集合缩减到目标数量,同时给每个保留场景重新分配概率,解决“下游优化能不能算得动”的问题。三者缺一不可。

只蒙特卡罗不约减,场景多到随机优化根本没法收敛;只约减不好好生成,初始样本本身就偏了,后面全是白做。我习惯把这套流程叫“先发散、再收敛”:先用随机性铺满可能性空间,再用距离判据和概率转移做减法。这跟拍视频生成三维场景也很像——先拍大量素材,再从中挑选关键帧重建几何结构,不是为了保存所有画面,而是为了用少量高信息量的画面还原真实空间。

2. 核心算法拆解:蒙特卡罗、启发式与同步回代

2.1 蒙特卡罗场景生成:从概率分布到出力时序

蒙特卡罗算法本质上就是用重复随机试验来逼近复杂概率分布。对风速来说,最常用的两参数Weibull分布,概率密度函数是:

[ f(v;k,c)=\frac{k}{c}\left(\frac{v}{c}\right)^{k-1}e^{-(v/c)^k} ]

其中 (k) 是形状参数,(c) 是尺度参数。得到分布后可以用逆变换采样:生成 ([0,1]) 均匀随机数 (u),代入反函数 (v = c(-\ln(1-u))^{1/k}) 得到风速样本。这里一定要先做分布拟合,再采样,不能直接拿原始风速时序去随机抽,否则会把测量误差和极端异常值当成自然波动。

但直接对全天风速整体采样是不够的,因为时序有自相关性,白天和晚上的风力是连续演化的,不是独立同分布的。我经常用AR(1)模型叠加Weibull边缘分布,先按小时相关性生成基础序列,再去匹配分布,这样得到的风速场景才有时序感。光伏部分类似,用Beta分布描述辐照度,按日出日落时段削去夜间零值,再叠加大气透明度随机波动。最后把风速通过风机功率曲线映射成功率,辐照度通过光电转换模型映射成功率,就得到一条包含风电光伏两个随机源的场景。

这套流程里最常见的错误是只对“单时刻出力”采样,忽略24小时之间的关联。调度决策关心的是连续时段的净负荷曲线,如果每个时刻独立采样,会出现白天风速很大、夜里光伏满发这种物理上几乎不存在的组合,下游优化结果就不敢信了。

2.2 启发式策略:让随机采样少做无用功

如果只是无脑蒙特卡罗,N取2000甚至5000,里面会有一大堆彼此非常接近的场景,这就是无效样本。启发式策略的出发点,是在采样或初筛阶段就引入“哪些区域更该被照顾”的判断。常见做法有三种。

第一种是拉丁超立方抽样。把概率空间分层,保证采样点均匀覆盖分布的各分位数,特别是让低概率但高影响的风速区间也能采到。同样采500个点,普通随机采样的尾部可能只有几个样本,拉丁超立方能保证每个分位区间都有样本,场景的覆盖度明显提升。

第二种是基于距离的贪心初筛。先大致算一遍两两场景之间的距离,把完全重复的场景直接合并。这一步不用太精确,因为后面还有严格的同步回代,但可以先砍掉明显冗余的样本,节省后续计算量。

第三种是重要性采样思想。对极端出力区间加大采样权重,比如连续阴天或者强阵风时段,让这些“稀有但危险”的场景在初始集合里出现更多次,然后再在概率修正阶段把权重调回来。这样做的目的是防止尾部场景在约减时被过早删掉。

我一直强调,启发式不是某套固定公式,而是一个“减少无效计算”的总原则。同步回代消除本质上也是启发式——它每一步都在做局部最优决策,用最短距离找最不值得保留的场景。

2.3 同步回代消除法的原理与数学直觉

同步回代(Backward Reduction,也叫同步回代消除)这个名字看起来很绕,思路其实很简单:从大集合一步步往回删,每次删掉一个场景,把它的概率转给离它最近的保留场景,一直删到目标数量。数学上,它在最小化约减前后分布之间的Kantorovich距离,也就是Wasserstein距离的一种离散版本。

每一步要最小化的目标函数通常是:

[ p_i \cdot d(i,j) ]

其中 (d(i,j)) 是场景 (i) 和场景 (j) 之间的距离。为什么乘上 (p_i)?因为删掉一个概率大的场景,信息损失显然更大;而如果两个场景距离很近,删掉一个也不会造成太大偏差。直觉上就是“优先删除概率小、又离别的场景最近的样本”。

被删场景的概率不能凭空消失,要并给最近的保留场景,这样整个概率质量守恒。具体流程是:计算所有保留场景两两距离;找到 (p_i \cdot d(i,j)) 最小的场景对;删除其中一个;更新概率;重复直到数量达标。这个算法每一步都是贪心的,但因为Kantorovich距离有良好的连续性,工程上效果通常很稳定。

这里要注意距离度量的选择。最常用的是欧氏距离,但如果你关心的是功率峰值,就可以用削峰后的损失函数做距离;如果你关心的是全时段总电量,可以用曼哈顿距离。场景内如果有风电和光伏两个随机源,一定要先做联合场景再算距离,否则单独约减风、光再合并,会破坏两个随机源之间的相关性。

3. 从零实现风光场景生成与约减

3.1 模拟数据和功率模型准备

下面是一套我在原型验证里常用的Python简化流程。用两参数Weibull拟合风速,光伏用Beta分布,再配上风机和光伏的功率转化函数。为了演示方便,把一天分成24小时,风速序列用AR(1)加随机扰动模拟,光伏序列用Beta分布乘日照开关。如果你手上有实际数据,可以把这里的分布参数替换成历史数据拟合结果,流程完全不用改。

距离计算之前,要把风电功率和光伏功率都归一化到 ([0,1]) 之间,不然功率量纲不同,距离会被量级更大的那项支配。这步看着小,但非常重要,我见过不少工程代码栽在这里。

import numpy as np def wind_power(v, v_in=3.0, v_r=12.0, v_out=25.0, P=1.0): p = np.zeros_like(v, dtype=float) low = (v >= v_in) & (v < v_r) mid = (v >= v_r) & (v < v_out) p[low] = P * ((v[low] - v_in) / (v_r - v_in)) ** 3 p[mid] = P return p def solar_power(g, g_ref=1000.0, P=1.0): # 简化模型:辐照度正比于输出功率,P为额定峰值系数 return np.clip(g / g_ref, 0, 1) * P

这里功率曲线用了最常见的三次方升段,实际风机曲线会有厂商给出的离散值,直接用插值更准。光伏模型也没有考虑温度修正,工程上可以再加一个温度系数项,让输出功率在高温时适当降低。

3.2 生成初始场景集合

下面这个函数生成N个长度为24小时的风光联合场景,每个场景是一个48维向量,前24维是风电归一化功率,后24维是光伏归一化功率。风速用AR(1)先生成带自相关的序列,再映射到风机功率曲线。严格做法是先做逆变换,让序列同时满足边缘分布和相关结构,这里给出一个工程上够用的版本。

def generate_scenarios(N=200, T=24, seed=42): rng = np.random.default_rng(seed) scenarios = np.zeros((N, T * 2)) for s in range(N): w = rng.standard_normal(T) ar = np.zeros(T) ar[0] = w[0] for t in range(1, T): ar[t] = 0.8 * ar[t-1] + 0.6 * w[t] v = 8.0 + ar * 4.0 v = np.clip(v, 0, 30) scenarios[s, :T] = wind_power(v) g = rng.beta(2, 3, T) * rng.uniform(900, 1100) mask = np.array([1.0 if (t >= 6 and t <= 18) else 0.0 for t in range(T)]) g = g * mask scenarios[s, T:] = solar_power(g.astype(float)) return scenarios

注意AR(1)系数0.8是随手给的,实际应该从历史数据估计。如果你有测风塔数据,用统计工具拟合AR参数更快。初始场景数N先设成200,足够验证逻辑;等把所有步骤跑通,再按第4章的优化方法往大调。

3.3 同步回代约减核心函数

这个函数是最核心的部分。我维护一个原始场景索引列表,每次计算当前集合的两两距离矩阵,把距离矩阵乘以对应场景概率,找到最小值位置,删掉被选中的场景,概率转移到距离它最近的保留场景。工程实现上推荐直接用scipy的cdist,它的精度和效率都比手写双重循环好。

from scipy.spatial.distance import cdist def backward_reduction(scenarios, probs, target_k): idx = list(range(len(scenarios))) p = probs.copy().astype(float) while len(idx) > target_k: sub = scenarios[idx] d = cdist(sub, sub) np.fill_diagonal(d, np.inf) weighted = d * p[idx][:, None] pos = np.argmin(weighted) a, b = divmod(pos, len(idx)) p[idx[b]] += p[idx[a]] idx.pop(a) return scenarios[idx], p[idx]

这段代码有个容易踩的细节:a和b是当前索引列表里的位置,p数组用原始全局索引访问,所以不用担心pop之后索引漂移。调用方式很简单:

N = 200 scen = generate_scenarios(N) prob = np.ones(N) / N K = 10 red_scen, red_prob = backward_reduction(scen, prob, K) print(red_prob.sum()) # 概率之和始终为1

如果你想验证概率转移是不是对的,可以额外打印每轮删除的场景编号和它被并给谁。我自己调试时会把“删除编号-合并编号-概率更新”打出来,对着场景图检查,比只看最终结果更容易发现问题。

3.4 约减效果怎么看

只把N减到K不叫成功,还要看约减前后的概率分布差异。我最常用的指标有三个:一是各时刻出力均值曲线,约减前后的均值差应该很小;二是分位数,尤其是5%和95%分位数,如果尾部对不上,说明极端场景被删太多;三是保留场景的典型性,用一个二维散点图,横轴是全天总风电出力,纵轴是全天总光伏出力,点的大小表示概率,看看保留场景是否均匀覆盖原始样本云团。

如果所有保留场景都挤在中心,说明距离度量或者目标K有问题。这时候不要急着加N,先检查是不是光伏和风电量纲不匹配,或者目标K设得太小。我通常会把K从5开始往上扫,看误差下降曲线,找到拐点再定K。拐点前误差下降很快,拐点后增加K收益很小,那个拐点就是合理的场景数。

4. 实际项目中的坑与调参经验

4.1 约减失真:场景留够了但风险仍然偏小

场景约减最常见的坑,是约减之后期望值不变,但分位数误差很大。因为同步回代按“概率乘距离”选删除对象,概率小的极端场景很容易被先删掉,概率被并到相邻场景,尾部风险悄悄消失。我踩过一次比较深的坑:做储能容量配置,场景约到10个,均值曲线看起来和原始2000个场景差不到1%,但95%置信水平下的缺电时间少了将近一半。

后来我把极端场景设置了保护权重,也就是在距离矩阵里给尾部场景加权重,强制它们多活几轮,才把可靠性指标拉回来。具体做法是:先按总出力大小排序,把前后5%的场景标记为“受保护”,在计算加权距离时给这些场景的概率乘一个大于1的系数,让它们更难被选中删除。代价是约减后的均值误差会稍微变大,但可靠性指标明显更真实。

如果你是做规划,目标场景K最好不要低于10;做日内调度,K可以小到5,但还需要额外用鲁棒约束兜底。K太小会让随机优化退化成确定性优化,失去场景法的意义。

4.2 算力不够用:大规模约减的工程优化

同步回代的一个痛点是每次迭代都要重算距离矩阵,复杂度接近 (O(N^3))。N=2000、K=10时,纯Python版本可能要跑好几分钟,内存也容易爆。工程上我一般这样破。

第一,初始N不要盲目设大,先用500到1000跑一次,看概率分布检验,不行再增量采样。第二,用KDTree的最近邻查询来近似找最近场景,避免全矩阵计算。KDTree的精度损失通常很小,但速度能快一个数量级。第三,分批约减,先把2000个场景分成4批,每批约到250,再合并成1000继续约减到10。误差比一步到位大一点,但工程上完全可以接受。第四,如果下游模型对精度要求很高,改成K-means聚类初始化加同步回代微调,先用聚类给每个场景打上簇标签,再在簇内用回代,兼顾速度和精度。

我实际跑过的工程里,通常用“启发式初筛+同步回代精减”的组合。启发式初筛可以用简单的距离聚类,把明显重复的场景合并;同步回代再在剩下的场景上做概率精修。这样组合下来,比单纯堆场景数更稳,也更容易解释给不懂算法的业务方听。

下面是我常用的方法选择参照表:

方法速度精度适用场景
纯同步回代慢高初始场景数不大,精度要求极高
KDTree近似回代快较高初始场景数大,需要快速出结果
K-means聚类约减快中需要先降维,再用回代微调
分批回代最快中高大规模场景,工程快速迭代

4.3 和“视频生成三维场景”一句话的联想

前几天看到“上传一段视频生成对应的三维场景”这类应用,第一反应是它跟风光场景约减在哲学上很像。视频生成三维场景,是从连续多帧图像里提取几何结构,用网格、点云或3D高斯做紧凑表示;场景约减则是从成千上万条风光时序里提取概率代表性场景。两者都在做同一件事:用尽量少但有效的离散元素,去承载一个连续且充满不确定性的原始空间。

区别只在于视频场景追求几何还原度,风光场景追求概率分布逼近和下游决策一致性。所以如果你能理解手机拍一圈视频就能重建一个房间的原理,再回头看蒙特卡罗加同步回代,其实并不难。关键帧的选择逻辑,和场景约减里“概率乘距离”的选择逻辑,本质上都是一种信息压缩。

最后说点个人体会。在风光场景生成与约减这套流程里,算法本身早就成熟,真正决定项目成败的往往是你对“哪些场景不能丢”的判断。不要只盯着均值和概率,要多问自己:保留的场景放到调度模型里,会不会让极端情况永远不出现?多留一两个尾部场景,计算量也就增加百分之几,可靠性却可能翻倍。我后来做这类项目,都会先把极端场景清单单独拉出来,无论如何要保住它们,然后再让同步回代去处理那些冗余样本。这套思路,放到任何不确定性建模场景里都适用。

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

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

立即咨询