拓冰建站拓冰建站
首页 / 资讯中心 / 正文

蒙特卡洛场景生成与时序相关性保留:新能源出力场景削减实战指南

坦白说做新能源调度和规划的人早晚都会撞上“场景”这个坎。风电光伏的出力不是一个确定的数而是一大片随时间和空间变化的不确定集合。早期大家都用典型日、典型曲线代替后来发现这玩意儿在极端天气和高峰负荷下根本不够看于是开始用MC蒙特卡洛模拟大样本生成场景再用场景削减把计算量压回可接受范围。但真正上手之后你会发现最简单的MC独立抽样会生成一堆“椒盐噪声”——相邻时段的出力剧烈跳动跟真实的风速光伏演化规律完全对不上。这篇文章我就拿实际项目中的体会把MC场景生成如何在保留时序相关性的前提下做出来以及场景削减怎么不掉链子一步步拆开讲。适合谁看正在做新能源出力建模、电力系统随机优化、储能容量配置的工程师和研究生能解决什么问题让MC生成的不再是“独立碎片”而是有时间记忆的合理场景让场景削减后的典型集合仍然保持原始概率分布特征核心思路MC负责大样本采样时序相关性靠马尔可夫链/Copula/时间序列模型注入场景削减用聚类和同步回代消除的组合拳收尾。1. 先搞明白“场景”到底在描述什么不确定性1.1 场景不是几条曲线而是一个概率空间的离散近似在随机规划里场景Scenario本质上是随机变量联合分布的一个离散样本。假设我们有24个时段的风电出力那一个场景就是一个24维向量[P_1, P_2, \dots, P_{24}]。MC的工作方式是从风速分布通常是Weibull分布中独立抽取24个风速值再通过功率曲线换算成出力。这里有个隐蔽的坑如果每个时段独立抽取生成的第5个时段和第6个时段出力可能一个是500MW另一个瞬间掉到50MW这在真实物理世界里几乎不可能发生——风机有惯性风有持续性相邻时段出力天然强相关。我见过不少项目直接用Matlab或Python里np.random.weibull轰出5000个场景丢给优化器结果调度模型算出来一堆不合理的机组启停方案。原因就是场景集合没有正确反映时序结构优化器会在“假场景”上钻空子得到看起来最优、实际无法执行的方案。所以做场景生成之前一定要在心里明确我们采样的是随机过程不是一堆独立同分布的数。1.2 时序相关性从哪里来物理过程的内禀规律新能源出力的时序相关性根子在气象过程的连续性。风速变化有惯性一个气团过境通常持续数小时光伏出力更是由太阳轨迹和云层遮挡时间尺度决定。用随机过程的话说这些序列有显著的自相关函数ACF和偏自相关函数PACF在频域上表现为低频分量占主导。从数学上看一个离散时间随机过程(X_t)的自相关函数定义为(\rho(k) \frac{\mathbb{E}[(X_t-\mu)(X_{tk}-\mu)]}{\sigma^2})。如果MC抽样拿掉的正是这个(k1,2,\dots)的相关结构那生成场景的频谱就从“红色噪声”低频为主变成了“白噪声”均匀频谱物理意义完全丢失。1.3 场景削减又是为什么计算复杂度与精度之间的拔河假设用MC生成了5000个场景做两阶段随机优化时每个场景都要对应一组二阶段变量问题规模直接乘以5000。对大规模电网来说这是灾难求解器内存和计算时间完全不可接受。所以需要场景削减把5000个场景压缩到50个甚至10个同时保留原概率分布的关键特征——均值、方差、分位数、相关性结构。这里要强调削减不是随机丢弃而是寻找一个“最接近原分布”的小规模离散分布。衡量两个离散分布之间的距离可以用Kantorovich距离Wasserstein-1距离这也是经典的同步回代消除法的基础。我在后面章节会给出具体操作路径。2. MC生成场景的两种路线直接法与间接法2.1 直接法从分布抽样映射到时序场景先看看最常见的直接法流程。假设要对某风电场进行场景生成数据分析步骤如下。第一步从历史数据拟合风速边缘分布。风电出力常用的风速描述模型是两参数Weibull分布[ f(v) \frac{k}{\lambda}\left(\frac{v}{\lambda}\right)^{k-1}\exp\left(-\left(\frac{v}{\lambda}\right)^k\right) ]其中(k)是形状参数(\lambda)是尺度参数。用极大似然估计可以很方便地求出来。然后通过风机功率曲线(Pf_{curve}(v))把风速样本映射成功率。第二步直接对24个时段独立抽样。这种方式生成第(i)个场景时在每个时段抽取独立的Weibull随机数拼接成一条序列。这样生成的场景如图1左边所示大量场景相邻时段间跳变剧烈。第三步也是直接法里大家容易忽视的——为什么不先把风速做时间相关性处理再映射成功率。风速时序比功率时序更容易建模因为功率曲线通常是一个带有切入风速、切出风速、额定风速的分段函数非线性会扭曲相关结构。所以你最好在风速层级注入时序相关性再通过功率曲线映射。2.2 间接法先建立随机过程模型再做MC间接法的思路是用一个能“记住过去”的随机过程模型来生成时序。最朴素但也非常有效的是ARMA自回归滑动平均模型[ x_t \sum_{i1}^{p}\phi_i x_{t-i} \varepsilon_t \sum_{j1}^{q}\theta_j \varepsilon_{t-j} ]其中(\varepsilon_t)是白噪声。对风速序列通常先做标准化和季节性处理然后定阶用AIC/BIC估计参数(\phi_i)和(\theta_j)最后用MC模拟大量的残差序列通过模型递推生成完整的时序。这看起来比直接法多了一步但好处巨大ARMA能自然保持样本内的时序相关性。另外还有一个更工程化的方案——马尔可夫链MC场景生成。把风速/功率离散化成若干个状态区间比如10个状态从历史数据统计状态转移矩阵然后从给定初始状态出发按转移概率随机游走生成一条序列。这个路线我在后面详细展开。2.3 两种路线的对比什么时候选哪个方法优点缺点适用场景独立MC抽样实现简单、边缘分布拟合准确丧失时序相关性生成场景波动失真只关注概率分布、不关注时序结构的粗粒度分析ARMA/ARIMAMC时序相关结构保持好统计特性成熟对非线性、非平稳过程拟合欠佳风电场风速、负荷时序场景生成马尔可夫链MC能捕捉非平稳非高斯过程状态跳变符合物理状态离散化引入误差状态数增多计算量上升光伏出力云层遮挡导致突变、极端天气过程建模CopulaMC灵活建模多个风电场之间的空间相关性及时间相关实现复杂度较高边缘分布与联合分布分离建模需仔细验证含多个新能源场站的区域级场景生成从我自己的项目经历看做单风电场时序场景先试ARMA不行再上马尔可夫链做多风电场联合场景时高斯Copula配合MC采样是性价比很高的方案。3. 保留时序相关性的核心操作马尔可夫链与Copula的实战细节3.1 风速/功率序列的马尔可夫链建模步骤这一节是本文最有实操价值的部分拿风电场景举例走一遍完整流程。第一步状态离散化把功率序列按容量百分比划分成N个区间比如10%一档共10个状态0-10%、10-20%、……、90-100%。这里注意边界处理落在边界上的点统一归入较高区间避免样本量在边界上被“撕裂”。还有一个小技巧不要用均匀分箱而是用历史出力数据的分位数去划分状态边界这样每个状态包含的样本量差不多转移概率估计更稳定。第二步统计时间转移矩阵对历史数据每隔一个采样间隔通常是1小时或15分钟统计状态转移情况。统计频次矩阵[T]其中[T[i][j]]表示从状态i转移到状态j的次数。然后行归一化就得到转移矩阵[P][ P_{ij} \frac{T_{ij}}{\sum_{k1}^{N} T_{ik}} ]第三步MC生成大规模时序场景生成第[s]个场景时先按初始状态分布随机抽取第一个时段的状态[S_1]然后对每个后续时段根据当前状态[S_t]按转移矩阵的第[S_t]行做加权随机抽样得到[S_{t1}]。不断递推下去就得到一整条状态序列。最后把状态值映射回区间内的代表值可以取状态区间的均值也可以在区间内再随机抽样更精细。这个流程我实测过生成5000个24时段场景在Python里用Numpy向量化操作不到一秒钟就跑完了效率非常高。关键是转移矩阵本身就把“相邻时刻的相关性”编码进去了。第四步马尔可夫链的阶数选择一阶马尔可夫链只记忆当前时刻虽然简单但对风速这种有明显“惯性”的过程一阶往往不够。大家可以尝试二阶马尔可夫链状态变成“当前时段上一时段”的组合状态状态维度变成[N^2]。以N10为例二阶就是100个组合状态转移矩阵规模为100×100完全可接受。实际效果比一阶有可见提升尤其是在捕捉持续性强的大风过程时。3.2 引入Copula处理多风电场/光伏电站的空间相关性单场站的时序问题可以用马尔可夫链解决但遇到区域级项目比如一个区域内5个风电场就需要同时考虑时间和空间的相关性。我的做法是三步走。对每个场站的出力序列分别做边缘分布变换。先求经验CDF或者用参数分布拟合把每个观测值映射到[0,1]均匀分布空间消除单个场站各自的分布形态差异。在均匀空间里估计高维高斯Copula的相关矩阵[R]。高斯Copula的好处是把复杂的相关结构用一个相关矩阵就刻画出计算方便对大多数新能源场景够用。用MC生成大量相关均匀随机数从多元正态分布[N(0, R)]采样再把采样值通过标准正态CDF变换回[0,1]最后通过各场站的边缘分布逆变换得到对应出力值。这样生成出的场景不同场站同一时刻的出力有合理的空间相关比如同一气团影响下几个风电场出力同时偏高同时每个场站在时间维度上也保留了各自的时序特征——前提是你用于拟合Copula的原始序列本身已经包含了时序信息。3.3 时间分辨率与采样步长的选择这里有一个经常被忽略的细节时序相关性的“记忆长度”与采样步长强相关。如果生成15分钟分辨率的场景马尔可夫链的转移矩阵非常集中相邻时段出力变化很小如果生成小时级场景转移矩阵对角元会低一些。所以做削减之前先想清楚最终优化模型的时间粒度。如果日后要跟机组组合模型对接通常是1小时那就直接用1小时步长建模不要用15分钟生成后再聚合聚合过程会人为引入额外平滑掩盖真实波动幅度。4. 场景削减聚类、同步回代与评价指标的完整实操4.1 聚类削减用K-means从5000个场景提炼典型场景场景削减最简单、最直观的方法是K-means聚类。把每个场景看成一个24维点的坐标用K-means把这些点聚成K个簇每个簇的质心作为典型场景簇内样本数量占总样本的比例作为该典型场景的概率。具体操作时有三点经验特征标准化要慎重。如果直接用原始功率值做欧氏距离夜间光伏出力全是0会拉低整个特征空间的区分度。建议按时段做Z-score标准化或者按“出力波动量”构造特征即相邻时段差值这样聚类结果更能体现时序形态的差异。K值怎么选。常用方法是肘部法则配合轮廓系数。轮廓系数在0.7以上说明簇内紧密、簇间分离清楚。但要根据后续求解规模决定K上限——一般随机优化的场景数控制在10~30之间再多求解器就吃力了。初始质心。K-means对初始质心敏感强烈建议用K-means初始化多跑几次选目标函数最小的结果避免落入局部最优。4.2 同步回代消除法基于概率距离的经典削减算法聚类削减的思路是“聚合出代表”而回代消除的思路恰恰相反——“从完整场景集合中逐个删除影响最小的场景”。这里给出同步回代消除Simultaneous Backward Reduction的完整算法流程。设原始场景集合为[S {s_1, s_2, \dots, s_N}]每个场景概率为[p_i]。计算任意两个场景之间的距离[d(s_i, s_j)]。最常用的是2-范数距离[ d(s_i, s_j) \sqrt{\sum_{t1}^{T}(s_i^{(t)} - s_j^{(t)})^2} ]同步回代消除的核心步骤对每个场景[s_k]找到它与其余场景的最近邻距离[d_k^{min} \min_{j \neq k} d(s_k, s_j)]找到“最不孤独”的场景[k^* \arg\min_k p_k \cdot d_k^{min}]即概率与最近邻距离乘积最小的场景删除场景[k^]把它原来的概率累加到它的最近邻场景上[p_{j^} p_{j^} p_{k^}]这里[j^]是[k^]的最近邻重复步骤1-3直到场景数量达到预设值。这个算法的思想很清晰优先删掉“离近邻很近且自身概率又小”的场景它的信息大概率能被近邻覆盖删掉后把概率送给近邻保持总概率和为1。我在实际代码中做了个小优化每次只对受影响场景附近的场景数量重新计算最近邻距离而不是对N个场景全部重新扫描能把时间复杂度从[O(MN^2)]降到一个可接受的范围。当初始场景数N5000、削减后M20时常规写法大概要十来秒优化后秒级出结果。4.3 削减效果的评估不止看均值还要看分布和极值很多文章只说“削减后均值和原场景集合接近”但我在实际项目里发现优化问题往往更关心尾部风险比如极端低风速时段。所以我建议至少做三个维度评估评估维度具体指标削减合格标准位置各时段均值、加权均值相对偏差3%离散程度各时段标准差相对偏差10%时序结构相邻时段差值的平均绝对误差相对偏差10%尾部/极端值5%分位数、95%分位数相对偏差10%另外一定要画“削减前后场景带”即所有场景的各时段分位数包络线用眼睛亲眼确认削减后的K个典型场景仍然覆盖了原场景的主要区间。机器指标会说谎但目视检查骗不了人。4.4 案例效果5000个场景削减到20个会发生什么拿我做过的一个实际风电场数据来说明。原始数据是某风电场一整年8760个小时的出力数据用马尔可夫链MC生成了5000个24小时场景。做了同步回代消除把场景数压到20个。削减前的5000个场景在24个时段上的出力均值是247MW标准差约58MW削减后的20个场景均值255MW标准差约55MW偏差在可接受范围内。但有个有趣的现象削减后场景的5%分位数比原场景略偏乐观。原因是低概率且极端恶劣的出力场景在削减过程中被优先删除概率被摊到了相对温和的近邻上。这提醒我们如果优化问题非常看重可靠性比如要评估系统爬坡能力是否充足建议在做场景削减时不只看距离还要额外保留几个极端场景。具体做法是把原始场景里出力最低和波动最大的那几个场景单独拎出来不参与削减直接在削减后的集合里固定加上再适当调整它们概率保证总概率和为1。5. 实操代码框架与工程踩坑记录5.1 Python代码骨架从原始数据到削减后场景下面给出一个精简但完整的代码流程。我用的是PythonNumpyScikit-learn环境没有特殊要求Python 3.8以上就行。import numpy as np from sklearn.cluster import KMeans from scipy.stats import weibull_min # 1. 加载历史出力数据shape: (天数, 24时点) data np.load(history_power.npy) # 假设已经预处理过无缺失值 # 2. 马尔可夫链状态离散化按分位数为边界 num_states 10 bounds np.percentile(data, np.linspace(0, 100, num_states1)) bounds[-1] 1e-6 # 避免边界溢出 state_seq np.digitize(data, bounds) - 1 # shape: (天数, 24时点) # 3. 统计转移矩阵 trans_mat np.zeros((num_states, num_states)) for day in range(state_seq.shape[0]): for t in range(state_seq.shape[1]-1): i, j state_seq[day, t], state_seq[day, t1] trans_mat[i, j] 1 trans_mat trans_mat / trans_mat.sum(axis1, keepdimsTrue) # 4. 用MC生成5000个场景 n_scenarios, T 5000, 24 scenarios np.zeros((n_scenarios, T)) init_state_prob np.bincount(state_seq[:, 0], minlengthnum_states) / len(state_seq) for s in range(n_scenarios): current np.random.choice(num_states, pinit_state_prob) scenarios[s, 0] (bounds[current] bounds[current1]) / 2 for t in range(1, T): current np.random.choice(num_states, ptrans_mat[current]) scenarios[s, t] (bounds[current] bounds[current1]) / 2 # 5. 同步回代消除法简化版完整优化版本略 def backward_reduction(scenarios, target_num): N len(scenarios) probs np.ones(N) / N active list(range(N)) while len(active) target_num: min_dist [] for idx in active: dists [np.sqrt(np.sum((scenarios[idx] - scenarios[j])**2)) for j in active if j ! idx] min_dist.append(min(dists)) # 找到 p_k * d_k^min 最小的场景 scores [probs[i] * md for i, md in zip(active, min_dist)] del_idx active[int(np.argmin(scores))] # 找到最近邻 neighbor min([j for j in active if j ! del_idx], keylambda j: np.sqrt(np.sum((scenarios[del_idx]-scenarios[j])**2))) probs[neighbor] probs[del_idx] active.remove(del_idx) probs [probs[i] for i in active] active list(range(len(active))) return np.array([scenarios[i] for i in active]), probs reduced_scen, probs backward_reduction(scenarios, 20)这段代码只是一个骨架。实际项目里建议把距离矩阵缓存下来避免每次删除场景都重新计算全量距离否则8000个场景重复几百次迭代会非常慢。5.2 踩坑记录时序相关性被“洗掉”的三个隐蔽环节这个项目里我吃了不少亏其中最隐蔽的三个坑值得单独说一下。坑一功率曲线的非线性扭曲相关结构。最早我直接在风速层做ARMA生成时序风速后通过功率曲线映射。因为夜间和低风速段功率曲线平坦高风速段又容易饱和原本风速序列里的平滑特征映射成功率后被压缩成一串“平台-跳变-平台”。后来我改成功率层做马尔可夫链才彻底解决。所以强烈建议做时序建模前先画一下历史出力序列的ACF/PACF图对比建模前后的自相关衰减速度如果相关系数衰减过快说明你模型的“记忆”不够。坑二场景削减时把簇心和原始场景混合使用。有次我在同一次优化里既用了聚类质心场景又混入了几个原始极端场景结果概率分配乱了套。要明确一点削减后所有场景必须来自同一个分布空间要么全是质心要么全是原始场景的带概率子集。混着用会导致概率含义不统一优化结果缺乏可比性。坑三K-means聚类的特征选错了。只对功率值本身做聚类会把大量“出力曲线形状类似但整体平移”的场景分散到不同簇。后来我改为对功率值、一阶差分、二阶差分三个维度拼接后共同聚类时序列形状得到更好区分。这个方法在处理光伏数据时效果尤其明显因为晴天和多云天的光伏曲线形状差异基本体现在波动纹理上而不是绝对高度。5.3 与后续优化模型的衔接场景削减不是终点接口设计才是把削减好的场景输入优化模型之前还有几件事要做。场景概率归一化无论用K-means还是同步回代消除都要检查最终概率之和是否等于1。出现精度误差时直接做一次归一化但前提是每个概率都严格非负。场景时段对齐如果优化模型要求从早上6点开始调度而历史数据从0点开始先做时段平移聚合再生成场景避免在模型里做截断拼接。与求解器的接口削减后的场景如果数量仍然偏大大于50为缩短求解时间可以考虑做两阶段聚合先用K-means聚出100个代表形态再用回代消除压缩到20个。这个方法能兼顾形态多样性和概率准确性。6. 个人总结这套流程的适用边界与后续扩展思路做完整套MC场景生成与时序相关性处理再到场景削减我有几点切身的体会想分享。首先时序相关性处理的侧重点取决于下游模型。如果下游是简单的容量可信度评估独立MC抽样可能够用如果下游是时序递推的机组组合或者储能充放电策略优化那时序相关性的失真会直接扭曲结果——尤其是储能它最怕的是“连续几天没风还是每天都有风”这种时间结构上的差异。其次马尔可夫链同步回代消除这套组合是当前工程实践里性价比最高的方案之一。它不像深度生成模型那样需要大量数据和GPU训练时间也能在几分钟内完成从历史数据到削减后场景的全流程。如果你对前沿方法感兴趣可以考虑用Wasserstein GAN或者扩散模型来做场景生成它们对高维复杂相关性的建模能力确实强但在中小规模数据比如只有一年历史数据下容易过拟合落地前需要充分验证。最后再分享一个小技巧在做场景削减前把原始MC场景集合打乱并拆成5折分别削减并对比结果如果5折削减后的典型场景分布非常接近说明你的生成和削减流程是稳定的可以放心把场景交给下游模型。如果折间差异很大那么问题大概率出在MC场景数不够或者削减算法收敛不充分需要回头调整参数。这一套方法论我在风电、光伏和负荷预测项目里都落地过整体效果稳定。希望这篇文章能给正在跟场景生成和削减纠缠的你提供一个能直接“抄作业”的路线图。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门