
简介面向深度学习与计算机视觉初学者的物体分类识别研究资料包以VggNet和ResNet两种经典卷积神经网络为主线系统梳理网络结构差异、训练优化策略及其在图像分类任务中的应用并配有可直接运行的Matlab代码便于从理论走向实践。压缩包共包含60012个文件大小约398.58MB其中60002张png图片构成主要图像与结果可视化数据4个.m脚本为网络搭建及训练/预测代码3个mat文件为预训练或训练好的模型权重另有pdf说明文档、fig界面文件和jpg示例图方便对照学习。资源目前已有759人学习下载读者可借助Matlab代码与权重快速完成VggNet/ResNet的前向传播和结果解析深入理解残差连接、梯度消失等关键概念附带的pdf与fig有助于梳理实验流程和界面设计整体目录区分度高适合入门学习者反复研读。1. VggNet与ResNet做物体分类识别从一张图到一个类别标签物体分类识别是计算机视觉里最基础的任务给定一张图片判断它属于哪个预先定义的类别。VggNet和ResNet是这类任务里两套绕不开的卷积神经网络一个靠反复堆叠3×3卷积把层数堆上去一个靠恒等映射把深层网络的训练稳定性提上来。Matlab深度学习工具箱里预置了VggNet与ResNet系列的预训练模型支持迁移学习和微调让研究者不用从零训练也能在自建数据集上跑出可用的分类结果。这个方向适合两类人一类是本科毕设和课程项目需要快速对比两种网络并产出实验数据另一类是做工程预研的工程师想用预训练模型验证一个分类场景是否可行。前者关心流程能不能跑通后者关心参数怎么调、坑在哪里、最后该选谁。这篇文章按这个顺序展开先把两种网络的差异讲透再给出Matlab里的完整复现路径最后把调参和踩坑经验一次性列清楚。2. VGG与ResNet网络结构对比为什么ResNet不怕深VGG靠堆层2.1 VGG的3×3卷积堆叠看着简单参数一点不省VGGNet在2014年的ImageNet竞赛里证明了“深度本身是有价值的”。当时的主流做法是用大卷积核捕捉更大范围的特征VGGNet反其道而行统一用3×3小卷积核通过连续堆叠来增大感受野。两个3×3卷积堆叠等价于一个5×5卷积的感受野三个堆叠则等价于7×7。小卷积核参数更少、非线性更强训练也更容易收敛。VGG16的结构很规整五个卷积段每段由2到3个卷积层构成通道数从64开始经过一次池化就翻倍到第五段变为512。卷积段之后是三个全连接层其中前两个的维度都是4096最后一个是1000分类输出。这个结构的最大问题不在卷积层而在最后三个全连接层。以VGG16为例最后一个卷积段输出的是7×7×512的特征图展平后送入第一个全连接层fc6连接权重数量是25088×4096约1.03亿个参数。三个全连接层加起来占了整个网络参数量的绝大部分。所以VGG16虽然只有16个权重层总参数量却达到1.38亿属于典型的“结构简单但参数爆炸”。这个特点在Matlab环境下特别明显加载一个训练好的VGG16权重文件比ResNet50大一个数量级做前向推理时全连接层的矩阵乘法也是主要耗时来源。如果任务只是做物体分类VGG也能完成任务但工程上的性价比不高。2.2 ResNet的残差块与恒等映射怎么把退化问题压下去ResNet要解决的并不是“网络不够深”而是“网络太深反而训练不动”。实验发现当普通卷积网络从20多层加深到50多层时训练集上的误差反而上升这不是过拟合而是优化困难。梯度在反向传播过程中经过多层连乘会指数级缩小或放大导致浅层权重几乎得不到有效更新。ResNet的残差块给出了一个很直接的解法。残差块的核心是让输出等于“输入加上经过卷积变换的结果”y F(x) x。这里的F是两层或三层卷积组成的映射x是输入本身这个从输入直接跳到输出的连线叫恒等映射或快捷连接。引入这条连线后梯度可以从深层直接传回浅层即使中间卷积路径的梯度衰减了恒等路径也能保住梯度。这就是ResNet在加深到152层时依然能稳定训练的原因。ResNet按深度分为18、34、50、101、152等版本。18和34用的是BasicBlock两个3×3卷积组成一个残差块50及以上的版本改用Bottleneck结构每个块由1×1、3×3、1×1三个卷积组成先用1×1降维、再在低通道数上做3×3计算、最后用1×1升维。Bottleneck的核心价值是降低计算量让ResNet50在深度远超VGG16的情况下参数量反而更小、推理更快。在Matlab里resnet18适合快速验证流程resnet50是大多数分类任务的首选。2.3 选型对照同是分类识别VGG和ResNet差在哪做物体分类识别时选VGG还是ResNet要同时看精度、训练成本、显存占用和后续可扩展性。下面这张表是两者在同输入分辨率下的典型差异。对比项VGG16ResNet50权重层数1650参数量约1.38亿约2560万输入尺寸224×224×3224×224×3卷积特征粒度底层纹理、中层轮廓、顶层语义同样粒度分层但顶层语义更抽象训练难度结构简单容易收敛依赖残差块但深而不退化显存占用高主要在后续层全连接较低卷积为主检测/分割扩展性全连接层拖累下采样特征复用更适合做骨干网络与FPN等结构组合ResNet作为骨干网络还有一个VGG难以替代的优势它的特征金字塔结构更自然。物体分类只用到网络最后一层输出但在更复杂的检测任务里FPN会把粗粒度特征和细粒度特征融合起来ResNet各阶段的输出天然适合做这种多尺度融合。VGG虽然也可以提取中间特征但全连接层把空间信息压平后续做定位就麻烦很多。因此我的选型原则是如果目标是快速验证一个分类想法、或者用Matlab做教学演示VGG的中间特征更直观翻车概率低如果目标是追求精度、做比较复杂的识别场景、或者给后续的检测分割留扩展空间无脑选ResNet系列。后面所有Matlab代码都按这两个网络同时给出来方便直接对比。3. 用Matlab跑通VGG与ResNet的物体分类识别预训练模型、微调与训练脚本3.1 环境准备Deep Learning Toolbox与预训练模型下载Matlab里做这个方向需要确认两件事Deep Learning Toolbox是否已安装以及Matlab版本是否支持预训练网络的加载接口。安装好之后用ver命令确认版本太老的话很多函数名字都不一样。% 确认工具箱是否可用 ver(nnet); % 加载预训练模型首次调用会联网下载权重文件 netVgg vgg16; netRes resnet50;vgg16和resnet50这两个函数在首次调用时会从MathWorks的服务器下载预训练权重并缓存到本地用户目录。VGG16的权重文件明显比ResNet50大很多如果你在下载阶段卡住可以先确认网络环境再把下载好的.mat文件放到Matlab提示的路径下。加载完成后用analyzeNetwork可以查看网络结构和所有层名这是后续替换层操作最重要的依据。3.2 从文件夹到imageDatastore把数据整理成Matlab能直接吃的样子物体分类的数据组织方式很简单在根目录下按类别建子文件夹每个子文件夹里放对应类别的图片。Matlab的imageDatastore可以直接读这种目录结构并根据子文件夹名自动打标签。这一步不需要手动读图、不需要写循环一行代码就能把数据集变成后续训练需要的格式。% data目录下每个子文件夹代表一个类别 imds imageDatastore(data, ... IncludeSubfolders, true, ... LabelSource, foldernames); % 按类别比例划分训练集和验证集 [imdsTrain, imdsVal] splitEachLabel(imds, 0.8, randomized); % 查看类别信息和各类别样本数 countEachLabel(imdsTrain)splitEachLabel的第二个参数是每个类别保留的训练比例0.8表示每个类别随机取80%作为训练集剩下20%作为验证集。这里要特别注意:验证集的作用是监控训练过程、判断是否过拟合它和训练集必须完全分开后面做数据增强时也不能把增强算子应用到验证集上。在把数据送入网络前还要把图片尺寸统一到网络要求的224×224。Matlab里处理这件事的标准做法是构建一个augmentedImageDatastore它会在读取图片时自动完成缩放、通道数调整还能顺带做数据增强。第一次接触这个方向的人容易在这步踩坑直接把原始尺寸的imageDatastore传给trainNetwork结果是报错或者在某些版本里也能跑但速度极慢因为每次都要做padding。% 训练数据统一为224x224不做数据增强先跑通基线 augTrain augmentedImageDatastore([224 224], imdsTrain); augVal augmentedImageDatastore([224 224], imdsVal);augmentedImageDatastore返回的对象可以直接传给trainNetwork。暂时先不加数据增强目的是先验证数据流、网络结构、训练配置都正确得到一个基线精度。后面调优时再在这里加imageDataAugmenter。3.3 替换分类层与冻结网络前层微调的完整代码预训练模型是在ImageNet上训练的最后输出的类别数是1000和你自己的数据集类别数不一致。迁移学习的标准做法是把网络最后的全连接层和分类层替换成自己的层然后让网络在新的数据集上继续训练。VGG和ResNet在Matlab里的层名不一样VGG16最后一层全连接叫fc8ResNet50叫fc1000分类层都叫ClassificationLayer_predictions。% 以ResNet50为例将最后两层替换为适应自己类别的层 numClasses numel(categories(imdsTrain.Labels)); newFC fullyConnectedLayer(numClasses, Name, fc_new); newClass classificationLayer(Name, class_new); lgraph layerGraph(netRes); lgraph replaceLayer(lgraph, fc1000, newFC); lgraph replaceLayer(lgraph, ClassificationLayer_predictions, newClass);替换层之后网络结构还是完整的可以继续冻结前层。冻结的意思是让某些层的权重在训练时不更新因为预训练网络浅层学到的是通用的边缘、纹理、颜色特征这些特征与你的具体任务无关但很有用。如果你的数据量很小比如每个类别只有几十张图冻结大部分前层能防止过拟合同时大幅减少要更新的参数数量。% 冻结网络前80%的层只更新靠近分类层的特征 layers lgraph.Layers; numLayers numel(layers); for i 1:floor(numLayers * 0.8) if isprop(layers(i), WeightLearnRateFactor) layers(i).WeightLearnRateFactor 0; layers(i).BiasLearnRateFactor 0; end end lgraph layerGraph(layers);这段代码遍历所有层把前80%的层的WeightLearnRateFactor和BiasLearnRateFactor设为0。0表示该层的权重和偏置不参与更新相当于被冻结。注意有些层类型没有可学习参数isprop会自动跳过不会报错。如果需要把整个网络都放开训练跳过这个循环即可。3.4 训练与精度评估把trainNetwork跑起来并看结果Matlab训练分类网络的核心调用是trainNetwork它接收三个输入数据、网络结构、训练选项。训练选项里学习率、批次大小、迭代次数这些参数是决定最终精度和训练耗时的关键这里先给一组能稳定复现的基线参数详细调法放到下一章。options trainingOptions(sgdm, ... MiniBatchSize, 16, ... MaxEpochs, 6, ... InitialLearnRate, 1e-4, ... Momentum, 0.9, ... ValidationData, augVal, ... ValidationFrequency, 30, ... Plots, training-progress, ... Verbose, false); [netTrained, info] trainNetwork(augTrain, lgraph, options);sgdm是带动量的随机梯度下降适合迁移学习场景。MiniBatchSize是16因为迁移学习时学习率本身很小批次大小不需要太大16在显存和梯度稳定性之间比较均衡。InitialLearnRate取1e-4这是迁移学习的常用起点比从头训练小两个数量级避免破坏预训练权重。MaxEpochs取6因为你的数据量通常不大训练几个epoch就已经能达到不错的精度。训练完成后用验证集做评估。这里注意classify接收的是augmentedImageDatastore不是原始的imageDatastore否则图像尺寸不对会直接报错。YPred classify(netTrained, augVal); YVal imdsVal.Labels; % 计算整体准确率 accuracy mean(YPred YVal); disp([Validation accuracy: , num2str(accuracy)]); % 画混淆矩阵看每个类别的具体表现 confusionchart(YVal, YPred);混淆矩阵比单一准确率信息量大得多。它能告诉你哪些类别之间容易互相混淆比如把“猫”识别成“狗”还是“狐狸”这直接影响后续要不要针对容易混的类别增加样本。看到准确率低先不要急着调网络结构先看混淆矩阵是哪几类在打架。4. 学习率、MiniBatchSize与冻结层三个让识别精度差5个点的参数4.1 学习率与动量的调法先从训练曲线判断方向训练完成后trainNetwork返回的info变量里保存了每个迭代的损失值和验证准确率画出来是最直观的诊断工具。常见的不良曲线有三类:损失一直不降说明学习率太小或者数据本身有问题;损失剧烈震荡说明学习率偏大;损失直接变成NaN说明学习率大到了让权重更新发散。迁移学习场景下我一般把初始学习率锁定在1e-4到3e-4之间。如果训练曲线平稳但收敛慢后期再适当调大;如果你发现经过3个epoch损失几乎不动先检查数据增强和标签是否正确不要盲目加大学习率。sgdm的Momentum保持0.9即可这个参数很少需要动除非你发现损失曲线来回摆动可以试降到0.85。4.2 MiniBatchSize与显存的关系为什么ResNet50最吃显存MiniBatchSize决定了一次前向和反向传播同时处理多少张图。批次越大梯度越接近真实梯度训练曲线越平滑但显存占用也越高。ResNet50虽然参数量比VGG16少但每一层的特征图通道数更多训练时的激活值占用反而高于VGG16所以ResNet50在显存不足时更容易报Out of Memory。场景建议MiniBatchSize说明CPU上跑VGG168大batch会让单次迭代极慢GPU上跑VGG161632显存充足可以上32GPU上跑ResNet50816显存8GB以下先用8显存不足减半重跑不要同时用大batch和大图增强判断batch是否合理的标准不是“损失降得快”而是单次迭代耗时和显存占用是否可接受。一个大概率的经验是:当你看到内存不足报错时直接把MiniBatchSize减半而不是去改网络结构这是最快见效的做法。4.3 冻结层数冻结多少才算迁移学习冻结层数没有绝对标准主要看你自己的数据量和任务图像与ImageNet图像的相似度。数据量小且图像与ImageNet领域接近时冻结前80%或更多;数据量大、图像领域差异大时只冻结前30%让后面的层也参与适应新分布。推荐直接用下表作为起点数据规模与原任务相似度推荐冻结比例每类50张高常见物体80%90%每类50张低医学/遥感50%60%每类100500张不限30%50%每类1000张不限不冻结或仅冻结前10%冻得越多训练越快但网络对新任务的适应能力越弱;冻得越少精度上限越高但过拟合风险也越大。实际操作中不必纠结精确值先冻结一半跑一个基线再把冻结比例调整到20%或80%对比验证集精度就行。4.4 用imageDataAugmenter做数据增强翻转、平移与颜色扰动数据增强的作用是让模型见过更多样化的样本从而对平移、旋转、缩放等变化不敏感。Matlab里的imageDataAugmenter可以在读取图片时随机做几何变换和颜色变换不需要额外生成图片占用磁盘空间。% 定义训练集的数据增强策略 augmenter imageDataAugmenter( ... RandRotation, [-15 15], ... RandXTranslation, [-3 3], ... RandYTranslation, [-3 3], ... RandXScale, [0.9 1.1], ... RandYScale, [0.9 1.1]); augTrain augmentedImageDatastore([224 224], imdsTrain, ... DataAugmentation, augmenter);RandRotation是随机旋转角度范围正负15度是稳妥取值。RandXTranslation和RandYTranslation是平移像素范围这里正负3个像素对224×224的输入来说比较温和。RandXScale和RandYScale是缩放范围0.9到1.1表示随机缩小或放大10%。验证集一定不要加数据增强否则验证精度会带有随机性没法作为稳定的判断依据。5. 避坑记录VGG与ResNet在Matlab里最容易翻车的5个问题5.1 现象训练精度很高验证精度却很低这是我见过最多的情况直接原因往往是数据集切分不彻底。很多人先做完整数据增强、再切分训练和验证集或者验证集直接用训练集里复制出来的图片导致验证集和训练集重度重叠模型“记住”了训练样本验证分数虚高或失真。解决:先用splitEachLabel切分原始数据集再用不同的augmentedImageDatastore分别包装训练集和验证集。记住一条铁律数据增强只在训练分支做验证分支永远使用原图缩放后的数据。5.2 现象replaceLayer报错提示找不到指定层名不同Matlab版本中预训练网络的层名不一致尤其是R2019a和R2022a之间fc1000、ClassificationLayer_predictions这些名字并不是在所有版本里都一样。很多人照着旧版的教程写代码直接替换层名就翻车。解决:加载模型后先执行analyzeNetwork(net)在图形界面里确认最后一层的准确名字再写replaceLayer。也可以用net.Layers(end-2).Name这样的方式在命令行里直接查看最后一个可学习层和分类层的名字替换时使用变量引用而非硬编码字符串。5.3 现象ResNet50训练到一半内存不足ResNet50虽然参数少但中间特征图的通道数多、分辨率下降慢训练时的激活值比VGG16大。当你同时开了training-progress绘图、用了大MiniBatchSize、还在验证频率里频繁计算验证时显存很容易被打满。解决:先关闭Plots选项减少图形绘制占用的资源;再把MiniBatchSize从16降到8或4。如果仍然不足检查是不是验证集本身一次加载了太多图片可以把验证集也改成augmentedImageDatastore并设置相同的小批次大小。5.4 现象训练损失在前期变成NaNNaN出现几乎都是学习率过大导致的权重发散也有小概率是输入图片里存在损坏文件或全黑图片读取后出现无穷值。如果你用的是adam优化器而不是sgdm发散概率会更高因为adam的步长不受梯度尺度约束。解决:把InitialLearnRate降到1e-5重跑一次确认是否为学习率问题。如果问题还在遍历一下数据目录检查imageDatastore的read函数是否报错。数据清洗完成后重新生成imageDatastore不要在原对象上继续操作。5.5 现象分类结果全部偏向数量最多的那个类类别不均衡时全连接分类层会学到“猜数量多的类”这种捷径因为多数类的梯度占比大。这种情况下整体准确率可能看起来还行但混淆矩阵会暴露真相少数类的召回率几乎为0。解决:先用countEachLabel检查每类样本数。样本少的类别通过数据增强多做几轮随机变化或者采集更多原始数据。还可以在训练选项里使用ClassWeights给少数类更高的权重这是Matlab里直接可用的方案。6. 进阶验证用gradCAM看分类依据再决定上不上ResNet模型精度高不代表模型学到了正确的语义特征。一个常见情况是模型靠背景或纹理特征做判断比如在“牛”类别里模型实际学到的是一片草地。要验证模型到底在看什么可以用gradCAM生成类激活图把分类依据可视化出来。Matlab从R2021a开始内置了gradCAM函数调用方法很直接。% 读取一张验证集图片 img imread(fullfile(data, char(YVal(1)), 1.jpg)); img imresize(img, [224 224]); % 指定一个靠近分类层的卷积层生成类激活图 scoreMap gradCAM(netTrained, img, activation_layer_name); % 叠加显示在原图上 imshow(img); hold on; imagesc(scoreMap, AlphaData, 0.5); colormap jet;gradCAM的第三个参数是目标层名选最后一个卷积段输出的激活层效果比较理想。不要选全连接层gradCAM需要空间维度才能生成热力图。根据热力图的位置你能判断模型是否关注物体主体:如果热力图集中在物体轮廓内部说明所学特征可靠;如果高亮区域散落在背景上说明训练数据里有干扰因素需要清洗数据或增加样本多样性。最后说一个我做这类对比实验的习惯先跑通VGG16的完整流程得到一个基线精度确认数据、标签、训练配置都没有问题再换ResNet50做同样的实验。两个网络用完全相同的训练选项和数据划分出来的精度差才有说服力。从经验来看在中小规模数据集上ResNet50的精度通常比VGG16高2到5个百分点而且在做检测、分割等后续任务时ResNet的扩展性明显更好。除非你有特别的可视化或教学需求否则直接选ResNet系列是更稳妥的路。希望这篇从原理到排错的整理能帮到你照着第3章的脚本把第一个基线模型跑出来后面的调优就顺手了。本文还有配套的精品资源点击获取