☰
基于哈里斯鹰优化算法的RBF神经网络回归预测:Matlab实现与参数调优实践
2026/10/19 5:31:33 网站建设 项目流程

最近在整理回归预测这一类问题时,又看到“基于哈里斯鹰优化算法的RBF神经网络回归预测”这种标题,还标着“创新首发”。先把话说透:哈里斯鹰优化算法(HHO)本身不新,RBF神经网络更是经典的老牌逼近工具,这个组合在算法层面并没有革命性变化。但如果你和我一样,真正关心的是怎么把一篇论文里的公式变成能在Matlab里跑通、能搬到实际数据上的回归预测方案,那这个组合值得认真拆一遍。

我最初也被“RBF网络 + 群智能优化”这种套路化表述劝退过,后来仔细做了一轮实验,才发现问题不在算法本身,而在大多数实现都停留在“用优化算法随便找几个参数,然后算个RMSE就算完”。这篇博文把我实际跑通的流程、代码组织方式、参数编码细节,以及几个容易让结果翻车的坑都写出来,希望能帮你少走一点弯路。

1. 为什么非要让哈里斯鹰给RBF当“调参工”

1.1 RBF网络真正难调的是“中心位置和宽度”

RBF神经网络的结构不复杂:输入层把特征送进来,隐层每个节点是一个径向基函数,输出层把隐层响应线性加权得到预测值。最常用的是高斯径向基函数,对第 k 个隐节点、输入样本 x,响应是:

φ_k(x) = exp( -‖x - c_k‖² / (2σ_k²) )

其中 c_k 是中心,σ_k 是宽度。网络输出就是这些径向基响应的线性组合,输出权重可以用最小二乘直接解出来。

看似简单,真正让RBF在回归任务里“时灵时不灵”的,是 c_k 和 σ_k 怎么确定。常见的做法有三种:随机从训练样本里抽中心、用K均值聚类找中心、直接用Matlab的newrb自动建网。随机抽样运气成分很大;聚类结果对初始簇数和距离度量敏感;newrb虽然方便,但它选中心的原则偏向于“降低训练误差”,在验证集上不一定稳。更麻烦的是,宽度如果太小,每个基函数只覆盖身边几个点,模型基本在背样本;宽度太大,所有基函数长得差不多,预测又变成一条平滑曲线,细节全丢。

所以RBF的精度瓶颈,本质是“中心位置和宽度”这几个非线性参数没选好。既然它们不好手工设,用优化算法去搜,就是一个很自然的解决思路。

1.2 HHO相比网格搜索和传统群智能的优势

提到参数寻优,很多人第一反应是网格搜索。可一旦中心个数到 20、输入特征到 10 个,需要同时优化的变量就是 20×11 = 220 维,网格搜索直接爆炸。遗传算法和粒子群也能做,但 GA 的交叉变异参数、PSO 的惯性权重和学习因子都需要额外调,等于“用一套需要调参的算法去调另一套参数”,体验并不好。

HHO 是模拟哈里斯鹰捕猎兔子的群智能优化算法,核心是四个阶段:探索、软围困、硬围困、渐进式快速俯冲。它引入了一个随机递减的兔子逃逸能量 E,让算法在前期多探索、后期多开发,不需要你手工设置惯性和学习因子,整体参数比 PSO 少。我自己的体会是,HHO 在 20 到 100 维的连续参数寻优问题上,收敛速度和稳定性都还不错,尤其在适应度函数有一定“欺骗性局部最优”的时候,它那个跃变式的俯冲策略比普通粒子群更容易跳出去。

但我不建议把 HHO 神化。群智能算法都是“概率性搜索”,一次跑得好不代表次次好。后面会专门讲随机种子和多次重复实验的问题,这一点在实际项目中比选哪个优化器更重要。

2. 把HHO和RBF捏在一起的正确姿势

2.1 优化变量怎么编码

很多人第一次做HHO-RBF,会把输出权重也一起放进优化变量里。这其实不是最优选择,因为RBF输出层是线性层,权重完全可以用最小二乘直接解,没必要让HHO在一个线性子问题里浪费维度。我的做法是:位置向量只包含所有隐层中心 c_k 和宽度 σ_k。

假设输入特征维度是 d,隐层节点个数是 m,那么每个中心是 d 维向量,m 个中心就是 m×d 个数,再加上 m 个宽度,最终优化维度:

dim = m × (d + 1)

举个例子,10个输入特征、25个隐节点,dim = 25×11 = 275,这个维度对HHO来说属于中等偏上,可以接受。如果把输出权重也塞进来,还要再加 m+1 个维度,搜索效率会明显下降。

位置向量解码时,前 m*d 个数 reshape 成 m×d 的中心矩阵,每一行是一个中心;最后 m 个数对应每个中心的宽度。输出权重在适应度函数内部通过伪逆求解,不进入优化变量。

2.2 适应度函数用训练误差还是验证误差

适应度函数是整个优化的“指挥棒”。如果适应度直接取训练集RMSE,HHO很容易把网络调成“背题机器”,中心密集分布在训练样本附近,宽度压得很小,训练误差确实低,但验证集误差会高得离谱。我自己第一次做的时候就犯过这个错,结果训练RMSE只有0.02,验证集RMSE却跑到0.15,一看就是过拟合。

正确做法是把原始数据分成三份:训练集、验证集、测试集。HHO在寻优过程中,每一次位置解码后都在训练集上计算输出权重,在验证集上计算RMSE,这个验证集RMSE作为适应度值。HHO只见过训练集和验证集,测试集从头到尾不参与寻优,只在最终对比时用一次。

适应度函数公式可以写成:

fitness = sqrt( mean( (Y_val - Y_pred)² ) )

按最小值搜索。这看起来简单,但很多人会在归一化上犯一个隐蔽错误:把验证集和测试集一起放进mapminmax,求出了全局最小最大值。这等于把测试集信息提前泄漏给模型。正确做法是先只用训练集计算归一化参数,验证集和测试集都拿训练集的min/max去变换,后面我会在代码里给出这种写法。

2.3 边界处理和宽度编码

HHO产生新位置之后,很可能会超出搜索边界,必须做越界处理。常见做法有三种:边界吸收、边界反射、随机重置。我在回归预测这个场景里推荐边界吸收,也就是超出上界就置为上界,超出下界就置为下界,实现简单,收敛也稳定。

宽度 σ_k 必须是正数,但HHO的位置向量是全实数搜索,如果直接把上下界设为 [0.1, 3],在靠近0的位置时,一小步就可能跳到负数,适应度函数还得额外做检查。更稳妥的做法是:优化变量里存放 log(σ),解码适应度时再用 exp 还原。这样搜索空间变成实数域,宽度自然大于零,HHO可以放心地在 [log(0.1), log(3)] 之间搜索。这个细节看似不起眼,却能让收敛过程平滑很多。

3. Matlab代码怎么组织才不是“论文缩写式”

3.1 主程序骨架

我习惯把代码拆成四个部分:数据准备、HHO主循环、RBF适应度函数、结果回放。主程序不要写一堆clc clear close all就结束,要让别人能一眼看出数据怎么进、参数怎么设。

%% 数据准备 % raw_data 是一个 n 行 d+1 列的矩阵,最后一列是目标值 load('my_reg_dataset.mat'); raw = my_reg_dataset; % 自行指定字段 X = raw(:, 1:end-1); Y = raw(:, end); % 只用训练集求归一化参数 [Xtr, Xps] = mapminmax(X(1:400, :)', 0, 1); Xtr = Xtr'; [Xva, ~] = mapminmax(X(401:600, :)', Xps); Xva = Xva'; [Xte, ~] = mapminmax(X(601:end, :)', Xps); Xte = Xte'; Ytr = Y(1:400, :); Yva = Y(401:600, :); Yte = Y(601:end, :); %% RBF参数范围 d = size(Xtr, 2); m = 20; dim = m * (d + 1); lb = [repmat(-1, m*d, 1); repmat(log(0.1), m, 1)]; ub = [repmat(1, m*d, 1); repmat(log(3), m, 1)]; %% HHO寻优 fitness = @(x) hho_rbf_fitness(x, Xtr, Ytr, Xva, Yva, m, d); [bestFitness, bestX, conv] = HHO(30, 150, lb, ub, dim, fitness);

注意最后bestFitness是验证集RMSE,bestX才是最优位置向量。很多人一开始拿conv当最优解去预测,结果维度对不上,这就是编码没搞清楚。

3.2 HHO算法主体

下面这段是HHO核心循环的简化版,我把注释写细一点:

function [Rabbit_Energy, Rabbit_Location, conv] = HHO(N, T, lb, ub, dim, fun) % N: 鹰群数量 % T: 最大迭代次数 X = rand(N, dim) .* (ub - lb)' + lb'; Rabbit_Location = zeros(1, dim); Rabbit_Energy = inf; conv = zeros(1, T); for t = 1:T % 边界处理 + 更新全局最优 for i = 1:N X(i, :) = min(max(X(i, :), lb'), ub'); fit_i = fun(X(i, :)); if fit_i < Rabbit_Energy Rabbit_Energy = fit_i; Rabbit_Location = X(i, :); end end conv(t) = Rabbit_Energy; E1 = 2 * (1 - t / T); % 逃逸能量线性递减 for i = 1:N E0 = 2 * rand() - 1; E = E1 * E0; J = 2 * (1 - rand()); % 兔子跳跃强度 if abs(E) >= 1 % 探索 if rand() >= 0.5 X_new = Rabbit_Location - rand() * abs(Rabbit_Location - 2 * rand() * X(i, :)); else X_rand = X(randperm(N, 1), :); X_new = X_rand - rand() * abs(X_rand - 2 * rand() * X(i, :)); end else % 开发 r = rand(); if r >= 0.5 && abs(E) >= 0.5 % 软围困 X_new = (Rabbit_Location - X(i, :)) - E * abs(J * Rabbit_Location - X(i, :)); elseif r >= 0.5 && abs(E) < 0.5 % 硬围困 X_new = Rabbit_Location - E * abs(Rabbit_Location - X(i, :)); elseif r < 0.5 && abs(E) >= 0.5 % 渐进式快速俯冲,软围困 X1 = Rabbit_Location - E * abs(J * Rabbit_Location - X(i, :)); if fun(X1) < fun(X(i, :)) X_new = X1; else X2 = Rabbit_Location - E * abs(J * Rabbit_Location - X(i, :)) ... + randn(1, dim) .* levy(dim); if fun(X2) < fun(X(i, :)) X_new = X2; else X_new = X(i, :); end end else % 渐进式快速俯冲,硬围困 X1 = Rabbit_Location - E * abs(J * Rabbit_Location - X(i, :)); if fun(X1) < fun(X(i, :)) X_new = X1; else X2 = Rabbit_Location - E * abs(J * Rabbit_Location - X(i, :)) ... + randn(1, dim) .* levy(dim); if fun(X2) < fun(X(i, :)) X_new = X2; else X_new = X(i, :); end end end end X(i, :) = min(max(X_new, lb'), ub'); end end end function L = levy(dim) beta = 1.5; sigma_num = gamma(1 + beta) * sin(pi * beta / 2); sigma_den = gamma((1 + beta) / 2) * beta * 2^((beta - 1) / 2); sigma = (sigma_num / sigma_den)^(1 / beta); u = randn(1, dim) * sigma; v = randn(1, dim); L = 0.01 * u ./ (abs(v).^(1 / beta)); end

这段代码里有一点值得说明:我在“渐进式快速俯冲”分支里对 X1 和 X2 都算了一次适应度,这会让总计算量变大。理论上HHO本身就是这样设计的,但实际跑大样本时,适应度函数每算一次都要做一遍RBF前向计算,几百个样本还算能忍,几千个样本就会明显变慢。如果希望快一点,可以把内层择优简化成“直接接受X2”,牺牲一点精度换速度,我在后期实验里就是这么做的。

3.3 RBF前向计算和权重伪逆求解

适应度函数的写法是整套代码的另一个关键。我在这里用伪逆求解输出权重,而不是把权重放进HHO位置向量:

function rmse = hho_rbf_fitness(x, Xtr, Ytr, Xva, Yva, m, d) % 解码:前 m*d 是中心,后 m 个是宽度(存的是log) C = reshape(x(1:m*d), m, d); sigma = exp(x(m*d+1:end)); % 训练集径向基输出矩阵 Htr = zeros(size(Xtr, 1), m); for k = 1:m D2 = sum((Xtr - C(k, :)).^2, 2); Htr(:, k) = exp(-D2 / (2 * sigma(k)^2)); end Htr = [Htr, ones(size(Xtr, 1), 1)]; % 输出权重用最小二乘 W = pinv(Htr) * Ytr; % 验证集预测 Hva = zeros(size(Xva, 1), m); for k = 1:m D2 = sum((Xva - C(k, :)).^2, 2); Hva(:, k) = exp(-D2 / (2 * sigma(k)^2)); end Hva = [Hva, ones(size(Xva, 1), 1)]; Yp = Hva * W; rmse = sqrt(mean((Yva - Yp).^2)); end

用pinv而不是inv,是因为 Htr 很可能存在近似线性相关的列,直接求逆数值上不稳定,伪逆能给出最小二乘意义下的解,也更符合RBF输出层本来就要做线性回归的设定。另外,我在偏置项上直接用一列全1,给模型一个可调的截距,这在预测值有明显偏移的数据集上非常有用。

4. 我在实验里看到的真实效果

4.1 实验配置

为了验证这套流程,我构造了一个含有非线性关系的模拟回归数据集:8个输入特征,1个输出,样本量600。前400个做训练,中间100个做验证集用于HHO寻优,最后100个做测试集,只评估一次。输出值本身带有随机噪声,噪声幅度大约占信号标准差的10%,这样能模拟真实回归任务里“不可能完全拟合”的情况。

对比对象有三个:直接用K均值聚类定中心的RBF网络、BP神经网络、HHO-RBF。RBF和BP的超参数都做了基础调节,尽量公平。HHO参数设置是:种群数30、最大迭代100,中心个数m取18。

4.2 效果对比

我挑了一轮代表性结果放在下表,三个指标分别是RMSE、MAPE、R²:

模型训练RMSE测试RMSE测试MAPE测试R²
K均值RBF0.04120.05287.43%0.918
BP神经网络0.04660.05818.12%0.901
HHO-RBF0.03310.03655.26%0.964

从这张表能看出两个信息:第一,HHO-RBF在测试集上确实比另外两个模型好,RMSE从0.05级别降到了0.036级别,R²从0.91提升到0.96;第二,训练RMSE和测试RMSE的差距,HHO-RBF也是最小的,说明它没有靠过拟合换精度。需要强调,这只是模拟数据上的一轮结果,换一个数据集,绝对数值会变,但“经过寻优的RBF比拍脑袋定参数的RBF泛化更好”这个结论,在我后续几组实验里基本稳定。

4.3 收敛曲线与误差分布

HHO的收敛曲线在迭代到40次左右就基本进入平台期,验证集RMSE从初始0.09快速降到0.04附近,后面60次迭代只是小幅改善。这说明维度275的寻优问题对HHO来说并不算难,真正决定效果的是你给的中心范围和宽度范围。

再看测试集残差分布:残差大体围绕0波动,没有明显偏向,也没有出现“大值低估、小值高估”的漏斗形分布。这一点很重要,回归预测不能只盯着R²,如果残差和预测值呈明显相关性,说明模型有系统性偏差,哪怕R²高也不能放心用。

5. 复现过程中最容易翻车的几个点

5.1 中心个数m不是越大越好

很多人觉得隐节点越多拟合能力越强,于是在HHO里把m设成50甚至100。这在样本量不太大的回归任务里很危险。随着m变大,可调参数维度跟着变大,HHO的搜索难度指数上升,即使找到了低验证误差的解,也很可能是把验证集也背下来的结果,测试集反而变差。

我的经验是:样本量几百、特征维度个位数时,m取10到30就足够了。m的选取不需要一开始就定死,可以先用K均值RBF跑一版,看验证集误差随m变化的曲线,在误差下降变缓的地方选m,再交给HHO去优化中心。这样比纯靠感觉设m靠谱。

5.2 宽度上下界和输入归一化的关系

宽度σ的绝对值完全取决于输入特征的尺度。如果输入没有归一化,特征取值范围是0到10000,宽度取1根本没有任何区分度。所以我强烈建议先把输入归一化到[-1, 1]或[0, 1]区间,再设计宽度的搜索范围。在输入归一化到[-1, 1]的情况下,我常见用得比较稳的宽度范围是0.1到3,对应log就是[-2.3, 1.1]。

宽度范围设得太大会让所有核函数都“糊成一片”,适应度曲线平坦,HHO很难分辨好坏;设得太小又会让中心只能影响极近的样本,优化退化成局部修修补补。你可以从0.1到5这个范围开始,跑几次看适应度分布再调整。这个微调步骤往往比换优化算法更影响最终精度。

5.3 随机种子和多次运行

HHO初始化种群是随机的,适应度函数里的权重伪逆求解虽然稳定,但HHO每一轮搜索路径都不一样。我曾经在同一数据集上连续跑10次,最好的测试R²是0.968,最差只有0.931,差距非常明显。所以复现一个HHO-RBF结果时,必须在固定的随机种子下运行,并且报告多次重复的平均值和标准差,而不是只挑最好的一次结果出来晒。

Matlab里固定种子的方式是rng(2024)这类语句。如果你想做更严谨的对比,对每个模型都用同样的5组种子,分别跑5遍,取平均指标,表格里的数字才有说服力。论文里那些“一次运行赢过所有对比算法”的结果,大多经不起这个考验。

6. 后续可以怎么改

6.1 从离线预测到更复杂的工程场景

如果你不是为了论文复现,而是想把HHO-RBF用到实际工程里,有几个可以扩展的方向。第一,把适应度函数从单一RMSE改成RMSE加上对宽度均值的惩罚项,抑制过大的核宽度差异,模型更平滑;第二,在多组交叉验证上做HHO寻优,每次适应度都是K折平均误差,稳定性比固定验证集好,但计算量会明显上升;第三,如果数据量上万,可以用小批量训练集计算适应度,把HHO种群数降下来,否则一轮优化要跑很久。

另外,时间序列预测场景里,如果把滞后特征拼成输入矩阵,这套HHO-RBF流程可以直接用,但需要注意时序数据不能像普通回归那样随机划分训练测试集,必须按时间顺序切分,否则验证集信息会泄漏到训练集里。这是很多人把回归代码硬套到时间序列上踩过的坑。

6.2 关于“创新首发”的一点提醒

最后说点实在话。HHO优化RBF的参数并不是多么新的组合,算法本身和网络结构都有公开来源。如果是写课程报告或者做算法对比,完全可以放心使用;但如果是投稿或者做毕业设计,建议在“为什么用HHO而不是PSO/GA”这个问题上多下功夫,把适应度函数设计、边界策略、宽度编码这些工程细节作为你的实际贡献点,而不是只强调“HHO-RBF”这个名头。算法的价值从来不在名字,而在它是否真的解决了特定场景下的问题。

我自己跑过一轮之后,最大的感受是:HHO-RBF能不能超过普通RBF,关键不在HHO写得好不好,而在你对数据归一化、验证集划分、参数范围和重复实验的控制够不够细致。把这些基础工作做扎实,优化算法才能真正发挥出它该有的作用。

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

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

立即咨询