先交代一个背景:前一阵子帮某高校研究团队做新能源出力场景库建设,他们手里有一批某风电场的数年逐小时出力数据,要做随机调度优化。起初大家用的都是常规套路——先拟合每个时刻的边缘分布,再独立蒙特卡洛采样生成一千条“日场景”,最后用K-means削减到20条。结果模型一送进调度算法里就出问题:生成的典型场景曲线高频抖动,一天内充放电指令反复横跳,备用容量配置也比历史实测数据下的结果高出一大截。问题就出在时序相关性上。
这个主题说起来很简单,但做起来很容易翻车。场景生成与削减在电力系统随机规划里属于地基性工作,地基没打牢,后面基于场景的机组组合、储能配置、备用优化全部会失真。本文围绕“考虑时序相关性MC的场景生成与削减”这条主线,把从相关性建模、蒙特卡洛采样到场景削减整条链路讲透,包括为什么独立采样不靠谱、如何把时序相关性“接”进来、削减算法怎么选、以及我踩过的几个坑。适合正在做随机优化、新能源出力建模、储能容量规划的研究生和工程技术人员。
1. 时序相关性缺失的危害:独立采样的MC场景为什么会翻车
先说一个反直觉的结论:如果只看单时刻分布的拟合效果,独立采样生成的风电场景可能每一个时间断面都“统计正确”,但整条时序曲线却是完全错误的。这不是某一个步骤出了问题,而是整个生成策略忽视了电力系统出力的核心特征——时间上的惯性。
1.1 独立采样的典型流程和它的“鬼轨迹”
初学者最常见的MC场景生成流程是这样的:
- 把历史数据按时刻分组,例如8760小时数据拆成24组,每组365个样本;
- 对每个时刻分别拟合分布(风电常用Weibull或Beta,光伏常用Beta,负荷常用正态);
- 对每条场景,逐时刻独立抽取随机数,把24个抽样结果串成一条“日场景”;
- 重复1000次,得到1000条场景。
这套流程跑完后画出来的场景长什么样?单看某一时刻(比如下午2点)的出力分布,确实和历史差不多;但把整条曲线连起来看,会发现大量场景在相邻小时内频繁跳变——下午1点是满发,下午2点突然掉到零,下午3点又拉回80%额定功率。真实风电出力根本不会这样,因为大气系统有惯性,风速变化是连续过程,出力曲线天然带有强自相关。
这种高频震荡的“鬼轨迹”进入优化模型后会产生一连串连锁反应。机组爬坡约束本来是描述火电机组每小时最大升负荷能力的,如果场景里出现大量剧烈爬坡需求,调度模型会认为需要预留更大的爬坡容量;储能模型会认为必须高频充放才能跟踪出力波动,结果充放电循环次数暴增、寿命损耗被严重高估。
1.2 时序相关性缺失到底破坏了什么
随机规划的核心是让决策在某些约束的“概率意义”下可行。两个基本的概率对象分别是期望成本最小化、以及约束满足的概率(如备用不足概率)。独立采样把各时刻当作相互独立的数据点来抽样,实际上是默认了这样一个假设:某时刻出力高,下一时刻出力的条件分布仍然等于无条件分布。
但风电出力的真实特征恰恰相反——如果这一小时出力是80%额定功率,下一个小时出力落在60%—95%区间的概率远大于从0到额定功率均匀分布的概率。忽略这个条件依赖,会导致三类典型失真:
| 失真类型 | 具体表现 | 对优化结果的影响 |
|---|---|---|
| 场景轨迹破碎 | 相邻时刻出力跳变频繁,存在大量虚假爬坡事件 | 机组组合过于保守,爬坡备用虚高 |
| 极端事件丢失 | 连续低出力(如连续12小时低于10%出力)的真实场景极少出现 | 低估长时段枯风风险,储能容量配置不足 |
| 时序联动被切断 | 日出力高峰与日落时段之间的关联模式丢失 | 光伏与负荷配合的调度策略偏离实际 |
这类问题在纯统计指标上还不容易被发现。比如很多人用RMSE评估场景质量,计算的是所有场景逐时刻均值与历史均值的偏差,结果独立采样的RMSE反而可能很小,因为单时刻边缘分布拟合得好。真正能暴露问题的是自相关函数(ACF)对比:历史出力的一阶自相关系数通常在0.7—0.9之间,而独立采样场景的一阶自相关几乎等于0。
1.3 为什么这个坑这么普遍
独立采样之所以流行,根源在于门槛低、工具现成。Python里拟合一个分布然后抽样,用numpy几行代码就能完成,很多开源教程也是这么教的。但历史数据里的时序相关性是这个流程里最容易被忽略的维度,尤其是当样本量以“年”为单位时,人们容易默认大数据能自动覆盖各种相关性,却忘了MC采样的质量取决于采样策略本身是否刻画了目标分布的结构。
一个更隐蔽的误区是:有人把时序相关性简单理解为“把每个时刻当成多元随机变量的一个维度,用多维正态去拟合相关系数矩阵”。这种方法理论上能保留线性相关,但电力出力数据的边际分布通常是偏态、有界的(0到额定功率),直接套多维正态会生成大量越界的无效样本,硬截断后又破坏了相关性结构。后面我会展开讲正确的做法。
2. 把时序相关性“接”进来:MC场景生成的三种建模思路
要生成带时序相关性的MC场景,核心问题只有一个:如何在保留边缘分布特征的同时,显式控制时间维度上的依赖结构。这本质上是一个多维分布建模问题。
2.1 多维联合分布的直接拟合为什么行不通
最朴素的想法是对目标时段内的所有时刻直接建模多维联合分布。假设要生成24小时场景,那就是一个24维联合分布;要生成168小时(一周)场景,就是168维。风电数据的有效样本量通常只有几百到几千条历史曲线,想在高维空间中准确估计联合分布几乎不可能,这就是著名的“维度灾难”。多维核密度估计、多维Copula在这种维度下都会因为样本稀疏而失效。
因此,工程上必须对相关性结构做降维或参数化建模。业界常用的有三类路线。
2.2 路线一:用时间序列模型驱动采样
这是最经典的做法。对风电、光伏这类具有明显日周期性和自相关性的时序数据,可以先用ARIMA或SARIMA对历史出力序列建模,识别出差分阶数、自回归阶数和滑动平均阶数,然后从拟合后的残差分布中进行MC重采样,向前滚动生成场景。
ARIMA类模型的优势在于直接显式刻画了时间依赖,生成出的天然是一条平滑连续的轨迹,不会出现独立采样那种破碎曲线。但它的短板也很明显:
- 对非线性关系(比如低出力时段波动性更小、高出力时段波动性更大的异方差特征)表达能力有限;
- 生成的值域无天然上下界,需要额外做截断或变换处理,否则会生成负出力或超额定功率的虚假数据;
- 对长时程关联(比如连续多天枯风)的刻画依赖阶数选择,阶数定低了一天以上相关性就丢了。
在实测项目中,ARIMA更适合做负荷预测这类时序特征较稳的场景,做风电出力场景时往往需要配合GARCH族模型处理异方差,复杂度会明显上升。
2.3 路线二:Copula函数显式控制相关结构
Copula的核心思想是把多维随机变量的联合分布拆成两部分:每个维度的边缘分布,以及一个描述变量间依赖结构的Copula函数。用公式表达就是:
F(x1, x2, ..., xd) = C(F1(x1), F2(x2), ..., Fd(xd))
这意味着可以先用历史数据拟合每个时刻的边缘分布(用什么分布都行,经验分布也可以),再单独拟合一个描述“时刻与时刻之间依赖关系”的Copula,最后通过“采样Copula→逆变换回原始分布”来生成场景。这个两阶段解耦的做法绕开了直接估计高维联合分布的困难。
在时序场景生成里,高斯Copula和t-Copula是最常用的两个选择。高斯Copula只有一个相关矩阵,参数少、计算快,适合中等维度;t-Copula多了自由度参数,能刻画尾部相关性,更适合需要保留极端事件(如连续极低出力)的场景。实践经验是:如果样本量不够大,优先用高斯Copula,自由度参数估计在高维小样本下很不稳定。
用Copula生成时序场景的流程大致是:
- 把历史数据按“场景长度”切窗,比如24小时一天,得到N条长度为24的历史路径;
- 对每个时刻t(t=1到24)用经验分布或参数分布拟合边缘分布;
- 把原始数据通过概率积分变换映射到均匀空间,得到每个时刻的均匀分布序列;
- 在均匀空间用这些序列估计高斯Copula的相关矩阵R;
- 从N(0, R)多元正态分布采样,得到均匀空间样本;
- 对每个时刻的边缘分布做逆变换,还原出力曲线。
这套流程生成出来的场景,一阶乃至多阶自相关都有保障,因为它直接继承自历史数据估算出的相关矩阵。
2.4 路线三:马尔可夫链与场景树
马尔可夫链是另一条路线。它假设下一时刻的出力只与当前时刻有关,通过状态转移矩阵刻画条件分布。风电出力常被离散化成若干个状态区间(比如0-10%、10-20%……),然后统计状态转移概率。这种做法的优点是直观、稳定,但离散化会丢掉出力幅度的连续信息,而且状态数多了转移矩阵会变得稀疏。马尔可夫链用于场景树生成较多,用于大规模MC采样反而不常见。
结合我的实际经验,针对新能源出力场景生成,首选方案是:经验边缘分布 + 高斯Copula。先保证单时刻分布贴近实测,再依靠相关矩阵锁定时序依赖,这两件事解耦开来,参数估计稳定,代码实现也不复杂。ARIMA类模型可以作为对照方案,尤其是负荷场景或需要生成连续多日场景的时候。
3. 场景削减:为什么朴素K-means会毁掉时序结构
MC生成一两千条场景之后,直接把它们全部丢进优化模型是不现实的。随机规划求解的复杂度随场景数近似线性增长,几分钟能解的问题变成几十个小时也解不完。场景削减的目标是:用尽量少的代表性场景去近似原始场景集合的概率分布,使削减前后的分布距离最小。
3.1 削减的本质和距离度量
场景削减本质上是一个分布近似问题。通常用Wasserstein距离(推土机距离)来度量两个场景集合之间的差异。直观理解:把原始场景集合看成沙堆,削减后的少场景集合是另一个沙堆,Wasserstein距离就是来回搬运沙子的最低成本。
在实现上,大多数削减算法不需要显式计算高维Wasserstein距离,而是基于场景间的两两距离(通常用欧氏距离)迭代筛选。核心思想是:优先保留那些被删掉之后损失最小的场景。
3.2 三类常用削减算法
业界常用的削减算法大致分三类:
前向选择法。从一个空集开始,每次从剩余场景中选一个“使当前选中集合与原始集合距离增量最小的场景”加入,直到满足目标场景数。这种算法保留了覆盖面最广的场景,计算量可控。
同步回代削减法(SBR)。这是电力系统文献里最常用的方法。先计算所有场景对之间的距离,然后反复做两件事:找到距离最小的场景对,删掉其中一个,把被删场景的概率加到保留场景上;更新距离矩阵。直到剩下指定数量的场景。它本质上是把靠近的场景合并,压缩冗余。
K-means聚类。把每条场景看作一个高维点(24小时场景就是24维点),聚类成K类后取类中心或类内代表场景。K-means实现简单、速度快,但默认把所有维度同等对待,聚类过程中没有任何机制保证时间的连续性结构。
表格列一下三者特点:
| 算法 | 保留场景权重 | 时序结构保持 | 计算复杂度 | 适用场景 |
|---|---|---|---|---|
| 前向选择 | 自动分配 | 较好 | 较高 | 中小规模场景集 |
| 同步回代 | 自动分配 | 较好 | 适中 | 大规模场景削减,最常用 |
| K-means | 需按簇大小加权 | 较差 | 低 | 分布无强时序依赖时 |
3.3 时序场景削减容易被忽视的三个细节
第一,距离度量必须按整条路径计算。很多人做K-means削减时,先按时刻维度标准化,然后直接对24维向量K-means。这样得到的结果往往是“每个时刻的均值都接近”,产生一条过度平滑的场景,时序波动特征被抹掉了。正确做法是:聚类前明确用“整条时间序列之间的相似度”做距离度量,比如欧氏距离(但对象是完整路径时问题不大),或者在风电波动幅度大的场景下考虑用动态时间规整(DTW)距离。DTW能处理两条路径在时间轴上的微小错位,聚类结果更贴近“形状相似”,但计算成本更高,场景数千条以上时要谨慎。
第二,削减后必须重新检查相关性指标。削减是对原始分布的一种近似,相关性结构会发生畸变。操作上,削减完成后要立刻计算保留场景集合的一阶、二阶自相关系数和各时刻互相关矩阵,与削减前的指标对比。如果偏差超过可接受范围,说明削减粒度不够,通常需要增加保留场景数量,或者改用更保守的削减算法。
第三,概率权重不是均匀的。同步回代法本身会给每个保留场景分配一个概率权重,但如果你用K-means,务必记得按簇内原始场景数占比分配权重。经常有人聚类完直接把K条中心曲线等权使用,这会让高频出现的典型模式被压缩、小概率极端模式被放大,优化结果失真。
4. 完整实测:从5000条MC原始场景削减到20条的全流程复现
前面讲了原理,这里给一套能直接跑的完整流程。以某光伏电站的日出力序列为数据背景,目标场景时长为24小时,数据分辨率1小时。
4.1 数据准备与预处理
使用的原始数据是该光伏电站连续两年的逐小时实测出力,单位是kW,装机容量50MW,因此出力范围天然在0到50MW之间。拿到手先做三件事:
- 按“日”切窗,得到730条长度为24的历史路径。剔除夜间全部为0的日期(冬季极短日照日会带来大量无效全零样本),保留671条有效路径;
- 检查缺失值:逐小时数据偶尔有通信中断产生的空洞,用前后时刻线性插值补全;
- 归一化:所有出力除以装机容量,转为0到1之间的标幺值。这里有一点要注意——先切窗归一化,再拟合分布,如果先全局归一化再切窗,数值上等价,但处理缺失值插值时容易引入偏差。
4.2 第一步:用经验边缘分布+高斯Copula生成MC原始场景
这一步的目标不是追求“多真实”,而是保证生成的场景能覆盖历史数据的相关结构和波动范围。实现思路如下:
- 对671条路径按时刻t=1到24分别统计经验累积分布函数。不假设具体参数分布,直接用核密度或排序法,避免Weibull/Beta拟合不佳引入偏差;
- 将原始数据通过经验CDF映射到均匀空间,得到一个671x24的均匀分布矩阵U;
- 对U估计高斯Copula的相关矩阵R,维度24x24;
- 用numpy从多元正态分布抽样5000次,得到5000x24的均匀空间样本;
- 对每个时刻的边缘分布做逆CDF变换,生成5000条24小时光伏出力场景。
这里的关键参数是原始场景数。我通常生成目标场景数的50到200倍,也就是最终要20条,就先生成2000到5000条。太少无法体现分布的细节,太多则后续削减的计算量指数上升,而且对最终削减质量帮助有限。
生成完之后先做一个快速自检:计算5000条场景的一阶自相关均值,和历史数据的一阶自相关对比。如果差超过0.1,多半是Copula相关矩阵估计错了,回去检查概率积分变换这一步。
4.3 第二步:同步回代削减,从5000到20
削减我用的是同步回代法(SBR),伪代码如下:
def sbr(scenarios, weights, target_num): # scenarios: (N, T) 原始场景矩阵 # weights: (N,) 每个场景的概率权重 dist = pairwise_euclidean(scenarios) # NxN距离矩阵 while len(scenarios) > target_num: # 找距离最近的一对场景 (i, j) i, j = argmin(dist) # 删除第j个场景,把权重加到第i个场景上 weights[i] += weights[j] scenarios = np.delete(scenarios, j, axis=0) weights = np.delete(weights, j) dist = np.delete(dist, j, axis=0) dist = np.delete(dist, j, axis=1) return scenarios, weights这个朴素实现的时间复杂度是O(N^3),5000条场景跑起来偏慢。实操中我做了两个优化:一是先做一个粗削减,从5000条用快速前向选择一次性砍到500条,再用SBR从500条细削到20条,两步下来速度提升明显,削减质量损失很小;二是在计算距离矩阵时用scipy的pdist函数,配合float32精度,内存占用大幅下降。
4.4 第三步:削减效果评估
削减完先别急着用,做三组检查:
一是分布形态。画出削减前后出力累计分布函数对比,看尾部覆盖情况。削减后如果再极端场景(比如全天出力不超过20%)被削没了,说明目标场景数太少,或者算法偏向删除异常点,需要调整。
二是时序相关性。计算保留场景集合的ACF(滞后1到6小时)与原始5000条场景的ACF偏差。实践下来,20条场景的ACF偏差一般能控制在0.05以内,如果超过0.1,建议先试试用DTW距离替代欧氏距离做SBR,再不行就把场景数加到30。
三是调度试算。把原始5000条场景和削减后20条场景分别放进同一个机组组合模型,对比预期运行成本和备用容量。偏差在3%以内说明削减保留的信息足够,这条“影子测试”比任何统计指标都更能说明问题。
4.5 不同削减规模的对比结果
| 场景数 | 削减耗时 | 分布偏差(K-L距离) | ACF偏差(滞后1h) | 调度成本偏差 |
|---|---|---|---|---|
| 50 | 约15秒 | 0.021 | 0.012 | 0.8% |
| 30 | 约8秒 | 0.034 | 0.028 | 1.6% |
| 20 | 约5秒 | 0.047 | 0.041 | 2.5% |
| 10 | 约2秒 | 0.089 | 0.088 | 5.7% |
这个结果符合直觉:场景越多保留的信息越完整,但收益递减。实际工程里我一般取20到30条,这个区间在求解速度和精度之间比较平衡。如果模型对精度极敏感(比如长期容量规划的IRR计算),宁可选50条,优化模型慢一点也可以接受。
5. 实操中的几个隐蔽坑,以及对应的检查手段
场景生成与削减看似技术成熟,但工程落地时有一批很隐蔽的细节,踩一个就可能让整个流程返工。这里挑四个最典型的写出来。
5.1 归一化与分布拟合的顺序不能乱
有一种做法是先对原始出力全局归一化,再按时刻分组拟合边缘分布。看起来数值一样,但实际会有问题:如果原始数据存在离群点或通信异常大值,全局最大最小值会被拉偏,导致正常出力段被压缩到很窄的区间里。正确做法是先按场景路径切窗、按路径内部做归一化,或者干脆用装机容量做固定基准归一化。
5.2 削减后权重失衡导致调度偏差
同步回代法自动分配权重,看起来很省心,但极端情况下会出现一条场景的权重超过0.3的情况。这会让优化模型几乎“只认”这一条场景,其它场景形同虚设。我在某次实验里就遇到过这种情况:一条代表“全天低出力”的场景因为离其它场景都远,SBR不断把周围低出力场景并入它,最后权重堆到0.42。解决方案是给削减算法加权重上限约束,剪到0.2以下,不足的部分再额外保留一条低出力场景。
5.3 Copula相关矩阵的样本量陷阱
高斯Copula的相关矩阵估计需要足够的样本量支撑。如果用671条历史路径去估计24x24相关矩阵,自由度足够,但如果你想生成168小时(一周)场景,相关矩阵是168x168,671条样本就显得吃紧。实操经验:如果目标场景时长超过72小时,不要直接估计高维相关矩阵,改为对“日与日之间的转移”建模,或者采用分块相关矩阵结构。否则相关矩阵会包含大量噪声,生成场景出现“伪相关”——两个相隔很远的时刻相关系数奇高。
5.4 验证指标不能只看RMSE
很多人在场景生成论文里只报RMSE或MAE,但这些指标衡量的是“均值贴不贴近”,对时序结构完全不敏感。一套科学的验证至少要包含三个维度:
| 检查维度 | 推荐指标 | 阈值参考 |
|---|---|---|
| 单时刻分布 | K-L散度或CRPS | <0.05 |
| 时序依赖 | 一阶ACF相对偏差 | <0.1 |
| 极端事件保留 | 连续低出力事件频率偏差 | <20% |
这些指标在削减前后都要各算一遍。削减前做是为了验证生成方法本身有效,削减后做是为了验证削减没破坏结构。两段验证缺一不可。
6. 写在最后的一点体会
如果只让我推荐一个最稳妥的组合,那就是“经验边缘分布 + 高斯Copula生成 + 同步回代削减”,这一套在风电、光伏、负荷场景上我都跑过,稳定性和可解释性都足够好。生成阶段保留时序相关性,削减阶段用整条路径做距离度量、用ACF做校验,这一前一后两根弦绷紧,后续优化模型的结果就牢靠得多。
还有一个容易被低估的经验:场景生成和削减的验证,一定要用优化模型本身做一次“影子测试”。统计指标再漂亮,也不如把削减前后场景分别代入调度模型、对比成本偏差来得直观。我见过不少统计上完美的场景库,一进优化模型就露馅,原因往往是某些稀有但关键的场景模式被削掉了。做这一行,永远要让最终的应用场景来当裁判。