做风电功率预测误差建模的人都知道真正让调度头疼的不是预测值本身而是“误差”——你报出一条预测曲线调度看到的是一段区间这段区间有多宽、落在哪一侧直接决定备用容量、调频需求和市场报价。如果你负责的是一个风电集群事情还要再复杂一层误差不是一个个独立小问题它天生带着时空相关性。同一片风团推过来几个风电场可能在同一小时里一起偏大或一起偏小上一小时低估了风下一小时往往还是低估。这篇博文把我实际项目中用Matlab做风电功率预测误差建模的完整流程写出来先用AR(1)处理时间维度再用高斯Copula捕获空间维度最后生成一组时空一致的误差场景服务于后端的随机优化或者概率预测。1. 为什么风电预测误差需要单独建模1.1 误差模型的真实用途备用、报价与滚动优化先把概念理清。预测误差定义为 e_t P_t^{实际} - P_t^{预测}。当 e_t 0说明实际出力高于预测系统可能面临上调压力当 e_t 0说明实际低于预测需要补备用。传统做法是把误差当成高斯白噪声或者干脆只用一个固定的标准差。但在实际电力调度中这样做的代价非常大。误差模型的核心应用有几个层面。第一是旋转备用容量计算备用要覆盖某个概率水平下的最坏误差误差分布尾部厚不厚直接决定备用买多少。第二是日前和日内市场报价误差分布不对称时向上报价和向下报价的期望收益完全不同偏差方向一旦判断错现货市场的亏损很快体现。第三是滚动优化控制像模型预测控制里做随机约束时每一步都需要输入未来若干时点的误差路径。第四是风电场群并网评估并网点注入功率的总波动来自所有场站误差的叠加单场独立假设在这里错得最明显。如果你只对单个风电场分别拟合正态分布前三个应用也能跑起来。但一到“联合不确定性”的场景比如算整个风电场群的总功率误差、做多场站联合优化独立假设会把概率系统性算错。我见过不少项目在并网点评估阶段才发现问题每一个单场误差看起来都很正常但把总和分布一算跟实际偏差一大截根源就是忽略了空间相关性。1.2 时空相关性不是玄学是气象过程很多风电场的误差序列滞后1小时的自相关系数能到0.5以上甚至0.8。原因其实很朴素数值天气预报被同化系统修正往往需要几小时到十几小时。初始场若低估了风速预测系统就会连续若干个小时持续低估误差是个有记忆的过程不是白噪声。空间相关性也好解释相邻几十公里的风电场看到的是同一片风况差值主要来自局部地形和风机尾流。某一区域上空风速整体增大这一片的风电场就会同时超出预测出力。有个很直观的类比把每个风电场的预测误差想成“云影”一片云移动过来地面上几块临近区域会同时变暗。云的面积越大、越厚相关半径越长。大部分风电场的空间相关性基本就是距离的衰减函数——相距越近相关性越高。这个特性对决策的影响用数字说话。假设5个风电场单场误差标准差都是1若完全独立场群总误差标准差是 sqrt(5)≈2.24若两两相关系数为0.6总标准差是 sqrt(5 2×0.6×10) ≈ 4.12几乎是两倍。也就是说独立假设在这里绝对不只是“稍微乐观”而是把风险系统性低估了接近一半。这个例子我建议记下来无论是写分析报告还是说服调度同事都很管用。2. 方案选型用什么模型扛住时空耦合2.1 独立高斯为什么不行先做残酷的对比。下表是我在实际选型时反复比较的几个方案直接照抄可以参考。方案边缘分布空间相关时间相关优点缺点独立高斯拟合正态忽略忽略实现最快显著低估总不确定性多变量高斯正态线性相关忽略能抓线性相关边缘必须正态尾部太瘦AR(1)高斯Copula任意核密度/t/正态秩相关AR(1)承载灵活、可解释、易落地需要分两步串联参数略多时空高斯过程/深度学习联合建模灵活核函数灵活精度上限高数据需求大黑盒调试成本高独立高斯的问题除了前面算过的方差低估还有一个致命点风电误差边缘分布通常有偏有厚尾有上下截断。正态分布把这些特征全部抹平了典型结果就是90%区间覆盖不住真实误差而在5%和95%分位数附近频繁失守。多变量高斯比独立高斯好一些它能把空间线性相关放进协方差矩阵里。但风电场误差的联合分布不是那么简单边缘分布各有形态尾部协同也未必对称。协方差矩阵描述的是线性关系放在偏态边缘上会失真而且多元正态要求所有边缘都是正态这点在风电场上基本不成立。2.2 时间交给AR(1)空间交给Copula我最终选定的路线是“AR(1)削时、Copula管空”。时间相关性用自回归模型处理非常成熟。AR(1)的含义是当前时刻误差受上一时刻误差影响但会向均值回归。写成 e_{i,t} c_i φ_i * e_{i,t-1} ε_{i,t}其中φ_i是滞后系数ε_{i,t}是残差。把φ_i估计出来之后残差ε里因时间记忆带来的序列相关性就被剥离掉了剩下的是“空间协同扰动”。空间协同这一步交给Copula核心在于“边缘分布与相依结构分离”。你可以让每个风电场的误差残差服从任意分布——核密度、t分布、偏态分布都行——再单独用一个秩相关矩阵把场站间的协同关系刻画出来。高斯Copula建模的是秩相关表达式简洁Matlab有现成函数支持参数只有一个相关矩阵稳定性好。我实际项目里试过直接用多变量高斯效果不理想。风电误差的边际偏态太明显多元高斯把尾巴修得太细高置信区间总盖不住。换成“核密度边缘高斯Copula”之后尾部行为改善很明显。两段式还有一个工程优势第一步的AR(1)谁都能看懂第二步的Copula不用自己写优化器调试时能分别验证时间部分和空间部分各自是否正确不用一锅端排错。2.3 两个关键选型取舍第一个取舍是Copula选高斯还是t。高斯Copula只能表达对称的尾部相关若极端大风事件中误差容易“抱团”应该考虑t Copula。但t Copula多一个自由度参数估计时的数值稳定性差一些场景数量也要更大才能把尾部描述清楚。我的经验是先用高斯Copula打底做覆盖率检验时若90%以上区间失守再升级成t Copula不要一上来就给模型加复杂度。第二个取舍是残差空间相关矩阵用“全矩阵估计”还是“距离衰减结构”。风电场数量多时直接拟合相关矩阵很可能遇到非正定问题。更物理的做法是假设协方差函数随距离指数衰减比如 Corr(i,j)exp(-d_ij/L)参数只有特征相关长度L。缺点是场站间若存在峡谷、海风等异质性单纯距离衰减不够。项目初版我建议先直接估计相关矩阵反正Matlab有现成的矩阵修正工具兜底真实数据样本充足时这个估计通常是可靠的。3. Matlab实现从模拟数据到误差场景3.1 生成带时空相关性的模拟数据为了把流程完整跑通我用Matlab模拟5个风电场、8760小时的运行数据。真实项目中把读矩阵的部分换成数据库读取即可。rng(20240101); N 5; % 风电场数量 T 8760; % 小时点数 period_day 24; % 基准出力含日周期和场间相位差 t (1:T); base zeros(T, N); for i 1:N shift 0.5*(i-1)/(N-1); base(:,i) 0.45 0.30*sin(2*pi*t/period_day shift) ... 0.10*sin(2*pi*t/(period_day*7) 0.2*shift); end base max(0, min(1, base)); % 空间相关结构按场站距离指数衰减 dist abs((1:N) - (1:N)); Sigma 0.9.^dist 0.1*eye(N); % 加对角保证正定 % 时间相关结构AR(1) 驱动 phi 0.55; spatial_noise mvnrnd(zeros(1,N), Sigma, T); % T x N E_true zeros(T, N); for k 2:T E_true(k,:) phi*E_true(k-1,:) spatial_noise(k,:); end % 实际出力 基准 误差预测出力 基准 P_pred base; P_real max(0, min(1, base 0.15 * E_true)); E_obs P_real - P_pred; % 我们要建模的误差序列我在模拟里故意让初始误差接近正态这样你验证流程时比较直观。真实场站的误差通常有偏态第3.4节的核密度边缘正好可以处理。P_real做了上下限截断这是物理约束真实数据也一样。3.2 时间相关性检查ACF与AR(1)拟合建模前先确认误差序列确实存在时间记忆。不要跳过这一步否则后面所有结论都没意义。figure; for i 1:N subplot(2,3,i); autocorr(E_obs(:,i), 20, NumSTD, 2); title([场 num2str(i) 误差ACF]); end若滞后1阶的ACF柱超出两条置信线时间相关性成立。下一步估计AR(1)参数并提取残差beta0 zeros(N,1); % AR截距 phi_hat zeros(N,1); % AR滞后系数 resid zeros(T,N); for i 1:N Y E_obs(2:end,i); X [ones(T-1,1), E_obs(1:end-1,i)]; beta X \ Y; beta0(i) beta(1); phi_hat(i) beta(2); resid(2:end,i) Y - X*beta; resid(1,i) 0; end这种回归写法不依赖额外工具箱适合建模脚本。装了Econometrics Toolbox的话也可以改用 arima(1,0,0) 配合 estimate 实现结果基本一致。拟合完成后一定要回头检查残差序列的ACF如果残差ACF已经落在白噪声带内说明时间依赖被剥离干净如果滞后系数本身不显著说明时间记忆很弱模型退化成独立同分布也能用。3.3 空间相关性检查残差相关系数矩阵AR(1)剥离的是时间依赖空间依赖应该还保留在残差矩阵里。Rres corrcoef(resid); figure; imagesc(Rres); colorbar; colormap(jet); axis square; xticks(1:N); yticks(1:N); title(残差空间相关系数矩阵);理想情况下你会看到对角为1近场之间的系数明显高于远场。我这组模拟数据里相邻场相关系数约0.73相隔一个场约0.54符合指数衰减设定。如果真实数据跑出来Rres几乎全是0那么空间相关性在你的误差过程中不存在后面的Copula环节可以直接省略。判断正确比硬建模重要得多。3.4 边缘分布拟合与高斯Copula参数估计残差的每个分量先做边缘分布拟合。这里选择核密度而不是正态分布是因为风电误差残差常常有偏态和厚尾核密度不需要提前假设参数族。pd cell(N,1); U zeros(T,N); for i 1:N pd{i} fitdist(resid(:,i), Kernel); % 默认高斯核 U(:,i) cdf(pd{i}, resid(:,i)); % 映射到(0,1) end gaussCopula copulafit(Gaussian, U); Rho gaussCopula.Rho;这里有三个实操要点。其一U的每一列要严格落在(0,1)开区间核密度尾部常给出接近0或1的CDF值极端情况需要做微小裁剪。其二样本量太小时相关矩阵的估计方差会很大用于拟合的历史时点最好不低于1000个。其三若残差数据分辨率低、重复值很多核密度CDF会出现平台段影响Copula拟合质量可以给残差加微小抖动或者改用非参数秩变换。3.5 蒙特卡洛采样生成时空一致的误差场景模型参数齐了接下来生成场景。给定当前时刻的历史误差E_obs(end,:)误差场景由两部分组成时间维度用AR(1)给条件均值空间维度用Copula采残差扰动。先看单步场景nScen 500; u_sim copularnd(Gaussian, Rho, nScen); % nScen x N e_sim zeros(nScen, N); for i 1:N e_sim(:,i) icdf(pd{i}, u_sim(:,i)); end % 加回 AR(1) 条件均值 next_mean beta0 phi_hat .* E_obs(end,:); E_scen_1step e_sim repmat(next_mean, nScen, 1);如果要做未来24小时的场景路径把单步场景当作下一步的状态继续递推即可H 24; E_paths zeros(nScen, H, N); E_prev repmat(E_obs(end,:), nScen, 1); for h 1:H u_new copularnd(Gaussian, Rho, nScen); eps_new zeros(nScen, N); for i 1:N eps_new(:,i) icdf(pd{i}, u_new(:,i)); end E_prev repmat(beta0, nScen, 1) ... E_prev .* repmat(phi_hat, nScen, 1) ... eps_new; E_paths(:,h,:) E_prev; endE_paths就是 nScen × 24 × 5 的场景张量可以直接喂给随机优化或概率预测模块。注意时间维度上的时序记忆由AR(1)自身承担而同一时刻内跨场站的协同由Copula承担两层结构在生成时互相独立又互为补充这正是“时空一致”的工程实现方式。4. 评估与分析看模型有没有抓住相关结构4.1 别只盯单场覆盖率拿到场景后很多人第一件事就是算单场的90%区间覆盖率。我得先泼盆冷水时空模型的主要价值在联合分布单场覆盖率并不会因此大幅提升。单场边缘分布只要拟合得当独立模型也能达到相近的覆盖率。于是常看到有人因此下结论“时空模型没用”其实是他看错了指标。真正该看的是“并网点总误差”的分布。假设各场容量相同总误差取平均值即可% E_obs_test 与 E_scen_test 为测试期逐时刻的真实误差和场景数组 total_obs mean(E_obs_test, 2); total_scen mean(E_scen_test, 2); quant_low quantile(total_scen, 0.05); quant_high quantile(total_scen, 0.95); coverage mean(total_obs quant_low total_obs quant_high);在我的模拟数据上独立模型的总误差区间覆盖率常常只有55%到70%时空模型能恢复到88%到92%。原因就是第2.1节的方差叠加公式独立模型低估了联合不确定性区间过窄真实误差自然盖不住。记住评价群体预测误差模型时“总误差的区间置信度”比“单场区间置信度”更有说服力。4.2 场景相关性的还原度时空模型的另一个核心检验是看生成的场景是否保留了两两相关性。Rscen corrcoef(squeeze(E_paths(:,1,:))); % 首步场景相关矩阵 Rtrue corrcoef(E_obs(end-H:end,:)); % 真实误差相关矩阵对比Rscen与Rtrue若矩阵形式接近说明场站间的“协同跳动”被学到了若Rscen接近对角阵说明残差Copula没起作用。这个检查很朴素但能帮你定位问题出在时间侧还是空间侧相关性还原度差先查Copula相关矩阵和边缘变换是否正确相关性还原度正常但路径ACF不对再回头查AR(1)。4.3 概率预测打分CRPSCRPS是概率预测领域的标准评分同时惩罚预测分布“不够集中”和“位置偏移”。Matlab没有内置CRPS我一般用样本版经验公式写起来很紧凑function crps calc_crps_emp(ens, obs) % 样本版 CRPS % CRPS E|X-obs| - 0.5*E|X-X|X与X独立同分布 xx ens(:); crps mean(abs(xx - obs)) - 0.5 * mean(abs(xx - xx)); end注意这里用到了 n×n 的差矩阵n不超过5000时内存没问题再大就要分块计算。用总误差分别算独立模型和时空模型的CRPS时空模型通常更低模拟数据下比值在0.6到0.85之间属于正常。4.4 时间路径还原度最后检查场景的时间路径是否像真实误差一样“有记忆”。取其中一条场景路径画ACF并与历史误差ACF对比。如果路径的ACF在滞后1到6小时呈现明显非零区间说明AR(1)Copula的组合确实复现了时序性。这里有个生成时容易犯的错误有人为了“时空一致”在同一场景的连续时间段里复用同一批Copula样本结果把时间路径打成一条平滑假曲线。正确做法是每个小时独立重新从Copula抽取残差时序依赖完全交给AR递推空间协同靠的是同一小时内跨场样本之间的相关性两边不冲突。5. 踩坑记录与Matlab实操笔记5.1 copulafit报非正定矩阵场景多、历史窗口短的时候相关矩阵估计很容易不满足半正定。Matlab的copulafit内部有特征值修正但极端情况仍可能报错。我的处理顺序是先缩短历史窗口排查共线性再尝试给相关矩阵对角线加0.001最后才用nearestSPD这类修正工具。若两个场站间距只有1到2公里共线性太高直接考虑合并成一个等效风电场更省事。5.2 核密度边缘的边界外推核密度对尾部数据外推的能力有限。采样阶段u_sim里可能出现接近0.9999的值icdf映射出的误差场景会异常极端甚至超出功率物理范围。我的习惯是生成功率场景后做硬截断把功率限制在额定容量上下限之间。截断会牺牲一点尾部信息但物理上不可行的场景对调度没有任何价值反而会把优化模型推向不切实际的极端解。5.3 AR(1)系数接近1的外推发散部分风电场误差的记忆性很强φ估计出来接近0.9多步递推时条件均值会缓慢漂移碰到数据段内存在非平稳现象漂移会更严重。这里必须先用adftest判断平稳性。不平稳就改成差分模型或者对误差序列分段建模。我的经验是检修、限电、极端天气时段会让误差序列的平稳性明显变差滚动建模时输入窗口最好限制在最近30到60天。5.4 场景数量与场景缩减nScen取多少合适单纯做概率评估500到2000个场景足够。但场景一旦喂给两阶段随机优化求解规模会随场景数线性增长几千个场景基本跑不动。建议先用2000个场景验证模型质量再通过快速前向选择或k-means聚类缩减到20到50个典型场景。缩减后的场景务必重新计算权重不能简单等权平均这点很多人容易漏。Matlab里没有统一的场景缩减函数我一般用标准聚类加权重修复的方式核心代码几十行不影响主体模型结构。我个人在实际操作中的体会是这套“AR(1)高斯Copula”流程的难点不在实现而在评估视角。我第一个相关项目里因为只报单场覆盖率差点把方案内部否掉后来改成看总误差场景和相关性还原度模型价值一下就清晰了。如果你也在做风电场群的预测误差项目我的建议是先跑通模拟数据画出ACF和相关矩阵热力图确认自己对建模对象有感觉再切换到真实场站数据。把第3.1节的模拟代码替换成读数逻辑后面的一整套流程都能直接复用。后续想继续深化可以研究t Copula增强尾部协同或者用变差函数刻画非对称空间相关但先把这套基础框架吃透出了问题能自己定位后面进阶才走得稳。
阅读完成 · 觉得有帮助?