做新能源出力场景研究的同行,应该都有过这种经历:手里攥着一批历史数据,想用蒙特卡洛(MC)批量生成未来可能出现的出力曲线,回头跑随机规划或可靠性评估。结果曲线是生成了一堆,拿去一算,要么场景数量太大把优化模型直接撑爆,要么削减完剩下的几条曲线看起来“怪怪的”——不是早晚高峰对不上,就是连续几小时的爬坡趋势明显不合理。
这个“怪”的根源,多半就是时序相关性出了问题。我最近完整梳理了一遍“考虑时序相关性MC的场景生成与削减研究”这条线,从问题定义、生成建模、削减算法到评价指标,踩了不少坑,也把一些容易稀里糊涂的地方彻底搞清楚了。这篇内容把整条流程拆开来讲,适合正在做随机优化、概率潮流、可靠性评估,或者刚接触场景法的同学参考。没有太多数学黑魔法,尽量全是能直接落地的思路和代码套路。
1. 这个研究到底在解决什么问题
先说清楚标题里“考虑时序相关性MC”是怎么回事。这里“MC”通常指蒙特卡洛模拟(Monte Carlo Simulation),也就是用随机抽样的方式,从风速、光照、负荷等随机变量的概率分布里反复采样,拼出一大批可能出现的时序场景。MC的优点是原理简单、适应性强、分布假设灵活,但它的“原罪”也很明显:如果只是逐时刻独立抽样,生成的场景就会丢掉时间维度上的记忆性——昨天的风速高,今天大概率也不会低到离谱,这种连续变化规律一旦被忽略,场景就变成一堆毫无业务逻辑的噪声曲线。
所以“考虑时序相关性”就是在MC框架里加入时间维度的约束,让场景生成结果具备真实出力的持续性、波动性和爬坡特性。这里有两种常见的技术路径:
| 技术路径 | 核心思想 | 适用场景 |
|---|---|---|
| 时间序列模型 + MC | 用自回归(AR/ARMA)、马尔可夫链等描述时刻之间的转移关系,再叠加随机项 | 风速、光伏出力、负荷等具有明显时序统计特征的数据 |
| Copula + MC | 用Copula函数刻画变量间与时刻间的依赖结构,再从联合分布中抽样 | 多风场/多区域之间的相关性建模,兼顾空间与时序 |
两种路径不冲突,很多研究都是混合用的。以风速为例,既要有相邻时刻的自相关,又要有不同风场之间的空间相关性,那就“马尔可夫链做时间转移 + Copula做空间耦合”,这就是“考虑时序相关性MC”的完整含义。
场景削减则是完全另一个层面的问题。MC生成场景的数量通常很大,几千上万条都有可能。在随机规划模型里,每条场景对应一组约束和变量,场景数直接决定求解规模。削减就是把一大把场景浓缩成十几条或几十条“代表性场景”,同时保证概率分布和统计特征不能严重偏离原始场景集。一句话总结:生成管“像不像”,削减管“少而精”,时序相关性是两者共同的核心命门。
所以这篇文章的主题可以拆成三个核心问题:
- 怎么生成一条“时序上合理”的随机场景?
- 怎么从大量场景里选出一小撮“最能代表全局”的?
- 削减之后怎么证明结果没跑偏,尤其是时间相关性没有丢?
把这几个问题串起来,就是一套能直接复用的场景生成与削减研究流程。
2. 场景生成的关键:把“时序相关”变成可计算的约束
2.1 先理解时序相关性到底指什么
很多人一说到时序相关性就想到自相关系数,这没错,但不够。在场景生成里,时序相关的本质是条件概率关系:知道t时刻的出力之后,t+1时刻的出力分布会随之改变。以光伏出力为例,上午10点出力高,那11点出力大概率也高,而不是重新回到一个均匀的随机值。这个“概率上的惯性”,才是时序相关性的业务含义。
把这种惯性落进模型,常用两种等价表达:
- 马尔可夫链:把出力划分为有限个状态(比如低出力、中出力、高出力、满发),用状态转移矩阵表达P(状态_{t+1} | 状态_t)。采样时先随机生成初始状态,再按转移矩阵一步步走下去,得到的状态序列就是一条场景。
- 自回归模型:用X(t) = a1 * X(t-1) + a2 * X(t-2) + ε(t) 这类的线性关系直接刻画数值上的连续性,ε(t)是随机扰动。AR模型更适合出力数值本身波动平缓的情况,但极端天气下容易失真。
实际项目里我更喜欢马尔可夫链,因为它对分布形状没有严格要求,能自然处理“有大量ゼロ时段”的光伏夜间出力,以及“强风时段与静风时段并存”的风速分布。AR模型的好处是数值精度高、参数估计简单,但前提假设强,分布崎岖时效果一般。两者用于场景生成时,都还需要叠加蒙特卡洛抽样来体现随机性。
2.2 一个可直接套用的马尔可夫链生成流程
用马尔可夫链生成出力场景,标准流程分四步:
**第一步,状态划分。**把出力值映射到有限个状态区间。区间怎么切很讲究,均匀切分最简单,但不一定合理。风速数据往往在低风速区间概率密度高,均匀切分会导致低风速区间被过度细分、高风速区间样本稀疏。经验做法是:先看出力分布直方图,用分位数划分状态边界,让每个状态里的历史样本数量大致均衡,这样转移概率估计才稳。
**第二步,估计状态转移概率矩阵。**对历史数据逐时刻统计“当前处于状态i、下一时刻处于状态j”的次数N_ij,转移概率就是P_ij = N_ij / Σ_j N_ij。这里有个细节:如果要保留“季节性”或“日特性”,不能把全年的数据混在一起估计一套矩阵。风电分季节、光伏分时段(晴天/阴天),分别估计转移矩阵再按场景模拟需要切换,效果会好得多。
**第三步,蒙特卡洛状态序列采样。**随机生成初始状态,比如按平稳分布或按早起时段的历史分布抽取第一个状态。之后每个时刻根据当前状态i,按转移概率随机抽取下一个状态j。一直走完24小时(或96个时段),就得到一条时序状态序列。
**第四步,状态到出力数值的回映。**这一步很多人忽略。状态是区间,不能直接当出力用。常用的做法是在状态区间内均匀抽样,或者根据该状态内历史出力的经验分布抽样。后一种效果更好,能让场景保留原始分布的峰态和偏态。
大概的逻辑贴在这里,方便直观理解:
import numpy as np def generate_mc_scenario(P_trans, state_bins, hist_samples_by_state, n_steps=24): n_states = P_trans.shape[0] cur = np.random.choice(n_states, p=stationary_dist(P_trans)) scenario = [] for t in range(n_steps): # 从当前状态的直方图中采样具体出力值 value = np.random.choice(hist_samples_by_state[cur]) scenario.append(value) cur = np.random.choice(n_states, p=P_trans[cur]) return np.array(scenario)注意这块只是骨架,实际使用时还要处理状态数选择、平滑转移矩阵(避免零概率)、多风场联合采样等问题,后面第五部分再细说。
2.3 时序相关性的评价:不能只看形状
场景生成完,怎么判断“有没有体现出时序相关性”?光画几条曲线看走势不够,需要用数字验证:
- 自相关函数(ACF)对比:计算历史数据的1阶、2阶、…、k阶自相关系数,再计算生成场景集的平均自相关系数,两者对比。如果历史数据1阶自相关是0.85,生成场景只有0.3,那说明马尔可夫链状态划分太粗或转移矩阵估计不准。
- 持续时间分布:风力发电的一个典型时序特征是“持续高出力/持续低出力”的时段长度。可以统计历史数据和生成场景中连续处于同一状态的小时数分布,看是否吻合。
- 爬坡率分布:相邻时段出力变化量ΔP的分布,是时序波动性的直接体现。生成场景如果爬坡率分布和历史数据差别明显,说明时序信息仍然丢失。
我见过不少研究,场景生成后只对比了均值、方差、概率密度,完全没看自相关和持续时间分布,这样的场景拿去算可靠性,结果往往偏乐观或偏保守,评估结论很容易失真。
3. 场景削减为什么是绕不开的一步
3.1 削减算法选型:聚类 vs 回代消除 vs 前代选择
目前主流的削减算法可以粗略分成三类,各有各的脾气。
**第一类是聚类法,最典型的就是K-means。**把每条场景看成一个高维向量,例如24小时风速场景就是一个24维的点,然后跑K-means聚类,聚成K类,每类的质心就是代表性场景,权重是该类场景数量占比。优点是速度快、实现简单,几行代码就能跑;缺点是聚类结果容易受离群点影响,而且质心是“平均”出来的,可能导致代表性场景过度平滑,丢失极端情况。
**第二类是同步回代消除(SBR,Simultaneous Backward Reduction)。**思路是不断从场景集中删除“对整体概率分布影响最小”的场景,每删除一条,就把它被删掉的概率权重加到离它最近的场景上。这种方法在随机规划领域应用极广,因为它直接以概率距离(通常是Wasserstein距离)为准则,削减前后的分布变化有理论保障。缺点是计算量较大,需要维护场景两两之间的距离矩阵,场景多的时候内存吃不消。
**第三类是快速前代选择(FFS,Fast Forward Selection)。**和SBR方向反过来,不是删,而是选。从空集开始,不断挑选“能使当前选中集合对未选中集合的分布逼近误差最小”的场景加入。在场景规模特别大的时候,FFS比SBR更高效,但代码复杂度高一些。
三类算法的特性对照如下:
| 算法 | 原理 | 优点 | 缺点 | 适用规模 |
|---|---|---|---|---|
| K-means聚类 | 向量空间划分,质心代场景 | 简单快速 | 质心易平滑,极端场景易丢失 | 万级以上 |
| SBR回代消除 | 逐步删除概率距离影响最小者 | 分布逼近有理论保障 | 距离矩阵内存大,计算量大 | 千级以内 |
| FFS前代选择 | 逐步选入最有代表性的场景 | 比SBR更高效 | 实现复杂,参数敏感 | 万级左右 |
实际做研究,我一般先用K-means压到几百条,再用SBR或FFS精修到几十条,这样速度和精度都能兼顾。
3.2 削减时的“时序陷阱”与距离度量设计
削减算法核心都依赖“场景距离”这个概念。不同场景之间怎么算距离,直接决定削减结果的质量。这也是整个研究里最容易踩坑的地方。
最简单的距离是欧氏距离,逐时刻求差平方和。这个做法的问题在于:它把每一个时刻孤立地比较,完全忽略了时序结构。举个例子,场景A为“白天平滑上升的阴天光伏曲线”,场景B为“白天剧烈波动的阵雨光伏曲线”,某个时刻的出力值碰巧相近,欧氏距离就会很小,被认为“相似”,但事实上它们的爬坡特性完全不同,对电网调度的含义也截然不同。
要体现时序相关,就得改造距离函数。常用手段包括:
- 动态时间规整(DTW)距离:允许两个序列在时间轴上轻微错位对齐后再比较,能有效度量形状相似性。
- 加入差分项的距离:把场景向量从{P(1), P(2), …, P(T)}扩成{P(1), …, P(T), ΔP(1), …, ΔP(T-1)},让爬坡量也参与距离计算。这个做法简单粗暴,但实测很有效。
- 加权欧氏距离:对距离近的时刻赋予更高权重,强化对局部波动形态的匹配。
有一点必须强调:削减后得到的代表性场景,不能直接用质心代表完事。SBR和FFS选出来的场景是原始场景中的“真实样本”,本身就自带原始时序特征,这是它们比K-means更受学术青睐的一个重要原因。K-means的质心是人工合成的,虽然形态平均好看,但时序统计特性往往已经被磨平了。
4. 实操:一个完整的“生成-削减-评估”示例
4.1 数据准备与基础设定
假设我们要处理一个风电场24小时出力场景,时间分辨率为1小时,也就是每条场景是一个24维向量。历史数据长度假设为一整年(8760个点)。
第一步先把历史风速数据转换成归一化出力数据,数值范围0到1。然后按季节切分,以冬季数据为例,估计冬季的马尔可夫转移矩阵。
状态数怎么定?我建议先从5到10个区间开始试。状态太少,转移矩阵表达不了出力变化的细腻程度;状态太多,转移矩阵参数估计会不稳定,尤其是样本量不足时容易出零概率。用一年数据估算,8个状态基本够用。分位区间划分可以简单用:
import numpy as np data = np.load('wind_power_winter.npy') # 冬季时序数据 bins = np.quantile(data, np.linspace(0, 1, 9)) # 8个状态的边界 state = np.digitize(data, bins[1:-1]) # 得到每个时刻的状态编号4.2 场景生成:马尔可夫链 + 状态内抽样
按第二节的逻辑,先统计转移概率矩阵,再做状态内分位数抽样。生成5000条场景,每条24个点。
def estimate_transition(states, n_states): P = np.zeros((n_states, n_states)) for t in range(len(states) - 1): P[states[t], states[t+1]] += 1 # 行归一化,并处理零行(全部置为均匀分布) row_sums = P.sum(axis=1, keepdims=True) row_sums[row_sums == 0] = 1 P = P / row_sums return P def sample_from_state(state_id, state_samples): return np.random.choice(state_samples[state_id])生成5000条场景之后,先看一眼总体的均值曲线和方差带,再算一下历史数据和生成数据的1阶自相关系数对比。如果自相关系数明显偏低,把状态数往上调,或者改用基于AR(1)+马尔可夫残差的混合模型,效果通常会改善。
4.3 场景削减:两步法落地
削减这一步,用“K-means粗削 + SBR精削”的两步法。先用K-means把5000条聚成200类,这一步会得到一个200维的集合。接下来对这200个中心或代表性样本跑SBR,最后得到20条代表性场景。
SBR的核心循环伪代码如下:
def sbr_reduce(scenarios, probs, target_num): n = len(scenarios) dist = compute_pairwise_dist(scenarios) # 欧氏/DTW/差分加权距离矩阵 remaining = set(range(n)) while len(remaining) > target_num: # 寻找使削减后分布变化最小的场景j best_j, min_impact = None, np.inf for j in remaining: impact = 0 for k in remaining: if k != j: nearest = find_nearest(k, remaining - {j}, dist) impact += probs[k] * dist[k, nearest] if impact < min_impact: min_impact, best_j = impact, j # 删除j,把j的概率加到最近场景上 nearest_to_j = find_nearest(best_j, remaining - {best_j}, dist) probs[nearest_to_j] += probs[best_j] remaining.remove(best_j) return list(remaining), probs直接两层循环计算会很慢,实际工程中要用“概率距离变化量增量更新”的优化版本,也就是只对被删除场景的邻近场景重新计算影响。这一步对5000条场景来说,纯Python实现可能要跑好几分钟,先把距离矩阵算出来存内存,再用numpy向量化能压缩到几十秒。
距离度量建议直接用差分加权距离,即:
D(A, B) = Σ_t (A_t - B_t)^2 + λ * Σ_t (ΔA_t - ΔB_t)^2
λ一般取0.5到1之间,表示对爬坡差异的重视程度。如果目的是风电场出力可靠性分析,爬坡差异直接影响出力波动,λ可以调大;如果是电量评估,趋势项更重要,λ取小一些。
4.4 削减效果评估指标一览
削减完不能光看“还剩几条曲线”。我的习惯是至少跑五个指标:
- 概率分布拟合度:削减前后所有场景的经验CDF对比,用KS检验或Wasserstein距离量化。
- 均值与标准差:逐时刻均值曲线和标准差曲线,对比削减前后偏差。
- 自相关性:削减后场景集的1阶、2阶自相关系数是否仍在历史数据的置信区间内。
- 极端场景保留情况:削减后的场景里是否还包含高出力时段、零出力时段这类对系统可靠性起决定作用的情况。
- 代表性场景权重合理性:每条场景的权重是否与原始场景集在该区域的出现频率大致匹配。
下面给个评估表格示例:
| 指标 | 原始5000条 | K-means粗削后(200条) | SBR精削后(20条) |
|---|---|---|---|
| 均值曲线MAE | — | 0.021 | 0.035 |
| 1阶自相关系数偏差 | 0.82(历史值) | 0.79 | 0.77 |
| 最大爬坡率保留率 | — | 92% | 84% |
| Wasserstein距离 | — | 0.012 | 0.019 |
从结果看,20条代表性场景相比5000条,统计偏差依然很小,满足随机规划使用要求。
5. 常见问题与避坑经验
5.1 场景数量到底取多少才合适
这是被问得最多的问题。场景数量没有一个通用标准,取决于你下游模型对精度的敏感度。一般建议先用“场景数-目标指标收敛曲线”来确定:跑一个代表性分析(比如系统期望失负荷量),分别用5、10、20、50、100条场景计算,看指标随场景数增加的变化趋势。当场景数增加而目标指标变化小于1%-2%时,就可以认为收敛了。强行追求场景少,损失的是极端事件的表达力,这是削减方案里最常见的隐性成本。
5.2 削减后场景同质化严重怎么办
K-means削减的通病,代表性场景全部向“平均形态”靠拢,极端高风速场景和极端静风场景都被削没了。解决办法有两个:一是在距离函数上对极端场景所在的区域做加权,提高它们被选中的概率;二是削减时预留一部分“特殊场景”名额,直接从原始场景里挑出极端典型(比如全年最大爬坡事件、最长静风事件)单独保留,不参与削减,最后合并进代表性场景集。这个做法在我的项目里效果很好,代价只是多两条场景,却保住了尾部风险特征。
5.3 生成场景的自相关性上不去
很多人用马尔可夫链生成场景后,发现自相关系数总比历史低一截。常见原因是对“状态内采样”环节处理太随意——如果每个状态内都是均匀分布采样,那么同一状态内的相邻时刻替换跳动大,会人为拉低自相关。
我推荐的改进顺序是:
- 提高状态数,让高自相关的局部区间被离散得更细。
- 状态内不用均匀采样,改用该状态内历史出力值的经验分布。
- 引入残差自相关模型,在马尔可夫链基础上再用AR(1)对数值序列做平滑。
- 检查时间分辨率是否过粗,1小时分辨率下风速自相关在相邻时刻本来就衰减快,可以考虑15分钟粒度建模再聚合回1小时。
5.4 SBR算法跑得太慢
场景量一大,SBR距离矩阵是内存和时间的双杀。万级场景下直接维护n×n矩阵非常吃力,我的建议是:先跑K-means粗削把规模降到1000以内,再跑SBR精削。粗削阶段的信息损失,在精削阶段会通过距离准则得到一定恢复,最终20-50条场景的质量差别不大。
另外,距离矩阵存储用float32而不是float64,内存直接减半。计算时用scipy的cdist和numexpr这类加速库,体验会好非常多。
6. 一些做研究的额外心得
整理完这套流程,我自己的感受是:场景生成研究中,“时序相关性”不是一个可选项,而是决定场景可信度的地基。很多刚入门的同学一上来就跑到最复杂的生成模型,比如扩散模型、生成对抗网络来生成场景,看起来很高端,最后拿到的场景却连最基本的自相关都对不上,原因就是基本功没有夯实。先从马尔可夫链+蒙特卡洛把时序骨架搭好,再考虑用更复杂的模型来精细刻画分布形态,这条路会稳很多。
场景削减则需要时刻记住:**削减的目标不是“让曲线好看”,而是“让下游决策计算不偏”。**每次削减完,都该回到业务场景问一句——这个代表性场景集算出来的优化结果,是不是和用原始场景集算出来的结果足够接近?如果接近,削减就是成功的;如果不接近,再漂亮的曲线集也没有意义。
最后给个小建议:这一整套流程,最好固定成一份标准工程模板,从数据切分、状态划分、生成采样、两步削减到评估指标,全部自动化。做研究时要反复调整参数,手工重跑代价太高。我目前是把整个流程封装成几个函数,输入历史数据和削减比例,输出代表性场景集、权重和评估报告,后续换数据集只需要改一行路径。
把这一套跑通之后,后面再想升级到多风场联合场景生成、考虑温度相关性、或者接入更复杂的随机优化模型,都是在这个地基上添砖加瓦的事。