ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

HHO-LSBoost多输入回归预测:哈里斯鹰优化与Matlab实现

HHO-LSBoost多输入回归预测:哈里斯鹰优化与Matlab实现 做预测建模这些年我最深刻的体会就是调参的功夫往往比跑模型本身还多。尤其是用集成学习做回归预测时弱学习器的数量、学习率、树深这些超参数直接决定了模型的上限可手动一个个试又费时又费力。所以当我尝试把哈里斯鹰优化算法Harris Hawks Optimization, HHO和最小二乘提升LSBoost组合成一套HHO-LSBoost多输入回归预测方案时整个流程一下就顺畅了——优化算法自动去找LSBoost的最优超参模型自己搭、自己调、自己出结果。这篇文章就把这套方案的思路、算法原理、Matlab实现细节和实际踩坑经验整理出来给正在做回归预测、集成学习或智能优化算法相关工作的朋友一个可以直接参考的完整方案。这套方案适合谁如果你手头有多维输入、需要预测一个连续数值的任务比如工业过程参数预测、环境监测数据回归、设备退化趋势估计、电力负荷预测这类场景并且你已经在用或准备用Matlab做实验那这篇文章正好对应你的需求。需要说明的是我会给出HHO-LSBoost的核心代码逻辑但完整的依赖文件、工具箱配置和可运行脚本需要结合你本地环境来做微调先理解原理再动手比复制粘贴更重要。1. 为什么是LSBoostHHO方案选型的背后逻辑很多人一上来就问不就是集成学习加个优化算法吗为什么偏偏选LSBoost和HHO其实选型逻辑并不复杂关键在于“LSBoost有什么痛点HHO能解决什么痛点”。1.1 LSBoost的优势与难以绕开的调参问题LSBoost全称Least Squares Boosting是Boosting家族里专门针对回归任务设计的变体。和AdaBoost处理分类不同LSBoost每一轮都拟合上一轮预测值与真实值之间的残差通过最小二乘的思路确定弱学习器的权重从而逐步降低整体误差。Matlab里用fitrensemble一行就能调用底层自动帮你管理一堆回归树用起来非常方便。LSBoost有几个非常吸引人的特点第一它对异常值不像单一决策树或SVM那么敏感因为每一轮只关心残差相当于在“反复修补”误差集中的地方第二它天然支持高维特征不需要像神经网络那样做复杂的特征工程第三它训练速度快尤其当弱学习器是比较浅的回归树时几百轮迭代也就几秒钟的事情。痛点也很明显LSBoost的超参数太多了。树的数量NumLearningCycles、学习率LearnRate、最小叶子节点数、树的最大分裂数再加上采样方式每个参数组合都会带来完全不同的泛化性能。手动调参靠的是经验和运气网格搜索又面临组合爆炸——比如树数量取20个候选值、学习率取10个候选值光是这两个维度就是200次完整训练数据集稍大一点跑一个晚上都出不了结果。随机搜索虽然比网格快但本质上还是在赌概率没法保证搜到的是局部甚至全局较优点。1.2 HHO为什么能胜任超参数自动寻优HHO是2019年提出的新型元启发式算法模拟的是哈里斯鹰群体捕猎兔子的过程。捕猎时一部分鹰负责“侦察”随机区域发现猎物后通过不同围困策略包抄而兔子在逃跑过程中能量逐渐耗尽鹰群会在恰当的时机完成致命俯冲。这套机制放在优化问题里就对应了探索Exploration与开发Exploitation的动态平衡。和其他进化算法对比HHO有几个很实用的优势无梯度要求适应度函数只要能算出一个数值就行不用求导哪怕LSBoost的内部结构是一个复杂的黑箱也能直接优化。参数少HHO一共就两个核心控制参数——种群大小和最大迭代次数不像粒子群要调惯性权重、学习因子不像遗传算法要考虑交叉率变异率上手成本极低。收敛速度快思想来源是包围捕猎搜索步长会随着猎物能量动态调整前期大范围扫描后期小范围精细搜索收敛曲线的下降速度通常比较明显。把HHO和LSBoost放在一起等于用HHO这个“调参手”去搜索LSBoost的超参数空间优化目标就是模型在验证集上的回归误差。这套组合理念不新鲜PSO优化XGBoost、GWO优化SVR都有人做过但HHO因为参数少、收敛快做起来会省心很多而且Matlab实现HHO的代码量非常小几十行就能跑起来。2. HHO算法原理与Matlab实现细节要写好HHO-LSBoost第一步不是直接写主循环而是先把HHO的数学机制吃透。这一步如果只照着论文公式抄很容易写出“语法对但搜索能力差”的版本。网上不少HHO代码有个通病忽略了对越界粒子的重新初始化导致进化后期种群挤在边界上搜索效率骤降。下面我把HHO的核心公式和实现要点拆开讲。2.1 探索阶段随机搜索与位置更新HHO的开始阶段鹰群并不知道兔子在哪只能在整个搜索空间里随机探索。原始论文给出两种随机策略通过一个随机数q来决定走哪条路当q 0.5时鹰个体随机挑选种群中的另一只鹰作为参考新位置围绕这只参考鹰做扰动x_new x_rand - r1 * abs(x_rand - 2 * r2 * x)当q 0.5时鹰群以猎物的位置当前全局最优解和种群平均位置的偏离为基准加入随机扰动x_new x_rabbit - x_mean - r3 * (lb r4 * (ub - lb))这里r1到r4都是[0,1]的随机数lb和ub是参数下界和上界。这个阶段的价值在于保证种群在进化早期不扎堆尽量覆盖整个搜索空间防止一开始就陷入局部区域。在Matlab里实现时我习惯先初始化一个pop矩阵行是鹰个体列是待优化参数。x_rabbit维护当前最优个体每一轮探索完成后立即计算适应度并更新x_rabbit。这样写的好处是逻辑清晰后面加上开发阶段的围困策略也非常顺畅。2.2 开发阶段四种围困策略与能量因子HHO最有创意的地方就是那个**逃逸能量E**的设计。E会随着迭代次数从1逐渐降到0决定鹰群当前更偏向“散开探索”还是“集中捕杀”。每一轮计算方式如下E 2 * E0 * (1 - t / T)E0在[-1,1]之间随机取值t是当前迭代次数T是最大迭代次数。注意E0的符号很关键当E0 0时能量递减曲线整体在正半轴鹰群处于相对保守的围困阶段当E0 0时则进入更激进的追捕状态。根据E的绝对值和随机数r开发阶段分成四种情况条件围困策略位置更新核心思想abs(E) 0.5且r 0.5软围困兔子能量尚足鹰群不直接扑击而是不断缩小包围圈abs(E) 0.5且r 0.5硬围困兔子能量很低鹰群直接朝兔子当前位置突进abs(E) 0.5且r 0.5软围困渐进式俯冲鹰群试探性俯冲并根据俯冲位置的好坏决定是否接受abs(E) 0.5且r 0.5硬围困渐进式俯冲直接朝兔子位置俯冲如果效果不好再采用Levy飞行的随机步长补刀软围困的位置更新公式是delta_x x_rabbit - x J 2 * (1 - rand) x_new delta_x - E * abs(J * x_rabbit - x)而渐进式俯冲则先计算一个候选点Y再在Y的基础上叠加一个Levy飞行步长得到Z最后比较Y和Z谁更好就选谁。这一步其实是HHO的“大招”因为Levy飞行产生的是重尾分布的长距离跳跃帮助鹰群跳出局部最优。在Matlab代码里四种策略用if-elseif分支来实现特别要注意abs(E) 0.5和abs(E) 0.5的切换要放在最外层r的随机判断放在内层顺序弄反会得到完全错误的搜索行为。我最初调代码时就栽过这个跟头收敛曲线一直是平的查了很久才发现是把r判断放外面了。2.3 HHO参数设置种群规模、迭代次数与边界设定HHO虽然参数少但设置不合理照样翻车。按照我的经验种群规模10到20就够用。HHO的搜索策略决定了它不需要很大的种群来维持多样性种群太大反而拖慢每轮训练速度毕竟每一只鹰都要跑一遍完整的LSBoost训练和交叉验证。最大迭代次数50到100次居多。如果数据量大一次适应度计算可能就要几十秒迭代100次就是几十分钟甚至几小时建议先用小迭代次数验证代码正确性再逐步加大。我一般先跑30次看收敛曲线是否明显下降如果已经稳定就没必要硬加到100。边界设定这是最容易被忽略的一环。每个待优化参数都要一个合理的上下界。学习率范围设成[0.001, 1]还好但如果连树数量都设成[1, 1000]HHO在初期会频繁尝试极端大值导致LSBoost训练时间暴增。建议根据实际问题把范围收紧比如树数量[10, 200]学习率[0.01, 0.5]。这些边界本质上不是限制HHO的能力而是替你节省无效搜索的时间。还有一个细节边界设错会让HHO的Levy飞行经常跑出界如果不做约束处理下一轮的位置可能全是NaN。常规做法是越界个体随机重置到边界内更讲究一点的话可以用边界处的镜像值效率更高但代码复杂一些。我一般用随机重置就够了实测下来对收敛结果影响不大。3. LSBoost回归模型的搭建与关键配置HHO只是“调参手”真正干活的是LSBoost。在Matlab里搭LSBoost非常简单但配置得当和配置随意结果能差出一大截。这一节讲清楚怎么正确地搭建LSBoost回归器以及为什么某些配置项需要重点照顾。3.1 Matlab中fitrensemble的正确调用姿势Matlab从R2011a起就在Statistics and Machine Learning Toolbox里提供了fitrensemble函数专门用于集成回归。要构建LSBoost模型核心参数是Method和Learnermodel fitrensemble(X_train, y_train, ... Method, LSBoost, ... Learner, tree, ... NumLearningCycles, 100, ... LearnRate, 0.1, ... MaxNumSplits, 8);Method设成LSBoostLearner默认就是回归树。NumLearningCycles是弱学习器数量也就是树的数量LearnRate是学习率它的作用和梯度下降里的学习率类似越小越不容易过拟合但需要更多树来补偿。MaxNumSplits控制每棵回归树的最大分裂数这是一个经常被忽略但影响巨大的参数。默认值是n-1n是样本数也就是完全不限制树深。在LSBoost里如果树都长得很深每棵树都会过拟合到残差中的噪声反而拖累整体泛化。通常2到20之间的值更合理。这个参数就是HHO需要去优化的关键维度之一。还有个常用配置是NumVariablesToSample设置每个分裂点随机抽取的特征数类似随机森林的随机子空间思想对提高集成多样性有帮助。不过在LSBoost里它不是必须的如果你的特征数很多比如超过50维可以考虑加上并把它也纳入优化范围。3.2 数据预处理与评估指标LSBoost本身不像SVM那样对量纲敏感到非得归一化不可回归树的分裂过程关心的是阈值比较特征是否归一化不影响分裂点的相对优劣。但放进HHO框架后我仍然建议做归一化原因有两个一是LSBoost内部的分裂搜索使用数值阈值归一化后数值稳定不容易产生浮点溢出的边界情况二是当你把多个不同量纲的特征放在一起时如果某个特征方差极大树很容易优先拿它做分裂虽然不一定错但会掩盖其他特征的贡献。归一化用最简单的min-max归一化就行[X_norm, ps] mapminmax(X, 0, 1);注意mapminmax默认按行操作所以输入要转置训练集拟合好映射ps之后测试集也要用同一个ps做变换千万不能用测试集重新计算最小值和最大值否则会造成数据泄露测试结果会偏乐观。评估指标我通常看三个RMSE、MAE和R²。RMSE对大误差更敏感适合突出模型的极端预测能力MAE衡量平均偏差更贴近工程场景R²反映模型对总体方差的解释程度。在HHO的适应度函数里我优先用RMSE或MAE作为优化目标因为R²的数值范围比较固定在有些分布不均匀的数据上区分度不够。3.3 为什么适应度要用交叉验证而不是单一训练/验证划分这个问题特别关键。很多初学者写优化算法调参时直接用一次训练集/验证集划分的RMSE作为适应度这样跑出来的参数往往在验证集上表现好换一组数据就失效——本质上是把验证集信息泄漏到了搜索过程里。我在HHO-LSBoost里用的是5折交叉验证的RMSE均值作为适应度。做法是把训练集分成5份每轮用4份训练LSBoost、1份预测轮转5次取平均误差。这只每个适应度调用要训练5次LSBoost时间成本翻倍但换来的是参数选择的稳定性。数据集比较小的时候这个代价是值得的。如果数据量很大比如几万条5折交叉验证会让整个优化过程非常漫长还有一个折中方案用简单的Hold-out验证集做初筛等HHO收敛了再对最优参数附近的候选解做一次5折交叉验证确认。虽然不是最严谨但能显著提升调参效率。4. HHO-LSBoost完整流程与核心代码前几节把算法原理和配置说清楚了这一节进入正题怎么把这些东西组织成一套跑得通的Matlab代码工程。我会按模块拆解给出关键代码骨架并说明每段代码的设计意图。4.1 编程架构与函数划分一套完整的HHO-LSBoost代码至少需要三个文件main.m主脚本负责读数据、调用HHO、输出结果。HHO_lsboost.m哈里斯鹰优化算法主体输入是适应度函数句柄、参数边界、种群规模和迭代次数输出最优参数和收敛曲线。obj_fun.m适应度函数输入是某个鹰个体的参数向量内部完成5折交叉验证LSBoost输出回归误差。分成三个独立函数的好处是以后想换成别的优化算法或者把LSBoost换成SVR、BP神经网络只需要替换中间模块不需要重写整个框架。我自己在做对比实验时就是一套主脚本配多套优化算法非常省事。主脚本的数据读取部分就不多写了重点看HHO和适应度函数怎么设计。4.2 适应度函数设计适应度函数是连接HHO和LSBoost的桥梁。它接收的参数向量x里包含三个维度NumLearningCycles、LearnRate、MaxNumSplits。注意参数向量传给HHO时会经过归一化范围内的取值所以第一步要把它映射回真实参数空间。function rmse obj_fun(x, X_train, y_train) % 参数还原 nTrees round(x(1)); % 树数量必须取整数 learnRate x(2); % 学习率保持连续值 maxSplits round(x(3)); % 最大分裂数取整数 % 参数合法性检查 if nTrees 5 || maxSplits 1 rmse 1e10; return; end % 5折交叉验证 rng(42); % 固定随机种子保证可复现 cv cvpartition(size(X_train, 1), KFold, 5); losses zeros(5, 1); for k 1:5 trainIdx training(cv, k); testIdx test(cv, k); model fitrensemble(X_train(trainIdx, :), y_train(trainIdx), ... Method, LSBoost, ... Learner, tree, ... NumLearningCycles, nTrees, ... LearnRate, learnRate, ... MaxNumSplits, maxSplits); pred predict(model, X_train(testIdx, :)); losses(k) sqrt(mean((pred - y_train(testIdx)).^2)); end rmse mean(losses); end这里有几个关键细节round()取整非常重要。HHO在连续空间里搜索但树数量和最大分裂数都必须是正整数。参数合法性检查不能省。HHO前期会随机跑到边界附近如果不加检查fitrensemble可能因为树数量太小或者分裂数太小直接报错。rng(42)固定随机种子是为了让同一个参数组合每次计算出的适应度一致否则交叉验证的随机划分每次都不同HHO会比较出错误的最优解。4.3 HHO主循环的Matlab实现下面给出HHO主循环的核心骨架。为了让代码可读性更高我删掉了一些边缘检查代码保留最重要的搜索逻辑function [best_pos, best_fit, convergence] HHO_lsboost(obj_fun, lb, ub, dim, N, T, X_train, y_train) % 初始化种群 X repmat(lb, N, 1) rand(N, dim) .* repmat(ub - lb, N, 1); fit zeros(N, 1); for i 1:N fit(i) obj_fun(X(i, :), X_train, y_train); end [best_fit, idx] min(fit); best_pos X(idx, :); convergence zeros(T, 1); for t 1:T % 逃逸能量衰减 E0 2 * rand - 1; E 2 * E0 * (1 - t / T); for i 1:N r rand; q rand; % 探索阶段 if abs(E) 1 if q 0.5 rand_idx randi(N); X(i, :) X(rand_idx, :) - rand * abs(X(rand_idx, :) - 2 * rand * X(i, :)); else X_mean mean(X); X(i, :) best_pos - X_mean - rand * (lb rand * (ub - lb)); end % 开发阶段 else deltaX best_pos - X(i, :); J 2 * (1 - rand); if abs(E) 0.5 r 0.5 % 软围困 X(i, :) deltaX - E * abs(J * best_pos - X(i, :)); elseif abs(E) 0.5 r 0.5 % 硬围困 X(i, :) best_pos - E * abs(deltaX); elseif abs(E) 0.5 r 0.5 % 软围困 渐进式俯冲 Y best_pos - E * abs(J * best_pos - X(i, :)); Z Y levy_flight(dim) .* (ub - lb) .* rand(1, dim); if obj_fun(Y, X_train, y_train) fit(i) X(i, :) Y; elseif obj_fun(Z, X_train, y_train) fit(i) X(i, :) Z; end else % 硬围困 渐进式俯冲 X_mean mean(X); Y best_pos - E * abs(J * best_pos - X_mean); Z Y levy_flight(dim) .* (ub - lb) .* rand(1, dim); if obj_fun(Y, X_train, y_train) fit(i) X(i, :) Y; elseif obj_fun(Z, X_train, y_train) fit(i) X(i, :) Z; end end end % 边界处理 X(i, :) max(X(i, :), lb); X(i, :) min(X(i, :), ub); % 更新适应度 new_fit obj_fun(X(i, :), X_train, y_train); if new_fit fit(i) fit(i) new_fit; end end % 更新全局最优 [current_best, idx] min(fit); if current_best best_fit best_fit current_best; best_pos X(idx, :); end convergence(t) best_fit; fprintf(第 %d 次迭代, 最优RMSE %.6f\n, t, best_fit); end endlevy_flight函数可以用一个简单的Matlab实现替换function L levy_flight(dim) beta 1.5; sigma (gamma(1 beta) * sin(pi * beta / 2) / ... (gamma((1 beta) / 2) * beta * 2^((beta - 1) / 2)))^(1 / beta); u randn(1, dim) * sigma; v randn(1, dim); step u ./ abs(v).^(1 / beta); L step; end这一段代码基本就是论文算法的标准实现结合上一节适应度函数就能跑通整条流程。跑完之后best_pos就是HHO找到的最优LSBoost超参数组合用它重新在完整训练集上训练一次模型再对测试集做预测计算各项评估指标绘制预测值与真实值的对比图即可。4.4 一次完整运行的时间与收敛预期具体运行时间取决于三个因素数据规模、交叉验证折数、种群和迭代次数。我用一个800条样本、8个输入特征、50棵树的配置做过测试单次适应度计算大约0.4秒N15、T50意味着750次适应度调用总时间大概是5到8分钟。这个时间在学术实验里完全可以接受。如果数据上万条建议把迭代次数压到20到30次先跑通再决定是否加大。收敛曲线上比较理想的情况是前10次迭代RMSE快速下降20次以后进入平台期这种时候搜索可以提前终止。如果收敛曲线一直在缓慢下降甚至震荡说明适应度函数存在较大噪声优先检查是不是没有固定随机种子。5. 常见问题与排查技巧实录代码能跑通和跑得好是两个层面的事情。我在调HHO-LSBoost的过程中踩过不少坑这里整理成几个高频问题全是实际操作中会遇到的状况。5.1 收敛慢、结果不稳定的原因与对策如果你发现HHO跑了50次迭代RMSE还在小幅波动或者每次运行得到的最优参数差异很大通常是三方面原因随机种子没有固定适应度函数里的交叉验证划分必须固定种子否则同一个参数的适应度每次算出来都不同HHO的搜索方向会被噪声带偏。建议在obj_fun和main里都写上rng(固定值)。参数边界设置过宽当学习率允许在[0.001, 1]之间搜索时HHO会花大量迭代尝试0.9、0.8这种大学习率而这类参数在LSBoost里基本都会过拟合。把边界收紧到[0.01, 0.3]附近收敛会快很多。种群多样性丢失如果收敛曲线一开始就骤降然后长期不动说明HHO过早聚集到了局部最优附近。解决办法是加大Levy飞行的步长或者把种群规模从10提高到20增加初始覆盖范围。另外LSBoost本身的NumLearningCycles如果设得太大比如超过500单次训练时间会大幅上升。可以用早停机制来解决Matlab里设置ValidationData并打开EarlyStopping但注意在交叉验证循环内部使用时要提前划分好验证子集代码会稍复杂一些。5.2 过拟合问题优化之后测试集反而更差这种情况我遇到过一次HHO在训练集的交叉验证上找到了很低的RMSE但测试集表现比默认参数还差。排查后发现原因有两个一是交叉验证的折数太少3折导致适应度评估不够稳定二是fitrensemble训练出来的模型没有做早停树数量被HHO推到了边界上的大值。解决过拟合有几个有效手段在适应度函数里就限制树的复杂度把MaxNumSplits的上界设小一点比如不超过20。使用5折或10折交叉验证折数太少偏差大折数太多计算量大5折对一般数据量是合理折中。对测试集的预测结果不要只看一次运行。HHO是启发式算法每次运行有一定随机性最终结论建议多重跑几次报告均值±标准差这才是学术上严谨的做法。5.3 代码运行报错与处理速查下面这张表整理了我在实际运行中最常见的报错场景和对应解法报错信息常见原因解决方案Error using fitrensemble ... Invalid argument参数向量还原后出现非正值或非整数在obj_fun开头做合法性检查把非法值直接返回大数作为惩罚NaN or Inf values in training dataHHO越界位置没有正确约束Levy飞行生成极端值检查边界处理代码确保每轮迭代后位置都被约束在lb和ub内Out of memoryNumLearningCycles被搜索到极大值比如上千把树数量边界收紧到[10, 200]必要时在适应度函数里加一个硬上限训练时间过长数据量大且迭代次数多减小种群或迭代次数先用30次迭代跑通后续再加大预测结果全是同一个值LSBoost学习率过大或树数量过少检查最优参数手动降低学习率再看预测效果还有一个经验之谈fitrensemble在Matlab新版本里如果特征矩阵是稀疏矩阵有些配置会警告甚至报错建议先把数据转为普通double矩阵。这个问题在R2020a之后都遇到过几次跟版本有关系排错时别忽略。5.4 一个值得尝试的改进方向HHO-LSBoost跑通后如果你想继续扩展有一个很自然的改进方向把特征选择也放进HHO的搜索空间里。也就是说每个鹰个体的位置不光包含树数量、学习率、最大分裂数还包含一组二进制掩码表示哪些特征参与训练。这样HHO不仅能调参还能做特征筛选对高维数据效果往往比纯调参提升更明显。实现上可以把二进制掩码和多维连续参数拼接在一起离散部分用round取整大部分代码结构不需要改动。6. 写在最后的几点个人体会这套HHO-LSBoost方案我自己前前后后跑了有小半年从最开始的HHO代码都写不全到后来能在一个晚上完成多个数据集的对比实验。印象最深的一次是拿一套工业设备监测数据做预测网格搜索跑了快三个小时没出结果换HHO之后四十分钟收敛交叉验证RMSE还比网格搜索低了7%。虽然不能说HHO在所有场景下都比其他优化算法强但至少在LSBoost这种训练成本中等的模型上它的性价比确实很高。最后分享两个小技巧。第一HHO收敛之后建议把最优参数附近再做一次小范围的精细搜索方法很简单把lb和ub设为最优参数的±20%再跑一轮20次迭代的HHO往往还能再扣出一点误差。第二Matlab代码里多打印中间结果尤其是每一轮的best_fit和运行时间出了问题能第一时间判断是卡在搜索环节还是卡在训练环节。调模型这事儿没有捷径就是一遍遍跑、一遍遍改、一遍遍记录但你手上如果有HHO这种自动调参工具至少能把精力从烦躁的调参里解放出来留到真正需要动脑子的地方去。
返回列表