拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Matlab深度学习多特征分类实战:从featureInputLayer到trainNetwork

先说个最近发生的事。前阵子一个做设备故障诊断的老同事问我手里有一张几百行的特征表已经做完了时域、频域特征提取大概四十几个特征列标签是四种故障类型想用深度学习做分类。他第一反应是去装Python环境但又舍不得已经跑顺的Matlab数据预处理脚本。我告诉他Matlab里用深度神经网络做多特征输入的分类模型完全是原生支持的并不需要去写一堆底层框架代码。把表格数据整理成“每行一个样本、每列一个特征”的矩阵再接上featureInputLayer和几个全连接层trainNetwork一跑就出结果。他半信半疑地试了一下当天就把模型跑通了。这篇东西我打算把完整的思路、网络结构设计、训练参数配置、一个从零到一的分类案例以及我踩过的一些坑一次性讲清楚。适合手里已经有多维表格特征、想做深度分类模型但不想切换Python生态的工程师也适合用Matlab做课程设计或者毕业设计的学生参考。下面按我实际做项目时的顺序来写。1. 为什么我坚持用Matlab做多特征分类而不是直接上Python1.1 表格类多特征数据在深度学习里到底是怎么输入的很多人一听到“深度学习”第一反应是卷积神经网络、图像分类觉得Matlab在这块不如Python生态。实际上深度神经网络的输入不一定是图片对于表格型多特征数据输入就是一个二维矩阵行数是样本数列数是特征数。每一个样本在这个矩阵里对应一行这一行所有数值就是该样本的多维特征向量。分类模型的输出是对应的类别标签。这种结构在Matlab里非常直观。你不需要像写PyTorch那样先定义Dataset、DataLoader再把numpy数组转成Tensor。你只要保证数据是数值矩阵标签是categorical类型或整数向量trainNetwork就能直接吃进去。Deep Learning Toolbox里专门有一个featureInputLayer就是干这个事的。它的作用就是把“特征矩阵”作为网络入口后面再接全连接层、激活层、分类层。我第一回用这个层的时候有一种“原来这么简单”的感觉。1.2 Deep Learning Toolbox在表格数据上的实际体验Deep Learning Toolbox给我的感受是可视化和脚本化结合得比较好。trainingOptions里设一个Plots,training-progress训练时就能实时看到损失曲线和准确率曲线对判断模型有没有收敛非常直观。这一点对于不想写自定义日志脚本的人来说很省事。另外Matlab的很多机器学习工具箱和数据处理函数是打通的。比如你用statistics toolbox里的cvpartition做训练集验证集划分用zscore或者normalize做特征标准化用confusionchart画混淆矩阵这些和深度学习工具箱配合起来很顺。整个流程可以写在一个脚本里不用来回在不同环境之间倒数据。这是我坚持用Matlab做这类问题的主要原因之一。1.3 什么时候该用Matlab什么时候劝你换框架不是所有情况都适合用Matlab。我自己有一个简单的判断标准写出来供参考。场景推荐工具原因特征表几千行以内几十到几百个特征Matlab脚本一体化调试方便出图直观需要做自定义复杂网络、动态图、大规模分布式训练PythonPyTorch/TensorFlow生态更灵活社区资料更多需要把模型部署到嵌入式设备或C环境两者都可但Matlab Code生成方便Simulink与代码生成工具链成熟团队同事都在用Matlab做信号处理和特征提取Matlab模型交接和维护成本低数据量达到百万级训练需要多机多卡PythonMatlab分布式支持相对弱一些这里我要多说一句如果你的数据量很小只有几百行深度学习不一定比传统机器学习有明显的优势但它作为特征变换和分类器组合的一部分仍然有用。我文章后面给出的网络结构其实就是一个多层感知机MLP很适合中小规模表格数据。2. 网络结构设计从数据维度到featureInputLayer的关键选择2.1 网络入口不要用CNN用featureInputLayer多特征输入的核心是特征矩阵每列是一个特征特征之间没有二维空间关系。如果你直接用图像输入层那等于强行把特征当成图像像素反而破坏了特征之间的独立性。正确做法是用featureInputLayer作为第一层指定输入特征维度就是特征数量。举个例子如果数据矩阵有50列特征那就这样定义输入层featureInputLayer(50, Normalization, zscore, Name, input)这里的Normalization,zscore表示在训练时自动对每个特征做零均值单位方差归一化。这个参数很关键因为很多表格特征量纲差异大不归一化会让深层网络很难收敛。我看到很多新手在外部用zscore手工归一化其实这里可以内置。2.2 网络深度和宽度别一上来就堆到128、256表格数据的特征数量通常不像图像像素那么多所以网络不需要特别深。我用一个朴素的逻辑来设计输入特征数通常是几十维第一层隐藏单元可以设成特征数的1到2倍后面逐层减半最后一层隐藏单元接近类别数。这样既保留特征组合能力又不容易过拟合。以50个特征、4分类为例一个比较稳的结构layers [ featureInputLayer(50, Normalization, zscore) fullyConnectedLayer(64) reluLayer fullyConnectedLayer(32) reluLayer fullyConnectedLayer(16) reluLayer fullyConnectedLayer(4) softmaxLayer classificationLayer ];如果特征只有4个、类别有3类比如后面要讲的Iris那64个隐藏单元就偏大了。我一般会先用特征数类别数到特征数两倍这个区间搜索一轮。4个特征时第一层取8到16就够。网络不是越深越好深度增加会带来更大的过拟合风险和更长的训练时间。2.3 激活函数、Dropout和批归一化的搭配思路对多特征分类这种结构我常用的激活函数是ReLU因为它计算简单并且能缓解梯度消失。最后一个全连接层后面接softmax层得到每个类别的概率。分类层classificationLayer负责计算交叉熵损失整个过程不需要你手工实现损失函数。要不要加Dropout我的经验是当特征数量比较多、训练样本相对少的时候Dropout比较有用。可以在隐藏层后面加一个dropoutLayer比如dropoutLayer(0.5)0.5表示断开一半神经元的连接。这个层的本质是让模型不依赖单个特征组合强迫网络学到更鲁棒的表征。如果样本量很充足Dropout反而可能降低训练效率。批归一化batchNormalizationLayer在表格数据里也可以加能加速收敛但在小批量上统计量不稳定如果MiniBatchSize设得很小批归一化的效果会打折扣。我个人是样本量上千才会优先考虑批归一化几百行的小样本就慎用。2.4 一套可复用的基础网络模板下面我给出一个可以直接套用的模板特征数变量featureNum和类别数变量classNum自己替换。% 参数设置 featureNum 50; classNum 4; % 定义网络层 templateLayers [ featureInputLayer(featureNum, Normalization, zscore, Name, in) fullyConnectedLayer(64, Name, fc1) reluLayer(Name, relu1) dropoutLayer(0.3, Name, drop1) fullyConnectedLayer(32, Name, fc2) reluLayer(Name, relu2) fullyConnectedLayer(classNum, Name, fc_out) softmaxLayer(Name, softmax) classificationLayer(Name, classout) ]; % 查看网络结构 analyzeNetwork(templateLayers);使用analyzeNetwork可以检查各层尺寸是否匹配。这一步我非常推荐因为多特征分类最常见的报错就是输入维度不匹配。3. 训练配置那些文档里不会细说的超参数问题3.1 trainingOptions各项参数的真实意义trainNetwork靠trainingOptions统一管理训练参数。我第一次用的时候只设置了MaxEpochs和MiniBatchSize结果模型不收敛。后来把几个关键参数都搞清楚了训练就顺利了。所有参数里我最关注这几个Solver优化器我基本用adam它对学习率不那么敏感收敛稳定。InitialLearnRate初始学习率太小收敛慢太大会震荡。0.01是个常见的起点。MiniBatchSize每次迭代用的样本数需要根据数据量调。MaxEpochs把整个训练集重复看多少遍。ValidationData验证集用来监控过拟合。ValidationFrequency每隔多少轮迭代做一次验证。OutputNetwork当设置成best-validation训练结束会返回验证损失最小的网络而不是最后一轮的网络。Plots设置成training-progress可以实时看到曲线。Verbose控制命令行日志输出。我通常这样配置options trainingOptions(adam, ... MaxEpochs, 80, ... MiniBatchSize, 16, ... Shuffle, every-epoch, ... InitialLearnRate, 0.01, ... ValidationData, {XValidation, YValidation}, ... ValidationFrequency, 10, ... ValidationPatience, 10, ... OutputNetwork, best-validation, ... Verbose, true, ... Plots, training-progress);3.2 学习率与MiniBatchSize的联动调整学习率和批量大小不是独立的。批量越小梯度噪声越大学习率要适当降低批量越大梯度估计越稳学习率可以提高一些。我习惯这样试先用MiniBatchSize16、学习率0.01跑一遍如果损失下降过程中出现大幅震荡就把学习率降到0.001如果收敛很慢就适当增大MiniBatchSize到32或64。我遇到过一种情况验证损失一直在降但训练损失出现锯齿状说明学习率偏高批量又小。把学习率降到0.001之后马上平稳了。这个经验对多特征分类这种中小规模数据尤其重要因为样本量不大每个batch的随机性更明显。3.3 验证集、早停和防止过拟合的组合拳多特征分类模型很容易过拟合尤其是特征多、样本少的时候。我的固定组合是划分一个验证集设ValidationPatience作为早停再加上OutputNetwork为best-validation。这样即使训练到后半程过拟合了返回的模型仍然是验证损失最低的那一版而不是最后一版。ValidationPatience的作用是验证损失连续多少次迭代没有下降训练就会提前结束。这个值我一般设10到20。要注意的是早停虽然省时间但如果训练还没充分收敛就停了可能欠拟合。所以我通常会先用MaxEpochs设大一点靠早停机制自己决定何时终止。3.4 训练过程监控怎么看loss曲线判断模型状态Matlab训练窗口会画两条损失曲线训练损失和验证损失。我判断模型状态的方法很简单两条曲线都在下降且比较接近训练正常。训练损失下降验证损失先降后升开始过拟合早停或Dropout可以解决。两条曲线都不怎么动学习率太低、网络容量不够或者数据预处理有问题。训练损失震荡剧烈学习率太高或MiniBatchSize太小。另外要看准确率曲线分类问题里准确率比损失更直观。如果验证准确率已经稳定在较高水平但还有几个轮次波动可以不用管最终会取验证损失最优的模型。4. 一个完整案例基于Iris多特征分类从训练到评估4.1 数据导入与预处理Iris数据集有150个样本、4个特征、3种类别非常适合演示多特征分类。虽然问题简单但流程跟真实项目完全一致。我先加载数据把标签转成categorical类型。load fisheriris X meas; % 150行4列每列是一个特征 Y species; % 150行1列字符元胞数组 Y categorical(Y); % 转成分类变量 % 划分训练集和测试集 rng(1); cv cvpartition(Y, HoldOut, 0.2); XTrain X(training(cv), :); YTrain Y(training(cv), :); XTest X(test(cv), :); YTest Y(test(cv), :); % 再从训练集中切一小部分作为验证集 cv2 cvpartition(YTrain, HoldOut, 0.15); XValidation XTrain(training(cv2), :); YValidation YTrain(training(cv2), :); XTrain XTrain(test(cv2), :); YTrain YTrain(test(cv2), :);这里要说明一点测试集一定要在开始就切出来不能放到训练后再切。因为一旦你用整个数据集做过归一化或者调参测试集的信息就已经渗入模型选择了后面评估出来的指标就不干净了。4.2 构建分类网络并设置训练选项Iris特征是4个类别是3个所以网络结构用小容量版本featureDim size(XTrain, 2); classNames categories(YTrain); layers [ featureInputLayer(featureDim, Normalization, zscore, Name, input) fullyConnectedLayer(12, Name, fc1) reluLayer(Name, relu1) fullyConnectedLayer(6, Name, fc2) reluLayer(Name, relu2) fullyConnectedLayer(numel(classNames), Name, fc_out) softmaxLayer(Name, softmax) classificationLayer(Name, classout) ]; options trainingOptions(adam, ... InitialLearnRate, 0.01, ... MaxEpochs, 60, ... MiniBatchSize, 16, ... ValidationData, {XValidation, YValidation}, ... ValidationFrequency, 5, ... ValidationPatience, 10, ... OutputNetwork, best-validation, ... Plots, training-progress, ... Verbose, true); net trainNetwork(XTrain, YTrain, layers, options);我把输入层里的Normalization,zscore开着这样在训练时就会自动用训练集均值和标准差做归一化预测时也会用同一组统计量不会造成数据泄漏。如果你之前手动用zscore函数归一化测试集也要用训练集的统计量去变换而不是重新对整个矩阵算。4.3 训练后评估准确率、混淆矩阵、ROC曲线训练结束后用classify对测试集预测然后计算准确率和混淆矩阵。这是最基本的评估。YPred classify(net, XTest); acc sum(YPred YTest) / numel(YTest); fprintf(Test accuracy: %.2f%%\n, acc * 100); figure; confusionchart(YTest, YPred);confusionchart会在图上直接显示每一类的预测正确数和误分类数非常直观。Iris数据集本身线性可分性不错测试准确率通常能在90%以上。如果还想看ROC曲线可以用predict先拿到每个样本的类别得分然后调用rocmetrics需要R2021a以上版本[YPredScore, scores] predict(net, XTest); % scores是 N x 类别数 的矩阵列顺序对应类别顺序 figure; rocObj rocmetrics(YTest, scores, classes categories(YTest)); plot(rocObj);rocmetrics会输出每一类的ROC曲线和AUC值。多分类ROC的绘图会自动做一对多处理比手写方便得多。4.4 模型保存、加载与对新样本预测训练好的net是一个SeriesNetwork或DAGNetwork对象直接保存成mat文件下次加载就能用。save(irisDNNClassifier.mat, net); % 新数据进来时 S load(irisDNNClassifier.mat); newFeature [5.1, 3.5, 1.4, 0.2]; newLabel classify(S.net, newFeature); disp(newLabel);注意newFeature要跟训练数据的特征顺序完全一致否则预测结果毫无意义。比如训练时第1列是花萼长度第2列是花萼宽度预测时也必须按同样的顺序给值。5. 多特征分类的踩坑实录五类问题从定位到解决5.1 “训练数据中的观测数必须等于响应数”是最常见的错这个报错几乎每个用trainNetwork的人都遇到过。原因是XTrain的行数观测数和YTrain的元素数对不上。Matlab要求数据矩阵的每一行对应一个标签如果你在构造训练集时不小心把标签多取了一行或者少取了一行就会触发这个错误。排查思路很简单训练前用size检查所有变量。assert(size(XTrain,1) numel(YTrain), XTrain和YTrain行数不一致);另外如果YTrain不是categorical类型而是double类型Matlab可能也会报类似错误。一定要确认响应变量是categorical或者用categorical(YTrain)转换。5.2 标签顺序和混淆矩阵错位categorical类型的标签在Matlab内部会按字母顺序排序。比如故障类型“低、中、高”会排成“低、中、高”吗不一定按ASCII码排“中”可能排在“低”前面。这导致混淆矩阵的类顺序和你想的可能不一样。解决方法是在训练前显式指定类别顺序。Y categorical(Y, {setosa, versicolor, virginica});这样网络输出的类别顺序就和你定义的一致混淆矩阵看起来也清楚。如果不指定顺序你从分类结果里看到的类别顺序可能与数据中出现顺序不一致造成不必要的困扰。5.3 predict与classify用混导致结果变成概率classify返回的是预测标签predict返回的是每个类别的得分矩阵。对于分类网络predict返回的“得分”实际上是softmax输出的概率。如果你不小心用predict去跟真实标签比较就会得到一堆0到1之间的小数然后各种报错。我自己的习惯是只要是想得到标签一律用classify只有需要计算ROC、AUC或者自定义阈值时才用predict。写完代码后我会跑一个简单测试YPred classify(net, XTest); assert(is categorical(YPred) || iscellstr(YPred) || isnumeric(YPred), 检查是否错误使用了predict);实际不需要这么麻烦只要记住分类用classify概率用predict就行。5.4 外部归一化泄漏测试集信息有人喜欢在训练之前先用zscore(全部数据)做归一化再把归一化后的数据划分成训练集和测试集。这是一个隐蔽但危险的做法因为归一化统计量包含了测试集的信息相当于“模型提前偷看了测试分布”。更合理的做法是把数据集先划分再用训练集的均值和标准差去变换训练集、验证集、测试集。如果你用了featureInputLayer的Normalization,zscoreMatlab内部会自动处理这个问题。但如果你一定要在数据层面预先归一化我建议用这种方式[Z, mu, sigma] zscore(XTrain); XValNorm (XValidation - mu) ./ sigma; XTestNorm (XTest - mu) ./ sigma;测试集变换时只使用训练集算出来的mu和sigma不要重新算。5.5 特征列顺序在训练和预测之间不一致这个坑没那么显眼但一旦踩了就是灾难。我帮别人排查过一个模型训练时特征顺序是“均值、峰值、方差”预测时因为换了个数据导出脚本顺序变成“峰值、均值、方差”。模型照样跑准确率直接下降十几个点表面还看不出任何报错。根本原因很简单神经网络对特征列的语义是位置敏感的第一列权重是给第一个特征的。如果你把特征顺序打乱相当于换了一个问题。解决方法是把特征列名也保存下来预测前用列名对齐数据而不是靠“我记得这个脚本之前是按这个顺序生成的”。我的做法很简单训练前把特征名列表存成一个变量保存模型时一并保存。featureNames {SepalLength, SepalWidth, PetalLength, PetalWidth}; save(irisDNNClassifier.mat, net, featureNames);下次预测新数据时先检查列名是否一致不一致就报错。6. 训练不收敛时的排查思路从数据到模型的顺序检查6.1 先看Loss曲线是发散还是停滞多特征分类模型如果训练不好不要急着改网络先看损失曲线。曲线发散经常是学习率过大曲线停滞可能是学习率过小或数据归一化没做好。如果数据本身量级差异大特征输入层没用zscore损失曲线常常表现为一开始下降一点点后面就完全不动。有个直观的检查方法把训练曲线的纵坐标范围拉大看看损失是不是在0.7附近上上下下。对于二分类或者多分类初始损失接近ln(类别数)例如4类就是约1.39。如果训练到20个epoch仍然在初始值附近没下降那问题大概率出在数据输入或者学习率上。6.2 用analyzeNetwork提前发现尺寸不匹配在训练之前建议先调用analyzeNetwork(net)或者analyzeNetwork(layers)。这个函数会打印每一层的输出尺寸并指出尺寸不匹配的地方。多特征分类里常见的错误是输入层写成了特征数50但实际数据只有49列analyzeNetwork会立刻报出来。我也见过很多人把featureInputLayer(50)写成了featureInputLayer([50,1])多了一个维度。实际上featureInputLayer只接受特征数作为标量写向量反而会出错。用analyzeNetwork能少走很多弯路。6.3 数据规模太小深度学习不一定比浅层模型好最后说一个比较扎心的经验当样本量只有一两百时一个三层的MLP和线性SVM的准确率可能不相上下。深度学习对中小规模表格数据的提升并没有想象中那么显著它的优势更多在于自动特征组合和非线性拟合能力。如果特征数少、样本少我建议先跑一个逻辑回归或者决策树做基线深度学习模型跟基线对齐了再谈调优。我之前做一个项目特征20个样本500随手搭了一个两层MLP测试准确率78%但随机森林跑出来有82%。后来我把网络结构调整成输入16、隐藏8加了L2正则和早停才勉强到83%。这个案例告诉我们深度学习不是银弹表格数据场景下传统机器学习仍然值得作为对比对象。7. 我个人最推荐的多特征分类项目落地流程如果把上面内容浓缩成一套可以照做的流程我建议按下面这个顺序走能避免大部分返工先梳理业务目标和数据字典明确特征列和标签列类别定义清楚。数据清洗处理缺失值、异常值对类别特征做one-hot编码或数值映射。划分训练集、验证集、测试集比例建议0.7/0.15/0.15或者根据数据量调整。用featureInputLayer的zscore归一化网络结构从浅到深搜索先跑一个简单MLP。设置早停和best-validation用验证集决定是否调整网络层数或Dropout。测试集只用来做最终评估除非要写报告否则不要反复拿测试集调参。保存模型的同时保存特征列名、类别顺序、预处理参数保证部署时一致。在实际操作中我最看重第4和第7步。第4步决定了模型拟合能力的上限第7步则决定了模型能不能从实验环境顺利走到实际预测场景。很多项目失败不是因为网络结构不够复杂而是因为线下线上特征处理不一致。最后分享一个小技巧如果你希望模型可解释性强一点可以在训练后查看第一个全连接层的权重找出对每个类别影响较大的特征。Matlab里直接访问net.Layers(2).Weights就能拿到权重矩阵。这个方法不是万能的但有时候能帮你发现哪些特征被模型重点使用了对后续特征筛选挺有帮助。以上就是我在Matlab里做深度神经网络多特征输入分类模型的完整经验。这套方法我已经在好几个项目里反复用过不能说它比所有Python方案都好但在中小规模表格数据、尤其是团队已经使用Matlab的场景下它确实是一条高效而且稳定的路。你如果也正在为这类问题发愁不妨按文章里的流程先跑通一个最小案例再逐步扩展。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门