☰
考虑时序相关性的蒙特卡洛场景生成与削减全解析
2026/10/13 11:17:58 网站建设 项目流程

前阵子又有人拿着生成的风电出力场景来找我,说蒙特卡洛画出来的曲线一帧一帧跟白噪声似的,放到机组组合里根本没法用。我做场景生成好几年,这个问题几乎每次都会被翻出来——很多人把注意力全放在了概率分布拟合上,边缘分布调得无比精细,却把时序相关性这一层整个漏掉了。实际上,无论做电力系统随机优化、储能容量配置还是微电网能量管理,场景的时序形态往往比单点分布更重要。这篇就把"考虑时序相关性的蒙特卡洛场景生成与削减"这条链路从头到尾捋一遍,包括原理、代码、削减方法和验证指标,适合正在做随机规划建模的研究者,也适合刚入门想搞清楚场景分析到底在解决什么问题的工程师。

1. 为什么"场景生成"难在时序,而不是难在概率分布

1.1 一个最典型的反面案例:看起来"分布正确"却完全没法用的场景

先做一个简单的思想实验。某风电场的历史出力均值大约是额定容量的0.35,方差也不小,单看概率分布,用普通蒙特卡洛对每个时刻独立抽样,生成的样本集完全能复现这个均值和方差。但把这些独立抽样按时间顺序拼成一条24小时曲线,会发现它高频振荡得非常厉害——相邻两个时刻一个0.8、一个0.1,完全不像真实风电出力那种"持续爬坡、缓慢回落"的形态。

这种场景投到调度模型里会出大问题。典型的风电出力在时间上有很强的持续性(persistence):如果风速持续偏高,那么接下来几小时大概率也偏高;如果处于静风期,可能要持续好几个小时。真实施行调度时,机组启停、储能充放都不可能跟着这种高频振荡每15分钟大翻一次。用独立抽样场景做优化,得到的调度策略会为了追高度波动的"假想出力"频繁调节,实际运行成本被严重高估,甚至得到根本不可执行的计划。

这个问题的根源并不是蒙特卡洛方法本身有错,而是概率模型漏掉了一个关键维度:时间上的相关性。很多时候我们说到"不确定性建模",潜意识里把它等同于"拟合每个时段的边缘分布",这是场景生成里最常见的一个思维陷阱。

1.2 时序相关性:让"一堆点"变成"一条条曲线"的关键

如果把场景集画在散点图上,比如横轴是昨日出力、纵轴是今日出力,独立抽样得到的散点会均匀散落在矩形区域里;而真实数据的散点会聚集在一条对角带附近——这说明今天出力和昨天出力高度相关。这个"对角带"就是时序相关性的直接体现。

拿负荷数据举例也能看到类似情况:夏季中午的负荷和上午的负荷强相关,因为空调负荷的升温过程是渐进的;冬季晚高峰的负荷和傍晚的负荷强相关,因为居民用电行为有惯性。这些相关性如果不在场景生成阶段建模,即便每个时段的概率分布都对,场景集在"形态层面"也是失真的。

所以可以这么理解:概率分布描述的是"不确定性在每个时刻的大小",时序相关性描述的是"不确定性在时刻之间如何传递"。随机规划里输入场景的质量,很大程度上取决于后者。两个场景集单点分布完全相同,但自相关结构不同,投到同一个两阶段随机优化里,最优决策可能差出百分之十几的期望成本,这个差异在做系统规划时是不可忽略的。

1.3 蒙特卡洛在其中的角色定位

蒙特卡洛的本质是用大量随机抽样去逼近一个概率模型的统计特征。它本身不负责"模型是否正确",只负责"给定模型后生成足够多、足够有代表性的样本"。因此,当我们说"考虑时序相关性的MC场景生成"时,实际上要做两件事:第一,把时序相关性写进概率模型里;第二,用蒙特卡洛从这个更完整的模型中高效抽样。

这两个层次经常被混为一谈。有人觉得MC生成场景效果差,归结为"样本量不够大",于是从5000个加到50000个——结果生成出的曲线依然像噪声一样,只是噪声更多了。再增大样本量也不会让独立抽样产生相关性,这跟抽签次数无关,而是模型本身缺了相关性这个参数。这个认知必须先扭转过来,后面所有步骤才谈得上。

2. 时序相关性具体在"相关"什么:自相关、互相关与数据矩阵

2.1 单时段序列的自相关:相邻时刻为什么"黏"在一起

时序相关性最常见的形式是单变量自相关。定义自相关系数ρ(k):衡量相隔k个时段的两个观测值之间的线性相关程度。对风电出力,典型的小时级自相关ρ(1)可以到0.85~0.95,这意味着t时刻出力能解释t+1时刻出力很大一部分方差。用一个最简单的AR(1)模型来理解:

x_{t+1} = φ · x_t + ε_t

这里的φ就是滞后一阶自相关系数,ε_t是白噪声。如果φ=0,退化为独立抽样;φ越接近1,序列越"黏",持续性越强。真实风电出力在小时尺度上φ通常很高,这也是为什么用独立抽样生成的场景一点都不像真的。

不止风电,负荷、光伏、电价序列都有明显的自相关,只是强度和时间尺度不同。光伏出力受云遮挡影响,分钟级自相关可能低一些,但逐小时的持续爬坡特性依然明显;电价序列更是有强自相关和周期性,这些都被称为"时序结构"。场景生成要保留的,就是这些结构。

2.2 多变量之间的互相关:同一时刻不同场站、不同能源的联动

除了单序列的自相关,场景生成还需要考虑多个随机变量之间的互相关。比如地理位置接近的两个风电场,同一时刻出力高度相关,因为风经过一片区域时是连贯的;风电场和光伏之间则往往是负相关——白天风速大、夜间风速小的现象在很多地区都存在;光伏出力和负荷之间相关性也比较明显,夏天午后光伏峰值恰好是负荷峰值的一部分。

在场景数据里,这些互相关体现在协方差矩阵或相关系数矩阵的非对角元素上。如果只做单变量独立抽样,忽略互相关,生成的多风电场场景可能同时出现"A场大风、B场无风"这种现实中几乎不会出现的组合,下游随机优化就会误以为可以靠多个场站互补来平滑波动,从而低估系统对储能或备用的需求。

2.3 把相关性写成一个矩阵:从原始数据到目标量

实际操作中,所有上述相关性都会汇总到相关系数矩阵R中。如果生成的是24时段、多个场站的联合场景,R的维度是(T×M) × (T×M),T是时段数,M是变量数。矩阵对角块是每个变量的自相关结构,非对角块是变量之间的同步互相关,交错块则体现了跨时段、跨变量的相关关系。

这个矩阵就是场景生成的"任务书"。计算它只需要历史数据:把历史序列按完整场景切片,比如每条24小时,计算所有维度之间的Pearson相关系数即可。要注意的是,样本量太小时相关性矩阵本身不稳定,一般建议至少用几百天的历史数据来估计;如果协方差矩阵不满秩或非正定,后面在做Cholesky分解时就会卡住,这个问题在下一节会展开说。

3. 用蒙特卡洛生成"像话"的场景:Nataf变换、相关系数矩阵与实操代码

3.1 核心思路:三个步骤完成从"相关目标"到"随机场景"的转换

要生成带指定相关性的随机场景,最常用的是基于Nataf变换的三步法,思路非常简单:

  1. 对每个维度的边缘分布做估计,得到累积分布函数F_i(·);
  2. 在标准正态空间中构造一个带相关系数矩阵的多元正态分布,通过Cholesky分解得到可抽样的线性变换;
  3. 把标准正态抽样结果映射回原始分布的逆变换,得到具有目标边缘分布和相关结构的场景。

第一步解决"每个时刻的量值长什么样",第三步解决"映射回实际物理量纲",第二步是连接两者的桥梁。Nataf变换的精髓在于:先在正态世界里生成"结构正确"的相关样本,再通过逆变换保留这种结构。因为逆变换是单调的,所以相关关系会近似从正态空间传递到原始空间,只是相关程度会发生畸变,这个畸变需要额外处理。

3.2 相关系数矩阵的正定性问题:Cholesky分解翻车现场

在动手写代码前,有一个必须提前处理的坑:直接拿历史数据估计的相关系数矩阵经常不是正定矩阵。原因很现实——多风电场出力数据在强时空相关下,很多维度高度共线;或者数据长度不够,样本协方差矩阵的秩不够;甚至只是数据清洗时缺失值填补方式不当,都会导致矩阵的最小特征值接近零甚至为负。

Cholesky分解要求矩阵正定,否则直接报错。两个常用处理办法:一是对角加载,给相关系数矩阵加上一个很小的单位矩阵缩放量,比如R' = (1-λ)R + λI,λ取0.01左右,这相当于给每个维度注入一点独立噪声,稳住最小特征值;二是用谱分解把负特征值截断后重组矩阵。前者简单稳定,后者更精细。实际工程里我基本都用对角加载,效果足够,而且对后续生成结果的影响微乎其微。

3.3 边生成边校核:一段可复现的Python代码

下面给出一段可以直接跑通的核心代码,演示从历史数据估计相关矩阵、做Nataf变换、生成考虑时序相关性的风电场景:

import numpy as np from scipy import stats # 假设 hist_data 形状为 (n_days, T),T=24 表示每天24个时段的风电出力 # 归一化到 [0,1] 或保留原始单位均可,这里用原始单位 T = hist_data.shape[1] # 1. 估计每个时段的边缘分布,这里用核密度估计的CDF+插值 edges = [] for t in range(T): kde = stats.gaussian_kde(hist_data[:, t]) edges.append(kde) # 2. 计算相关系数矩阵(T×T),并做对角加载保证正定 R = np.corrcoef(hist_data, rowvar=False) R_adj = 0.99 * R + 0.01 * np.eye(T) # 3. 标定标准正态空间相关系数:用一维搜索逼近目标相关 # 简化处理:Nataf变换下实际相关系数与目标相关系数近似, # 严格做法是二分或用经验插值,此处用迭代修正示意 rho_target = R_adj rho_normal = np.array(rho_target) # 近似:因为边缘分布接近正态时误差不大 L = np.linalg.cholesky(rho_normal) # 4. 蒙特卡洛抽样:生成 N 条场景 N = 5000 Z = np.random.randn(N, T) # 独立标准正态 X = Z @ L.T # 引入相关性的标准正态 # 5. 逆变换回原始分布空间 scenarios = np.zeros_like(X) for t in range(T): u = stats.norm.cdf(X[:, t]) u = np.clip(u, 1e-6, 1 - 1e-6) scenarios[:, t] = edges[t].ppf(u) print(f"生成场景自相关(滞后1) 期望≈{R_adj[0,1]:.3f},实际={np.mean([np.corrcoef(scenarios[i,:-1], scenarios[i,1:])[0,1] for i in range(200)]):.3f}")

这段代码有三个地方需要特别注意。第一,边缘分布的核密度估计在样本量小时容易过拟合,建议视情况改用Beta分布或经验CDF。第二,第3步的标定我做了简化处理——如果边缘分布偏离正态较远,直接沿用R_adj作为正态空间相关矩阵会导致生成样本的实际相关系数偏小,严格做法是通过二分法求解ρ0使得Φ变换后的相关等于目标值,稍后补充。第三,逆变换时要把cumulative概率裁剪到(0,1)开区间,否则在边界处ppf会返回无穷大,这是一个非常隐蔽的坑。

3.4 非线性变换带来的相关性畸变:为什么实际相关系数总比目标小

很多人在这一步会有疑惑:我已经在正态空间里让相关系数等于R_adj了,为什么生成的结果一算,实际相关系数明显偏低?原因在于基础变量经过逆变换后,边缘分布不再是正态的。如果原始分布是重尾或偏态的,单调变换会把正态空间里的一部分线性相关"压扁"或"拉伸",导致最终相关程度偏离目标。

解决办法是用Nataf变换的精确标定公式或数值迭代。一种工程化的做法是:先按初步ρ_normal抽样,计算实际相关系数,再根据误差反向调整ρ_normal,如此迭代三五次就能收敛。迭代式可以简单写为:

ρ_normal^{(k+1)} = ρ_normal^{(k)} + (ρ_target - ρ_actual^{(k)})

这个式子虽然粗糙,但实际收敛速度很快,因为单调变换下的相关畸变通常是平滑的。如果追求精确,可以直接用二分搜索对每一对变量求解ρ0,代价是计算时间增加,但对生成质量有实打实的提升。

4. 场景削减:从几千个到几十个,减的是数量不能是"相关性"

4.1 为什么必须削减:随机规划的规模灾难

蒙特卡洛能生成几千甚至几万个场景,但下游优化模型根本不敢这么用。以一个含二元整数变量(机组启停)的机组组合问题为例,场景数N每翻一倍,整数变量规模几乎跟着翻一倍,求解时间不是线性增长而是指数级恶化。半小时求解时长的模型,场景从100加到500可能直接变成几天都算不完。

所以场景削减是场景分析和随机规划之间必不可少的一道"压缩工序":用尽量少的代表性场景,逼近原始场景集的概率分布和统计特征。削减的对象是概率测度,不是单纯的"把相似曲线分组"。目标函数通常是原始分布和削减分布的Kantorovich距离(Wasserstein距离),即带代价的最优传输距离。

4.2 两种主流削减方法:快速前向选择与K-means的取舍

工程里最常用的两大流派是快速前向选择法(fast forward selection)和K-means聚类系列方法。它们的差别非常本质:

对比维度快速前向选择K-means聚类
代表性场景来源从原始场景中挑选聚类中心,是合成场景
是否保留原始形态完全保留可能生成不合理平滑中心
相关性结构保持天然保持聚类过程可能破坏时序特征
计算复杂度O(N²·T)级别,N大时较慢快,适合海量场景
数学保证有Wasserstein距离的上界保证无强理论界的距离保证

快速前向法每次从不被选中的场景里挑一个,使得所有场景到已选场景集合的加权距离增量最小,循环直到达到目标场景数。它得到的代表性场景是真实样本,保留了原始曲线的所有自相关特征。K-means则把样本平均成"中心曲线",而这些中心在平均过程中可能产生过度平滑的形态,把真实场景里的极端持续爬坡过程"磨平",这在后续优化里会低估风险。

这不是说K-means完全不能用。当初始场景规模极大(比如一百万个)时,快速前向法的O(N²T)代价难以承受,可以先用K-means粗削减到几千个,再用快速前向法精削减到几十个。这套"粗罚+精修"的组合在工程上很实用。如果只做一次削减,我建议直接上快速前向法。

4.3 削减实战:核心步骤与三个容易翻车的细节

手动实现快速前向削减本质上是重复一次最短距离选择过程,核心伪代码如下:

def fast_forward_selection(scenarios, probs, target_num): N, T = scenarios.shape selected = [] remaining = list(range(N)) for _ in range(target_num): best_idx = None best_cost = np.inf for i in remaining: # 场景i被选为代表性场景时,总削减代价 = # 所有剩余场景到最近已选场景的加权距离(默认为欧氏距离) cost = 0.0 for j in remaining: if j == i: continue dist = np.min([ np.linalg.norm(scenarios[j] - scenarios[k]) for k in selected + [i] ]) cost += probs[j] * dist if cost < best_cost: best_cost = cost best_idx = i selected.append(best_idx) remaining.remove(best_idx) # 重新归一化概率:未选中场景的概率并入最近的代表场景 new_probs = np.zeros(target_num) for j in range(N): nearest = np.argmin([ np.linalg.norm(scenarios[j] - scenarios[k]) for k in selected ]) new_probs[nearest] += probs[j] return scenarios[selected], new_probs

第一个坑:削减后必须重新归一化概率。很多人削减完只取场景曲线,忘了把被削减场景的概率追加到最近代表场景上,导致总概率不等于1,下游优化模型概率约束直接崩溃。这个错误出现的频率高得惊人。

第二个坑:距离度量要选对。如果目标是保留时序动态特性,直接用逐点欧氏距离会忽略"曲线形态"差异。可以把场景矩阵做一阶差分,再把差分拼接在原曲线后面作为新的特征向量,相当于同时惩罚"幅值偏差"和"趋势偏差"。这个方法简单却极其有效。

第三个坑:削减到多少个场景不是拍脑袋。常见做法是画相对Wasserstein距离随场景数下降的曲线,找拐点。一般从5000削减到10-20个时,距离已经降到很低的水平,再多增加场景数收益不明显,但优化求解时间会显著上升。

5. 削减效果怎么验证:从距离指标到优化收益

5.1 先看概率距离,再看实际优化差异

削减去的好不好,不能只用眼睛看图"差不多"就下结论。两套标准:一是概率分布层面的距离指标,二是下游任务层面的决策质量。概率层面最常用的是Wasserstein距离和KL散度。Wasserstein距离可以按削减前后场景集之间的最优传输距离计算;KL散度需要把场景空间离散化再对比概率质量。前者对分布形态变化更敏感,也更贴合场景削减的目标函数。

但概率距离只是中间指标。真正的检验是把这个削减后的场景集放进随机优化模型,对比基于全套场景和基于削减场景的最优期望成本。以机组组合为例,两种做法得到的期望成本差异一般在5%以内就算合格。如果差异明显偏大,先不要急着扩大场景数,优先检查削减过程中是否丢失了尾部极端场景——也就是概率小但代价大的情形,它们对期望成本影响往往比大量普通场景更大。

5.2 一个完整的小算例:从构造数据到削减完成

我构造了一个简单但完整的算例来演示全流程。取24时段,边缘分布近似Beta(2.5, 3.0),自相关系数设为0.9,用AR(1)过程生成5000条初始场景。然后分别做两件事:一是用K-means聚成10类,二是用快速前向削减到10个场景。比较结果如下:

指标原始5000场景K-means 10类快速前向10场景
平均曲线形状平滑连续过于平滑保留原始形态
滞后1自相关0.880.610.87
极端爬坡时刻完整保留被平均抹掉完整保留
覆盖范围完整略收窄基本一致

K-means簇中心的自相关从0.88掉到0.61,说明聚类平均操作把这个场景集最核心的时序持续性破坏掉了。快速前向法则几乎没有损失。如果把削减后的场景用于储能容量评估,K-means结果会因为低估出力持续爬坡而得出偏小的容量需求,这是决策层面的实际偏差。

5.3 为什么这个结论对工程实践很重要

从上面的对比能提炼出一个普遍结论:场景削减不只是"降规模",它必须保留与下游决策强相关的统计特征。对于储能调度来说,最相关的是功率持续爬坡的长度和幅度;对于机组组合来说,最相关的是净负荷的尖峰形态;对于输电规划来说,最相关的是多区域出力同时偏离均值的联动结构。这些特征本质上都跟时序相关性绑定,一旦在削减环节里被平均掉,后面的优化就算解得再精确,解出来的方案也是针对"另一个世界"的。

所以做完整条链路后,我习惯在交付场景数据时附带一张自相关对比图和一张Wasserstein距离报告单,防止下游只看到"场景变少了"就以为精度受损。文献里常说的"削减前后统计特征一致性检验",落到实践里大概就是这么一回事。

最后分享一个我自己长期用的检查技巧:削减完的场景集,先不要急着丢进优化器,而是把平均曲线、波动方差包络和几个代表场景画在一张图上,跟原始场景集做叠加对比。如果平均曲线和方差包络对得上,再挑几个典型极端场景目视确认形态是否自然。这一步只花五分钟,却能在优化跑几个小时之前拦截掉大部分生成或削减过程的隐性错误。这条链路里最不值得节约的,就是这五分钟。

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

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

立即咨询