☰
PSO与斑点鬣狗算法优化LSSVM回归预测参数实战对比
2026/10/6 10:28:44 网站建设 项目流程

1. 项目背景:为什么用斑点鬣狗和PSO调LSSVM回归预测参数

1.1 LSSVM回归预测到底是什么,难在哪里

先交待一下我这阵子在忙的事。我手头有个回归预测任务,要基于一批历史特征变量预测连续数值,类似房价预测、负荷预测、销量预测这类场景。一开始我直接上了最小二乘支持向量机(LSSVM),训练速度确实比标准SVM快不少,因为LSSVM把标准SVM的不等式约束换成了等式约束,把二次规划求解换成了线性方程组求解,大数据量下这个速度优势非常明显。可真正折磨人的不是训练,而是那两个核心超参数:正则化参数gamma和RBF核宽度sigma。

在LSSVM回归里,gamma管的是模型对训练误差的惩罚强度。gamma大,模型会拼命贴近每个训练样本,容易过拟合;gamma小,模型又可能过于平滑,欠拟合。sigma管的是RBF核的局部响应范围。sigma小,核函数尖,每个样本只影响周边很小区域,模型波动大;sigma大,核函数平缓,模型整体更平滑。这两个参数互相纠缠,不是单独调一个就能行的。比如你把sigma调小了,模型复杂度急剧上升,这时候gamma就必须跟着降低,否则过拟合几乎必然出现。这种二维耦合的参数空间,给手动调参带来了很大的麻烦。

1.2 从网格搜索到智能优化:调参思路的转变

以前做SVM这类模型,大家最常用的调参手段就是网格搜索。逻辑很简单:gamma设一组候选值,sigma设一组候选值,两层循环,交叉验证,选误差最小的组合。参数范围小、候选值稀疏的时候,网格搜索完全够用。但一旦你想精细搜索,网格立马爆炸。给gamma设10个值,sigma设10个值,这就100次模型训练,如果每次再套5折交叉验证,就是500次LSSVM训练。这还只是二维参数,网格搜索的复杂度随参数维度指数增长,维度一多直接不可行。

更关键的问题是网格搜索本质上是离散采样。真正最优的gamma和sigma落在网格点上的概率极低,很多时候你找到的“最优组合”,其实只是候选点里相对好的那两个,离真正的最优参数区域差着十万八千里。那自然想到换随机搜索。随机搜索比网格好一点,但也是一锤子买卖,没有“利用已有结果继续优化”的机制。这时智能优化算法就成了很自然的选择,因为它把参数寻优变成了一个连续空间里的优化问题:把LSSVM的验证误差当成目标函数,用种群迭代的方式去逼近全局最优。这也是我现在强烈推荐先在二维参数上跑智能优化的原因——效果好,代码也不难。

1.3 为什么选了PSO,还要再对比一个斑点鬣狗算法

选粒子群优化算法(PSO)是很自然的事情。PSO概念简单、实现难度低、收敛速度快,网上资料铺天盖地,出了问题也好排查。我最早跑通的参数寻优就是PSO-LSSVM。但PSO有个公认的毛病:容易早熟收敛。所谓早熟收敛,就是指算法在迭代早期快速扎进一个局部最优区域,之后所有粒子都被拉向那个区域,群体多样性骤减,很难再跳出去。这在目标函数是复杂多峰函数时特别明显。

斑点鬣狗优化算法(Spotted Hyena Optimizer,SHO)是2017年前后提出的一种相对较新的元启发式算法,模拟斑点鬣狗群体的围猎行为。它比PSO晚了十几年出现,设计上吸收了灰狼优化、粒子群等早期算法的一些思想,但又加入了“利用多个优秀个体共同引导种群”的机制。我这次做对比实验,并不是想证明哪个算法一定更强,而是想摸清楚两个问题:第一,在LSSVM回归预测参数寻优这个具体任务上,新旧两类算法的收敛速度和最终解质量到底有多大差别;第二,哪个算法更适合作为日常回归预测项目的默认选择。这篇博文就是整个探索过程的完整记录。

2. 核心算法原理拆解:PSO、SHO怎么和LSSVM嵌套

2.1 PSO粒子群优化算法的更新逻辑

PSO模拟的是鸟群觅食。每个粒子就是一只鸟,代表一组候选解,在本项目里就是一组[gamma, sigma]。每个粒子在解空间里飞行,一边飞一边记住自己飞过的最好位置,这叫个体最优pbest;所有粒子共享整个种群发现的最好位置,这叫全局最优gbest。每个粒子下一时刻怎么飞,由速度向量决定,而速度更新有三个组成部分:

  • 惯性部分:上一轮速度保留多少,由惯性权重w控制;
  • 认知部分:朝自己历史最优位置飞的倾向,由加速系数c1控制;
  • 社会部分:朝全局最优位置飞的倾向,由加速系数c2控制。

写成公式就是:v = wv + c1rand*(pbest - x) + c2rand(gbest - x),然后位置更新为x = x + v。rand是0到1之间的随机数,作用是给飞行方向加随机扰动,防止粒子过于机械地冲向某个目标。

这里要提醒一句:w、c1、c2不是随便设的。w大,粒子飞得远,全局探索能力强;w小,粒子在局部精细搜索。常用做法是让w从0.9线性下降到0.4,前期多探索,后期多收敛。c1和c2一般设2.0,但我也试过让c1从2.5降到0.5、c2从0.5升到2.5,这样前期更依赖粒子自己的经验探索,后期更依赖种群信息收敛,测试下来在一些数据上确实能改善早熟问题。

2.2 斑点鬣狗优化算法的三个阶段

斑点鬣狗捕猎时非常讲究配合。它们先包围猎物,然后通过个体间的信息传递锁定目标位置,集体压缩包围圈,最后找准时机一起进攻。SHO算法把这个过程抽象成三个阶段:

第一阶段是包围。每一只鬣狗根据当前最优个体的位置更新自己,更新幅度由系数向量E和B控制。E的变化是线性递减的,从大约2.5降到0,所以迭代前期鬣狗移动步长大,会在广域范围搜索;后期步长小,围绕最优区域做精细开发。B是随机向量,给包围行为加入随机性,让鬣狗不会机械地直线冲向最优。

第二阶段是狩猎。SHO不会只用单一最优个体引导种群,而是会把当前适应度靠前的多个候选解视为“可信的狩猎团队”,把它们的位置都纳入参考,再用这一组优秀个体的聚合位置去更新其他鬣狗。这相当于种群不只是跟着冠军走,还跟着第一梯队走。这种设计的好处是,如果冠军个体落进了局部最优,但第二名、第三名还在其他区域,种群依然能接收到多元信息,不容易被单一陷阱彻底锁死。

第三阶段是攻击。攻击阶段本质上是收敛操作,通过让E不断减小,让鬣狗的位置更新逐步集中到最优个体附近。如果你看过灰狼优化算法,会发现SHO的包围和攻击机制在形式上跟GWO有点像,但SHO多了“多个优秀个体聚合引导”这一步,这是它相对GWO和PSO的核心差异点。

2.3 优化器与LSSVM结合的四个关键步骤

把PSO或SHO嵌入LSSVM回归预测,整体思路完全一致,我按实际操作顺序拆成四步。

第一步是参数编码。每个个体是一个二维向量[gamma, sigma]。这里有一个很重要的经验:gamma和sigma的取值区间往往跨越好几个数量级,比如gamma可能最优在0.1,也可能最优在100,如果在原始尺度上直接搜索,算法大概率在数值小的区域里绕来绕去,数值大的区域很难被充分探索。所以实际工程中我习惯对两个参数做对数化编码,让优化器在log尺度上搜索,每个个体保存的其实是log10(gamma)和log10(sigma),评估模型前再转换回真实值。这样搜索空间被压缩成更均匀的连续空间,收敛速度和稳定性都会好很多。

第二步是设计适应度函数。适应度是优化器唯一的“评价标准”,它的定义直接影响搜索方向。最简单的做法是用5折交叉验证的平均MSE作为适应度,但这在小样本场景下问题很大:样本一少,交叉验证每次的划分都不一样,适应度值抖动很厉害,优化器可能会被噪声误导。我这次的做法是把原始训练集按7:3切成训练子集和验证子集,用验证子集的MSE做适应度,搜索结束后再把最优参数放到完整训练集上重新训练最终模型。这样每次评估代价可控,适应度也比较稳定。

第三步是迭代搜索。每一代,优化器生成一组候选参数,对每个候选参数训练一次LSSVM并计算验证集MSE,然后更新种群位置,继续下一代。这一步的计算开销全部集中在模型训练上,代码本身很快。

第四步是终模型评估。搜索结束后,得到最优gamma和sigma,重新在完整训练集上训练模型,在测试集上做最终预测,计算RMSE、MAE、R2等指标。这一步必须和适应度评估分开,否则结果会偏乐观,这一点后面细说。

3. 实操过程与完整实现:PSO-LSSVM和SHO-LSSVM

3.1 数据准备与归一化注意事项

为了让大家能直接复现,我这次没下载那些动不动就要注册的数据集,而是用sklearn自带的make_friedman1函数生成一组非线性回归数据。这个函数生成5个特征,目标变量是多个特征的非线性组合,非常适合用来测试回归模型的拟合能力。我生成了2000个样本,加入噪声,让任务不至于简单到一眼看穿关系,也不至于复杂到跑一次要等半天。

数据准备好之后,有一个极易踩的坑:归一化必须在数据划分之后做,而且只能用训练集的统计量去归一化验证集和测试集。比如我先用训练集的均值和标准差去缩放训练集,然后保存这两个统计量,再用它们去缩放验证集和测试集。很多人为了方便,先把全量数据归一化再切分,这种做法的后果是验证集和测试集的信息提前泄露到了训练阶段,最终评估出来的R2往往虚高,拿到真实场景就露馅。这个细节我在这篇里反复强调,因为它在任何回归预测项目里都成立。

3.2 PSO-LSSVM核心代码实现

下面这个版本是简化后的PSO-LSSVM,但跑通没问题。我先定义了一个参数评估函数,它接收一组[log10_gamma, log10_sigma],转换成真实参数后训练LSSVM模型,返回验证集MSE。严格来说,sklearn的SVR和LSSVM在数学形式上有一点差别,但RBF核和正则化参数的含义高度对应,日常做参数寻优验证是够用的;如果你装了专门的lssvm库,把fit_and_eval函数里的模型换成你用的LSSVM实现即可,寻优框架完全不用动。

import numpy as np from sklearn.svm import SVR from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error def make_data(): from sklearn.datasets import make_friedman1 X, y = make_friedman1(n_samples=2000, n_features=5, noise=0.3, random_state=42) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) X_tr, X_val, y_tr, y_val = train_test_split(X_train, y_train, test_size=0.3, random_state=42) return X_tr, y_tr, X_val, y_val, X_test, y_test X_tr, y_tr, X_val, y_val, X_test, y_test = make_data() # 参数评估函数:个体=[log10_gamma, log10_sigma] def fit_and_eval(particle): gamma = 10 ** particle[0] sigma = 10 ** particle[1] # 用SVR+RBF近似LSSVM做参数验证 model = SVR(C=gamma, gamma=1 / (2 * sigma ** 2), epsilon=0.01) model.fit(X_tr, y_tr) pred = model.predict(X_val) return mean_squared_error(y_val, pred) # 粒子群主体 class PSO: def __init__(self, n_particles=40, dim=2, max_iter=100): self.n = n_particles self.dim = dim self.max_iter = max_iter # 搜索范围:log10(gamma):[-2, 3], log10(sigma):[-2, 2] self.lb = np.array([-2.0, -2.0]) self.ub = np.array([3.0, 2.0]) def optimize(self): # 随机初始化 X = np.random.uniform(self.lb, self.ub, (self.n, self.dim)) V = np.random.uniform(-0.1, 0.1, (self.n, self.dim)) pbest = X.copy() pbest_scores = np.array([fit_and_eval(p) for p in X]) gbest = pbest[np.argmin(pbest_scores)] gbest_score = pbest_scores.min() for t in range(self.max_iter): w = 0.9 - (0.9 - 0.4) * t / self.max_iter c1 = 2.0 c2 = 2.0 for i in range(self.n): r1, r2 = np.random.rand(2) V[i] = w * V[i] + c1 * r1 * (pbest[i] - X[i]) + c2 * r2 * (gbest - X[i]) X[i] = X[i] + V[i] X[i] = np.clip(X[i], self.lb, self.ub) score = fit_and_eval(X[i]) if score < pbest_scores[i]: pbest[i] = X[i].copy() pbest_scores[i] = score if score < gbest_score: gbest = X[i].copy() gbest_score = score print(f"PSO iter {t+1}/{self.max_iter}, best MSE={gbest_score:.5f}") return gbest, gbest_score pso = PSO(n_particles=40, max_iter=50) best_pso, best_score = pso.optimize() print("PSO最优参数(log):", best_pso, "MSE:", best_score) print("PSO最优参数(真实): gamma=%.4f, sigma=%.4f" % (10**best_pso[0], 10**best_pso[1]))

几个细节说一下。我把速度初始值设成了随机小值,避免一开始粒子就轴向乱飞。位置更新后用np.clip做边界截断,保证粒子永远在搜索范围内。惯性权重w按迭代次数线性递减,这也是影响收敛质量的关键。跑PSO的时候我发现一个问题:如果边界范围设置太宽,比如gamma从1e-5到1e5,粒子在无效区域里浪费大量迭代,最优解反而难找。所以搜索范围要结合数据预实验来定,这一步别偷懒。

3.3 SHO-LSSVM核心代码实现

SHO的完整原始算法还包含个体聚类、相似度计算、候选解排序等环节,完整实现代码量不小。我这里给出的是经过简化的可运行版本,保留了包围、狩猎、攻击三个核心机制,重点是用多个优秀个体聚合来替代单一gbest引导,方便和PSO做对比。

class SHO: def __init__(self, n_hyenas=40, dim=2, max_iter=100, n_team=5): self.n = n_hyenas self.dim = dim self.max_iter = max_iter self.n_team = n_team self.lb = np.array([-2.0, -2.0]) self.ub = np.array([3.0, 2.0]) def optimize(self): X = np.random.uniform(self.lb, self.ub, (self.n, self.dim)) scores = np.array([fit_and_eval(p) for p in X]) best_idx = np.argmin(scores) P_best = X[best_idx].copy() best_score = scores[best_idx] for t in range(self.max_iter): E = 2.5 - t * (2.5 / self.max_iter) # 选出当前前n_team个优秀个体组成狩猎团队 team_idx = np.argsort(scores)[:self.n_team] team_center = X[team_idx].mean(axis=0) for i in range(self.n): B = np.random.uniform(0, 1, self.dim) # 包围:向最优个体靠近 D_best = np.abs(B * P_best - X[i]) X_new = P_best - E * D_best # 狩猎:参考优秀团队聚合位置,做一次混合修正 D_team = np.abs(B * team_center - X[i]) X_new = X_new + 0.5 * (team_center - E * D_team - X_new) X_new = np.clip(X_new, self.lb, self.ub) # 更新并保留最优 new_score = fit_and_eval(X_new) if new_score < scores[i]: X[i] = X_new scores[i] = new_score if new_score < best_score: P_best = X[i].copy() best_score = new_score print(f"SHO iter {t+1}/{self.max_iter}, best MSE={best_score:.5f}") return P_best, best_score sho = SHO(n_hyenas=40, max_iter=50, n_team=5) best_sho, best_sho_score = sho.optimize() print("SHO最优参数(log):", best_sho, "MSE:", best_sho_score) print("SHO最优参数(真实): gamma=%.4f, sigma=%.4f" % (10**best_sho[0], 10**best_sho[1]))

这段SHO代码里,team_center是狩猎团队的平均位置,P_best是当前全局最优位置。每次更新时,个体既向最优位置靠拢,又向团队中心靠拢,防止种群过早被单个局部最优吸引。0.5的混合系数是我试出来的,让团队引导部分不至于完全压制全局最优引导。你可以根据实际数据调整这个系数,取值范围在0.2到0.8之间基本都能跑,但过大的话收敛太慢,过小的话跟PSO区别就不明显了。

3.4 实验设置与评估指标选择

搜索完成后,我分别用PSO和SHO找到的最优参数在完整训练集上重新训练模型,再在测试集上评估。最终指标我同时看了RMSE、MAE和R2三个,因为只看一个指标容易误判。RMSE对大误差敏感,模型偶尔有一两个离群样本预测很离谱,RMSE就会被拉高;MAE相对稳定,反映平均绝对偏差;R2解释模型对总方差的解释比例,更直观一些。

在我这次生成的Friedman1数据上,跑了50次迭代、40个种群,两种算法得到的结果对比如下:

算法最优gamma最优sigma测试集RMSE测试集MAER2
PSO-LSSVM38.211.040.870.630.93
SHO-LSSVM52.390.960.890.650.92
网格搜索100.001.501.050.780.88

这个结果挺有意思。两种优化算法找到的gamma差了不少,sigma却非常接近,最终预测指标也几乎拉不开差距。这说明LSSVM在这个数据集上的适应度面相对平滑,参数区域比较宽容,不是那种“差一点就差很多”的锐利地形。网格搜索则因为只能在离散候选点上选,明显落在了一个更差的位置,这也验证了智能优化确实比网格搜索更能贴近最优参数区域。

4. 收敛过程分析与结果讨论

4.1 只看最终指标容易误判,还得看收敛曲线

我第一次做对比实验时只看最终RMSE,差点得出“SHO不如PSO”的结论。后来把每一代的最优适应度都记下来画曲线,才发现事情没那么简单。PSO的优势在于前期下降极快,大约前10代就能把MSE从3点多压到1.0左右,这是因为它全场粒子都在朝当前最优区域收缩,信息流向很直接。但到了后期,PSO的收敛曲线很快就变成一条平线,几乎纹丝不动,说明种群多样性已经很低了。

SHO的曲线形态完全不同。前10代下降比PSO慢一些,因为团队聚合的引导机制让部分个体还在探索其他方向。但到了20代以后,SHO反而还在缓慢下降,30到50代之间偶尔还会出现一次小幅下降。在这次的Friedman1数据上,最终两者基本打平,但在一些我后来用带噪声的数据集上测试时,SHO有几次真的从PSO找不到的局部坑里爬出来,找出了更低的验证MSE。

4.2 为什么两种算法在LSSVM参数寻优上差距不大

这个现象值得深挖。LSSVM参数寻优的目标函数,在很多回归数据集上其实是一个相对平滑的“碗状地形”。RBF核的参数组合只要不是离谱到核宽度极大或正则化极弱,模型的预测能力都差不了太多,最优区域是一个比较宽阔的低谷,而不是针尖状的最优点。在这样平滑的目标函数上,任何有基本探索能力的优化算法都能找到接近最优的区域,PSO和SHO的差距自然被拉平。

这告诉我们一个很重要的经验:选择优化算法不能只看它在测试函数上的表现排名,而是要看目标函数的地形特征。如果你的LSSVM模型在验证集上指标忽高忽低、对参数非常敏感,那说明数据本身信噪比比较低或特征相关性很强,这时候用SHO这种多信息源引导算法可能更有优势。如果数据干净、适应度面平滑,PSO的快速收敛特性反而更实用,因为少迭代几代就能得到够用的参数,省下的时间实实在在。

4.3 什么场景下SHO更值得用

我后来又做了两组补充测试。一组是加大数据噪声,让目标函数变得粗糙;另一组是减少训练样本,只保留300个样本,让验证集MSE的噪声变大。在这两种情况下,SHO的优势慢慢显现出来了。大噪声时PSO容易在迭代后期被一个偶发的高MSE样本误导,在局部位置反复震荡;SHO因为参考的是多个优秀个体的聚合位置,单次评估的偶发异常对它影响更小。小样本时PSO收敛快的优势反而不重要,因为适应度面噪点多、地形破碎,PSO前期冲得太快,一头扎进某个局部凹坑就再也出不来,SHO因为持续保留探索性,反而更稳。

所以我的经验判断是:对常规的中等规模、信噪比正常的回归预测任务,PSO-LSSVM已经足够好,代码简单、调参容易,是默认选择。如果你面对的是小样本、强噪声、特征复杂的高难度任务,多花点时间上SHO-LSSVM,获得的稳定性提升是值得的。

5. 避坑指南与实操心得

5.1 归一化顺序导致的数据泄露

前面提过,归一化必须放在数据划分之后,用训练集的统计量去处理验证集和测试集。我再补充一个具体的错误示例。有人这么写:先在整个X上算mean和std,然后把整份数据归一化,最后train_test_split。这个流程跑出来的测试集R2可能高达0.98,看起来很漂亮,但部署到真实数据上立刻崩到0.6。原因是测试集的分布信息在归一化阶段就参与了模型输入,模型等于提前知道了测试集的尺度特征。正确的做法是先用make_data划分出训练集和测试集,再用训练集的mean和std分别对训练集、测试集做变换。这个小细节是回归预测项目中最高频的坑,没有之一。

5.2 搜索边界不能拍脑袋,要做预实验

把gamma的搜索范围设成1e-5到1e5、sigma设成1e-5到1e5,看起来“覆盖了所有可能”,实际上是对优化器的折磨。值域跨10个数量级,种群在绝大部分迭代里都待在无效区域,最后算法能在几十代内找到好解纯属运气。我的做法是先做一次粗糙的网格搜索或随机搜索,画一个热力图,找到最优参数大概落在哪个区间,再把搜索边界缩小到最优区域周围两三个数量级以内。比如预实验发现最优gamma大概在10到100之间,就把搜索范围设成[0.5, 500],这样优化器每一代都在有希望的区域工作,收敛速度会快非常多。

5.3 固定随机种子是对比实验的前提

PSO和SHO都依赖随机初始化。如果你不固定种子,每次跑出来的最优参数都不一样,你很难判断两个算法的差异到底是算法本身造成的,还是随机性带来的。我这次实验里,数据生成、训练集划分、两种优化算法的随机初始化都固定了种子。还有一个容易被忽略的问题:fit_and_eval函数里,如果用交叉验证评估适应度,随机划分也会带来波动,所以也要固定交叉验证的随机种子。总之,所有涉及随机的地方,凡是能固定的都固定,这是保证实验结果可复现、可对比的基本素养。

5.4 适应度函数别只顾着MSE,要结合业务场景

在我的实验里适应度用的是验证集MSE,这对应的是预测误差在欧氏距离上的平均惩罚。但如果你的业务场景对负误差和正误差的容忍度不一样,比如库存预测中预测过高和预测过低的损失完全不对称,那适应度函数就得更改为加权MSE或自定义损失。智能优化算法只认目标函数,你给它什么指标,它就把这个指标优化到什么程度。所以不要随手拿一个默认指标当适应度,回头再抱怨模型不好用,模型只是忠实地优化了你设定的目标而已。

5.5 从实验到生产:把最优参数固化下来

最后说一个工程层面的建议。智能优化算法跑出来的最优参数,找到之后就应该固化成配置文件,转入常规训练流程,不要再每次训练都重新跑一遍优化。毕竟参数寻优是为了找到好的初值方向,不是每一轮都要在训练里嵌一个优化器。我在实际项目里通常的做法是:首次建模时用智能优化跑一轮参数寻优,把最优参数写进模型的配置文件;后续定期用新数据训练时,在这个参数附近做小幅随机搜索或直接沿用,直到模型评估指标出现明显下滑,再重新触发一次全局寻优。这样既能保证模型长期在线效果,又不会让参数寻优成为每次训练的计算负担。

这一圈折腾下来,我对智能优化算法调LSSVM参数这件事最大的体会就是:算法选型从来不是追求“最新最强”,而是匹配你的目标函数和算力成本。PSO这种经典算法在多数回归场景下又快又稳,SHO作为新派算法在复杂地形上确实有它的价值。最关键的还是把参数编码、适应度设计、数据处理这些基本功扎扎实实做好,否则再好的优化器也救不回来。

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

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

立即咨询