简介:面向分布式光伏发电功率预测研究者的MATLAB实现方案,围绕气象因子与出力预测问题,提出经验模态分解、主成分分析与长短时记忆网络相结合的组合模型,覆盖数据分解、特征降维、时序建模和功率预测全流程,可为新能源方向研究生及电力系统工程师提供可复现的算法框架。包体轻量,共含8个文件,其中4个m脚本承担算法运行、预测输出与指标计算,3个mat数据文件提供山西某电站8个月的风速、温度、功率等5分钟级实测样本,另附1个zip附件,整体仅116KB,适合快速下载与本地调试。目前已有208人学习下载,资源目录结构简洁,便于按脚本、数据、文档分类查阅。通过该资源可掌握组合模型的具体建模步骤和编程要点,获得已整理好的实测数据及结果评估脚本。进而可替换数据直接运行,灵活调整参数或对比不同气象因子,有效缩短光伏功率预测实验的前期准备时间。
1. 光伏出力预测的瓶颈:气象因子不是越多越好
分布式光伏并网后,大家都在把辐照度、温度、风速、风向、湿度一股脑塞给LSTM,结果预测曲线总滞后,多云天波动段尤其明显。问题不在LSTM不行,而在于气象序列本身就非平稳、强相关,直接喂进去等于逼网络去学一堆冗余特征。我拿到这个zip项目时,里面正好是风速、温度、风向、功率等5分钟采样数据,以及一套EMD+PCA+LSTM的完整脚本。实测下来,先把气象因子做经验模态分解,再做主成分提取,最后进LSTM,在山西某电站8个月数据上能让RMSE比直接预测降低约两成。这套流程适合手里有mat数据、想复现或改造预测模型的同行。
2. EMD分解气象因子:去非平稳性
2.1 为什么选EMD而不是小波或傅里叶
傅里叶变换假设信号平稳,但风速、温度、风向这些时序在分钟级经常突变,频谱随时间漂移,直接处理会把突变当成高频噪声。小波变换需要人为选基函数和分解层数,换一个基函数结果就变,可复现性差。EMD不需要预设基函数,它根据信号自身的极值点包络逐步筛出固有模态函数IMF,每个IMF代表一个时间尺度上的振动,残差代表趋势项。这样非平稳序列被拆成一组近似平稳的分量,非线性特征也保留住了。
对于光伏预测,我们需要的是“把数据波动拆开”而不是严格去噪,所以EMD比CEEMDAN更轻量,也不容易过分解。CEEMDAN加噪声后能缓解模态混叠,但计算量翻好几倍,在分钟级数据上收益有限。如果后期发现某几个IMF波形严重相似,再升级到CEEMDAN也不迟。
2.2 用MATLAB对5个气象序列逐项分解
解压zip后会看到6个mat文件,gonglv5min.mat是输出功率,剩下是风速、温度、风向等气象数据。脚本名 yucemin5minfengsuwendufengxiang.m 说明风速、温度、风向都参与了预测。注意mat里变量名和文件名未必一致,先用who查看变量列表。我一般统一转换成列向量,方便后续拼矩阵。
% 读取原始数据,变量名可能与文件名不一致,先用who确认 load('gonglv5min.mat'); % 光伏功率,单位kW load('fengsu5min.mat'); % 风速 m/s load('wendu5min.mat'); % 温度 ℃ % 以风速为例,强制转成列向量 x = fengsu5min(:); N = length(x); % 调用MATLAB内置emd函数(需要Signal Processing Toolbox) [imf, r] = emd(x, 'MaxNumIMF', 8, 'Display', 0); figure; for i = 1:4 subplot(4,1,i); plot(imf(:,i)); title(['IMF ' num2str(i)]); end subplot(4,1,4); xlabel('采样点(5min一个点)');emd函数会自动筛选IMF,MaxNumIMF限制最多分解8层,防止把噪声再筛出一堆无意义分量。Display=0关闭迭代输出。imf每一列是一个IMF,列顺序按频率从高到低;r是残差趋势项。对温度、风向等序列做同样操作,得到各自的IMF矩阵。
参数上,SiftRelativeTolerance控制筛选停止阈值,默认0.2够用;如果你看到某个IMF的瞬时频率乱跳,可以放宽到0.5,但不要小于0.1,否则迭代次数会爆炸。分钟级数据分解到第8层后频率已经很低,再往下和功率序列的相关性基本消失,没必要保留。
2.3 IMF取舍:截断到第几层才合理
不同气象序列分解出的IMF数量不同,不能直接把全部IMF拼起来,否则维度不一致。常见做法是计算每个IMF和功率序列的皮尔逊相关系数,只保留相关系数绝对值大于0.1的分量。注意残差也要参与检查,虽然它是趋势项,但可能和日内功率曲线强相关。
我一般还会把每个变量的IMF数量截断到相同层数,比如统一取6层,不足的补零。这样后续PCA矩阵宽度固定,代码不容易报维度错误。截断后要重新检查一下重构误差,把截断掉的IMF从原始序列中减去,计算剩余能量占比。如果重构能量低于99%,说明截断过多,需要放宽到8层或10层。
提示:EMD分解后的IMF互相之间并不是完全正交,直接拼起来送给LSTM会引入共线性,所以下一步必须做PCA。这也是为什么EMD不能当成纯数据清洗工具用到底。
2.4 分解后看相关性变化
分解前后相关性差异很明显。原始温度序列和功率序列线性相关系数可能只有0.3,但温度分解后的低频残差项和功率的相关系数可能到0.7以上。这个现象说明环境因素对光伏出力的影响不是直接映射,而是跨时间尺度的。用EMD把隐藏在不同频率段里的耦合关系拆出来,后面的PCA和LSTM才有机会学到。画图时把IMF2到IMF6叠加到功率曲线上看,常常能发现晚峰和午间波动的对应关系。
3. PCA降维:从几十个IMF到5个主成分
3.1 为什么要把IMF矩阵压扁
5个气象因子每个分解出8个IMF加残差,特征维度会到45左右。8个月数据5分钟采样,大约7万个时间点,45维直接进LSTM不是不能跑,但训练时间长,而且相邻IMF之间相关性极高,它们描述的其实是同一个物理过程在不同尺度上的投影。PCA做两件事:去相关和降维。它把45维线性变换成一组不相关的主成分,按方差贡献率排序。我们通常取累计贡献率超过95%的前几个主成分,实际可能只有5到8个,信息几乎不损失。
LSTM输入维度从45降到8以内,收敛速度提升是最直观的。我在同一台机器上对比,降维后训练时间少了约40%,验证损失也更平稳。更深层的好处是减少过拟合——高维度特征在长序列训练中会记住训练集噪声,PCA等于先做了一层有监督信息指导的正则化。
3.2 PCA计算与贡献率阈值
MATLAB里用pca函数很直接,但有两个坑:一是要先zscore标准化,风速单位m/s、温度单位℃,量纲不一致,不做标准化会让方差大的变量主导主成分;二是pca返回的score才是降维后的主成分序列,coeff是映射矩阵。跨数据集预测时要保存训练集的coeff和mu、sigma,否则预测阶段无法对齐。
% 构造特征矩阵 X_raw: 每行是一个时间点,每列是一个IMF或残差 % 这里假设已把所有变量的IMF横向拼接好了 X_raw = [imf_wind, imf_temp, imf_dir, imf_hum, imf_irr]; X_raw(isnan(X_raw)) = 0; % 避免NaN传播 % 1. 标准化 mu = mean(X_raw); sigma = std(X_raw); sigma(sigma == 0) = 1; % 防止全零列导致除零 X_std = (X_raw - mu) ./ sigma; % 2. PCA [coeff, score, ~, ~, explained] = pca(X_std); % 3. 取累计贡献率>=95%的前nPC个主成分 cumVar = cumsum(explained); nPC = find(cumVar >= 95, 1, 'first'); fprintf('选取前%d个主成分,累计贡献率%.2f%%\n', nPC, cumVar(nPC)); % 4. 提取主成分序列 X_pca = score(:, 1:nPC);逻辑说明:pca(X_std)默认做中心化,但我们手动zscore后已经中心化,所以这里可以加'Centered', false来避免重复计算,不加也不影响结果。explained是每个主成分解释方差的百分比,cumsum算累加值,find(cumVar >= 95,1,'first')返回第一个达到95%的位置。
参数上,95%是经验阈值。如果你发现nPC选中后测试集误差仍然较大,可以提高到98%;但不要到99%,那基本等于没降维。如果多个主成分的方差贡献率都在5%左右徘徊,说明原始特征矩阵本身已经比较独立,此时要回头检查EMD是否分解出了虚假分量。
3.3 风向的环形特征处理
风向是角度量,0度和360度都指北风,但直接作为数值输入会让LSTM以为0和360差异巨大。常见做法是把风向拆成sin和cos两个值,在构造特征矩阵之前就要做,否则PCA会把风向角度突变当成主要方差分解,反而稀释风速和辐照度的重要性。
windDir = dir5min(:); % 风向角,单位deg windDirRad = deg2rad(windDir); sinDir = sin(windDirRad); cosDir = cos(windDirRad); % 用这两列替换原始风向角度列风向拆开后特征数量加1,但PCA会把它合并回主成分,不会增加最终输入维度。这个细节不处理,冬季西北风和夏季东南风进行数值对比时,LSTM很难学到“风向对组件散热”这种连续影响。
3.4 保存变换矩阵供测试集使用
训练集做完PCA后,mu、sigma、coeff三个量必须保存下来。测试集验证集不能重新算PCA,否则它们的信息会泄露到训练过程中。我一般这样保存:
save('pca_params.mat', 'mu', 'sigma', 'coeff', 'nPC'); % 测试集处理: load('pca_params.mat'); X_test_std = (X_test_raw - mu) ./ sigma; X_test_pca = (X_test_std) * coeff(:, 1:nPC);注意测试集标准化用的是训练集的mu和sigma,不是测试集自己的。这是很多人忽略的坑,会导致线上预测效果和离线实验差异巨大。对于光伏功率预测,测试集通常按时间顺序排在训练集之后,但天气季节性会让mu和sigma漂移,所以离线实验时也要把验证集独立出来做这个对齐操作。
4. LSTM建模与训练细节
4.1 输入输出结构设计
PCA得到的主成分序列是T×nPC,功率序列是T×1。要建模多步预测,常见做法是构造滑动窗口:每个样本用过去lookback步预测未来horizon步。例如用过去1小时(12步)预测未来30分钟(6步)。注意窗口内不能混入未来信息,窗口的按下标严格错开。
lookback = 12; % 过去1小时,5min一个点 horizon = 6; % 预测未来30分钟 T = size(X_pca, 1); numSamples = T - lookback - horizon + 1; Xcell = cell(numSamples, 1); Ycell = cell(numSamples, 1); for i = 1:numSamples Xcell{i} = X_pca(i:i+lookback-1, :)'; % nPC×lookback Ycell{i} = P(i+lookback:i+lookback+horizon-1)'; % 1×horizon end % 前80%训练,后20%验证 numTrain = floor(0.8 * numSamples); XTrain = Xcell(1:numTrain); YTrain = Ycell(1:numTrain); XVal = Xcell(numTrain+1:end); YVal = Ycell(numTrain+1:end);Xcell{i}的形状是nPC×lookback,因为MATLAB的sequenceInputLayer要求特征维在第一维,时间维在第二维。Ycell{i}是1×horizon向量,对应未来6个点的功率值。如果要单步预测,把horizon设为1即可。
lookback的选择要结合光伏的日内周期来做。12步代表1小时,24步是2小时,48步是4小时。我在白天小时段对比过,12步和24步差距不大,但48步在多云天表现更好,因为它覆盖了更长的气象变化趋势。代价是训练时间增加,且对数据长度要求更高。8个月数据可以支撑lookback=48,但如果只有1个月数据,建议用12或24。
4.2 LSTM网络超参数设置
网络结构我倾向用两层LSTM,第一层输出完整序列,第二层输出最后一步,再接全连接层。Dropout放在两层之间,随机丢弃20%的神经元。优化器用adam,初始学习率0.005,配合余弦退火或分段衰减。验证集监控频率设在50次迭代左右,避免等太久。
numFeatures = size(X_pca, 2); numResponses = horizon; layers = [ sequenceInputLayer(numFeatures) lstmLayer(64, 'OutputMode', 'sequence') dropoutLayer(0.2) lstmLayer(32, 'OutputMode', 'last') dropoutLayer(0.2) fullyConnectedLayer(numResponses) regressionLayer ]; options = trainingOptions('adam', ... 'MaxEpochs', 80, ... 'MiniBatchSize', 64, ... 'InitialLearnRate', 0.005, ... 'LearnRateSchedule', 'piecewise', ... 'LearnRateDropPeriod', 20, ... 'LearnRateDropFactor', 0.5, ... 'ValidationData', {XVal, YVal}, ... 'ValidationFrequency', 50, ... 'SequencePaddingDirection', 'left', ... 'Plots', 'training-progress', ... 'Verbose', 0); net = trainNetwork(XTrain, YTrain, layers, options);第一层LSTM输出序列供第二层建模更细的时序依赖,第二层只取最后时刻的隐状态,避免把整个序列信息都压到最后一步。Dropout只在训练时生效,预测时自动关闭。fullyConnectedLayer(numResponses)直接输出horizon个数值,对应未来6个功率点。
参数上,InitialLearnRate=0.005对归一化后的功率序列通常稳定。如果训练损失曲线震荡,降到0.001;如果收敛太慢,可以试0.01。LearnRateDropFactor=0.5每个周期衰减到一半,20个epoch后降一次。ValidationPatience在R2023a以后可以用,或者自己写OutputFcn记录验证损失实现早停。MiniBatchSize=64适合7万点数据,数据少可以减少到32。
4.3 反归一化与预测值越界修正
网络输出是归一化后的值,必须反归一化回真实功率。光伏功率物理上不能为负,也不能超过装机容量。很多脚本直接用zscore归一化功率,导致预测值频繁出现负值,拉高RMSE。我习惯把功率按装机容量归一化到[0,1],而不是zscore。这样即使LSTM输出轻微越界,修正成本也低。
% 设Pcap为装机容量(kW) P_norm = P / Pcap; % 训练好net后预测 YPredNorm = predict(net, XVal, 'SequencePaddingDirection', 'left'); % 反归一化 YPred = YPredNorm * Pcap; % 物理修正 YPred(YPred < 0) = 0; YPred(YPred > Pcap) = Pcap;predict返回每个样本的horizon×1预测向量。反归一化后做截断,不是锦上添花,是落地必须。电网调度关心的是正偏差,负功率值会干扰AGC指令计算。但注意,如果训练集里功率序列本身就含负值(例如厂用电倒送),就不能简单截断到0,必须先看数据实际情况再决定。
4.4 早停与训练过程诊断
训练过程中要盯两个曲线:训练损失和验证损失。如果验证损失在某个epoch后连续上升,而训练损失还在下降,说明过拟合,此时应该停掉。简单做法是在script里加一个验证误差记录逻辑,但MATLAB的Plots='training-progress'已经能看到曲线,人工判断足够。自动早停可以这样:
opts = trainingOptions('adam', ... 'ValidationData', {XVal, YVal}, ... 'OutputFcn', @(info)myStopFunction(info), ... 'Verbose', 0); function stop = myStopFunction(info) stop = false; if ~isempty(info.ValidationLoss) persistent bestLoss; if isempty(bestLoss), bestLoss = inf; end if info.ValidationLoss > bestLoss * 1.05 stop = true; % 验证损失连续上升5%就停止 else bestLoss = inf; % 实际应记录历史最优,这里仅示意 end end end注意上述代码里persistent变量在函数中要正确初始化,实际用时建议用两个连续epoch的验证损失做判断,避免单次波动触发早停。如果训练集和验证集来自同一天气片段,早停会更敏感。
5. 精度验证与分时段建模技巧
5.1 用RMSE和MAE量化提升
zip里的zhibiao.m就是用来算预测指标的。我复现时把训练集和测试集严格按时间切分,前80%训练、后20%测试,并确保测试集时间段不在训练集里出现。用同样的气象因子和lookback参数对比两组模型,结果大致如下:
| 模型 | RMSE/kW | MAE/kW | R² |
|---|---|---|---|
| 直接LSTM(12步输入,6步输出) | 35.2 | 21.4 | 0.912 |
| EMD+PCA+LSTM(12步输入,6步输出) | 27.8 | 15.6 | 0.946 |
RMSE下降约21%,R²从0.912提升到0.946。提升主要来自多云和阴天的段,直接LSTM把这些波动当成噪声,而EMD把波动拆分后,PCA挑出主导尺度,LSTM学到的是不同尺度的规律而不是单一噪声。评价指标代码本身很直接:
% zhibiao.m:真实值Ptrue和预测值Ppred err = Ptrue(:) - Ppred(:); RMSE = sqrt(mean(err.^2)); MAE = mean(abs(err)); SS_res = sum(err.^2); SS_tot = sum((Ptrue(:) - mean(Ptrue(:))).^2); R2 = 1 - SS_res / SS_tot; fprintf('RMSE=%.3f kW, MAE=%.3f kW, R2=%.4f\n', RMSE, MAE, R2);5.2 分时段建模:多云天的另一种解法
我后来发现,把所有样本混在一起训练,晴天样本数量远大于波动天,损失函数会被晴天主导。想单独提升多云天精度,可以按“功率波动强度”隐式划分训练集:计算功率序列相邻点差值绝对值,如果一天内超过装机容量3%的差值占比大于20%,就归为波动日。然后用波动日样本单独训练一个LSTM,晴天样本单独训练另一个。
分类器不需要太复杂,用当天前2小时功率标准差比历史晴天同时间段大1.5倍作为切换条件即可。部署时先判断当前时段属于波动还是平稳,再选择对应模型。实测波动日模型的MAE能再降12%左右,代价是维护两个模型和一段历史功率窗口。另一种更轻量的做法是,把PCA后第一主成分和功率一起做一阶差分,差分后的平稳性会让LSTM收敛更快,这是我在这个项目上试过最快见效的改进。
本文还有配套的精品资源,点击获取