首页 / 资讯中心 / 文章详情

MATLAB中GRNN回归预测实践:spread参数调优与仿真避坑指南

MATLAB中GRNN回归预测实践:spread参数调优与仿真避坑指南 ★ FEATURED ARTICLE
简介GRNN神经网络.zip 是一份基于 MATLAB 的广义回归神经网络GRNN实现代码包面向需要开展非线性回归、时间序列预测或系统辨识建模的科研人员与初学者。包内仅含 1 个 m 文件代码量约 2KB但完整演示了 GRNN 从网络结构初始化、高斯核函数计算到前向传播与权重更新的核心流程注释中一并说明了输入层、传播层、平滑层和输出层的作用便于对照理解。资源已在 CSDN 上吸引 1319 人学习下载适合想快速上手 MATLAB 神经网络编程、弄清 GRNN 原理细节或将其作为基线与改进起点的读者。下载后可直接打开脚本进行训练与预测实验也可在此基础上修改平滑参数、核宽度或数据维度迁移到自身回归任务中。1. 在MATLAB里用GRNN做回归预测训练几乎不花时间真正决定结果的是一个参数先说一个反直觉的结论GRNN广义回归神经网络在MATLAB里的训练时间接近零因为它没有反向传播迭代本质上是把所有训练样本记住预测时逐点算相似度再加权平均。很多做回归预测的工程师被BP的调参折磨得想弃坑换个思路用GRNN小样本非线性拟合半天就能跑通。问题也随之转移真正需要花心思的只剩一个spread参数。这篇笔记从网络结构讲起给出可复现的MATLAB仿真流程再把我在实际使用中掉进去过的坑逐一拆开最后给出spread自动寻优的验证方法。适合做函数逼近、短期预测、小样本回归的从业者参考。2. 从网络结构到spread参数为什么GRNN不用迭代也能逼近2.1 四层结构输入层、模式层、求和层、输出层各干什么GRNN是RBF径向基网络的一个变种结构比BP简单得多一共四个层输入层、模式层、求和层、输出层。输入层不做计算只把特征向量送进网络。模式层每个神经元对应一个训练样本神经元输出的是输入向量和这个样本之间的高斯径向基函数值。这个值可以理解成当前输入和该样本有多像离得越近激活越大离得越远输出指数衰减。求和层是两个累加节点一个用样本目标值对激活值做加权和另一个只对激活值做纯累加。输出层把两个累加结果相除得到的就是预测值。这个相除操作是GRNN的精髓。加权和提供了局部逼近能力纯累加起到归一化作用两者的比值让输出天然落在训练样本目标值的合理范围内不会出现BP那种梯度爆炸或输出发散的情况。因为没有可学习的权重矩阵训练过程就变成了把训练样本存进网络这就是为什么建网快得离谱。代价也在这里预测时每来一个点都要和全部训练样本算一遍距离样本量大时预测耗时线性增长这一点在选型时必须心里有数。从统计角度看GRNN做的实际上是非参数回归里的Nadaraya-Watson估计预测值是训练目标值的局部加权平均。这个特性决定了它特别适合两类场景一是样本量不大但特征维度不高的回归问题二是目标函数复杂但没有可靠先验模型的黑匣子拟合。反过来如果训练集超过几万条或者特征维度高到几百维GRNN的预测速度和距离退化问题就会比较突出这种情况我更倾向用SVR或者LightGBM。2.2 spread的几何含义高斯核宽度直接改变预测曲面的粗糙度GRNN里的高斯核可以写成exp(-||x - c||² / (2σ²))c是某个训练样本σ就是核宽度。MATLAB的newgrnn函数第三个参数spread控制的就是这个宽度。spread小核的响应范围窄预测只受附近少数样本影响训练点基本都被穿过但样本稀疏区域的曲面会剧烈震荡spread大核响应范围宽远近距离的样本贡献趋同预测曲面变光滑但太大时所有预测都会向全局均值靠拢。这个规律用一条一维曲线就能直观看到也是我建议每个接手GRNN的人先做的验证。我用一个带噪声的正弦波数据跑过对比演示spread分别取0.2和1.8效果差异非常明显代码如下。rng(7); x linspace(-3, 3, 80); y sin(x) 0.25 * randn(80, 1); % spread较小逼近训练点但曲线毛刺明显 net_small newgrnn(x, y, 0.2); hat_small sim(net_small, x); % spread较大曲线平滑细节被抹掉 net_large newgrnn(x, y, 1.8); hat_large sim(net_large, x); figure; plot(x, y, o); hold on; plot(x, hat_small, -, LineWidth, 1.2); plot(x, hat_large, -, LineWidth, 1.8); legend(训练样本, spread0.2, spread1.8, Location, northwest);这段代码里newgrnn的输入是x的转置因为MATLAB要求样本按列排列80个样本对应P矩阵形状1×80。预测时sim的输入同样要转置保持和训练一致。spread0.2时预测曲线会穿过几乎每个样本点但曲线不光滑spread1.8时曲线接近一条平缓的带在原点附近的波峰波谷都被削平了。真实使用中我一般先把spread从0.05到1.5按0.05的步长扫一遍看验证集误差曲线选出拐点位置的取值而不是凭感觉定。这里补一个新手容易误解的点spread不是学习率没有训练中逐渐收敛这回事GRNN的spread在建模时一次性确定确定之后网络就固定了。所以调spread不是在训练网络而是在调节核函数的感知尺度理解这一点对后续做自动寻优很有帮助。下一章进入完整仿真数据和代码都是可以直接跑的。3. MATLAB仿真全流程数据清洗、newgrnn建网、预测评估3.1 数据准备按列排布输入以及必要的归一化写MATLAB代码第一步是确认矩阵方向。newgrnn要求输入矩阵P是D×ND是特征维度N是样本数每一列是一个样本。目标是T是S×NS是输出维度。这是整篇笔记里最容易被绕进去的地方很多人第一步就把矩阵方向搞反后续所有维度的报错都从这里来。我习惯在数据准备阶段就用变量名区分原始矩阵和网络输入矩阵比如X_raw是N×D的常见表格形式Xn是归一化后的D×N形式避免混在一起。归一化我优先做GRNN本身对尺度不是极度敏感但距离计算依赖欧氏距离如果某个特征取值0到1000另一个取值0到1大尺度特征会直接主导相似度计算小尺度特征的信息就被淹没了。常见做法是min-max归一化到[-1,1]或[0,1]用训练集的上下界做变换测试集沿用同一套上下界。这样可以避免数据泄漏后面评估也更可信。rng(20240712); n 500; x1 rand(n, 1) * 6 - 3; x2 rand(n, 1) * 6 - 3; % 构造非线性目标两个输入的交叉项加噪声 y 1.5 * sin(x1) .* cos(0.8 * x2) 0.4 * randn(n, 1); X_raw [x1, x2]; % min-max 归一化到 [-1, 1]保存边界用于反归一化 X_min min(X_raw); X_max max(X_raw); Xn (X_raw - X_min) ./ (X_max - X_min) * 2 - 1; y_min min(y); y_max max(y); yn (y - y_min) / (y_max - y_min) * 2 - 1; % 随机划分训练集 350 条测试集 150 条 idx randperm(n); train_idx idx(1:350); test_idx idx(351:end);归一化的计算式里用了MATLAB的隐式扩展X_max是1×2向量X_raw是500×2矩阵做减法时自动按列广播。这段逻辑不复杂但要注意反归一化时Y用的也是训练集的y_min和y_max而不是测试集的。把边界变量留在工作区等到预测完统一还原这也是后面避坑章节里要细说的问题。3.2 建网和仿真newgrnn与sim的对称转置数据准备好之后就进入建模环节。newgrnn的调用很简洁三个参数分别是网络输入、网络目标、spread。网络输入用Xn(train_idx,:)转置变成2×350网络目标用yn(train_idx)转置变成1×350。预测时sim传入网络和测试输入测试输入也要转置成2×150得到1×150的预测值。转置出现在两端逻辑上是对称的写代码时习惯性先检查size再往下走。% 训练GRNNspread取0.4作为初始值 net newgrnn(Xn(train_idx, :), yn(train_idx), 0.4); % 测试集预测 pred_n sim(net, Xn(test_idx, :)); pred_n pred_n(:); % 反归一化回到原始量纲 pred (pred_n 1) / 2 * (y_max - y_min) y_min;pred_n是网络输出的归一化值形状是1×150用(:)拉成列向量后和y(test_idx)对齐。反归一化的操作是min-max映射的逆运算前面的1再除以2对应的是当初归一化时乘以2减1的逆变换。如果把pred_n的形状忘了拉直后续做误差计算时会出现维度不匹配的隐性问题MATLAB有时候会自动广播出奇怪的结果而不是直接报错。spread在这个例子里取了0.4这是针对归一化数据的初始值。需要说明的是spread的合适区间和输入是否归一化有关系。如果输入是原始尺度、数值范围在几十到几百之间spread要相应调大归一化到[-1,1]之后0.1到0.8是比较常见的工作区间。这是经验值不是理论推导出来的每个数据集最好都做一次寻优。3.3 计算指标并保存模型RMSE、R²和net的复用预测做完后的第一步是算误差而不是直接画图。RMSE衡量绝对误差大小R²衡量模型对目标方差的解释程度。这两个指标配合使用一个看量级一个看比例。很多场景只看R²高就以为模型通用其实R²高不代表误差小目标值本身波动很小时R²也会虚高。我一般两个都打印记录在实验表格里。y_true y(test_idx); rmse sqrt(mean((pred - y_true).^2)); ss_res sum((pred - y_true).^2); ss_tot sum((y_true - mean(y_true)).^2); r2 1 - ss_res / ss_tot; fprintf(RMSE: %.4f, R2: %.4f\n, rmse, r2); % 保存训练好的网络后续直接用load调用 save(grnn_model.mat, net, X_min, X_max, y_min, y_max);保存时连同归一化边界一起存进.mat文件很有必要。只存net不存边界换一批新数据做预测时还得重新计算边界容易出错。加载模型后预测流程是新输入减X_min除以范围乘2减1送进sim再反归一化输出四步不能少。下面这个接口形状表可以贴在工作区旁边我每次写GRNN代码都会对着看一眼。接口参数形状要求说明newgrnnPD×N训练输入按列存放样本newgrnnTS×N训练目标按列存放样本newgrnnspread1×1径向基宽度simnet, PD×M测试输入按列存放样本sim返回值YS×M预测输出如果要做多输出回归T矩阵的形状变成S×N即可S是输出变量个数。GRNN会为每个输出单独构造一组求和层输出之间互不影响。我做过一个同时预测温度和湿度的模拟项目就是直接构造2×N的目标矩阵newgrnn一次建网完成没有额外改动。多输出的结果评估时每个目标分开算RMSE不要合并成一个综合指标否则看不出哪个方向预测漂了。到这里一个完整的GRNN建模流程已经闭环。下一章把最容易踩的坑逐个拆开这些坑我几乎都在项目里遇过。4. 避坑清单MATLAB里GRNN最容易出问题的五个环节4.1 现象newgrnn报维度错误或sim输出形状诡异现象是训练时newgrnn(P,T,spread)直接报错错误信息指向矩阵维度不一致或者训练没报错但sim出来的结果维度和预期对不上画图时矩阵拼不到一起。原因是P矩阵的行列方向反了。GRNN内部按列是样本的方式读取数据很多人按表格习惯把样本放在行上直接传进newgrnnD变成了NN变成了D维度自然对不上。解决方法是养成写代码前检查size的习惯在newgrnn和sim两个入口都加一行断言例如assert(size(Xn_train, 2) 350)维度不对立刻停下来定位而不是等到预测结果出来后一头雾水。4.2 现象预测结果整体偏移量级和真实值差一大截这个坑出现在归一化环节。只对输入做了归一化目标值没有做或者做了归一化但预测完后忘了反归一化都会导致预测结果和真实值不在一个量纲上。还有一种更隐蔽的情况反归一化时用了测试集的y_min和y_max而不是训练集的。训练集和测试集的分布范围几乎不可能完全一样边界不一致时还原出来的结果就会整体偏移。解决方法是把归一化边界变量和模型一起保存预测时统一从工作区读取训练集的边界做反归一化绝不在预测分支里重新计算min和max。4.3 现象spread取太小训练集拟合完美测试集一塌糊涂如果预测曲面在训练样本附近剧烈震荡过拟合的特征非常明显。原因在于spread小于样本间距时模式层只有离预测点最近的训练样本能产生有效激活其他样本的核响应几乎归零GRNN退化成查表根本谈不上泛化。反之spread取太大核宽度覆盖整个样本分布范围每个预测点接收到的权重都差不多输出趋近全局均值拟合曲线平滑但完全失去细节。这个不是玄学是核函数的数学性质决定的。解决思路我不建议靠肉眼调直接用验证集做网格扫描下一章给出具体脚本。4.4 现象测试集R²高到离谱换成真实新数据却明显变差评估指标虚高最常见的原因是数据泄漏即测试样本和训练样本有重叠或高度相关。随机种子固定后如果不打乱直接按顺序划分数据的时序相关性和重复样本会让GRNN在测试集上见过相似的输入评估结果自然虚高。另一个常见原因是重复样本没有去重同一输入在不同批次出现多次随机划分后同一条数据被分到训练和测试两边模型等于直接背出了答案。解决方法是先用unique检查重复样本再做randperm洗牌划分。时序数据尤其要注意GRNN不具备外推能力按随机划分得到的评估不能代表滚动预测的真实表现。4.5 现象单步预测效果不错滚动多步预测越滚越偏GRNN本质是内插工具预测点一旦落在训练样本分布范围之外所有核响应都来自最近的那个边界样本输出会迅速拉向边界值或均值。滚动预测时第一步偏差进入下一步的输入误差被持续放大几步之后曲线就开始飘。这个坑在时序预测里几乎必然出现。解决方法是限定预测步长或者把滞后期特征做成滑动窗口让GRNN始终在训练分布范围内做内插而不是要求它外推。我一般还会设置一个置信提醒当预测输入的最大最小值超出训练集范围时脚本主动警告而不是默默输出结果。5. 让spread自己找最优值网格搜索与留一法结合的验证技巧5.1 网格搜索用验证集误差选spread而不是用测试集spread寻优的常见做法是网格搜索加验证集划分。先划分训练集和测试集再在训练集内部切出一块验证集只拿验证集误差来选spread。测试集从头到尾只参与最后评估不参与参数选择否则选出来的参数会对测试集过拟合。下面这段代码在训练集内部切出50条验证数据循环建网并记录RMSE。val_idx train_idx(1:50); sub_train_idx train_idx(51:end); spreads 0.05:0.05:1.5; val_rmse zeros(size(spreads)); for i 1:numel(spreads) net_i newgrnn(Xn(sub_train_idx, :), yn(sub_train_idx), spreads(i)); pn sim(net_i, Xn(val_idx, :)); pn pn(:); pv (pn 1) / 2 * (y_max - y_min) y_min; val_rmse(i) sqrt(mean((pv - y(val_idx)).^2)); end [min_rmse, min_pos] min(val_rmse); best_spread spreads(min_pos); fprintf(best spread %.2f, val RMSE %.4f\n, best_spread, min_rmse);spreads数组按0.05步长从0.05扫到1.5覆盖了我见过的绝大多数归一化数据区间。min(val_rmse)返回最小误差和对应位置best_pos是数组下标spreads(min_pos)取出最优spread。网格搜索结束后用best_spread重新在完整训练集上建模再对测试集做最终评估。这套流程看起来繁琐实际运行时间取决于训练样本量GRNN建网速度快几百条样本的网格扫描几秒钟就能跑完。5.2 留一法小样本时最可信的误差估计网格搜索在验证集上选参数验证集划分方式会轻微影响选型结果。样本量只有一两百条时验证集切太大建模数据不够切太小验证误差不稳。这时可以考虑留一法交叉验证每条样本单独做一次验证其余全部用来训练误差取平均。GRNN建网极快留一法在小样本下计算量完全可控。n_small size(Xn, 1); loo_rmse_sum 0; for i 1:n_small tr_idx setdiff(1:n_small, i); net_loo newgrnn(Xn(tr_idx, :), yn(tr_idx), best_spread); p_loo sim(net_loo, Xn(i, :)); loo_rmse_sum loo_rmse_sum (p_loo - yn(i))^2; end loo_rmse sqrt(loo_rmse_sum / n_small);这段代码的思路很直接setdiff取出除第i条外的所有样本作为训练集用验证样本作为测试点累积每个样本的预测误差平方。留一法估计的误差比单次验证集更稳定也更接近真实泛化误差代价是建模次数等于样本数。样本量超过两三千时就不再推荐跑一次完整留一位会用很长时间这时切一个固定验证集就够了。我现在的习惯是拿到新数据集先清理重复样本再做归一化和验证集划分最后强制走一遍网格搜索选spread。哪怕时间紧张也不跳过验证集直接用测试集调参这是从那一次R²虚高到0.97、换数据却崩盘的教训里学到的。先选参数再评估测试集只看一次这套流程守住GRNN在工程里是相当省心的工具。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站