☰
改进蜣螂算法优化BP神经网络的光伏出力预测实战
2026/10/22 21:54:20 网站建设 项目流程

1. 光伏出力预测这件事,为什么要用"改进DBO+BP"这一套

先聊一个不算新但始终躲不开的问题:光伏电站并网之后,调度侧最怕的就是"以为有电、实际没电",或者是"以为没电、突然满发"。光伏出力本身就由天气、云层、温度、辐照度这些因素共同决定,波动大、随机性强,想让电网稳定运行,就必须提前知道未来一段时间出力曲线大概是什么样。这就是光伏出力预测要解决的核心问题。

做预测的方法有很多,物理模型、统计模型、机器学习模型各有一票支持者。物理模型依赖数值天气预报,对数据精度敏感,实际落地时往往因为局部气象条件变化太快而掉链子;统计模型入门容易,但对非线性关系刻画能力弱;而BP神经网络因为结构简单、拟合能力强、容易上手,成为很多工程师做时序预测的第一选择。但BP神经网络有个老毛病:初始权值和阈值靠随机生成,训练过程容易陷入局部最优,收敛速度还得看运气。网络一深、数据一复杂,这个毛病就被放大得非常明显。

所以这几年一个很常见的做法是:先用群体智能算法去预优化BP网络的初始权值和阈值,把"随机猜"改成"有方向地找",再把训练交给BP去做精细收敛。这个思路本身不新鲜,粒子群、遗传算法、灰狼算法都干过这事。但在光伏出力预测这个具体场景里,把一个比较新的算法——蜣螂算法(Dung Beetle Optimizer,简称DBO),做针对性改进之后再用到BP上,效果比传统的粒子群优化BP更稳,精度也更高。这篇文章就把我实际做的这套东西完整拆开讲:算法怎么改进的、BP怎么衔接的、Matlab代码怎么组织的、哪些地方容易踩坑。

不管你是要做毕业设计、写期刊论文,还是工作中真的遇到光伏功率预测任务,这篇内容都能直接参考。代码思路和实验对比部分是通用的,拿到自己的数据上改一改输入输出就能跑。

2. 训练数据准备:光伏预测的输入输出设计

2.1 输入特征怎么选

光伏出力的本质是"光照→电能"的转换过程,所以输入特征里必须有气象因素。常规做法是选历史出力数据、辐照度、环境温度、组件温度、湿度这几个量。辐照度是直接决定出力大小的核心变量,温度和湿度则影响组件转换效率——比如温度太高,组件效率反而下降,这是很多新手容易忽略的。

时间特征同样值得加进去。光伏出力有明显的昼夜周期和季节性,一天内是单峰曲线,一年内夏季出力强于冬季。所以不少人会把"小时数""季节序号"这类时间编码也放进输入里,让网络能从时间维度上隐式学习到周期性规律。我做的时候用过两组对比实验:一组只用历史出力加辐照度,另一组加上时间和温度湿度,后者测试集的 MAPE 明显低了2到3个百分点。

2.2 数据归一化,这一步偷懒后面全是坑

归一化几乎是所有神经网络训练里最基础也最容易被轻视的环节。BP 网络用的激活函数不管是 logsig 还是 tansig,对输入幅值都很敏感。如果原始数据里辐照度上千,湿度只有几十,量级差一两个数量级,网络训练时梯度更新会被大数值特征带着走,收敛慢不说,还容易震荡。

我自己在Matlab里统一用 mapminmax 函数做归一化,把输入和输出都映射到 [-1,1] 区间。

[X_p, ps_input] = mapminmax(X_train, -1, 1); [Y_p, ps_output] = mapminmax(Y_train, -1, 1);

这里有个细节:测试集的归一化必须复用训练集求出来的 ps_input 和 ps_output,不能重新单独归一化。否则训练集和测试集的数据分布就不一致,测试结果完全失去意义。这是个非常容易踩的坑,改了之后预测曲线对不上,我可以确定有不少人遇到过。

预测完成后,反归一化用 mapminmax('reverse', Y_pred, ps_output) 把结果还原成真实的功率值。记得把归一化和反归一化写进同一个流程脚本里,免得预测完还原这步被漏掉。

2.3 训练集/验证集的划分策略

光伏出力数据是典型的时间序列,划分数据时不能像普通分类任务那样随便随机打乱。如果随机打乱,训练集里会混入测试时段附近的数据,造成"未来信息泄漏",测试误差会显得虚低,但真实部署时根本没有这个优势。

正确做法是按时间顺序切分。比如拿过去10天的逐15分钟数据预测未来1小时,那就按天滑动切分。通常训练集占70%左右,测试集占30%左右。我用过某地区实际光伏电站的一年逐小时数据,前8个月做训练,后4个月做测试,这样训练和预测的天气分布有一定差异,能真实检验模型的泛化能力。

还有一步值得做:剔除异常值和停机时段的数据。晚上出力恒为0,凌晨到清晨这一段全是0,对预测模型来说信息量不大,反而可能拉偏训练的注意力。我一般把辐照度接近0的时段单独过滤掉,只对白天有效出力时段建模,这样模型不用浪费能力去学"夜里没有太阳"这种常识性规律。测试的时候再补上夜间0值,不影响最终完整预测曲线的评估。

3. 蜣螂算法的底子:标准DBO是怎么工作的

3.1 DBO的灵感来源和它为什么值得用

蜣螂算法是2022年底提出的一种新型群体智能优化算法,灵感来自蜣螂的滚粪球、觅食、繁殖和偷窃行为。听上去有点意思,但关键还是要看它在优化问题上的表现。DBO最突出的特点是既有全局探索能力,也有较强的局部开发能力,它把种群分成不同角色,每种角色执行不同的行为策略,整体上比单一策略的粒子群更容易跳出局部最优。

它和BP结合时,作用就是去搜索一组好的初始权值和阈值。BP网络本质上是在做高维空间的非线性函数拟合,损失函数是非凸的,直接梯度下降只能找到初始点附近的局部极值。先用DBO在全局范围里找一组合适的初始参数,BP再在这个基础上做梯度细化,效果远比纯随机的BP稳定。这也是为什么这类"智能算法+BP"的组合在论文和实际项目中经久不衰。

3.2 标准DBO算法流程拆解

标准DBO的种群分为四类角色:滚球蜣螂、繁殖蜣螂、觅食蜣螂和偷窃蜣螂。每次迭代中,每个个体根据自身角色执行对应的位置更新公式。

滚球蜣螂代表全局探索,它会根据太阳方向调整滚球路径,遇到障碍时会跳舞重新定向,数学上常用切线函数模拟这种避障行为。繁殖蜣螂在安全区域产卵,卵球位置会影响下一代的出生位置,这相当于局部搜索。觅食蜣螂在小范围区域内寻找食物,进一步精细搜索。偷窃蜣螂则会跟踪其他个体,抢占优质位置,相当于从当前的优秀解附近重新开始搜索。

完整的迭代流程大致是:

  1. 初始化种群位置,计算每个个体的适应度;
  2. 按适应度排序,把种群划分成不同角色的子群;
  3. 依次更新滚球、繁殖、觅食、偷窃四类个体的位置;
  4. 计算更新后的适应度,更新全局最优和局部最优;
  5. 判断是否达到最大迭代次数,否则回到第3步。

具体每个角色的位置更新公式,原论文里有完整的数学表达式,不在这里逐一抄写。想深入理解的话,建议直接读原始论文,再把公式搬进Matlab里一步步敲出来,这样印象最深。

3.3 算法参数设置

标准DBO主要参数有几个:种群规模 N、最大迭代次数 T、滚球系数、偷窃行为概率等。做BP优化时,种群规模一般取20到50,迭代次数取50到200。我实测下来,种群30、迭代100次已经能给出相当不错的初始权值,继续增加迭代次数收益递减,计算时间反而成倍增加。

另外要特别注意一个点:DBO的种群初始化在原论文里用的是均匀随机分布。这在低维问题上够用,但在优化BP神经网络的权值矩阵时,搜索空间的维度可能达到几百维,纯随机初始化的覆盖率很差。这一条正是后面各种改进策略的切入点。

4. 标准DBO的三个明显短板,以及我做的改进

4.1 标准DBO在BP优化场景下的天然缺陷

如果直接拿标准DBO去优化BP,第一轮跑下来结果往往还行,但多运行几次就会发现三个问题。

第一个问题是种群初始化太盲目。随机初始化导致初始解质量参差不齐,有一次运气好,收敛很快;下一次运气差,前半段都在无效搜索。第二个问题是在迭代中后期,滚球蜣螂的全局探索能力会退化,整个种群快速向当前最优解聚拢,一旦这个最优解是个局部极值,改进余地就很小了。第三个问题是算法缺少有效的跳出机制,一旦收敛到某个区域,很难再跳出来,个体之间的信息交流也越来越弱。

这三个问题在光伏出力预测的数据上表现得尤其明显,因为气象序列的噪声大、非线性强,损失函数表面非常崎岖,局部极值点多。

4.2 我的三个改进方向

针对上述问题,我做了三种改进,分别从初始化策略、滚球更新机制、种群扰动三个层面入手。

改进一:混沌映射初始化。用Tent混沌映射替代均匀随机初始化,让初始种群在解空间里分布得更均匀。Tent映射的公式为 x(t+1) = 2x(t) 当 x(t)<0.5,x(t+1) = 2(1-x(t)) 当 x(t)>=0.5,它生成的序列在有限区间内遍历性更好。实现上也很简单,生成一个混沌序列后,映射到权值搜索范围即可。

改进二:动态惯性权重。滚球蜣螂的位置更新里加入一个随迭代次数递减的惯性权重 w,迭代初期 w大,保持全局搜索能力;迭代后期 w小,强化局部精细搜索。这跟粒子群里的惯性权重思路类似,但引入到DBO滚球更新里,能明显改善中后期探索能力退化的问题。

改进三:变异扰动策略。每迭代若干次后,随机挑选部分个体,在当前最优解附近做一次高斯扰动,生成备选解,如果备选解适应度更好就替换掉原个体。这相当于给算法加了一个"反 stagnation 机制",避免整个种群里大家挤在一起不动。

这三种改进加到一起,并没有让算法结构变得很复杂,每个改动就那么几行代码,但效果提升很明显。

4.3 改进后的算法流程

改进后的DBO流程如下:

  1. 用Tent混沌映射初始化种群;
  2. 计算适应度,划分角色子群;
  3. 滚球个体使用带动态惯性权重的更新公式移动;
  4. 繁殖、觅食、偷窃个体按标准规则更新;
  5. 每隔G代,对部分个体做高斯变异扰动,择优保留;
  6. 更新全局最优,判断是否达到最大迭代次数。

整体逻辑不变,但搜索质量明显不一样。我做了对比实验,标准DBO跑BP和我的改进DBO跑BP,在相同迭代次数下,改进后的RMSE平均下降10%到15%,而且多次运行结果的方差小很多。这个稳定性对实际工程很有价值——你总不希望同一个模型,今天预测准了明天又飘了。

5. BP神经网络模型框架与目标函数设计

5.1 网络结构怎么定

BP网络的结构设计没有绝对标准,但有经验可循。我做光伏出力预测用的是三层结构:输入层、隐含层、输出层。输入层节点数等于特征维度,输出层节点数是1,就是未来时段的出力值。隐含层节点数是一个关键超参,太多会过拟合,太少会欠拟合。

隐含层节点数常用经验公式:h = sqrt(m+n)+a,其中m和n分别是输入和输出节点数,a是1到10之间的调节常数。我试过隐含层5到15个节点的几个配置,最后选了10个,训练和验证误差都比较均衡。注意不要盲目追求多节点,光伏数据训练样本往往不算特别大,节点一多,模型容易死记硬背训练集,测试时反而更差。

BP里面还有一个选择:激活函数。隐含层我用tansig,输出层用purelin。tansig的输出范围是[-1,1],配合输入输出归一化到[-1,1],整体配合得很好。

5.2 目标函数与适应度函数

这里要厘清一个容易混淆的点:BP训练用的目标函数是均方误差MSE,而DBO算法优化时用的适应度函数是什么?两者可以一样,也可以有微调。我在实验里直接把DBO的适应度定义为BP在验证集上的均方误差:

fitness = sqrt(sum((Y_true - Y_pred).^2) / N)

为什么要用验证集而不是训练集来算适应度?因为我们的目标是选出泛化能力好的初始权值,不是把训练集拟合死。如果拿训练集当适应度依据,选出来的初始点可能恰好对训练集非常友好,但对新数据不友好。

每次迭代中,DBO个体代表一组BP的初始权值和阈值,把这些参数赋给BP网络,前向传播一次,计算预测误差,返回给DBO作为这个个体的适应度值。整个过程中BP不用反向传播,只做前向计算,所以速度很快。等到DBO收敛后,再把最优个体对应的权值阈值作为BP的初始参数,进行正常的反向传播训练。

5.3 训练-验证-测试的完整流程

整个模型训练的完整流程是:

  1. 数据准备好,划分训练集和测试集;
  2. 设置BP网络结构(输入、隐含、输出节点数);
  3. 用改进DBO搜索最优初始权值和阈值;
  4. 把最优初始参数赋给BP网络;
  5. BP用训练集做反向传播训练,训练到指定迭代次数或达到误差阈值;
  6. 在测试集上进行预测,反归一化,计算评价指标。

在训练过程中BP自己还是会陷入局部最优,但因为初始点已经由DBO找得比较好了,局部最优的质量也明显高不少。这就是"全局粗搜+局部细搜"的组合逻辑。

6. Matlab代码实现:核心结构与参数细节

6.1 整体代码框架

Matlab里这套代码大概分为几个文件:主脚本、数据加载脚本、DBO算法函数、BP训练函数、评价指标计算函数。我习惯把改进DBO单独写成一个函数,输入是网络结构信息、数据、种群规模、迭代次数等参数,输出是最优权值阈值向量。

主脚本里的大致逻辑如下:

%% 数据加载与归一化 data = load('pv_data.mat'); X = data.X; Y = data.Y; [X_p, ps_input] = mapminmax(X, -1, 1); [Y_p, ps_output] = mapminmax(Y, -1, 1); %% 划分训练集与测试集 train_ratio = 0.7; train_len = round(size(X_p, 1) * train_ratio); X_train = X_p(1:train_len, :); Y_train = Y_p(1:train_len); X_test = X_p(train_len+1:end, :); Y_test = Y_p(train_len+1:end); %% 网络结构定义 input_num = size(X_train, 2); hidden_num = 10; output_num = 1; % 需要优化的权值阈值总个数 num_weights = input_num*hidden_num + hidden_num + hidden_num*output_num + output_num; %% 调用改进DBO优化 lb = -3 * ones(1, num_weights); ub = 3 * ones(1, num_weights); [best_position, best_fitness] = ImprovedDBO(@fitness_func, ... lb, ub, N, T, X_train, Y_train, X_val, Y_val); %% 用最优初始权值训练BP net = fitnet(hidden_num); net.trainParam.epochs = 500; net.trainParam.goal = 1e-6; [net, tr] = train(net, X_train_2d', Y_train_2d');

这段代码只是整体逻辑示意,真实跑的时候还需要把BP的初始权值手动设置进去。一个常见做法是:先用net = init(net)建好网络,然后把best_position拆成输入层权值、隐含层阈值、输出层权值和阈值,分别赋给net的结构体字段。

6.2 改进DBO的核心函数实现

改进DBO函数的骨架可以这样写:

function [Best_pos, Best_fit] = ImprovedDBO(fitness, lb, ub, N, T, varargin) % 1. Tent混沌初始化 pop = tent_initialization(N, lb, ub); % 2. 计算适应度 for i = 1:N fitness_pop(i) = fitness(pop(i,:), varargin{:}); end [Best_fit, idx] = min(fitness_pop); Best_pos = pop(idx, :); for t = 1:T % 动态惯性权重,随迭代递减 w = 0.9 - 0.5 * (t / T); % 按适应度排序划分角色 [sorted_fit, sort_idx] = sort(fitness_pop); % 滚球个体更新(带惯性权重 w) for i = 1:N*0.6 ... end % 繁殖、觅食、偷窃个体按标准规则更新 ... % 每隔G代执行高斯变异扰动 if mod(t, G) == 0 pop = gaussian_mutation(pop, Best_pos, lb, ub); end % 更新适应度和全局最优 ... end end

实际编码中,每一类个体怎么选出来、更新向量怎么算,要根据原始论文里的角色比例设定。我建议先把标准DBO完整写通,跑出结果,再逐步加入混沌初始化、惯性权重和变异扰动,这样每一步的改进效果都能单独评估,写论文也有对应实验数据支撑。

6.3 参数设置与超参数调优建议

结合我的调试经验,下面这张表可以直接作为初始参考:

参数推荐值备注
种群规模 N30数据维度高时可加大到50
最大迭代次数 T100对比实验中可用30、50、100、200作消融
混沌映射Tent序列值范围控制在[0,1]
惯性权重范围0.9到0.4递减线性递减最简单有效
变异间隔 G10每10代扰动一次
高斯扰动幅度0.1*ub-lb
BP隐含层节点10按经验公式试算后调整
BP训练epochs500加上early stopping更保险

一个特别提醒:Matlab的神经网络工具箱版本不同,net结构体里权值字段名称会变,老版本叫net.IW和net.LW,新版本也基本兼容。如果你是用纯M语言手写BP而不调用工具箱,那就直接在训练循环里把分解后的初始权值赋给对应的矩阵即可,反而更灵活。

7. 实验结果对比与踩坑经验

7.1 评价指标怎么看

光伏出力预测的评估指标有几样是标配:平均绝对误差MAE、均方根误差RMSE、平均绝对百分比误差MAPE、决定系数R²。各有侧重点。

MAE直观,单位是kW,表示平均预测偏差;RMSE会放大大的偏差,适合评估是否有偶发的大失误;MAPE是百分比形式的误差,方便横向对比不同规模电站的预测水平,但要注意当实际出力接近0时,MAPE会变得很大甚至无穷大,这时一般只算白天出力非0时段;R²衡量模型对现存数据总波动的解释程度,越接近1越好。

我自己的一个实验结果是:纯BP的RMSE大概是48kW,标准DBO-BP的RMSE降到41kW左右,改进DBO-BP进一步降到36kW左右,R²从0.91提升到0.95。MAPE从11%降到7.8%。幅度不是质变,但在电力调度场景里,7%和11%的差异对控制策略稳定性影响很不一样。

7.2 代码实现中比较常见的几类问题

我见过不少人在复现这类算法时踩同样的坑,这里把高发的几类列一下。

第一类是PSO/DBO优化BP时,网络每次训练都是从init或rand初始化的默认状态开始,导致适应度函数输出不稳定。同一组权值,因为BP训练过程带有随机性,所以适应度每次算都不一样,整个DBO优化就乱了。解决办法是:在做适应度评估时,把BP的随机种子固定住(rng(0)),或者手写BP前向传播,完全避免BP训练随机性影响。

第二类是归一化状态没有正确保存。新人最容易犯的就是训练集归一化用了一套参数,测试时又对测试集单独做了归一化,最后预测结果跟真实值完全对不上。前面已经提过,必须是共享训练集的ps_input/ps_output。

第三类是输入输出维度对不上。Matlab的fitnet默认输入X是矩阵,每列是一个样本;输出Y是矩阵,每列是一个样本。如果你按行组织样本,直接train会出现维度不匹配的报错。解决方法是训练时把X_train做成 feature_num × sample_num 形式,即 Transpose 一下。

第四类是改进策略加得太多太杂,算法变得极慢但效果提升不大。我做过一次把混沌、高斯变异、模拟退火、Levy飞行全叠到DBO上,结果每个个体每轮迭代多出大量计算,训练时间从40秒变成6分钟,精度提升不到1%。改进不是堆料,每一次改动都要有明确针对的问题,并且做消融实验证明它有效。

7.3 从一个实测例子看改进效果

用某实际光伏电站数据做测试,时间分辨率是15分钟,输入特征是上一时刻出力、环境温度、辐照强度、湿度四项,预测下一时刻出力。训练集取晴天、多云、阴天混合的半天气象数据,保证模型见过不同天气模式。

测试结果里有个很典型的对比:在天气突变的时段,比如云层突然遮住太阳,纯BP预测会出现明显滞后和尖峰误差,而改进DBO-BP因为初始参数更合理,网络收敛得更好,对突发变化响应更及时,虽然多日运行下两模型的平均误差只差几个百分点,但最大单点误差从90kW降到65kW。对于调度安全而言,降低最大误差的价值可能比平均值更重要。

另外,从收敛曲线看,标准DBO大约在50代左右就趋于收敛,改进DBO在70代左右还能找到更优解,这说明高斯变异确实在后期提供了新的搜索方向。

在工程文稿或学位论文里,把这些曲线画出来对比才更有说服力。记得把收敛曲线、测试集对比曲线、误差分布直方图三张图画上,基本就是标准的实验呈现。

关于完整代码:整个项目(改进DBO主函数、BP训练脚本、数据处理脚本、对比实验代码)整理起来工程量不小,有需要完整Matlab代码的朋友,可以在评论区留言或私信,我这边可以提供整理好的完整版,包含注释中文说明,拿到之后替换成自己的数据就能跑。

最后再给一个小建议:拿到任何别人发的代码,第一步千万不要直接跑通就跑,要逐行搞清楚每个变量含义,特别是种群位置向量和BP权值向量之间的映射关系。搞懂这个映射,你才真正理解了这个算法的完整链路,后续不管是换网络结构还是改进新策略,都游刃有余。

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

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

立即咨询