
简介这是基于Matlab实现人工神经网络方法的配套源码与数据集适合计算机、电子信息工程、数学等专业学生用于课程设计、期末大作业或毕业设计中的算法仿真与结果验证。压缩包共6个文件以1个m脚本和5个数据表格为主其中xls/xlsx文件分别存放了神经元分类识别结果、2016年各地区月平均气温、蠓虫分类样本及儿童颅脑发育指标等数据可配合Chapter10.m完成多种分类与回归场景的神经网络训练测试。包体仅106KB轻量易用解压后即可查看源码结构与数据格式。目前已有541人学习下载适合具备一定Matlab基础、希望快速上手人工神经网络实现的读者作为参考资料。1. 拿到Matlab人工神经网络源码先别急着双击运行资源包里放着Chapter10.m和好几个Excel文件看文件名就知道蠓虫分类、儿童颅脑发育指标、月平均气温、神经元分类识别这些正好覆盖了人工神经网络的两种典型任务——分类和回归。很多入门者以为下载下来直接替换数据就行结果一跑就是维度报错、不收敛、准确率个位数。原因很简单神经网络代码是和数据强耦合的换一组数据必须动网络结构、目标向量和评估方式。这篇文章就按“数据组织 - 网络构建 - 分类回归实战 - 参数调优 - 结果验证”的顺序把基于Matlab实现人工神经网络方法的完整套路写清楚新手能跟着操作用过的人也能看看哪些细节容易踩坑。2. 网络选型与数据组织BP神经网络为什么够用Excel数据怎么读进Matlab2.1 从分类和回归需求看BP神经网络的适用性人工神经网络方法涵盖感知机、BP网络、RBF、自组织映射等但这套资源里的场景——蠓虫分类、儿童颅脑发育指标预测、月平均气温回归——都是中小规模表格数据集样本量从几十到几百条特征维度不高用BP前馈网络最合适。BP网络在Matlab工具箱里对应newff或feedforwardnet实现成本低训练过程可视课程设计、期末大作业或毕业设计里容易被导师看懂。还有一部分人以为深度学习才能在Matlab里做神经网络其实对于2016年各地区月平均气温这种小数据深度网络很容易过拟合反向传播训练时间也长。BP作为基础的人工神经网络方法在Matlab里跑通后在理解梯度下降、误差反向传播、激活函数这些概念上更直接。后面如果真要迁移到深度学习工具箱做图像识别或时间序列从这份代码切过去也顺路。2.2 数据文件背后的建模目标我一般拿到源码后先不动代码而是把Excel文件逐个读一遍再建立“文件名 - 输入特征 - 目标变量 - 任务类型”的对应表。根据资源内文件可以整理成下面这样数据文件可能特征目标任务类型蠓虫分类.xls翅长、触角长等形态特征类别标签二分类神经元分类识别.xlsx若干电生理指标神经元类型多分类2016各地区月平均气温.xls月份、地区编号等气温值回归儿童颅脑发育情况指标.xls年龄、体重、头围等发育指标回归或分类这个表格不是凭空给的蠓虫分类是教科书里出现频率很高的案例通常用两个形态特征区分两种蠓虫神经元分类识别和儿童颅脑发育指标在Matlab配套的神经网络示例中也有同类结构文件名基本对应变量含义。真正运行前要确认的是每一列代表什么、最后一列是不是标签避免把目标变量当成特征输入到网络。2.3 用readtable和xlsread读取Excel数据Matlab读取xls文件有两条路老版本常用xlsread新版R2023b之后推荐readtable。不管哪个版本Excel文件本身有格式问题时会报错比如常见的“无法粘贴数据”提示另存为xlsx再读通常能解决。下面这段代码实现了读取、提取特征和标签% 读取蠓虫分类数据 data readtable(蠓虫分类.xls, PreserveVariableNames, true); % 假设最后一列是标签前面都是特征 X data{:, 1:end-1}; Y data{:, end}; % 如果使用老版本Matlab可替换为 % [num, txt, raw] xlsread(蠓虫分类.xls); % X num(:, 1:end-1); Y num(:, end);readtable读取后data{:, 1:end-1}的意思是取所有行的前几列作为特征矩阵X最后一列作为标签Y。PreserveVariableNames参数在Excel表头包含中文或特殊字符时很有用比如“翅长(mm)”这种列名不加这个参数Matlab会把非法字符转成其它形式。xlsread的num只返回数值列如果Excel中有文字说明要额外处理txt和raw。读入数据后还要注意数值类型标签如果是字符串比如“A类”“B类”需要先转换成数值索引% 将字符串标签转换为二分类0/1目标向量 [classNames, ~, labelIdx] unique(Y); T zeros(length(labelIdx), 2); for i 1:length(labelIdx) T(i, labelIdx(i)) 1; end这段代码先用unique找出所有类别labelIdx是每个样本对应的类别编号再构造一个两列的指示矩阵属于哪类哪列就置1。这种编码方式在神经网络分类里很常用最后输出层的两个节点分别对应两个类别的预测概率。2.4 划分训练集和测试集别按顺序截取新手最容易犯的错是直接用前70%做训练、后30%做测试。Excel文件里的样本常常按类别集中排列顺序截取会造成训练集里只有一类样本测试集里是另一类模型准确率直接崩掉。正确做法是随机打乱下标再划分rng(42); % 固定随机种子保证结果可复现 idx randperm(size(X, 1)); split floor(0.7 * length(idx)); trainIdx idx(1:split); testIdx idx(split1:end); P_train X(trainIdx, :); T_train T(trainIdx, :); P_test X(testIdx, :); T_test T(testIdx, :);这里rng(42)是把随机数生成器固定到相同状态让每次运行后训练集和测试集一致便于调试和评份。randperm返回一个随机排列的索引列表取前70%作为训练集后30%作为测试集。注意回归任务中测试标签T_test是一列数值不用做指示矩阵。3. 核心训练代码解析从newff到train再到sim的完整流程3.1 输入输出归一化mapminmax的正确用法在建立网络之前第一步是对特征和目标做归一化。BP网络用的激活函数在[-1,1]区域最敏感原始数据量纲差距过大会让梯度更新失衡。Matlab中mapminmax不仅会缩放数据还会保存归一化参数供测试集和预测时使用。% 使用mapminmax进行归一化范围设为[-1,1] [Pn_train, ps_input] mapminmax(P_train, -1, 1); [Tn_train, ps_output] mapminmax(T_train, -1, 1);这里传入的是转置后的矩阵因为mapminmax默认按列处理样本而我们的P_train是“样本数 x 特征数”的形式转置后变成“特征数 x 样本数”每一列是一个样本。ps_input是一个结构体保存了原始数据的最小值、最大值和缩放公式。后面测试集归一化必须用同一个ps_input而不能单独调用mapminmax再生成一套参数否则训练集和测试集的缩放标准不一致模型效果会严重失真。如果是分类问题且目标T_train是0/1指示矩阵T_train本身不需要归一化但同样是转置后传入newff。如果是回归问题Tn_train需要反归一化回来才能对比真实值。3.2 newff创建BP网络结构Matlab里newff用起来最直观几个关键参数是输入矩阵、目标矩阵、隐含层节点数、激活函数和训练函数。下面是一份可复用的创建代码% 创建BP神经网络 % 输入矩阵大小决定了输入节点数输出节点数由目标矩阵行数决定 net newff(Pn_train, Tn_train, [12, 6], {tansig, tansig, purelin}, trainlm); % 显示网络结构 view(net)newff的第三个参数[12, 6]表示隐含层1有12个节点隐含层2有6个节点。对于只有几百个样本的数据我通常先尝试一层隐含层节点数从5到15逐个试验如果误差下不来再加第二层。两层的网络拟合能力更强但参数数量也更多小数据上容易过拟合。激活函数方面隐含层用tansig输出层如果是回归用purelin如果是二分类可以用logsig。trainlm指Levenberg-Marquardt训练算法收敛速度快适合中小规模网络。view(net)会弹出一个交互式窗口可以在训练前检查网络拓扑比如输入维度、各层节点数和输出维度确认无误再进入训练。这一步能省下很多因为维度不匹配导致的报错时间。3.3 训练参数设置与train执行train函数默认会自动把数据按6:2:2拆成训练集、验证集和测试集这一点很多从老代码转过来的用户容易忽略。如果不希望它自动划分可以主动设置divideParam% 设置训练参数 net.trainParam.epochs 1000; % 最大迭代次数 net.trainParam.lr 0.01; % 基础学习率 net.trainParam.goal 1e-5; % 目标均方误差 net.trainParam.min_grad 1e-6; % 最小梯度 net.trainParam.max_fail 10; % 验证集连续不改善次数上限 % 按比例重新划分训练/验证/测试 net.divideParam.trainRatio 0.7; net.divideParam.valRatio 0.15; net.divideParam.testRatio 0.15; % 开始训练 [net, tr] train(net, Pn_train, Tn_train);trainParam里epochs设太大没必要因为验证集早停机制一般会在几十步就生效lr初始0.01比较稳如果训练曲线震荡可以把lr降到0.001。goal和min_grad是两种停止条件谁先满足谁先停。train函数返回的tr结构体里保存了每一次迭代的训练误差、验证误差和停止原因后面画性能曲线要用到。训练结束后会在命令行窗口打印性能曲线图上出现best validation performance和continue iteration两个标记。如果验证曲线在训练曲线上升时开始上升这就是过拟合信号此时应当降低隐含层节点数或调高max_fail。3.4 用sim和net完成预测及反归一化训练完成后对测试集做预测需要先归一化测试集再用sim计算输出最后反归一化。以下代码同时展示了分类和回归两种结果处理方式% 测试集归一化 Pn_test mapminmax(apply, P_test, ps_input); % 网络预测得到归一化后的输出 Y_pred_n sim(net, Pn_test); % 回归任务需要反归一化 Y_pred mapminmax(reverse, Y_pred_n, ps_output); % 分类任务则直接比较输出节点的最大值位置 [~, pred_class] max(Y_pred_n, [], 1);mapminmax的apply参数表示使用ps_input里保存的缩放参数对测试集做同样变换而不是重新计算。sim是传统写法新版Matlab也可以直接写成net(Pn_test)。回归预测用的反归一化是mapminmax(reverse, ...)必须传ps_output因为目标值缩放时用的是它。分类任务如果不要求输出概率可以省略反归一化直接取最大节点位置的索引。新手经常把训练集和测试集分开归一化导致模型对测试集预测结果完全错位。正确的顺序是先用训练集拟合出ps_input和ps_output再把这个结构体引用到测试集上。4. 三个典型任务实战蠓虫分类、气温回归与颅脑指标分析4.1 蠓虫分类二分类网络的输出设计与准确率评估蠓虫分类是典型的模式识别入门案例数据量小特征少非常适合验证网络是否搭建正确。在Matlab中构造目标向量时不要用单个输出节点加阈值0.5因为二分类问题在输出节点数等于类别数时网络拟合更稳定而且可以直接套用混淆矩阵工具。以两类蠓虫为例% 假设数据已经按2.3节读入X和YY是1/2标签 T full(ind2vec(Y)); % 将标签索引转换为指示矩阵 % 训练网络 net newff(Pn_train, T, [10], {tansig, logsig}, trainlm); net.trainParam.epochs 500; net train(net, Pn_train, T(:, trainIdx)); % 预测 Y_pred_n sim(net, Pn_test); [~, predIdx] max(Y_pred_n, [], 1); accuracy sum(predIdx Y(testIdx)) / length(testIdx); fprintf(蠓虫分类准确率: %.2f%%\n, accuracy * 100);ind2vec函数把1、2这种标签索引转换成稀疏指示矩阵full把它变成普通矩阵。网络中隐含层设10个节点输出层用logsig因为分类任务希望输出介于0和1之间若用purelin则可能输出负数或大于1的值后续取最大位置虽然没有问题但不符合概率输出的语义。最后准确率的计算注意维度predIdx是行向量Y(testIdx)如果是列向量需要转置后才能比较。实际运行中如果准确率卡在50%左右说明网络没有学到有效特征最大的可能性是数据顺序没打乱导致验证集和测试集分布不一致。把rng种子换一个值重跑也是一种排查手段如果换种子后准确率波动很大那说明模型方差高需要减少隐含层节点或增加数据量。4.2 2016各地区月平均气温回归预测的误差指标与反归一化气温数据是连续值网络输出层使用purelin通过回归指标判断预测效果。以这个资源中的2016年各地区月平均气温数据为例如果把地区编号和月份作为输入特征气温作为目标那么训练流程与分类任务前两步完全相同关键在于评估阶段。% 回归任务预测 Y_pred_n sim(net, Pn_test); Y_pred mapminmax(reverse, Y_pred_n, ps_output); % 计算回归指标MSE和R^2 T_test_actual T_test; % 原始值 mse_value mean((Y_pred - T_test_actual).^2); ss_res sum((T_test_actual - Y_pred).^2); ss_tot sum((T_test_actual - mean(T_test_actual)).^2); r2 1 - ss_res / ss_tot; fprintf(MSE %.4f, R^2 %.4f\n, mse_value, r2);MSE是均方误差数值越小说明预测值和真实值的平方偏差越小。R^2也叫决定系数取值范围通常为0到1越接近1说明模型解释了越多的数据变化。如果R^2接近0那可能的原因有两个特征和气温之间本身是非线性关系而网络隐含层节点太少学不到或者数据没有打乱测试集里地区的分布和训练集差异过大。对气温这种具有明显周期性的数据可以把月份编码为1到12但更好的做法是使用两个输入分量sin(2pim/12)和cos(2pim/12)因为月份是循环变量直接用1到12会让模型误以为12月和1月差距很远。这个技巧在周期性时间序列预测里很常见在本文的源码数据背景下尤其有效。4.3 儿童颅脑发育指标缺失值处理与多维特征归一化医学指标数据常用Excel存储经常出现空单元格。如果直接交给newffMatlab会报NaN输入错误。处理缺失值的方法很多对于这类小样本数据最简单的做法是删除缺失样本或者用该列的均值填充。下面是一个比较稳妥的流程% 使用fillmissing按列填充数值型缺失值方法为线性插值 data fillmissing(data, linear); % 或者只保留完整行 data rmmissing(data);fillmissing的linear方法对有序数据效果较好如果Excel表里样本之间没有顺序关系用movmean或者常数填充更合适。儿童颅脑发育指标这种数据年龄和头围之间的量纲差异很大所以一定要对特征列整体做mapminmax归一化避免大数值特征主导梯度。还有一点如果数据里既有数值列又有文本列要把文本转换成数值编码或者直接从特征矩阵中移除文本列。多维特征归一化时注意Matlab的mapminmax默认对每一行独立缩放所以输入矩阵转置后必须是行对应特征、列对应样本。如果特征数比样本数还多则应当考虑先用主成分分析降维再训练网络否则隐含层节点数和网络参数会爆炸式增长在有限样本下模型极不稳定。这个问题在只有几十个样本的儿童发育数据上要特别小心。5. 参数调优与过拟合控制从隐含层节点到早停机制5.1 隐含层节点数经验公式和循环试参人工神经网络方法在中小数据上效果好坏很大程度取决于网络结构的复杂度。隐含层节点过少无法拟合非线性关系过多则会把训练集噪声也背下来导致测试集泛化能力下降。常用经验公式为h sqrt(n m) a其中n是输入节点数m是输出节点数a是1到10之间的调节常数。可以写一个循环自动比较不同节点数下的验证集误差for h 3:2:15 net newff(Pn_train, Tn_train, [h], {tansig, purelin}, trainlm); net.divideParam.trainRatio 0.8; net.divideParam.valRatio 0.2; net.divideParam.testRatio 0; net.trainParam.showWindow false; [~, tr] train(net, Pn_train, Tn_train); % 取验证集误差作为选择依据 valErr(h) tr.best_vperf; end [bestErr, bestH] min(valErr); fprintf(最佳隐含层节点数: %d验证误差: %.6f\n, bestH, bestErr);这段代码设置showWindow为false可以在批量试验时关掉训练曲线窗口避免弹出一大堆图表。tr.best_vperf是训练过程中验证集出现过的最优性能指标用它来选节点数比用训练误差更真实。因为数据量小节点间隔取2从3到15覆盖了输入维度在2到5之间的常见区间。实际使用中保留一部分样本作为独立测试集不要在选节点数时把它也放进去。如果已经用验证集选好了结构再用测试集跑一次最终评估。5.2 学习率与训练函数的选择Matlab的newff支持多种训练函数在数据量小、特征少的情况下最常见的是下面三种训练函数特点适用场景trainlmLevenberg-Marquardt二阶优化收敛快回归和中等分类问题样本几百以内trainbr贝叶斯正则化防止过拟合医学指标等噪声较大、样本少的数据traingd梯度下降速度慢但稳定教学演示或验证底层原理trainlm默认使用近似二阶导数信息内存消耗随网络参数数量增长所以在大网络上会变慢。trainbr通过约束权重大小来控制模型复杂度对儿童颅脑发育这类样本量少、测量噪声高的数据集效果更好代价是训练时间明显增加。traingd是最原始的梯度下降每步只沿负梯度方向前进收敛速度慢适合理解参数含义。设置学习率时可以配合自适应调整策略net.trainParam.lr 0.01; net.trainParam.lr_inc 1.05; net.trainParam.lr_dec 0.7;训练过程中如果连续多次误差下降学习率乘以1.05加速收敛如果误差增大则学习率乘以0.7回退。这种策略在traingd和traingdm下生效trainlm因为有自己的阻尼因子调整机制设置lr_inc和lr_dec意义不大。5.3 早停机制与过拟合识别在神经网络训练过程中训练误差会持续下降验证集误差往往先下降后上升这个转折点就是模型从“学习特征”切换到“记忆噪声”的位置。Matlab的train函数自带早停逻辑由max_fail控制net.trainParam.max_fail 10;意思是验证集误差连续10次迭代没有更好表现训练就提前结束。这个值设为6到15之间比较合理。如果设太大早停机制失效设太小可能还没充分收敛就停了。训练结束后可以绘制三条曲线判断过拟合程度plotperform(tr)plotperform会显示训练集、验证集、测试集的均方误差随迭代次数的变化曲线。如果训练误差远低于验证误差且验证误差后期明显上升就是过拟合。此时优先减小隐含层节点数而不是提高迭代次数。另一种思路是在网络创建时加入正则化项比如调节net.performParam.regularization但trainbr已经内置了正则化按5.2节表格直接换函数会更省事。还有一种常见的误用是把测试集既用于调参又用于最终评估这样模型会在测试集上泄漏信息。正确顺序是先用训练集和验证集选结构、调lr最后只在测试集上跑一次得到无偏的准确率或MSE。6. 拿结果说话模型评估与Matlab调试技巧6.1 分类结果验证混淆矩阵和ROC曲线分类模型的准确率只能反映整体正确比例在样本不均衡时会有误导。比如两类蠓虫比例7:3模型把少数类全部猜错也能有70%准确率。更清晰的做法是使用plotconfusion和plotroc% T_test_onehot是测试集的指示矩阵 % Y_pred_n是网络输出的连续值可以直接作为得分 plotconfusion(T_test_onehot, Y_pred_n); plotroc(T_test_onehot, Y_pred_n);plotconfusion会绘制混淆矩阵对角线上是正确分类的样本数左下角和右上角的数字反映出哪两类更容易混淆。plotroc绘制每一类对应的ROC曲线曲线越靠近左上角说明分类效果越好AUC值就是曲线下的面积。如果使用单个输出节点需要先把预测值和目标值转换成二分类指示矩阵使用full(ind2vec(...))可以完成转换。6.2 回归结果验证残差图判断模型是否学到结构回归问题不止看R^2还要看残差是否随机分布。在Matlab里画残差图很简单residuals T_test_actual - Y_pred; scatter(T_test_actual, residuals, filled); xlabel(真实值); ylabel(残差); yline(0, k--);如果残差点围绕零线均匀分布说明模型预测误差与数值大小无关如果残差随着真实值增大而呈喇叭形展开说明异方差存在可以考虑对目标取对数后再训练。如果残差存在明显线性趋势说明模型漏掉了重要的输入特征或者隐含层节点数太少。6.3 几个高频排错点我在调试这份源码风格的项目时碰到的报错大多集中在三类。第一类是输入矩阵维度问题newff要求输入为“特征数 x 样本数”很多xls读出来是“样本数 x 特征数”转置后就能解决。第二类是训练过程中出现NaN先检查数据是否有缺失值再看学习率是不是太大。第三类是Excel数据读取后带文本表头readtable报错时给PreserveVariableNames传入true或者改用xlsread的num输出。另外如果训练过程秒退且误差不变可能是目标矩阵没有归一化或者类别编码不完整。写一个简单的自检脚本把尺寸、NaN个数、唯一值个数全部打印出来基本上两分钟就能定位问题。这个习惯在后续换数据时会节省大量时间。本文还有配套的精品资源点击获取