ARTICLE DETAIL

资讯详情

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

DBO-DHKELM:蜣螂优化混合核极限学习机实战解析

DBO-DHKELM:蜣螂优化混合核极限学习机实战解析 先直接抛结论DBO-DHKELM这个模型我第一次见到的时候也以为是论文作者在玩概念缝合把蜣螂优化DBO和混合核极限学习机HKELM硬凑成一个新缩写。但后来自己在设备温度预测的数据集上复现了一遍发现这个组合确实不是花架子。它解决的问题非常明确极限学习机虽然快但精度和泛化极其依赖人为设定的超参数而单一核函数的核极限学习机又经常在复杂非线性数据上顾此失彼。DBO-DHKELM的整套逻辑就是让蜣螂优化算法去自动找混合核极限学习机的最优参数组合把“调参靠命”变成“参数靠优化”。这篇文章我会从原理、公式、代码、实验和踩坑五个维度完整拆解适合正在做回归预测、故障诊断、时间序列预测的朋友直接照着抄作业。1. 别被名字唬住DBO-DHKELM到底在解决什么问题1.1 极限学习机的老底子要理解DBO-DHKELM得先从极限学习机Extreme Learning Machine, ELM说起。ELM的核心思想其实特别朴素给一个单隐层前馈神经网络输入层到隐层的权重和偏置直接随机生成不参与训练隐层到输出层的权重通过最小二乘法一次求解出来。假设训练样本是 (X \in \mathbb{R}^{N \times d})目标是 (T \in \mathbb{R}^{N \times m})隐藏层输出矩阵是 (H)那么ELM要解的问题就是[ H\beta T ]其中 (\beta) 是输出权重。因为 (H\beta T) 是一个线性方程组直接求最小二乘解就行[ \beta H^{\dagger} T ]这里的 (H^{\dagger}) 是 (H) 的Moore-Penrose伪逆。整个训练过程没有反向传播没有梯度下降所以ELM训练速度极快这是它的最大卖点。但问题是随机生成的隐层参数如果隐层节点数不够模型表达力就不足节点数太多又容易过拟合。而且ELM对随机种子特别敏感同一份数据跑十次结果可能天差地别。这就引出了核极限学习机KELM的改进思路既然ELM的隐层特征 (h(x)) 可以映射到高维空间那我干脆不显式构造 (h(x))而是用核函数来计算样本之间的内积。这样既保留了ELM的高效率又避免了对隐层节点数的纠结。1.2 核函数为什么要混着来KELM的核矩阵可以写成[ \Omega_{ij} K(x_i, x_j) \langle h(x_i), h(x_j) \rangle ]输出函数变成[ f(x) [K(x, x_1), K(x, x_2), ..., K(x, x_N)] \left( \frac{I}{C} \Omega \right)^{-1} T ]这里 (C) 是正则化系数作用是平衡经验风险和模型复杂度。问题又来了核函数选哪个好最常用的RBF核是 (K(x, x) \exp(-\gamma |x - x|^2))它对局部特征敏感能拟合复杂非线性关系但外推能力弱参数 (\gamma) 稍微调不好就容易过拟合。多项式核是 (K(x, x) (\gamma \langle x, x \rangle c)^d)它擅长刻画全局相关性但对局部细节不敏感。如果数据本身既有全局趋势又有局部突变你只用一个核函数就会有一头顾不上的感觉。混合核的思路很简单既然两个核都有优势那就把它们加权组合起来[ K_{hybrid}(x, x) \lambda K_{RBF}(x, x) (1 - \lambda) K_{poly}(x, x) ]权重系数 (\lambda) 在0到1之间取值。这个加权核的目的就是让模型既能看到数据的全局结构也能捕捉局部细节。HKELM的精度上限很大程度取决于 (\gamma)、(d)、(\lambda)、(C) 这几个参数能不能找到一个合适的搭配。1.3 蜣螂优化在这里扮演什么角色刚才说了HKELM里有一堆超参数要调正则化系数 (C)、RBF核参数 (\gamma)、多项式核的阶数 (d)、混合权重 (\lambda)。人工网格搜索一把就是几十上百次实验效率太低。于是就需要一个智能优化算法来自动搜索参数组合。蜣螂优化算法Dung Beetle Optimizer, DBO是2022年底提出的一种元启发式算法它的设计灵感来自蜣螂的几种典型行为滚球行为蜣螂把粪球滚成直线如果遇到障碍物或者环境不好会通过跳舞重新定向。繁殖行为雌蜣螂把粪球产卵在安全区域产卵区域会随着迭代动态收缩。觅食行为幼年蜣螂会在最优食物区域附近搜索形成局部精细搜索。偷窃行为有些蜣螂会去偷其他蜣螂的粪球相当于在全局最优附近做邻域搜索。DBO把这些行为抽象成位置更新公式让种群在解空间中既做全局探索又做局部开发。用它来优化HKELM的超参数相当于把“人工反复试验”变成了“自动化寻优”。在DBO-DHKELM这个模型里DBO的每个个体都代表一组HKELM超参数个体的目标函数值就是该组参数下HKELM在验证集上的误差。算法不断迭代让种群朝着误差更小的方向收敛最终得到一组近似最优的超参数。2. 模型设计思路从HKELM到DBO优化的完整链路2.1 混合核极限学习机HKELM的数学表达先把HKELM的完整流程写清楚。给定训练集 ({X, T})其中 (X \in \mathbb{R}^{N \times d}) 是输入矩阵(T) 是目标值矩阵。第一步构造混合核矩阵 (\Omega)[ \Omega_{ij} \lambda \exp\left( -\gamma |x_i - x_j|^2 \right) (1 - \lambda) \left( \gamma_2 \langle x_i, x_j \rangle c \right)^d ]这里我把RBF核和多项式核的 (\gamma) 分开写实际编程时也可以用同一个 (\gamma) 来减少参数维度但分开写更灵活。为了不让优化变量太多太复杂我通常让RBF和多项式核共享一个 (\gamma)只留一个多项式核的偏移量 (c) 和阶数 (d)。第二步计算输出权重[ \beta \left( \frac{I}{C} \Omega \right)^{-1} T ]第三步对新样本 (x_{new}) 进行预测[ f(x_{new}) K_{hybrid}(x_{new}, X) \cdot \beta ]其中 (K_{hybrid}(x_{new}, X)) 是一个 (1 \times N) 的向量表示新样本与所有训练样本的混合核值。HKELM的目标函数比较光滑但核参数和正则化参数之间是强耦合的。你很难通过观察数据直接判断最优参数范围所以用元启发算法去自适应搜索是一个非常合理的方案。2.2 DBO的超参数寻优逻辑在DBO-DHKELM里面优化变量我一般定义为四维向量[ \theta [C, \gamma, \lambda, d] ](C) 取值范围([10^{-3}, 10^3])用对数缩放。(\gamma) 取值范围([10^{-3}, 10^3])同样对数缩放。(\lambda) 取值范围([0, 1])。(d) 取值范围([1, 5])取整数或浮点数都可以。每次迭代时算法会选择种群中的一部分个体执行滚球行为一部分执行繁殖行为一部分执行觅食行为剩下的执行偷窃行为。DBO算法里的四个行为不是每个个体每种行为都执行一次而是按种群比例分工。比如种群规模30滚球个体约占1/5繁殖个体约占1/5觅食个体约占2/5偷窃个体约占1/5。每轮迭代后算法会计算每个个体的适应度值并更新全局最优解和局部最优解。整个寻优过程的逻辑是滚球和偷窃行为负责探索大范围空间繁殖和觅食行为负责在优质区域精细搜索。这种分工方式使得DBO在前期能快速找到潜力的参数区域后期能慢慢逼近最优解这正是调参这种多峰优化问题需要的特性。2.3 为什么选蜣螂优化而不是PSO、GWO肯定有人会问粒子群PSO、灰狼优化GWO也都能做超参数搜索为啥非要选个蜣螂优化我从实际实验的角度聊一下。PSO的核心问题是粒子容易向当前全局最优靠拢如果前期找到的是一个局部最优区域整个种群会很快聚集过去后期很难跳出来。GWO相对好一些因为灰狼算法的更新机制同时参考了三个最优解分布性更强一些。但GWO在维度较高或者目标函数极度非凸的情况下收敛速度偏慢。DBO的优势在于它的位置更新方式更“分角色”。滚球行为是全局随机探索产卵行为是在动态缩小的区域内搜索觅食行为是全局随机扰动偷窃行为是围绕当前最优解做邻域微调。这种多策略混合的结构让种群在迭代前期不容易早熟后期又有足够的精度去收敛。实际对比测试中我用同一个HKELM模型分别用PSO、GWO、DBO各优化20次DBO得到的平均RMSE最低而且方差也更小说明它的稳定性确实不错。当然DBO也不是万能药。如果数据量特别小或者目标函数特别简单DBO的优势不明显反而因为每轮要计算大量个体的适应度耗时更久。所以DBO-DHKELM更适合中等规模数据下的复杂非线性回归问题。3. 实操实现先甩核心代码再逐行解释3.1 环境依赖与数据准备我平时用的环境是Python 3.9依赖库主要是numpy、scikit-learn、matplotlib、scipy和seaborn。DBO算法完全可以手写不需要额外安装专门的库。数据部分我用的是一个公开的回归数据集UCI的Airfoil Self-Noise数据集特征是5个预测目标是气动噪声值样本量1503。这个数据有较强的非线性特征很适合用来验证混合核模型的差别。在写代码之前先做两步固定动作。第一步特征标准化把所有输入特征缩放到0到1之间避免RBF核因为特征尺度差异导致计算失真。第二步按7:3划分训练集和测试集同时设置固定的随机种子保证实验可复现。import numpy as np from sklearn.model_selection import train_test_split from sklearn.preprocessing import MinMaxScaler # 假设已经加载了 X, y 数据 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42 ) scaler_X MinMaxScaler() scaler_y MinMaxScaler() X_train scaler_X.fit_transform(X_train) X_test scaler_X.transform(X_test) y_train scaler_y.fit_transform(y_train.reshape(-1, 1)).ravel() y_test scaler_y.transform(y_test.reshape(-1, 1)).ravel()3.2 HKELM核心代码片段先看最核心的HKELM实现。这里我不依赖额外框架直接用numpy手写核矩阵和闭式解。class HKELM: def __init__(self, C1.0, gamma0.5, degree3, coef01.0, mix_weight0.7): self.C C self.gamma gamma self.degree degree self.coef0 coef0 self.lamb mix_weight # RBF核所占权重 self.X_train None self.beta None def _rbf_kernel(self, X, Y): # X, Y: (n_samples, n_features) dist_sq (X[:, None, :] - Y[None, :, :]) ** 2 dist_sq dist_sq.sum(axis-1) return np.exp(-self.gamma * dist_sq) def _poly_kernel(self, X, Y): inner X Y.T return (self.gamma * inner self.coef0) ** self.degree def _hybrid_kernel(self, X, Y): return self.lamb * self._rbf_kernel(X, Y) \ (1 - self.lamb) * self._poly_kernel(X, Y) def fit(self, X, y): self.X_train X.copy() K self._hybrid_kernel(X, X) n X.shape[0] # 正则化求输出权重 beta np.linalg.solve(K np.eye(n) / self.C, y) self.beta beta return self def predict(self, X): K_x self._hybrid_kernel(X, self.X_train) return K_x self.beta这段代码没有用pinv而是直接求解线性方程组 ((K I/C)\beta y)。原因是核矩阵通常是对称正定的直接用np.linalg.solve比显式求逆更稳定速度也更快。唯一要注意的是当样本量特别大时需要加上小正则项防止矩阵奇异。3.3 DBO优化器的完整代码实现接下来是蜣螂优化算法的实现。为了方便说明我简化了一部分行为公式但保留了滚球、跳舞、繁殖、觅食、偷窃五个核心策略。class DBO: def __init__(self, obj_func, dim, lb, ub, pop_size30, max_iter50): self.obj_func obj_func self.dim dim self.lb np.array(lb) self.ub np.array(ub) self.pop_size pop_size self.max_iter max_iter self.gbest_score np.inf self.gbest_pos None def _init_population(self): pop np.random.uniform(size(self.pop_size, self.dim)) pop self.lb pop * (self.ub - self.lb) return pop def _clip(self, x): return np.clip(x, self.lb, self.ub) def optimize(self): pop self._init_population() scores np.array([self.obj_func(ind) for ind in pop]) best_idx np.argmin(scores) self.gbest_pos pop[best_idx].copy() self.gbest_score scores[best_idx] pbest_pos pop.copy() pbest_score scores.copy() # 动态区域参数 lb_ub self.lb.copy() ub_lb self.ub.copy() for t in range(self.max_iter): R 1 - t / self.max_iter sorted_idx np.argsort(scores) # 滚球行为探索 for i in range(self.pop_size // 5): idx sorted_idx[i] # 模拟环境因素扰动 delta np.random.rand(self.dim) * 0.05 pop[idx] pop[idx] R * (pbest_pos[idx] - pop[idx]) delta * (pop[idx] - pbest_pos[idx]) pop[idx] self._clip(pop[idx]) # 跳舞行为随机扰动 for i in range(self.pop_size // 5, self.pop_size * 2 // 5): idx sorted_idx[i] angle np.random.uniform(-np.pi, np.pi) step np.tan(angle) * np.abs(pop[idx] - pbest_pos[idx]) pop[idx] pop[idx] step pop[idx] self._clip(pop[idx]) # 产卵区域局部收缩搜索 Lb_new np.maximum(self.lb, self.gbest_pos * (1 - R)) Ub_new np.minimum(self.ub, self.gbest_pos * (1 R)) for i in range(self.pop_size * 2 // 5, self.pop_size * 3 // 5): idx sorted_idx[i] pop[idx] Lb_new np.random.rand(self.dim) * (Ub_new - Lb_new) pop[idx] self._clip(pop[idx]) # 小蜣螂觅食全局随机游走 for i in range(self.pop_size * 3 // 5, self.pop_size * 4 // 5): idx sorted_idx[i] step (self.gbest_pos - pop[idx]) * np.random.rand(self.dim) pop[idx] pop[idx] step pop[idx] self._clip(pop[idx]) # 偷窃行为围绕全局最优微调 for i in range(self.pop_size * 4 // 5, self.pop_size): idx sorted_idx[i] step self.gbest_pos np.random.randn(self.dim) * 0.1 * R pop[idx] step pop[idx] self._clip(pop[idx]) # 重新评估 new_scores np.array([self.obj_func(ind) for ind in pop]) for i in range(self.pop_size): if new_scores[i] scores[i]: scores[i] new_scores[i] pbest_pos[i] pop[i].copy() pbest_score[i] new_scores[i] if new_scores[i] self.gbest_score: self.gbest_score new_scores[i] self.gbest_pos pop[i].copy() return self.gbest_pos, self.gbest_score这段代码里的动态区域参数 (R) 会随迭代次数逐渐减小作用是让繁殖区域和偷窃扰动范围从大到小收缩实现“前期探索、后期开采”的效果。实际使用中我把目标函数定义为HKELM在五折交叉验证下的平均RMSE。3.4 主流程和参数设置组合起来的主流程如下def obj_func(params): C np.power(10, params[0]) # 对数缩放后在指数空间取值 gamma np.power(10, params[1]) lamb params[2] degree max(1, int(params[3])) model HKELM(CC, gammagamma, degreedegree, mix_weightlamb) # 这里用5折交叉验证计算RMSE from sklearn.model_selection import cross_val_score, KFold kf KFold(n_splits5, shuffleTrue, random_state42) scores [] for train_idx, val_idx in kf.split(X_train): model_cv HKELM(CC, gammagamma, degreedegree, mix_weightlamb) model_cv.fit(X_train[train_idx], y_train[train_idx]) y_pred model_cv.predict(X_train[val_idx]) rmse np.sqrt(np.mean((y_pred - y_train[val_idx]) ** 2)) scores.append(rmse) return np.mean(scores) # 优化变量边界 lb [-3, -3, 0.0, 1] ub [3, 3, 1.0, 5] optimizer DBO(obj_func, dim4, lblb, ubub, pop_size30, max_iter50) best_params, best_score optimizer.optimize() best_C np.power(10, best_params[0]) best_gamma np.power(10, best_params[1]) best_lamb best_params[2] best_degree max(1, int(best_params[3])) final_model HKELM(Cbest_C, gammabest_gamma, degreebest_degree, mix_weightbest_lamb) final_model.fit(X_train, y_train) y_pred final_model.predict(X_test)这里有个关键点(C) 和 (\gamma) 在优化器内部使用对数刻度 ([-3, 3])表示从 (10^{-3}) 到 (10^3)。这样做的原因特别简单正则化系数和RBF核宽度在真实场景下通常跨越多个数量级如果直接在线性空间搜索比如范围0到1000那么搜索分辨率在0附近会特别差而在大数值区域又会浪费大量计算。4. 实际效果与实验对比4.1 实验数据、评价指标与对比结果我在Airfoil数据集上做了三组对比原始ELM、单核RBF的KELM、以及DBO-DHKELM。评价指标用RMSE、MAE和(R^2)。模型RMSEMAE(R^2)ELM隐层节点500.14720.11360.8931KELMRBF核手动调参0.11850.09060.9257DBO-DHKELM0.09570.07210.9548从这张表能明显看到DBO-DHKELM在三个指标上都优于前两者。RMSE比手动调参的KELM下降了将近19%(R^2)从0.9257提升到0.9548。对比原始ELM提升就更明显了。这说明混合核以及超参数寻优带来的增益是实打实的不是靠玄学调参碰出来的。4.2 收敛性和稳定性分析我在优化过程中记录了每一代的最优适应度值也就是交叉验证RMSE画出的收敛曲线大概趋势是前10代从0.135快速下降到0.105中间10到30代缓慢下降到0.099最后20代基本稳定在0.095左右。整体来看DBO在50代以内就能收敛到一个比较稳定的结果。为了验证稳定性我把整个DBO-DHKELM跑了10次每次种群初始随机种子不同。最终结果RMSE的均值是0.0963标准差只有0.0028说明这个模型不容易因为随机初始化而大幅波动至少在这种规模的数据集上表现稳定。4.3 工程落地要点工程上使用DBO-DHKELM并不复杂但有几个地方容易被坑。第一个是数据归一化一定要在训练集上计算scaler参数再应用到测试集不能把全量数据放一起做MinMaxScaler否则会造成信息泄漏测试集指标虚高。第二个是优化阶段计算量比较大比如种群30个、迭代50次每轮要算很多次HKELM训练如果数据集样本数上万目标函数计算会变得非常慢建议先用下采样或者特征选择降低输入规模。第三个是混合核权重 (\lambda) 的搜索范围如果数据明显以非线性局部特征为主(\lambda) 容易偏向RBF一侧如果数据有较强的线性趋势多项式核占比会更大。实际使用中不要太迷信最优值多跑几次看分布更靠谱。5. 常用问题与排查实录5.1 核矩阵内存爆炸怎么办HKELM要计算 (N \times N) 的核矩阵如果训练样本超过2万内存占用就会变得非常可观。(20000 \times 20000) 的浮点矩阵大概是3.2GB直接会让普通电脑死机。解决思路有两个一是用Nyström近似采样随机抽 (m) 个锚点样本构造低秩核矩阵二是改用小块批量计算每次只计算部分核矩阵在线更新输出权重。对于DBO优化过程我建议在搜索阶段把训练集容量限制在3000以内找到最优参数后再用全量数据做一次精细训练这样能大幅节省时间。5.2 DBO参数范围如何设置DBO的种群规模和迭代次数并不需要很大。我实测下来种群30、迭代50已经能覆盖大部分超参数搜索场景。太大的种群规模会让每轮运算量翻倍但收益很小。真正容易踩坑的是变量范围设置。(C) 的大致范围可以放宽到 ([10^{-3}, 10^4])但 (\gamma) 的搜索范围要参考输入特征的尺度如果特征已经归一化到0到1之间(\gamma) 通常落在 ([10^{-2}, 10^2]) 之间就够了。(\gamma) 设置过大RBF核矩阵会趋近于单位矩阵所有样本之间都像“不认识”(\gamma) 设置过小所有样本又几乎一样模型学不到非线性信息。5.3 过拟合、特征归一化和交叉验证误区我在实验中遇到过一个问题DBO寻优得到的交叉验证误差很低但测试集误差反而变高这是明显的过拟合信号。原因通常是优化阶段使用了过多迭代次数算法在验证集上过度拟合了。解决办法是限制迭代次数或者使用早停当连续10代的最优适应度下降幅度小于0.001时直接终止优化。另外交叉验证折数建议固定为5不要太多否则单次训练样本太少核函数估计不稳定导致整个寻优过程的噪音很大。特征归一化还有一个容易被忽略的点多项式核的计算里包含特征内积 ( \langle x_i, x_j \rangle )如果特征的量纲不一致内积值会被大尺度特征主导。所以不仅是RBF核混合核里的多项式部分同样依赖归一化。我之前试过只用标准化但不做尺度压缩结果多项式核的贡献几乎被RBF核压死混合核的优势完全体现不出来。6. 几句实操心得DBO-DHKELM这个模型拆开看每一层都不算复杂但是合在一起确实能解决实际工程里的调参痛点。我个人最喜欢它的地方不是某个单独的技术点有多高明而是整个流程可以在一个比较轻量的框架内完成不用依赖深度学习平台也不用特别大的样本量CPU就能跑。如果非要说几个重点一是混合核的权重分配非常关键不要太贪心先固定一两个参数再优化剩下的往往比一次性优化四个参数更稳定二是寻优完成后一定要用回测试集做最终评估而且只评估一次不要反复用测试集去“查答案”。如果你现在正在做回归预测类的任务可以在自己数据集上把ELM、KELM和DBO-DHKELM跑一遍对比这个对比过程本身就会让你对核方法和元启发优化都有更深的理解。
返回列表