直接上干货这次咱们用Matlab整点实用的CNN分类模型支持多维特征输入二分类和多分类都能玩。先看效果再说原理代码直接扒下来就能用替换数据分分钟搞定——这话听着就带劲因为用Matlab写CNN的教程是真的少大部分一搜全是Python PyTorch可咱们工业界、实验室里大量老系统、仪器采集程序都是基于Matlab的数据预处理、特征提取、画图、报表全在一条链路上你非要为了一个分类器去搭Python环境、学新框架、折腾CUDA那性价比属实太低。这篇博文不讲故事、不写情怀就讲一套我实际调通过的Matlab CNN分类模板它既能吃二维表格特征就是行是样本、列是特征那种也能吃多维特征比如把多个传感器/多个时间窗口拼成一个三维块训练完直接出混淆矩阵、ROC曲线二分类换多分类只需要改一个数字。下面我要写的内容每一段都是实操现场记录代码我会从头到尾列出来关键参数我逐个解释为什么这么设还会把我在两个实际项目里踩过的坑一并倒出来。1. 这套CNN分类模板的整体设计思路1.1 它到底解决什么问题先说清楚这套东西的使用场景。假设你手头有一个特征矩阵每一行代表一个样本每一列是某个特征维度可能是传感器均值、方差、峰值、频域能量、统计量之类的组合。你之前要么用SVM、随机森林做分类要么用BP神经网络但分类准确率卡在某个瓶颈上。这时候你想试试CNN是因为CNN能通过卷积核自动提取局部特征组合比如它自己会学到“特征3和特征7的某种相对关系有判别力”而不需要你手工构造交互项。而多维特征输入是指数据本身不只是一张二维表有的场景下每个样本是“多个通道的序列”比如振动信号分x、y、z三个方向每个方向截取200个点那一个样本就是3×200的矩阵或者你的原始数据是多个传感器同时采集时间窗口内有800个采样点8个通道那一个样本就是8×800。CNN天然能吃这种“多通道二维数据”只要把数据组织成 H×W×C 的形式Matlab的 imageInputLayer 就能直接接收。这套模板的定位就是你只需要把你的数据整理成“样本数×特征数”或“样本数×高度×宽度×通道数”然后按我的代码填进去训练、评估、可视化全自动。换句话说我不搞花里胡哨的网络结构就是一套短小精悍、能跑通、能出论文图表、能交付给现场使用的基线模型。1.2 为什么用Matlab而不是Python这个问题我被问过很多次。Python生态确实丰富PyTorch写CNN跟喝水一样但很多工程师手里的数据本身就是Matlab出来的或者需要和Simulink模型联动又或者单位电脑不允许随便装Python环境和驱动。Matlab的Deep Learning Toolbox从R2018b之后已经相当成熟trainNetwork、trainingOptions、confusionchart这些函数封装得非常好你不需要手写反向传播不需要手动管理GPU显存代码量比PyTorch还短。另外Matlab的调试体验和数据可视化是传统强项训练过程中你可以实时看训练精度、损失曲线结束后一键出混淆矩阵热力图甚至能导出训练过程动画。对于非纯算法岗的人来说这套工作流友好得离谱。当然缺点是部署相对笨重但它自带的Coder可以生成C代码也能导出ONNX给其他平台用这块后面我会提一下。1.3 模板的整体架构这套模板从数据到结果分五步数据导入与整理、数据归一化与格式转换、网络结构定义、训练参数配置、评估与可视化。我把每一步都封装成独立小节方便你按需替换。核心思想是“面向数据形状编程”就是不管你的原始数据长什么样最终都统一变成 [高度×宽度×通道数×样本数] 的四维数组喂给CNN。如果你只有普通表格特征那就把每行特征重排成 [1×特征数×1] 的形状相当于一个“单像素宽图片”照样能跑卷积。为了让你快速建立全局观我先给出代码调用总览后续每段代码都会展开解释。% 主入口示例 data readtable(your_data.csv); % 读数据 featMat table2array(data(:, 2:end)); % 特征 labels data.label; % 标签 normMat normalize(featMat, zscore); % 归一化 % 重排成CNN输入格式 X reshape(normMat, [1, size(normMat,2), 1, size(normMat,1)]); Y categorical(labels); % 定义网络 layers [ ... imageInputLayer([1 size(normMat,2) 1]) convolution2dLayer([1 3], 16, Padding, same, Name, conv1) batchNormalizationLayer(Name, bn1) reluLayer(Name, relu1) maxPooling2dLayer([1 2], Stride, [1 2], Name, pool1) fullyConnectedLayer(2, Name, fc1) softmaxLayer(Name, softmax) classificationLayer(Name, output)]; % 训练 options trainingOptions(sgdm, MaxEpochs, 30, MiniBatchSize, 32, Shuffle, every-epoch, ValidationData, ...); net trainNetwork(X, Y, layers, options);看到没核心代码就是这么短。接下来的内容就是把每一行的“为什么”讲透然后告诉你不同数据形态怎么改。2. 数据准备和格式转换80%的坑都埋在这2.1 从表格数据到CNN输入维度的转换细节很多初学者第一次跑CNN都会卡在“数据格式不对”。Matlab的imageInputLayer接收的是 h×w×c×n 的四维数组h是高度w是宽度c是通道数n是样本数。对于特征向量这种形式最自然的理解方式就是“高为1、宽等于特征数量、单通道的图”。关键是reshape时别把行列搞反。假设你的特征矩阵是 data——它的大小是 m×fm是样本数f是特征数。我们要的是每个样本按行取出形成一个 [1, f, 1] 的特征图所以最不容易错的办法是先把矩阵转置再reshape% data: m×f, 每一行是一个样本 X_raw data’; % f×m X reshape(X_raw, [1, f, 1, m]); % 高度1、宽度f、通道1、数量m这里有人会问为什么不是 [f, 1, 1]你要从CNN的角度想convolution2dLayer的卷积核会沿“宽”方向滑动如果你把特征放成竖向 [f, 1]那1维卷积核 [1,3] 就没办法正常滑动虽然技术上也能跑但参数含义会变得很拧巴。所以统一用 [1, feature_dim, 1] 是约定俗成的做法。如果你的数据本来就是图像比如灰度图是 28×28×1×n彩色图是 28×28×3×n那就完全不用restructure直接喂就行。如果数据是“多通道时序信号”比如8通道、每个通道800点那就是 [800, 8] 转成 [1, 800, 8] 或者 [800, 8, 1]这两种放法对应不同的卷积方向理解。我自己习惯把时间放宽度、通道放通道数即 [1, 800, 8]因为这样卷积核每次看到的是一段局部时间内的所有通道组合符合传感器融合的直觉。2.2 归一化的正确姿势和常见误区CNN对输入尺度很敏感不是因为它内部有什么玄学而是因为卷积核的权重乘以输入后如果数值量级差异极大梯度更新就会乱飘训练很难收敛。我见过有人直接把原始数据丢进去loss在前10个epoch像过山车一样下不来归一化之后10个epoch就开始稳定下降。在Matlab里normalize函数有几种模式zscore和range最常用。zscore是“(x - mean)/std”适用于特征本身近似正态分布、且没有极端离群点的场景range是“min-max缩放”适用于特征值本身有界且需要映射到[0,1]的场景。如果数据里有大量离群点直接zscore会导致一些小特征被压缩到几乎为零这时候建议先做分位数裁剪再归一化。% 推荐做法按列zscore归一化 normMat normalize(featMat, 2, zscore); % 第二个参数2表示按列操作对Matlab来说就是每一列特征独立归一注意归一化的均值和标准差必须在训练集上计算然后直接套到验证集和测试集上。如果对整个数据集统一归一化会造成轻微的信息泄漏最后评估指标会虚高。这个问题在表格数据上不明显但时间序列和图像上会放大养成好习惯很重要。2.3 标签格式categorical是标配Matlab分类层的标签要求是categorical类型而不是字符串数组或数值向量。很多人直接用数值0和1当标签然后训练报错“Invalid training label”。正确做法是Y_raw data.label; % 可能是double或cell字符串 Y categorical(Y_raw);如果你的标签是字符串直接用 categorical 转换。如果标签是数值0和1也能转但要注意二分类用0/1标签的时候categorical之后类别名称会变成“0”和“1”分类层会把它们视为两个类别没问题。多分类同理转成categorical之后Matlab会自动按字母序或首次出现顺序排列类别。如果你对类别顺序有要求可以用 reordercats 手动调整这个影响混淆矩阵的展示顺序但不会影响模型本身。3. 网络结构是怎么定出来的每一层我都跟你掰扯清楚3.1 从图像输入层到特征提取的流程网络结构是CNN的核心但我必须先把丑话说在前面——对于表格数据和中小规模数据而言网络层数不需要太深。很多人被ImageNet的成功经验带偏了一上来就是十几层卷积结果自己的数据只有几千个样本训练没几步就开始严重过拟合trainning accuracy接近100%验证集一塌糊涂。这里的关键是CNN的容量要跟数据规模匹配。我这套模板选的是一个非常稳健的基线结构layers [ imageInputLayer([1 featDim 1], Name, input, Normalization, none) convolution2dLayer([1 3], 16, Padding, same, Stride, [1 1], Name, conv1) batchNormalizationLayer(Name, bn1) reluLayer(Name, relu1) maxPooling2dLayer([1 2], Stride, [1 2], Name, pool1) convolution2dLayer([1 3], 32, Padding, same, Name, conv2) batchNormalizationLayer(Name, bn2) reluLayer(Name, relu2) maxPooling2dLayer([1 2], Stride, [1 2], Name, pool2) fullyConnectedLayer(32, Name, fc1) reluLayer(Name, relu_fc) dropoutLayer(0.5, Name, dropout) fullyConnectedLayer(numClasses, Name, fc_final) softmaxLayer(Name, softmax) classificationLayer(Name, output)];imageInputLayer的Normalization参数我直接设成‘none’因为前面我们已经手动归一化过了避免重复处理。如果你的原始数据没有归一化可以考虑在这里设成‘zscore’它会自动学习每个通道的均值和方差并内置到网络里。但我个人的建议是数据预处理应该在网络外面显式做这样你可以把同一套归一化参数导出到部署端免得部署时还要从网络里扒参数。3.2 卷积核尺寸和通道数的选择逻辑第一个卷积核我设置成 [1 3]意思是核的高度是1、宽度是3。因为输入是 [1 featureDim]特征在宽度方向上排列所以卷积核高度为1不会跨行去卷宽度3表示每次看相邻的3个特征。这相当于一个“特征组合器”把相邻三个特征的信息融合成一个值符合局部感受野的直觉。至于通道数第一层16、第二层32这个数值是我反复测试后觉得性价比最高的配置。16个初始通道足够提取多种局部特征模式32个第二层通道能让网络在更高语义层做组合。不要第一层就上64、128对于几千个样本的表格数据来说参数量增加几倍但准确率不会有本质提升反而训练更慢、更容易过拟合。如果有经验的读者想调整可以按这个原则数据量越大、输入特征维度越宽卷积核数量和层数可以适当增加反之则必须缩减。比如你只有500个样本、20个特征那我建议去掉第二个卷积块保留到pool1之后就直接接全连接否则一定会过拟合。3.3 BatchNorm和ReLU为什么绑在一起用BatchNormalization层的核心作用是让每一层的输入分布保持稳定避免训练过程中由于前面层参数更新导致后面层输入剧烈变化。它做两件事先对每个通道计算均值方差做归一化再通过可学习的缩放和平移参数恢复表达能力。它的好处是不光加速收敛还在一定程度上起到正则化作用减少对dropout的依赖。ReLU就是简单的max(0,x)它的作用是引入非线性。如果不加激活函数所有卷积和全连接堆叠起来本质上还是一个线性变换CNN的拟合能力就废了。ReLU在正区间梯度恒为1不会像sigmoid那样梯度饱和所以深层网络训练更稳。在Matlab里BatchNorm必须搭配训练过程使用预测时会自动用训练阶段累积的均值和方差不需要你额外处理。3.4 池化层和Dropout的真正作用maxPooling2dLayer的作用是降采样把特征图的宽高减半。它一方面减少计算量另一方面让后续的卷积操作能覆盖更广的范围相当于粗粒化地保留显著特征。我这里用的是大小[1 2]步长[1 2]意味着宽度方向每2个元素取1个最大的特征维度直接减半。如果特征维度特别小比如不到16那就不建议池化了直接保留到全连接层会更稳。dropoutLayer则是在训练时随机丢弃一部分神经元让网络不能过分依赖某个特定节点从而提高泛化能力。0.5的丢弃比例是默认的安全值。对于容易过拟合的小数据集这个层简直是救命稻草。我一般把dropout放在第一个全连接层后面而不是最后分类层前这样能让最后一层保持完整的特征输入效果更好。3.5 输出层与分类层的配置要点末尾的流程是全连接层输出类别数→ softmax层 → classificationLayer。fullyConnectedLayer里的第二个参数就是类别数。二分类就是2三分类就是3。softmaxLayer把全连接输出映射成每个类别的概率总和为1。classificationLayer接收概率分布和真实标签计算交叉熵损失并执行反向传播。有人会问二分类可不可以用sigmoid加binary cross entropyMatlab的Deep Learning Toolbox里分类层对二分类就是这么处理的但网络结构是完全一样的唯一的区别是类别数量设为2不用特殊处理。放心用就行。4. 训练参数怎么配这些选项决定你模型是菜鸟还是能打4.1 trainingOptions里每个字段的实战解读训练网络用的是trainNetwork(net, layers, options)或者直接trainNetwork(X, Y, layers, options)。options配置决定了训练的质量和速度我给出一个工业级较稳的配置options trainingOptions(adam, ... InitialLearnRate, 0.001, ... MaxEpochs, 60, ... MiniBatchSize, 32, ... Shuffle, every-epoch, ... ValidationData, {XVal, YVal}, ... ValidationFrequency, 20, ... LearnRateSchedule, piecewise, ... LearnRateDropFactor, 0.1, ... LearnRateDropPeriod, 20, ... Verbose, true, ... Plots, training-progress, ... ExecutionEnvironment, auto);逐个说字段solver用adam不用sgdm的原因adam自适应调整每个参数的学习率对学习率初始值不那么敏感表格数据和小批量场景下收敛更稳。sgdm调好了上限更高但需要手调momentum和学习率衰减策略图省事就adam。InitialLearnRate设0.001是adam的默认安全值。如果你发现loss降得太慢可以调大到0.01如果loss震荡就降到0.0003。MaxEpochs对于小数据集30到80轮是个合理区间。不用设太多否则后期只是在拟合训练集的噪声。MiniBatchSize默认32。如果GPU显存不够就降到16或8。数据量很大时用64或128能加速但会影响收敛稳定性。Shuffle设‘every-epoch’每个epoch打乱训练数据顺序防止模型学到顺序相关的假规律。ValidationData从训练集里切出一部分做验证我在后面细讲怎么切。ValidationFrequency每迭代20次验证一次。迭代次数是“iteration”不是epoch。如果总样本几百个、batch32那一个epoch也就十几二十次迭代所以20这个值大致可以保证每轮或隔轮都验证一次。LearnRateSchedule用piecewise分段式衰减每20个epoch学习率乘以0.1。这种方式是经典做法前期大步快跑后期小步精调。Plots设‘training-progress’可以在训练窗口实时看到loss和accuracy曲线我强烈建议开着肉眼判断模型是否正常比看任何日志都直观。ExecutionEnvironment设‘auto’自动选择GPU或CPU如果没GPU就CPU跑。4.2 训练集、验证集、测试集怎么切才科学切数据是模型评估里最容易被新手忽略的环节。正确流程是先把原始数据分成训练集验证集和测试集然后在训练过程中用训练集训练、验证集实时评估最后用测试集做一次性的最终评估。cv cvpartition(height(data), HoldOut, 0.2); % 20%做测试集 trainIdx training(cv); testIdx test(cv); % 在训练集内部再分验证集 valIdx randi([1 sum(trainIdx)], floor(sum(trainIdx)*0.15), 1);我见过太多人犯的错误是直接把全部数据丢进trainNetwork然后用同一批数据测试得出一个虚高的准确率。那个数字拿到现场一测就打回原形。正确的做法必须像上面这样留出独立测试集而且测试集只在最后用一次不要根据测试集表现反复调参否则测试集就在你心里变成了验证集评估意义会被污染。4.3 数据不平衡和二分类阈值怎么处理如果你的二分类数据类别比例严重失衡比如正样本只有5%负样本95%CNN很容易陷入“全部预测为负类”的局部最优。直接改分类层不现实实用的方案有几种第一种用MiniBatchSize控制采样。在构造训练集的时候把正负样本分开每个batch里按比例抽取。这个在Matlab里需要自定义minibatchqueue稍微麻烦一点但对不平衡数据效果非常显著。第二种在trainingOptions里设置‘ClassWeights’参数给少数类更大的权重。这个参数在Matlab里是trainingOptions(adam, ClassWeights, [1 5])这样的写法需要确认版本支持。如果版本不支持最简单的办法是复制少数类样本或做简单过采样。第三种是在最终评估时不要只盯accuracy要看混淆矩阵、召回率和精确率。很多工业场景下我们宁可牺牲一点负类的准确率也要保证正类的召回率这时候可以调整分类阈值。Matlab分类层的决策阈值默认是0.5如果要改变阈值你需要用predict函数拿到概率输出再自己设定阈值。[~, scores] predict(net, XTest); % 二分类scores第二列是正类概率 predLabel scores(:, 2) 0.3; % 降低阈值提高召回5. 训练过程中的实操观察记录和验证曲线分析5.1 正常训练曲线长什么样当你点下运行按钮训练窗口弹出图表之后前几个iteration可能loss很高这是正常的因为网络是随机初始化的。大概10到30个iteration后训练loss会出现断崖式下降验证准确率同步上升。这个过程可以粗略理解为网络先学到全局统计规律再慢慢学细节。以我自己跑过的某制造业传感器数据集为例初始loss约0.69第10个iteration降到0.45第30个iteration到0.2上下准确率从50%跳到90%以上后面曲线变缓60轮结束时训练集准确率98%、验证集准确率93%左右。这是典型的好迹象训练集和验证集差距不算太远说明没有严重过拟合。5.2 过拟合信号和应对方法如果出现训练准确率持续上升到99%但验证准确率在80%附近来回震荡这就是过拟合的红色警报。可能原因有数据量太小、网络容量过大、训练轮数过多。应对办法按优先级排列增加dropout比例从0.5调到0.6或0.7。减少网络层数或卷积核数量。提前终止设ValidationPatience或者手动把MaxEpochs降到30。做数据增强。表格数据的增强方式包括加噪声、缩放扰动、特征dropout时序数据可以随机裁剪或改变速度。Matlab的augmentedImageDatastore主要面向图像表格数据需要自己用循环生成。5.3 欠拟合信号和应对方法如果训练集和验证集准确率都很低且loss下降缓慢比如20个epoch后训练准确率还在70%以下那就是欠拟合或特征表达能力不足。这时候反向操作增大学习率、增加卷积核数量、增加层数、或者回到起点看特征工程是否合理。很多时候CNN救不了一个特征本身毫无判别力的数据集这点必须清醒认识。6. 全套评估代码混淆矩阵、ROC、分类报告一网打尽训练完成后evaluateClassification函数在R2022b后非常方便一条命令生成所有指标% 在测试集上做最终评估 YPred classify(net, XTest); % 混淆矩阵 cm confusionchart(YTest, YPred); cm.Title 测试集混淆矩阵; % 各类别精确率、召回率、F1 report evaluateClassification(YTest, YPred); % ROC曲线二分类或多分类都支持 rocObj rocmetrics(YTest, scores, classNames); plot(rocObj);这里我想多说一句混淆矩阵比准确率更能暴露问题。我见过一个二分类模型准确率97.5%看似很牛但打开混淆矩阵发现少数类只被正确检出了5%因为少数类只占总样本2%模型直接无脑预测多数类都能拿到98%。如果你只看accuracy就会把一个线都测不出来的残废模型当成宝贝。所以强烈建议每次训练完把混淆矩阵、F1-score、AUC都看一遍再下结论。对于多分类你可以用rocmetrics的One-vs-Rest策略画每个类别的ROC曲线Matlab自动处理了代码不变。AUC值在0.8以上算可用0.9以上算拟合得相当好。7. 从二分类扩展到多分类以及场景化适配7.1 二分类变多分类只需要改三行代码严格来说这套模板从一开始就兼容多分类。二分类转三分类只需要把fullyConnectedLayer的numClasses从2改成3。确保标签是三类categorical。如果类别不平衡ClassWeights改成三个权重。就这么简单。CNN分类层天然支持任意数量的类别不需要像SVM那样一个一个做OvR一对多拆分。我自己做过一个6分类的故障诊断项目就是拿这套模板直接跑的效果比同行用2层LSTM还稳。原因在于故障特征大多是频率和时域统计特征卷积天然的局部感知能力很适合捕捉它们之间的组合关系。7.2 如果输入变成图像或时序数据怎么快速改如果你的数据不是表格而是图像那更好办。图片本身已经是 h×w×c 的格式你只需要确保imageInputLayer参数和图像尺寸一致% 假设图像是64×64×3 imageInputLayer([64 64 3])但要注意卷积核尺寸也要改不能再是 [1 3]而应该改成 [3 3] 或 [5 5]因为图像是二维的卷积核在两个方向都要滑动。池化层也要改成 [2 2] 而不是 [1 2]否则你只在宽度方向降采样高度方向不动特征图变成细长条信息利用率差。如果输入是多通道时序信号那更简单。每个样本是一个 通道数×时间点 的矩阵比如8通道×800点。有两种组织方式第一种是 [1, 800, 8] 时间在宽度通道在通道维度第二种是 [800, 8, 1]时间在高度通道在宽度。我推荐第一种因为默认的特征图更符合“横轴为序列、纵轴为通道”的阅读习惯而且时间方向上的局部卷积可以更好地捕捉短期模式。8. 我踩过的坑附排查清单和避坑建议8.1 尺寸不匹配Matlab报错最凶的问题这个错误90%是reshape维度算错了。你reshape的目标必须严格等于总元素数。比如原始特征是 2000×502000个样本、50个特征你想变成 [1 50 1 2000]那总元素数是1×50×1×2000100000正好等于2000×50没问题。但如果你写反成 [1 2000 1 50]总元素数也是100000不报错但语义完全错了——每个样本由2000个“伪特征”组成训练出来的模型是垃圾。我的建议是在reshape之后加一行断言检查assert(size(X, 4) m, 样本数量不匹配); assert(size(X, 2) f, 特征维度不匹配);8.2 内存爆炸Out of Memory怎么办如果你数据量很大比如几万个样本、几千个特征直接把所有数据做成四维数组会吃到大量内存。一个[50000 1000]的特征矩阵转成[1 1000 1 50000]是200MB的double数组再加网络训练时的中间变量很容易爆内存。解决办法用single类型存数据数据量减半X single(X); 图像输入层支持single。用 tall 数组或 datastore 分批读入trainNetwork本身支持Datastore输入但代码复杂度会上来。减小MiniBatchSize训练内存峰值会明显下降。8.3 验证集loss上升但准确率也上升这个现象初看很诡异其实是因为loss和accuracy不是完全单调负相关。loss是交叉熵它反映了预测概率的置信度如果模型对错误的样本给出稍微高一点的置信度loss会上升但准确率可能没变或微升。这个没关系只要测试集表现稳定就行不用过度担心。8.4 CPU训练慢成狗怎么加速Matlab的trainNetwork在CPU上也支持但CNN卷积运算密集CPU跟GPU差距可能几十倍。没有GPU的情况下最有效的加速办法是减小网络规模减少卷积层数、减少通道数、减小特征维度用PCA先降维。如果特征有几百维我建议先做主成分分析降维到50维左右再喂CNN不仅能加速还能去除冗余噪声准确率反而可能提升。8.5 部署时模型怎么给出去训练好的net是一个SeriesNetwork或DAGNetwork对象。现场交付有两种选择第一种是直接打包Matlab Runtime让现场机器装个运行时就能跑适合内部使用。第二种是exportONNXNetwork导出ONNX然后转成其他平台或语言推理适合跨语言部署。还有第三种如果你只是要在Matlab里做实时预测直接用net作为函数调用即可延迟极低。9. 一套可直接复制的完整实战模板最后我把上面所有的散装代码整合成一个完整的训练脚本把你换数据时需要改的位置都用注释标注出来。你可以把它保存成一个main.m直接运行%% 1. 数据加载与标签准备 data readtable(your_data.csv); % 改成你自己的数据 featMat table2array(data(:, 1:end-1)); % 前面列是特征 labels data{:, end}; % 最后一列是标签 % 标签转categorical Y categorical(labels); classNames categories(Y); numClasses numel(classNames); %% 2. 数据划分训练集/验证集/测试集 rng(42); % 固定随机种子保证可复现 cv cvpartition(Y, HoldOut, 0.2); trainIdx training(cv); testIdx test(cv); featTrain featMat(trainIdx, :); featTest featMat(testIdx, :); YTrain Y(trainIdx); YTest Y(testIdx); % 从训练集中再分出验证集 valRatio 0.15; numVal floor(sum(trainIdx) * valRatio); valIdx randperm(sum(trainIdx), numVal); featVal featTrain(valIdx, :); YVal YTrain(valIdx); % 剩余训练数据 featTrainRest featTrain; featTrainRest(valIdx, :) []; YTrainRest YTrain; YTrainRest(valIdx) []; %% 3. 归一化在训练集上计算参数 mu mean(featTrainRest, 1); sigma std(featTrainRest, 0, 1); sigma(sigma 0) 1; % 避免除零 featTrainNorm (featTrainRest - mu) ./ sigma; featValNorm (featVal - mu) ./ sigma; featTestNorm (featTest - mu) ./ sigma; %% 4. 转换为CNN输入格式 [1 特征数 1 样本数] XTrain reshape(featTrainNorm, [1, size(featTrainNorm,2), 1, size(featTrainNorm,1)]); XVal reshape(featValNorm, [1, size(featValNorm,2), 1, size(featValNorm,1)]); XTest reshape(featTestNorm, [1, size(featTestNorm,2), 1, size(featTestNorm,1)]); %% 5. 网络结构定义可自定义 featDim size(featTrainNorm, 2); layers [ imageInputLayer([1 featDim 1], Name, input) convolution2dLayer([1 3], 16, Padding, same, Name, conv1) batchNormalizationLayer(Name, bn1) reluLayer(Name, relu1) maxPooling2dLayer([1 2], Stride, [1 2], Name, pool1) convolution2dLayer([1 3], 32, Padding, same, Name, conv2) batchNormalizationLayer(Name, bn2) reluLayer(Name, relu2) maxPooling2dLayer([1 2], Stride, [1 2], Name, pool2) fullyConnectedLayer(64, Name, fc1) reluLayer(Name, relu_fc) dropoutLayer(0.5, Name, dropout) fullyConnectedLayer(numClasses, Name, fc_final) softmaxLayer(Name, softmax) classificationLayer(Name, output)]; %% 6. 训练选项 options trainingOptions(adam, ... InitialLearnRate, 0.001, ... MaxEpochs, 50, ... MiniBatchSize, 32, ... Shuffle, every-epoch, ... ValidationData, {XVal, YVal}, ... ValidationFrequency, 20, ... LearnRateSchedule, piecewise, ... LearnRateDropFactor, 0.2, ... LearnRateDropPeriod, 15, ... Verbose, true, ... Plots, training-progress, ... ExecutionEnvironment, auto); %% 7. 训练 net trainNetwork(XTrain, YTrainRest, layers, options); %% 8. 评估 YPred classify(net, XTest); cm confusionchart(YTest, YPred); cm.Title 测试集混淆矩阵; [~, scores] predict(net, XTest); if numClasses 2 rocObj rocmetrics(YTest, scores(:, 2), classNames); else rocObj rocmetrics(YTest, scores, classNames); end plot(rocObj);换数据的时候只需要动第一步的readtable路径和最后一列的标签设置。如果特征维度特别大可以先把featMat做PCA降维再喂进来。如果你想把这个网络接到实时数据流里做预测只要把 predict(net, newData) 封装成函数就行我可以保证推理速度远快于训练速度几毫秒一个样本绰绰有余。我个人实际用下来的体会是这套模板最大的价值不在“先进”而在“可预期”。它结构简单、参数常规、收敛稳定你拿到手上之后80%的精力可以花在特征工程和数据质量问题排查上而不需要去调网络结构。CNN在表格数据上的效果虽然没有深度学习神话吹得那么玄乎但在特征维数中等、样本量几千以上的场景里确实能比随机森林和BP网络高出几个点的准确率尤其是特征之间存在复杂交互的时候。先跑通这个模板验证CNN在你的数据上到底有没有效果如果有效再考虑深挖更复杂的结构我觉得这是性价比最高的路线。最后再分享一个小习惯——训练前把关键中间变量比如mu、sigma、网络结构、训练选项用mat文件存一份这样即使你改了代码重新训练也能随时回看上次跑的配置对比实验的时候省太多事了。