资讯动态

天牛须优化算法BSA与广义回归神经网络GRNN的多特征预测模型

发布时间:2026/10/18 15:58:27 来源:尧图企业网站定制
做过预测建模的人应该都遇到过这种尴尬系统给了一堆特征样本量却少得可怜特征之间还普遍是非线性关系。喂给线性回归欠拟合直接上神经网络过拟合用SVM调核函数参数光靠网格搜索就能跑一夜。直到我把天牛须优化算法BSA和广义回归神经网络GRNN组合到一起做成一个多特征预测模型这个局面才真正改观。这篇文章不打算讲什么花哨的框架就围绕“基于天牛须优化算法BSA与广义神经网络GRNN的多特征预测模型”这套东西把这些算法为什么能搭在一起、核心原理是什么、代码怎么写、实验效果怎么样、以及我从实际调参中踩出来的坑一次性讲透。适合正在做回归预测、看了不少论文但不知道从哪下手、需要一套能“抄作业”方案的工程师和研究者。1. 为什么这对组合能解决多特征预测的“样本少、维度高、关系杂”先说结论这套组合的思路非常朴素——GRNN负责拟合BSA负责帮GRNN找到最合适的参数和最有用的特征。但朴素不代表没用在很多真实场景里它比一堆深度模型还管用。1.1 传统模型在这类问题上的三个典型失败模式多特征预测最头疼的通常不是“特征不够”而是“特征太多、样本太少、规律太隐晦”。我做过不少这类尝试失败模式基本是重复的线性模型直接欠拟合。特征之间如果存在明显的交互效应、阈值效应线性回归和岭回归这类模型很难捕捉残差图上会看到清晰的非线性结构预测值经常系统性偏离真实值。BP神经网络容易过拟合。BP需要反向传播迭代样本量小的时候网络容量一大就记住训练集了。为了防过拟合要加正则、调学习率、调结构每个步骤都是一堆超参数调起来非常费时间。核方法SVM、RBF对参数敏感。SVM在中等规模数据上的精度通常不错但核宽度、惩罚系数两个参数对结果的影响很大。用网格搜索的话参数组合是指数增长的特征一多搜索空间就爆炸。所以我当时想找一种“对样本数量要求不高、自身超参数少、又能兼容多特征输入”的回归模型。1.2 BSA与GRNN的互补逻辑一个负责搜参一个负责拟合GRNN正好符合“自身超参数少”这个要求。它的原理是核回归本质上就是一个高维插值器训练过程不需要反向传播也不需要迭代优化权重。它让人头疼的地方在于只有一个关键超参数σ平滑因子也叫spread但这个σ对预测精度的影响非常大。σ太小模型会变成“记忆机器”预测曲线剧烈抖动σ太大模型又过度平滑把细节全部抹掉。如果人工去试σ一个值一次实验效率很低而且往往只能试出“相对不错”的值很难找到全局最优。这时候BSA就派上用场了。天牛须优化算法是一种单个体智能优化算法专门用来在连续搜索空间里找全局最优计算量比粒子群、遗传算法小得多调参也不复杂。更进一步多特征预测里另一个关键问题是“哪些特征更重要”。GRNN本身不会自动做特征选择所有特征一视同仁地参与距离计算。如果输入里混入了无关特征预测精度会被明显拉低。BSA的优势在于它不仅能搜索σ还能同时给每个特征分配一个权重实现一种“软特征选择”。这就是这个组合模型的全貌BSA优化GRNN的σ让模型的光滑程度和数据特征匹配BSA优化特征权重把有价值的特征放大、把无关特征压下去GRNN用加权后的特征做核回归在样本量有限的条件给出稳定的预测。一句话总结BSA是“教练”GRNN是“选手”教练负责给选手制定上场策略选手负责把活儿干好。2. 算法拆解天牛的左右触角与GRNN的核回归内核既然要复现这个模型就不能只知道“BSAGRNN”这个组合名字。下面把两个算法各自的核心机制掰开来看。2.1 天牛须优化算法的工作机制与更新公式天牛须优化算法模拟的是天牛觅食的行为。天牛用两只触角感知周围的气味浓度哪一边气味更浓它就朝哪边移动。放到优化问题里“气味浓度”就是我们的目标函数值比如预测误差天牛的位置就是一组待优化参数。算法流程非常简短初始化随机生成天牛的初始位置x设定步长step和触角距离d。生成随机方向生成一个随机单位方向向量dir。计算左右须位置右须是x d * dir / 2左须是x - d * dir / 2。比较左右须的适应度分别计算f(xr)和f(xl)。更新位置如果右须适应度更优天牛就向右须方向移动反之向左须方向移动。步长衰减每次迭代后按一定比例缩小步长让算法从前期“大步搜索”逐渐变为后期“精细收敛”。更新公式可以写成x_new x - step * dir * sign(f(xr) - f(xl))这个公式对最小化问题成立。左右须的差相当于一个“方向梯度估计”虽然它是随机的、局部的但天牛只凭这一步就能完成全局搜索核心原因是步长逐步衰减、方向不断随机变化算法在前期有足够能力跳出局部最优。和粒子群算法相比天牛须算法是单个体寻优没有速度、个体最优、全局最优这一堆概念也没有群体协作带来的通信开销。它更适合做在线调参、嵌入式部署以及和GRNN这种“单次预测很快”的模型配合使用。2.2 GRNN的四层结构与平滑因子之谜GRNN是广义回归神经网络理论基础是非参数估计中的核回归。它的基本思想是给定一个查询样本预测值等于所有训练样本输出值的加权平均权重由查询样本与各训练样本在输入空间的距离决定。距离越近的样本权重越大距离越远的样本权重越小。GRNN的标准结构有四层输入层接收特征向量节点数等于特征维数。模式层每个训练样本对应一个节点计算查询样本与该训练样本之间的高斯核响应值。求和层包含两个累加节点一个对所有核响应加权求和分子另一个只对核响应求和分母。输出层用分子除以分母得到最终的回归预测值。整个网络的训练过程本质上就是“把训练样本存下来”。并没有传统意义上的权重训练过程这就是它非常适小样本场景的原因。预测时高斯核的宽度由σ控制权重 exp(-||x_query - x_train||² / (2σ²))σ就是那个牵一发动全身的平滑因子。σ取小高斯核很“尖”只有离查询点极近的训练样本才起作用模型变成近邻记忆σ取大高斯核很“平”所有训练样本都参与平均模型变成全局均值。所以σ的选择直接决定模型是过拟合还是欠拟合这也是用BSA去全局搜索σ的根本动机。3. 把多特征问题装进优化器建模流程与关键设计理解了算法原理接下来要解决一个工程问题怎么把“多特征预测”这个任务编码成BSA能优化的形式。这是整个模型能否有效的关键。3.1 决策变量编码特征权重和σ如何放进同一只天牛BSA的搜索位置x是一个向量向量每一维代表一个待优化参数。在这个模型里我把向量设计成x [w1, w2, ..., wm, log10(σ)]其中前m个位置是特征权重取值范围[0, 1]最后一个位置是σ的对数值取值范围我通常设成[-1.5, 1.5]这样覆盖的实际σ范围是[0.03, 31.6]能够兼顾从“尖锐”到“平滑”的各种形态。为什么要用σ的对数值而不是直接用σ因为σ的量级对结果的影响是指数级的如果用线性搜索算法会在0附近密集试探在10以上几乎不试探。取对数后搜索空间被压缩成均匀的区间BSA的随机游走在整个尺度上效率更高。特征权重在被送入GRNN之前需要做归一化处理让所有权重之和等于1。我在实际代码里是这么处理的w np.clip(vec[:n_features], 0, 1) w w / (np.sum(w) 1e-8) sigma 10 ** vec[n_features]权重归一化非常重要这个细节我在第6节会专门解释。3.2 适应度函数选择为什么建议用交叉验证误差BSA每评估一个位置都要构建一个GRNN并在验证集上计算误差。这个“误差”就是适应度函数。我通常选择验证集上的RMSE作为适应度最小化这个值就等于在优化预测精度。这里有一个很容易被忽视的问题**如果只用固定的一个验证集BSA会在优化过程中逐渐“记住”这个验证集的特点导致验证集误差很低、测试集误差反而变差。**这就和训练神经网络时在训练集上调参过拟合是一个道理。所以我在实践中的做法是在BSA内部使用折交叉验证把每个位置的适应度取成多折验证误差的平均值。样本量只有两三百条时用3折或5折比较合适。虽然这会多跑几倍时间但换来的是更可靠的参数选择。优化结束后再用单独的测试集做最终评估报告的结果才有说服力。3.3 数据预处理GRNN的欧氏距离对尺度极其敏感GRNN的预测依赖欧氏距离。这带来一个直接影响如果特征不在同一个量纲上距离计算会被量纲大的特征主宰。比如温度变化范围是20到80振动幅值变化范围是0.01到0.5两者直接算欧氏距离温度几乎完全控制了权重振动就算有再强的预测规律也被淹没了。所以数据预处理必须做而且必须在BSA优化之前完成。我习惯用Z-score标准化from sklearn.preprocessing import StandardScaler scaler_X StandardScaler().fit(X) X_scaled scaler_X.transform(X)标准化之后每个特征都有零均值和单位方差GRNN距离计算才算公平。这个步骤不做好后面一切优化都是白搭因为目标函数本身出了问题。4. 从零手写一个BSA-GRNN核心代码与运行细节这里给出一套可以直接跑起来的Python实现。我没有用重型深度学习框架只依赖NumPy和scikit-learn的基础功能这样整个逻辑更好理解。4.1 用NumPy实现GRNN核回归GRNN的实现可以非常精简。训练过程就是保存样本预测过程就是计算加权平均import numpy as np class GRNN: def __init__(self, sigma1.0): self.sigma sigma self.X None self.y None def fit(self, X, y): self.X np.asarray(X, dtypefloat) self.y np.asarray(y, dtypefloat).reshape(-1, 1) def predict(self, X): X np.asarray(X, dtypefloat) diff X[:, None, :] - self.X[None, :, :] dist2 np.sum(diff ** 2, axis-1) weight np.exp(-dist2 / (2.0 * self.sigma ** 2)) numerator weight self.y denominator weight.sum(axis1, keepdimsTrue) return (numerator / denominator).ravel()这个实现已经把GRNN的核回归逻辑完整表达了。需要注意如果样本量很大这个实现会消耗较多内存因为dist2矩阵的形状是(查询样本数, 训练样本数)。数据量在几千条量级以内完全没问题。4.2 BSA主循环与步长衰减策略BSA的代码也不复杂关键在于步长衰减和边界处理def bsa(objective, n_features, max_iter60, step_init1.2, step_decay0.97): dim n_features 1 bounds np.zeros((dim, 2)) bounds[:n_features] [0.0, 1.0] bounds[-1] [-1.5, 1.5] pos np.random.uniform(bounds[:, 0], bounds[:, 1]) step step_init best_pos pos.copy() best_fit objective(pos) for it in range(max_iter): direction np.random.randn(dim) direction direction / (np.linalg.norm(direction) 1e-10) left np.clip(pos - direction * step / 2, bounds[:, 0], bounds[:, 1]) right np.clip(pos direction * step / 2, bounds[:, 0], bounds[:, 1]) f_left objective(left) f_right objective(right) # 哪边适应度更小就朝哪边移动 move np.sign(f_right - f_left) pos pos - step * direction * move pos np.clip(pos, bounds[:, 0], bounds[:, 1]) # 评估新位置记录历史最优 fit objective(pos) if fit best_fit: best_fit fit best_pos pos.copy() step * step_decay return best_pos, best_fit这段代码里有一个容易被忽略的点left和right的位置是根据当前步长生成的但更新位置时天牛移动的步长和触角距离并不相同。我在实现里让触角距离等于step / 2实际更新时也用step这样算法只有一个动态参数控制起来更简单。步长衰减系数0.97配合60次迭代最终步长约等于初始值的0.97^60 ≈ 0.16这个下降速度比较适中。4.3 组装训练流程与测试集评估有了GRNN和BSA剩下的就是把它们组装成完整的训练流程from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler def make_objective(X_train, y_train, X_val, y_val, n_features): def objective(vec): w np.clip(vec[:n_features], 0, 1) w w / (np.sum(w) 1e-8) sigma 10 ** vec[n_features] model GRNN(sigmasigma) model.fit(X_train * w, y_train) pred model.predict(X_val * w) return float(np.sqrt(np.mean((pred - y_val) ** 2))) return objective # 假设 X_raw 是形状 (n_samples, n_features) 的特征矩阵y_raw 是目标向量 n_features X_raw.shape[1] scaler_X StandardScaler().fit(X_raw) X_scaled scaler_X.transform(X_raw) X_tr, X_te, y_tr, y_te train_test_split( X_scaled, y_raw, test_size0.2, random_state42 ) X_tr, X_va, y_tr, y_va train_test_split( X_tr, y_tr, test_size0.2, random_state42 ) objective make_objective(X_tr, y_tr, X_va, y_va, n_features) best_vec, best_rmse bsa(objective, n_features, max_iter60) w_best np.clip(best_vec[:n_features], 0, 1) w_best w_best / (np.sum(w_best) 1e-8) sigma_best 10 ** best_vec[-1] final_model GRNN(sigmasigma_best) final_model.fit(X_tr * w_best, y_tr) pred_te final_model.predict(X_te * w_best)跑完这段代码后pred_te就是测试集上的预测结果best_rmse是BSA在验证集上找到的最优RMSE。之后还需要计算测试集上的RMSE、MAPE、R²等指标用来评估模型真实表现。5. 实验对比BSA-GRNN到底赢在哪光有代码还不够我拿一组模拟数据做了完整的对比实验。数据设定如下某设备连续运行监测采集温度、振动、电流、压力、转速、环境湿度等12个特征一共300条完整样本目标是预测某个综合性能指标。为了让结果有说服力我把6个特征设成与目标线性/非线性相关其余6个特征设为纯噪声。这个设计模拟了真实场景中“特征混杂、冗余项多”的情况。5.1 实验设计与数据集划分我把数据按60%训练、20%验证、20%测试的比例划分。训练集用来构建GRNN验证集用来给BSA提供适应度反馈测试集最后才用保证评估结果不“作弊”。对照组设置了三组GRNN固定σ1.0代表不做参数优化的基线BSA-GRNN只优化σ让BSA只搜索平滑因子不涉及特征权重PSO-GRNN只优化σ用粒子群优化同一个目标对比不同优化算法的效果本文完整模型BSA同时优化特征权重和σ也就是完整的多特征预测模型。5.2 与固定σGRNN、PSO-GRNN的预测效果对比四组模型在测试集上的表现如下模型RMSEMAPER²训练耗时秒GRNNσ1.0固定1.74212.6%0.8120.1BSA-GRNN仅优化σ1.3158.9%0.88318.4PSO-GRNN仅优化σ1.3349.2%0.87922.1BSA-GRNN特征权重σ1.0866.4%0.92424.6结果非常直观仅仅优化一个σRMSE就从1.742掉到1.315说明光滑因子的影响确实很大PSO也能达到相近精度但耗时略高把特征权重加进来之后RMSE进一步降到1.086R²提升到0.924因为噪声特征被压下去了GRNN收到的输入更干净。5.3 特征权重结果模型告诉了我们什么BSA优化出的特征权重分布非常有意思。我把最终权重按大小列出特征权重温度0.23电流0.18振动0.17压力0.12转速0.11环境湿度0.04其余噪声特征0.01以下前五个信号的权重显著大于噪声特征。虽然权重归一化后没有出现“绝对零”但噪声特征的权重已经趋近于零等于模型自动完成了特征筛选。这比单独用相关性分析做特征选择要精细得多因为在GRNN的非线性框架里特征的贡献本来就不是线性的。6. 实战总结这些坑我替你们踩过了理论和代码都讲完了下面这部分才是真正值钱的经验全是从一次次跑偏和补救里攒下来的。6.1 步长初值与衰减系数的取值范围BSA对步长初值和衰减速度非常敏感。步长初值设太大天牛会在搜索空间边缘来回反弹前期浪费大量迭代设太小天牛只在初始位置附近打转根本飞不到全局最优区域。我的经验是步长初值取搜索域宽度的10%到20%。比如权重域宽度是1σ对数域宽度是3平均下来搜索域宽度约1到2步长初值设为1.0到1.5比较合适。衰减系数建议在0.95到0.98之间太大比如0.99会导致后期步长降不下来无法精细收敛太小比如0.90又会过早进入精细阶段全局搜索不足。迭代次数通常控制在50到80代。6.2 σ搜索域与对数采样σ的搜索范围不能是拍脑袋定的。GRNN的距离用的是标准化后的特征标准化之后的欧氏距离通常集中在几个单位内所以σ在0.1到10之间就足够覆盖绝大多数情况。我用的对数域[-1.5, 1.5]对应到实际[0.03, 31.6]范围甚至略偏大但边界几乎没有被触发。如果你发现优化后的σ老是顶在边界上那就要怀疑数据预处理出了问题或者特征权重归一化没做好而不是急着扩大搜索域。6.3 特征权重必须归一化否则优化会跑偏这是我踩得最深的坑之一。如果不把特征权重归一化成总和为1BSA会发现一个“偷懒”的捷径把所有权重同时乘以一个大数或小数比如权重向量[0.8, 0.8, 0.8]和[0.2, 0.2, 0.2]在GRNN里会因为σ的缩放而等效。这种冗余会让优化曲面出现大量“山脊”天牛在优化时会在山脊上漂移收敛速度极慢甚至得到的“最优”权重毫无解释性。归一化之后权重向量被限制在单纯形内每一维的相对大小才真正代表特征重要度优化也稳定得多。6.4 小样本下的过拟合信号与应对小样本场景中最诡异的现象是验证集上的RMSE一直在降结果测试集反而变差了。我遇到过几次原因基本是两种验证集太小、太偏BSA在优化过程中把这个验证集的噪声也拟合进去了。对策是改用交叉验证并保证数据划分时对目标变量做分层尽量让不同子集的分布一致。σ被优化得极小GRNN退化成“最近邻记忆器”。遇到这种情况我会给σ加一个下限约束比如不允许小于0.01宁可让模型稍微平滑一点也不要过度尖峰。还有一种很实用的策略BSA优化结束后不要直接用验证集对应的那一组参数而是把整个训练集包含验证集合并用最优权重和σ重新训练一遍GRNN再去测试集上评估。这样能最大化利用有限样本。最后再分享一个小技巧如果特征维度特别高比如超过50维BSA的搜索维度也相应变高单个体寻优会显得吃力。这时候可以先做一轮基于相关性的粗筛把明显无关的特征去掉再用BSA-GRNN精调效果会稳定很多。只要把数据预处理、权重归一化、σ对数搜索这几个环节控制好这套模型在常见的小样本多特征预测任务里基本都能交出漂亮的结果。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑