☰
考虑时空相关性的风电功率预测误差建模与Matlab实现
2026/10/16 14:35:17 网站建设 项目流程

上个项目做某风电场群的功率预测后评估,调度那边反馈了一句让我印象很深的话:单台机组的预测误差看着不大,打起捆来却总是“同涨同跌”,一到系统备用容量校核就露馅。把误差矩阵拉出来一剖,果然不是白噪声——同时刻不同场之间、同一场不同时段之间,误差都在抱团。

这篇文章想聊的就是这件事:考虑时空相关性的风电功率预测误差建模与分析,并且把Matlab实现的关键路径完整走一遍。包括误差数据怎么定义清洗、单节点误差分布怎么拟合、时间和空间的相关结构怎么量化、如何用“向量自回归 + t-Copula”生成符合真实误差特征的场景,以及最后怎么用覆盖率、PIT、相关矩阵距离这些指标去验证模型。适合正在做风电功率预测、概率预测、备用容量和储能调度相关工作的同学,尤其是想从“只给一个RMSE”升级到“能输出联合误差场景”的团队。

1. 为什么预测误差的“独立性假设”会出大事

1.1 误差里的“同源”和“同向”

风电功率预测误差不是凭空产生的,它主要来自几个源头:数值天气预报对风速、风向的预报偏差,功率曲线映射带来的非线性放大,机组状态变化(如限电、停机、尾流效应)导致的实测偏离,以及测量装置本身的误差。

其中最关键的一点是,数值天气预报的偏差往往具备强烈的空间一致性。一个天气系统扫过某区域时,如果冷锋到达时间被预报晚了两小时,那么区域内所有风电场都会同时出现“预测风速偏高”的情况,误差自然一起偏大。同样,在时间维度上,NWP每天更新的周期决定了某个系统性偏差一旦出现,会持续好几个小时甚至跨天。这就是“同源”和“同向”的含义:误差在时间和空间上都不是独立的。

1.2 独立性假设会低估多少风险

调度做备用容量校核时,最常用的做法是把每个场站的预测误差当成独立随机变量叠加。假设某区域有5个风电场,每个装机容量50MW,预测误差标准差都为3MW。

  • 若误差完全独立,联合误差标准差为 √(5×3²) ≈ 6.7MW;
  • 若误差完全正相关,联合误差标准差为 5×3 = 15MW。

两者差了不止一倍。实际风电场群多数处于“部分相关”状态,比如相邻10km的场间相关系数能到0.7以上,50km外可能降到0.3。如果直接用独立假设去算备用,相当于默认了“所有场不会同时错”,这在强天气过程过境时会被现实狠狠打脸。

1.3 哪些业务场景最受时空相关性影响

我梳理下来,至少四类业务会被相关性结构直接影响:

  • 系统备用容量:需要知道区域联合误差的尾部,独立假设会让备用少配;
  • 储能充放电计划:连续多个时段的误差正相关,意味着储能需要应对的是“持续性亏损”而不是随机波动;
  • 爬坡事件检测:相邻时段误差的强自相关会掩盖真实爬坡,或把预测偏差误判成爬坡;
  • 多节点概率预测:单独看每个节点都有区间覆盖,但多个节点同时越界的联合概率,只有联合分布才能算出来。

正因为这些影响,才值得专门做“考虑时空相关性的误差建模”,而不是简单地把误差序列当白噪声丢掉。

2. 数据组织与预处理,误差建模的第一个坑

2.1 误差怎么定义才算对

很多项目在误差定义上就翻车了。我建议严格写成:

e(t) = P_meas(t) - P_fc(t | t-L)

其中L是固定的预测提前期,P_fc(t|t-L)表示在t-L时刻发布的、针对t时刻的预测值。建模时不要把不同提前期的误差混在一个样本集里,因为提前1小时的误差分布和提前6小时完全不是一回事。如果你的系统发布的是滚动预测,尤其要注意对齐起报时刻,我见过不少把相邻时段错位对齐后自相关暴涨的案例。

这里还有个细节:用绝对误差还是归一化误差。我的习惯是优先用“以装机容量归一化的绝对误差”,也就是 e = (P_meas - P_fc) / P_cap。用实测功率做分母的相对误差在低风速段会被放得很大,PI控制器、低风速切机这些工况会让分母趋近于零,数值很不稳定。

2.2 清洗规则不能太随意

风电数据里混杂着大量“非预测误差”样本:计划检修导致的停机、电网限电、通讯丢点数秒到数分钟、机组在切入风速附近反复启停。这些数据如果不处理,会让误差分布的尾部变得非常难拟合。

我的清洗规则大致是:

  • 剔除实测功率为零但预测功率明显大于零的时段,优先标记为限电或停机;
  • 剔除实测功率超过装机容量105%的异常点;
  • 对单节点缺失超过连续三个时刻的时段做整段标记,不参与建模;
  • 风速接近切出风速且功率骤降的点,单独核查是不是保护动作,必要时剔除;
  • 不要用全局3σ剔异常,风电误差本身就是重尾的,盲目剔会把极值风险抹掉。

清洗后还要做一个关键动作:打标签。把限电、正常、通讯恢复等事件保存下来,后续做误差分段分析时有大用。

2.3 构造成矩阵格式

建模前,数据必须组织成统一的矩阵形式。假设有N个风电场节点、T个时刻,整理成:

  • 误差矩阵 E:T × N,E(t,i) 是第i个节点在t时刻的误差;
  • 坐标矩阵 coord:N × 2,每一行是第i个节点的大地坐标;
  • 预测功率矩阵 P_fc、实测功率矩阵 P_meas:T × N。

Matlab里最简单的做法是读取长表后用unstack直接透视。

T_raw = readtable('wind_data.csv'); % 列: time_id, node_id, p_fc, p_meas P_fc = unstack(T_raw, 'p_fc', 'node_id'); P_meas= unstack(T_raw, 'p_meas','node_id'); E = P_meas{:, :} - P_fc{:, :}; % 注意: unstack 会自动按时间对齐, 但务必确认索引一致性

矩阵格式定了,后面做相关性分析、VAR拟合、Copula采样都顺手。

3. 单节点误差画像,先把“人”看准再谈“关系”

3.1 误差分布根本不是正态分布

好多同学上来就用fitdist(e, 'Normal'),画完Q-Q图才发现两头翘得厉害。风电功率预测误差的典型特征是:尖峰、厚尾、略带偏态。尖峰说明大量样本集中在零附近(预测准的时候多),厚尾说明少数时刻误差大得离谱(天气突变或预测彻底跑偏),偏态则和功率曲线非线性有关——在额定功率附近,预测偏高往往对应实际功率上不去。

而且误差形态随风速区间变化很大。低风速段,功率曲线斜率陡,风速误差会被放大成功率的大幅波动;中高风速段,误差分布相对对称;受限电影响时,还会出现明显的负偏。所以第一步先按实测功率分箱,比如0~0.2P_cap、0.2~0.4P_cap这样的五六个区间,每个区间单独看直方图。

3.2 用KDE和GMM拟合边缘分布

单变量边缘分布是整个联合误差建模的地基,我通常在两套方案里选:

  • 核密度估计KDE:[f, xi] = ksdensity(e(:,i)),非参数,适应任意形态,适合样本量充足、图形形状奇怪的节点;
  • 高斯混合模型GMM:fitgmdist(e(:,i), k, 'Regularize', 1e-6),参数化,采样方便,适合后续要反复生成场景的场景。

为什么拟合边缘分布这么重要?因为后面用Copula建模空间结构时,第一步要求把每个变量的原始值转换成均匀分数:

u_i = F_i(e_i)

这个F_i必须是恰好等于真实分布的CDF。如果F_i建错了,后面拟合出来的Copula结构全是变形的。检验方法很直观:把训练数据经F_i转换后的u_i画直方图,应该大致服从[0,1]均匀分布。

3.3 分风速段建模的取舍

分风速段建模能显著改善边缘分布的拟合精度,但也有代价。分段之后每个子样本量变小,如果再同时做多维Copula,自由度会严重不足。我的经验是:

  • 如果数据量在一年以上的15分钟级采样,可以按实测功率分三段建边缘分布,Copula只对“中高功率段”单独拟合;
  • 如果数据量不够,不要在“边缘分布分段”和“Copula维度”上同时激进,宁可先不分段,把尾部风险交给t-Copula去表达。

一句话:先保证联合结构稳健,再追求局部分布细腻。

4. 空间相关性怎么量化并拟合

4.1 皮尔逊相关系数会骗人

做空间相关分析时,先把误差矩阵的相关矩阵打出来。我一般同时计算三组系数:

指标Matlab函数关注点
Pearsoncorr(E, 'Type','Pearson')线性相关,受离群值影响大
Spearmancorr(E, 'Type','Spearman')单调相关,稳健
Kendallcorr(E, 'Type','Kendall')秩相关,Copula拟合的基础

如果Pearson矩阵和Kendall矩阵数值差得远,说明变量之间存在明显的非线性依赖,这时候用线性高斯模型硬套会失真。风电误差正是这种场景,所以我推荐把Kendall相关矩阵作为建模的主要参考。

4.2 尾部依赖:一起“爆”比一起“平均”更危险

普通相关矩阵衡量的是“整体趋势一致”,但调度最担心的其实是“极端情况一起出现”。比如两个风电场平时误差相关性0.4,一旦寒潮大风天气来临,两者误差同时偏高一倍。这种极端情况下的依赖叫尾部依赖。

高斯Copula有个硬伤:尾部依赖系数恒等于零,意味着它认为极端事件不会同时发生,这在风电里显然不符合实际。t-Copula则能通过自由度参数ν控制尾部厚度,ν越小尾部依赖越强,对风电这种“天气异常时全网一起错”的场景更友好。

4.3 用t-Copula拟合空间结构

拟合t-Copula不复杂,核心代码就几行。前提是把每个节点的误差先转成均匀分数U。这里的U不是直接对原始误差做的,而是对VAR残差做完边缘分布变换后得到的,我在第6节一起写。如果只看空间静态结构,也可以直接对标准化误差做。

% U: (T-1) x N 的均匀分数矩阵 [rho, nu] = copulafit('t', U, 'ApproximateML', false); % rho: N x N 的相关矩阵; nu: 自由度标量 tail = copulastat('t', rho, nu); % 尾部依赖系数

拟合完可以做一个快速指标:tail矩阵对角线上越大的值,说明单节点极端误差越容易出现;非对角元素越大,说明两个节点“一起极端”的风险越高。把这个矩阵和业务上的风险分区对照,能很快找出哪些站点组合是“抱团重灾区”。

5. 时间相关性怎么描述并进入模型

5.1 ACF和PACF告诉你“误差有记忆”

空间关系交代完了,时间维度同样不能放过。对一个节点的误差序列做自相关函数ACF,典型现象是:

  • 滞后1个15分钟点,ACF往往还在0.7~0.9;
  • 滞后4小时(16个点),会降到0.3~0.5;
  • 滞后24小时,可能还有0.1左右的小尾巴,这与NWP日更新频率有关。

这说明误差序列有明显的短期记忆。如果直接对原始误差做独立采样,生成出的场景序列会“抖得过分”,储能调度按这个场景做充放电计划,会被不平滑的序列误导。

5.2 用VAR(1)把时空同步性一起吸收

既然时间和空间都有依赖,最简单的联合建模方式是一阶向量自回归模型:

y_t = c + A × y_{t-1} + ε_t

这里的y_t是N个节点标准化后的误差向量,A是N×N系数矩阵。A的非对角元素能表达一个很有意思的现象:上游节点的误差会影响下游节点的误差(风从上游吹到下游,误差信号也会跟着移动)。所以VAR(1)不只是处理时间自相关,它同时吸收了“空间传导”的线性部分。

估计方法很简单,直接用最小二乘。注意样本量要远大于N,否则设计矩阵会出现病态。

Y = (E - mean(E)) ./ std(E); % 标准化 Xlag = [ones(T-1,1), Y(1:T-1,:)]; % 截距项 + 滞后项 R = Y(2:T,:); B = (Xlag'*Xlag) \ (Xlag'*R); c = B(1,:)'; A = B(2:end,:)'; Res = R - Xlag*B; % 残差 (T-1) x N

5.3 对残差再检查

VAR(1)把线性的时空结构吸收掉之后,残差Res应该是时间上接近白噪声的序列。用autocorr(Res(:,i))检查,滞后1点之外的自相关应当基本落在置信区间内。但残差在空间上仍然可以有相关性,这正是交给Copula处理的部分。如果残差的ACF还是不干净,说明VAR(1)阶数不够或存在非线性时序效应,可以考虑升到VAR(2),不过风电场景里一阶一般够用。

6. Copula + VAR 的完整Matlab流程

6.1 建模和采样的整体结构

这段是全文的核心实操。整体分两大段:

建模阶段(离线,只做一次):

  1. 计算误差矩阵E,标准化得到Y;
  2. 拟合VAR(1),得到截距c、系数矩阵A、残差Res;
  3. 对残差逐节点做KDE,得到边缘分布F_i;
  4. 把残差经F_i变换成均匀分数U;
  5. 用t-Copula拟合U,得到rho和nu。

采样阶段(在线,反复执行):

  1. 从t-Copula中抽取S×N的均匀随机数U_sim;
  2. 用F_i的反函数把U_sim还原成残差场景ε_sim;
  3. 基于最近一个历史状态,按y_t = c + A×y_{t-1} + ε_t滚动生成误差场景;
  4. 反标准化、加回预测功率,得到最终的功率场景。

这个结构的好处是:线性时空依赖交给VAR,非线性尾部依赖交给Copula,两者各管一段,比单一模型稳健得多。

6.2 核心代码实现

下面这份代码是我在项目里精简后的版本,直接用矩阵运算,不依赖工具箱里偏慢的高级接口。

%% 建模阶段 % E: T x N, 误差矩阵; 1~N风场节点 E = P_meas - P_fc; Y = (E - mean(E)) ./ std(E); Xlag = [ones(T-1,1), Y(1:T-1,:)]; R = Y(2:T,:); B = (Xlag'*Xlag) \ (Xlag'*R); c = B(1,:)'; A = B(2:end,:)'; Res = R - Xlag*B; % 对残差逐节点做KDE,并转换成均匀分数 U = zeros(size(Res)); xi_cell = cell(N,1); f_cell = cell(N,1); cdf_cell = cell(N,1); for i = 1:N [f, xi] = ksdensity(Res(:,i)); xi_cell{i} = xi; f_cell{i} = f; cdf_temp = cumtrapz(xi, f); cdf_temp = cdf_temp / cdf_temp(end); cdf_cell{i} = cdf_temp; U(:,i) = interp1(xi, cdf_temp, Res(:,i), 'linear', 0); U(:,i) = min(max(U(:,i), 1e-6), 1-1e-6); % 防止边界溢出 end [rho, nu] = copulafit('t', U, 'ApproximateML', false); %% 采样阶段 function E_sim = gen_error_scenarios(Y_last, rho, nu, S, B, c, A, xi_cell, f_cell, cdf_cell) N = length(Y_last); E_sim = zeros(S, N, B); for b = 1:B U_s = copularnd('t', rho, nu, S); % S x N res_sim = zeros(S, N); for i = 1:N res_sim(:,i) = icdf_kde(U_s(:,i), xi_cell{i}, f_cell{i}, cdf_cell{i}); end y_sim = zeros(S, N); y_sim(1,:) = Y_last; for s = 2:S y_sim(s,:) = c' + y_sim(s-1,:) * A' + res_sim(s,:); end E_sim(:,:,b) = y_sim; % 别忘了反标准化 end end function e = icdf_kde(u, xi, f, cdf_vals) if u <= cdf_vals(1) e = xi(1); elseif u >= cdf_vals(end) e = xi(end); else e = interp1(cdf_vals, xi, u, 'linear'); end end

这里有个很容易踩的坑:copularnd得到的是[0,1]内的均匀样本,但KDE在样本边界之外的CDF是平的,反变换时会把尾部分位点直接截到样本最值,导致生成的极端误差被压低。如果业务很在意极端场景,建议边缘分布改用GMM或带平滑尾的分布,不要裸用KDE。

6.3 另一条路线:时空高斯过程

如果你节点数不多(比如少于15个),还可以直接用高斯过程做时空联合建模。把时间坐标、空间坐标拼成特征,误差作为观测值,用fitrgp配一个ARD平方指数核,核函数里同时包含时空距离:

k((x,t),(x',t')) = σ² × exp(-((x-x')²/2l_x² + (t-t')²/2l_t²))

这种方法的优势是能表达连续时空相关,不需要显式区分VAR和Copula,但缺点是样本量大时训练复杂度偏高,而且重尾特性表达不如t-Copula直接。我的建议是:节点多、重视尾部风险用VAR+Copula;节点少、重视平滑连续相关用GP。

7. 场景生成后的验证,别自嗨

7.1 验证清单

模型好不好的标准不是“拟合得好”,而是“生成场景像不像真实误差”。我每次做完模型都会跑四件事:

  1. PIT一致性:把验证集的误差经模型边缘分布变换后,画直方图应当近似均匀;
  2. 空间相关性复现:模型生成场景的相关矩阵和验证集真实相关矩阵做比较,计算Frobenius范数距离,越小越好;
  3. 时间相关性复现:用生成的场景序列算ACF,和真实误差的ACF曲线叠图比较;
  4. 业务指标:用场景算联合区间覆盖率、CRPS,和独立采样做对照。

7.2 一个典型算例

我在某风电场群(7个节点,15分钟级数据,提前4小时预测)上做过对比,结果大致如下:

方案区间覆盖率CRPS相关矩阵距离
独立采样84.3%0.1550.252
边缘分布+空间t-Copula88.7%0.1410.113
VAR(1)+空间t-Copula91.2%0.1320.074

加了空间Copula之后,区间覆盖率明显改善,相关矩阵距离大幅下降;再加VAR后,时间自相关被吸收,CRPS进一步下降。这个数字和很多文献里的结论是吻合的:空间结构贡献最大,时序结构锦上添花。

7.3 验证时容易糊弄过去的三个坑

  • 覆盖率计算时要明确是“单节点单时刻”还是“联合所有节点”,两者对场景质量的要求完全不同;
  • 样本内验证永远漂亮,必须留出连续时段做滚动验证,防止信息泄漏;
  • 只看RMSE会以为模型很好,但误差的时空相关性完全可能被RMSE掩盖,一定要加相关矩阵和ACF的对比。

8. Matlab工程落地的几点忠告

8.1 样本量不够怎么硬做

t-Copula的参数数量是N×(N+1)/2,N大了以后拟合容易过拟合。如果节点数超过20个但历史数据不足两年,我建议先做PCA降维,把误差矩阵的主成分维度压到5~8个,再在低维空间里拟合Copula,生成场景后再乘回特征向量。这个损失的信息量很小,但能大幅提升数值稳定性。

如果你用copulafit('t', U)总报收敛问题,先把'ApproximateML'设为true,它会用近似极大似然,速度更快、也更容易收敛,代价是参数估计精度略低。

8.2 分段模型的拼接问题

如果你坚持按风速分段建模,分段边界处会出现场景跳变。我的处理办法是:相邻区间用10%~20%的重叠带,边界附近按隶属度线性混合两个区间的分布,而不是硬切。不过还是那句话,数据少的时候不要同时分段又高维拟合,八成会崩。

8.3 和预测系统对接的接口设计

生产环境里,误差建模模块需要作为独立服务被调用。我习惯封装成这样一个接口:

function [P_sim, E_sim] = gen_error_scenarios(P_fc_target, Y_last, model) % model 结构体包含: c, A, rho, nu, xi_cell, f_cell, cdf_cell, mu_e, sd_e S = size(P_fc_target, 1); B = model.B; E_sim = zeros(S, size(P_fc_target, 2), B); for b = 1:B U_s = copularnd('t', model.rho, model.nu, S); % ... 生成E_sim end E_sim_raw = E_sim .* model.sd_e + model.mu_e; P_sim = repmat(P_fc_target, 1, 1, B) + E_sim_raw; end

模型参数用save/load持久化,随机数种子在每次调度周期开始时固定,保证场景可复现。这个细节在验收时很重要——如果每次跑出来的场景不一样,调度那边根本没办法做重演比较。

8.4 工具箱依赖清单

这套流程依赖的工具箱其实很常规:Statistics and Machine Learning Toolbox必备,ksdensity、fitgmdist、copulafit、copularnd、corr都在里面;计量经济学的mvregress可以用自编最小二乘替代,Econometrics Toolbox不是必须的;并行工具箱是可选项,B很大时可以parfor提速。所以即使团队只有基础安装,这套方法也能落地。

最后说说我做完整套流程最大的体会:相关性建模的优先顺序永远是先画图、再算数、最后才上模型。先看误差矩阵的散点图矩阵,再把Pearson/Kendall矩阵热力图打出来,ACF叠图放旁边,结构自然就清楚了。很多团队一上来就炫技用图神经网络,数据量不够、解释性又差,反而不如“VAR + t-Copula”这套经典组合可靠。模型复杂度能解决业务问题就行,余下的精力多放在数据质量和验证环节上,性价比高得多。

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

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

立即咨询