ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Matlab的VggNet与ResNet物体分类识别与迁移学习实践

基于Matlab的VggNet与ResNet物体分类识别与迁移学习实践 简介基于VggNet与ResNet的物体分类识别研究资料面向深度学习初学者以及有Matlab基础的计算机视觉研究者旨在帮助读者系统理解两类经典CNN架构的原理差异并快速上手图像分类实战。压缩包共60012个文件其中60002张png图像构成训练/测试或结果展示素材4个m脚本实现主流程、模型获取与文件列表整理3个mat文件保存训练好的网络权重另含fig交互界面与pdf图文说明整体约398.58MB。目前已有759人学习下载。资料中VggNet部分演示了连续小卷积核堆叠以增加深度的思路ResNet部分则展示残差块如何缓解梯度消失、支撑更深网络训练。代码可直接运行读者可加载预训练权重、进行前向传播与对比实验还可通过界面查看分类结果、分析各层特征适合用于课程设计、毕业设计或入门深度图像识别。1. 基于VggNet与ResNet的物体分类识别从特征提取到Matlab落地物体分类识别是图像处理里最常遇到的起点问题。VggNet 与 ResNet 这两个基础 CNN在 ImageNet 上预训练的权重经常被搬来当特征提取器和迁移学习骨架Matlab 的 Deep Learning Toolbox 把这两个模型封装成了可以直接加载的对象剩下的工作主要是怎么替换分类层、怎么设置学习率、以及如何把中间层特征用起来。这里不按代码包逐行解说而是顺着从零开始做一版 VggNet/ResNet 分类识别实验的完整路径把预训练模型加载、微调、多尺度特征融合和双网络集成的常见做法讲清楚。适合已经会基本图像分类、准备在自己数据集上跑对比实验的工程师和研究者。2. VggNet 和 ResNet 的架构差异与 Matlab 预训练模型选型在做实验之前先想清楚一个基本问题为什么要同时用 VggNet 和 ResNet原因有两个。第一这两个网络的归纳偏置不同VggNet 靠卷积堆叠抓细粒度纹理ResNet 靠残差短接保留粗粒度轮廓二者在错误样本上的分布通常互补。第二Matlab 对这两个模型提供了统一的加载接口迁移学习改造步骤高度相似适合做对照实验。2.1 VggNet 的卷积堆叠与细粒度特征提取VggNet 的核心思路是把卷积层串成连续的小卷积核堆叠用多个 3×3 卷积替代一个大感受野卷积。每个卷积层后接 ReLU网络层数越深组合出来的特征越抽象。以 Vgg16 为例前 13 个卷积层负责提取边缘、角点、纹理和语义零部件中间通过 fc6、fc7 两层全连接做特征整理最后 fc8 输出 1000 类 ImageNet 得分。对于和 ImageNet 类别相似的数据集直接换掉 fc8 和分类层即可对于差异较大的自定义数据集fc7 依然可以当作 4096 维通用特征向量来用。正因为这种堆叠结构VggNet 对小区域的纹理差异特别敏感。比如区分两种表面花纹相近的金属零件或者识别叶片上的病斑类型fc7 特征经常比 ResNet 的全局池化特征更有区分度。缺点是参数量大训练速度慢而且一旦输入图片分辨率远高于 224×224中间的卷积内存占用会飙升。2.2 ResNet 的残差结构如何兼顾粗粒度特征ResNet 引入残差块让每一块的输出等于F(x) x。这个恒等映射使梯度回传路径变短网络可以堆到 50 层甚至 101 层而不退化。分类任务里残差连接还会把浅层的位置、轮廓信息直接传到高层相当于保留了大尺度下的粗粒度特征。因此 ResNet 对物体尺度变化、遮挡和背景复杂的情况更稳。resnet50的输入尺寸同样是 224×224×3但整体结构比 VggNet 更紧凑。它先用一个 stride 为 2 的 7×7 卷积下采样再经过若干 stage每个 stage 内部包含多个残差块最后是全局平均池化、一个fc1000全连接层和分类层。微调时只需要替换这个全连接层和分类层前面的卷积块基本不用动。2.3 预训练模型对照表与加载命令Matlab 里加载预训练模型是最简单的一步但有几个容易忽略的差异。下表整理了常见基础模型的对应关系模型对象权重层数输入尺寸末尾连接层名加载命令示例vgg1616224×224×3fc8 / ClassificationLayer_fc8vgg16(Weights,imagenet)vgg1919224×224×3fc8 / ClassificationLayer_fc8vgg19(Weights,imagenet)resnet5050224×224×3fc1000 / ClassificationLayer_fc1000resnet50(Weights,imagenet)resnet101101224×224×3fc1000 / ClassificationLayer_fc1000resnet101(Weights,imagenet)加载示例net vgg16(Weights,imagenet); lgraph layerGraph(net); analyzeNetwork(lgraph);analyzeNetwork会弹出交互式图层结构图方便确认最后一层的名称。Weights,imagenet表示加载预训练权重如果省略Matlab 通常也会自动下载权重到本机用户目录下的matlab/toolbox/neuralnetwork文件夹首次运行需要联网。之后离线也能继续使用。选型建议调试阶段先跑 Vgg16因为结构直观层名简单定位问题快等到验证整个 pipeline 正常后再切到 ResNet50 做正式训练。最后把两个网络的结果做集成往往能拿到比单网络更好的准确率。3. 用 Matlab 实现 VggNet 与 ResNet 的迁移学习分类识别迁移学习的本质是保留预训练网络的卷积部分替换最后与类别数量相关的层再用自己的数据做小迭代训练。以一个五分类汽车类型识别任务为例假设图片已经按类别放在data/train和data/validation文件夹下每个类别一个子文件夹。3.1 数据集准备与 imageDatastore 划分Matlab 里最稳定的图像分类数据入口是imageDatastore。直接把类别子文件夹路径传进去它会自动按文件夹名生成标签。目录结构如下data/ train/ sedan/ suv/ truck/ validation/ sedan/ suv/ truck/加载代码trainDS imageDatastore(data/train, IncludeSubfolders, true, LabelSource, foldernames); valDS imageDatastore(data/validation, IncludeSubfolders, true, LabelSource, foldernames);如果只有一个全量数据集可以用splitEachLabel按比例切分allDS imageDatastore(data/all, IncludeSubfolders, true, LabelSource, foldernames); [trainDS, valDS] splitEachLabel(allDS, 0.8, 0.2, randomized);切分完要检查每个标签下的样本数量避免某个类在验证集中过少countEachLabel(trainDS) countEachLabel(valDS)我一般还会在切分后执行shuffle因为连续采集的图片通常存在背景相关性不随机打乱容易让验证集结果虚高。splitEachLabel的randomized选项只能随机切分不够彻底所以稳妥做法是再洗一次trainDS shuffle(trainDS); valDS shuffle(valDS);3.2 替换 VGG16 和 ResNet50 的全连接层VGG16 的原始输出是 1000 类需要把最后的全连接层和分类层替换成自定义类别数。核心操作是replaceLayer下面以 VGG16 为例numClasses 5; net vgg16(Weights,imagenet); lgraph layerGraph(net); newFC fullyConnectedLayer(numClasses, Name, fc8_new); lgraph replaceLayer(lgraph, fc8, newFC); newClassLayer classificationLayer(Name, classoutput_new); lgraph replaceLayer(lgraph, ClassificationLayer_fc8, newClassLayer);ResNet50 同理但替换的是fc1000和ClassificationLayer_fc1000netRes resnet50(Weights,imagenet); lgraphRes layerGraph(netRes); newFCRes fullyConnectedLayer(numClasses, Name, fc_new); lgraphRes replaceLayer(lgraphRes, fc1000, newFCRes); newClassRes classificationLayer(Name, classoutput_new); lgraphRes replaceLayer(lgraphRes, ClassificationLayer_fc1000, newClassRes);几个常见坑需要提前注意不替换分类层直接训练trainNetwork会报类别数不匹配。全连接层名称可以自定义但不能和现有层重名否则会覆盖已有层。替换后建议立刻调用analyzeNetwork(lgraph)检查连线是否完整。在较旧的 Matlab 版本里没有replaceLayer需要用removeLayersaddLayersconnectLayers手工接线代码会明显变长建议至少使用 R2021b 之后的版本。3.3 训练选项参数与常见坑训练前需要把imageDatastore包装成augmentedImageDatastore因为预训练模型要求输入为 224×224×3而原始图片尺寸通常不统一。同时可以加入随机平移、翻转等数据增强augTrain augmentedImageDatastore([224 224], trainDS, ... DataAugmentation, imageDataAugmenter(... RandXTranslation, [-10 10], ... RandYTranslation, [-10 10], ... RandXReflection, true)); augVal augmentedImageDatastore([224 224], valDS);训练选项是决定效果的关键部分。常用参数如下表参数常用值说明MiniBatchSize16 / 32显存不足时降为 16InitialLearnRate1e-4 ~ 1e-3迁移学习用 1e-4 起步MaxEpochs8 ~ 15小数据集不需要太长ValidationFrequency20 ~ 50每多少次迭代验证一次Plotstraining-progress实时查看损失曲线Shuffleevery-epoch每个 epoch 重新打乱训练集训练代码options trainingOptions(sgdm, ... MiniBatchSize, 32, ... MaxEpochs, 10, ... InitialLearnRate, 1e-4, ... ValidationData, augVal, ... ValidationFrequency, 30, ... Shuffle, every-epoch, ... Verbose, true, ... Plots, training-progress); netVGG trainNetwork(augTrain, lgraph, options);这里有一个容易踩的坑augVal作为验证数据时不建议再添加数据增强否则每次classify的结果都会因为平移翻转而抖动无法稳定评估模型。另外ValidationFrequency设置太小时验证会消耗大量时间但设置太大又看不到早期过拟合信号一般按训练样本数 / MiniBatchSize的 1~2 倍来设。4. 细粒度特征提取与自注意力增强VggNet 和 ResNet 的进阶用法迁移学习训练完的网络最后一层直接输出类别概率。但很多实际项目里类间差异很小比如不同车型、不同菌落形态仅靠最后一层特征往往不够。常见做法有两种把中间层特征提出来外接 SVM或者在网络末端加入注意力机制让模型放大有用特征。下面分别在 VggNet 和 ResNet 上说明。4.1 从中间层提取特征向量用于 SVM 分类VGG16 的fc7是 4096 维特征ResNet50 通常用pool5全局平均池化后的 2048 维特征。这些特征已经包含预训练语义提出来后可以离线训练线性 SVM。提取代码如下layerName fc7; % VGG16 trainFeatures activations(netVGG, augTrain, layerName, OutputAs, rows); valFeatures activations(netVGG, augVal, layerName, OutputAs, rows);ResNet50 对应的层名通常是pool5layerNameRes pool5; trainFeaturesRes activations(netRes, augTrain, layerNameRes, OutputAs, rows); valFeaturesRes activations(netRes, augVal, layerNameRes, OutputAs, rows);activations返回N×D的特征矩阵N是图片数D是特征维度。直接用fitcecoc训练多分类 SVMsvmModel fitcecoc(trainFeatures, trainDS.Labels, Learners, svm); predSVM predict(svmModel, valFeatures); accSVM mean(predSVM valDS.Labels);fitcecoc是 Matlab 自带的多分类 SVM 框架底层用一对一方案。对 5~20 个类别的小型数据集比重新训练 CNN 快一个数量级。下面这个表是不同特征层的适用场景参考网络层名特征维度适用场景VGG16fc74096纹理细粒度差异ResNet50pool52048语义粗粒度分类ResNet50activation_2_relu256×56×56边缘、角点ResNet50activation_10_relu512×28×28局部部件经验上SVM 在 VGG16 的 fc7 特征上往往比网络直接预测更稳因为 SVM 对高维特征做了更充分的边界拟合ResNet50 的 pool5 特征经过全局池化分类边界更平滑两者结果差异明显时就可以考虑做融合。4.2 在 ResNet 末端加入 SE 注意力模块自注意力机制在图像分类里的一个轻量实现是 SE Block。它先对特征图做全局平均池化得到每个通道的统计量再经过两个全连接层和一个 sigmoid输出 0 到 1 的通道权重最后把权重乘回原特征图。Matlab 没有现成的seBlock函数但可以通过基础层拼出来。以下是在resnet50的res5c_relu后面插入 SE Block 的脚本骨架lgraphSE layerGraph(netRes); % 全局平均池化 gapLayer averagePooling2dLayer([7 7], Name, se_gap, Stride, [1 1]); % 第一个全连接层降维到 16 维 fc1 fullyConnectedLayer(16, Name, se_fc1); % 第二个全连接层恢复到输出通道数 2048 fc2 fullyConnectedLayer(2048, Name, se_fc2); % sigmoid 激活 sigLayer sigmoidLayer(Name, se_sigmoid); % 乘法层 mulLayer multiplicationLayer(2, Name, se_mul); lgraphSE addLayers(lgraphSE, [gapLayer; fc1; fc2; sigLayer; mulLayer]); % 连接特征图 - 全局池化 lgraphSE connectLayers(lgraphSE, res5c_relu, se_gap); % 连接sigmoid 输出到乘法层第二输入 lgraphSE connectLayers(lgraphSE, se_sigmoid, se_mul/in2); % 连接原始特征图到乘法层第一输入 lgraphSE connectLayers(lgraphSE, res5c_relu, se_mul/in1); % 连接乘法层输出到后续池化层 lgraphSE connectLayers(lgraphSE, se_mul, pool5);这里se_gap的池化尺寸是[7 7]因为res5c_relu的特征图在 224×224 输入下通常是 7×7。如果输入尺寸或网络版本不同需要先用analyzeNetwork查看实际尺寸。fc1的 16 维可以改成输出通道数/16常见取 16如果网络不是 ResNet50fc2的输出维度必须改成对应的主干输出通道数。4.3 注意力模块的参数初始化与训练稳定性插入新层后新增两层全连接的权重是随机初始化的。训练初期SE Block 的随机输出可能打乱原有特征分布导致验证准确率先跌几个点再回升。我一般把整体初始学习率降到 1e-5同时把MaxEpochs适当增加让网络慢慢适应新层的注意力权重。另一个实用做法是在加入 SE 前先冻结主干只让 SE 层训练几个 epoch等损失下降后再解冻。Matlab 中设置冻结层要对网络对象逐个层设置LearningRateFactor但不同层类型属性不完全一致最省事的方式是用trainingOptions里较小的InitialLearnRate来模拟。很多新手误以为注意力模块会自动提高精度实际在类别差异很小时SE 需要配合足够的训练迭代才能稳定生效。5. 粗粒度与细粒度特征融合多尺度物体分类识别实验把高层语义和低层细节拼接起来能提升小物体和细类别的识别准确率。这一部分讲两个实用的融合路径ResNet 内部多阶段特征融合以及 VGG 和 ResNet 的跨网络特征融合。5.1 提取 ResNet 不同 stage 的输出做特征金字塔ResNet50 的多个 stage 输出不同分辨率特征如activation_2_relu约为 56×56、activation_10_relu约为 28×28、activation_22_relu约为 14×14。这些层名在不同 Matlab 版本里可能有差异先用analyzeNetwork确认。提取一批图片的多尺度特征时可以先写一个函数对每个 stage 的输出做全局平均池化再拼接成一条特征向量function f extractMultiScale(net, img) f1 activations(net, img, activation_2_relu); f2 activations(net, img, activation_10_relu); f3 activations(net, img, activation_22_relu); f [squeeze(mean(f1,[1 2])); squeeze(mean(f2,[1 2])); squeeze(mean(f3,[1 2]))]; end对整批数据调用multiFeatTrain []; for i 1:numel(trainDS.Files) img readimage(trainDS, i); multiFeatTrain(i,:) extractMultiScale(netRes, img); end这种做法的本质是特征金字塔。粗粒度分支保留全局形状细粒度分支保留局部纹理拼接后外接 SVM 或线性分类器比单独使用pool5更稳。5.2 VGG 特征与 ResNet 特征的 concat 融合分类VGG16 的fc7和 ResNet50 的pool5分别代表不同归纳偏置下的高层语义。VGG 偏重局部纹理ResNet 偏重全局语义拼接后往往能提升 1 到 3 个百分点featVGG activations(netVGG, augVal, fc7, OutputAs, rows); featRes activations(netRes, augVal, pool5, OutputAs, rows); featConcat [featVGG featRes]; svmFuse fitcecoc(featConcat, valDS.Labels, Learners, svm); predFuse predict(svmFuse, featConcat);需要注意featVGG是 4096 维featRes是 2048 维拼接后是 6144 维。小数据集下直接用 SVM 容易过拟合建议先做 PCA 降维[coeff, score, ~] pca(featConcat); featPCA score(:, 1:512);PCA 会把两个网络特征的量纲差异拉平之后再训练 SVM 结果更稳定。一个典型的精度变化如下表输入特征验证准确率VGG16 fc791.2%ResNet50 pool590.6%concat6144 维92.1%concat PCA512 维93.5%不同数据集上数值会有浮动但趋势通常一致融合优于单网络降维后稳定性更好。5.3 融合结果的精度评估与 t-SNE 可视化除了看准确率我还用 t-SNE 把特征降维到二维观察类别是否更容易分开。Matlab 的tsne函数在 Statistics and Machine Learning Toolbox 中feat2D tsne(featConcat, NumDimensions, 2); figure; gscatter(feat2D(:,1), feat2D(:,2), valDS.Labels);如果同一类别在图上形成密集簇不同类别之间重叠少说明融合特征有区分度。如果某两类始终叠在一起说明这两个类的样本特征本身太近需要回到数据增强层面补充角度或光照变化。6. VGG16 与 ResNet50 双网络投票集成与混淆矩阵验证技巧6.1 软投票集成对预测分数加权平均端到端训练好两个网络后直接把两个网络的输出概率相加作为最终预测通常比单个网络稳定。软投票代码probVGG predict(netVGG, augVal); probRes predict(netRes, augVal); probEnsemble 0.5 * probVGG 0.5 * probRes; [~, predIdx] max(probEnsemble, [], 2); predLabel valDS.Labels(1, predIdx);predict返回的是每个类别的概率矩阵行是样本列是类别。max得到每行最大概率的列索引再映射到标签。如果某个网络在验证集上明显更好可以把权重从 0.5/0.5 调成 0.3/0.7 或 0.6/0.4观察变化曲线。6.2 用混淆矩阵定位两个网络的互补错误混淆矩阵的价值在于发现互补性。生成 VGG16 的混淆矩阵predVGG classify(netVGG, augVal); figure; confusionchart(valDS.Labels, predVGG);对比 ResNet50 的混淆矩阵后如果 VGG16 总把sedan错分为suv而 ResNet50 能分开说明这两个类的纹理特征接近、形状差异大。这时应该针对该类别增加多角度样本而不是盲目增加训练轮数。6.3 一个可复用的验证脚本框架实际项目中我通常把验证代码封装成函数只留数据路径、网络路径两个输入function [acc, predLabels] evalModel(valDS, net, svmModel, layerName) if nargin 2 ~isempty(layerName) feat activations(net, valDS, layerName, OutputAs, rows); predLabels predict(svmModel, feat); else predLabels classify(net, valDS); end acc mean(predLabels valDS.Labels); end调用时统一输出准确率、混淆矩阵和 t-SNE 图这样在 VggNet/ResNet 对比实验里不会漏掉任何验证环节。后续换网络结构或新增数据集只需要替换网络对象验证流程保持一致。本文还有配套的精品资源点击获取
返回列表