☰
从VGG16看卷积网络深度设计:小卷积核堆叠与迁移学习价值
2026/10/16 17:29:15 网站建设 项目流程

前两天有个朋友发来一段代码,说他在某个小数据集上用VGG16做迁移学习,效果比一些看起来“更新”的网络还好,就是跑得慢。我顺手把2014年那篇《Very Deep Convolutional Networks for Large-Scale Image Recognition》翻出来又读了一遍。这篇论文对我而言确实是“常读常新”:当年它用最笨拙的方式,把卷积网络从8层直接推到16层、19层,靠堆叠3×3小卷积核刷新了ImageNet上的识别记录,并且之后几年里几乎所有检测、分割、风格迁移项目都在用它的预训练权重做底座。如果你正在学卷积网络结构,或者要复现一篇旧模型并做迁移学习,这篇文章基本就是绕不开的教科书。接下来,我会把这篇论文的动机、结构设计、小卷积核的数学直觉、论文里的消融实验、训练细节和工程遗产完整展开,顺便穿插一些我复现和调参时的真实体会。

1. VGG解决的核心问题:深度到底值多少精度

1.1 2014年前后卷积网络的“魔法时代”

2012年的AlexNet一举把ImageNet图像分类的错误率大幅压低,卷积网络从此成为视觉任务的主角。但是当时的普遍认知是分散且模糊的:有人觉得大卷积核性能好,于是用7×7甚至11×11;有人觉得网络不能太深,因为训练时会遇到梯度消失、收敛特别慢。没有一篇工作能清楚地回答“网络深度到底对精度有多大的影响”。

VGG这篇论文本质上做了一件很朴素但极有意义的事:把“深度”作为可控变量,用几乎一致的结构去增加卷积层数量,从11层一路加到19层,然后在同样数据集、同样训练流程下看精度的变化。结果证明了深度在当时的网络结构下是有效且必要的,而且加深到16到19层时仍然能稳定收益。这一点放在今天看似乎习以为常,但在当时需要很大的实验量来支撑。

1.2 我理解的论文真实贡献,不只是VGG16结构本身

很多初学者会把VGG等同于“VGG16那个网络结构”,这当然没错,但论文更值钱的地方在三点:

  • 系统验证了“小卷积核堆叠加深网络”可以稳定提高精度,并把感受野、参数量、非线性三者之间的账算得明明白白,后来的很多网络结构设计都从这个思想里延伸出来。
  • 给出了一组从浅到深、可复现的配置(论文里叫A到E),并且公开权重。当时大家随便下载一下就能用来做特征提取或迁移学习,这种开源红利对整个社区推进作用巨大。
  • 提供了完整的训练和调参经验,比如先训浅层网络再拿浅层权重初始化深层网络,这个操作后来成了很多深层网络训练的救命稻草。

1.3 这篇论文适合谁细读

如果你是刚接触深度学习的初学者,VGG是一个极好的“骨架模板”:每条卷积层、每个池化层都规规矩矩,通道数翻倍、尺寸减半的节奏一目了然,比很多花哨的结构容易理解得多。如果你是有几年经验的工程师,重新读一遍也会发现大量容易被忽略的细节,比如LRN(局部响应归一化)为什么被放弃、测试时多尺度裁剪为什么有用、权重初始化对深层网络有多重要。这篇文章接下来的内容,就是把这些关键细节一次拆开。

2. 逐层拆解VGG16:从224×224到分类输出的全路径

2.1 五种配置的差异:A、A-LRN、B、C、D、E

论文里最让人印象深刻的是那张配置总表。VGG团队用几乎一样的思路设计了一组网络,区别主要在总层数和少量结构改动。为了方便记忆,我通常用每个卷积块的通道数序列来区分,配置代码大概长这样:

cfgs = { 'A': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'], 'B': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'], 'C': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'], 'D': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'], 'E': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'], }

其中'M'代表尺寸减半的max-pooling。A是基准的11层网络;A-LRN是在A的基础上加局部响应归一化;B在A的基础上把某些位置的单层卷积变成了两层;C则在B的基础上又加了一些1×1卷积;D把C的1×1卷积换成了3×3;E在D基础上继续加深到19层。最常用的VGG16对应D配置,VGG19对应E配置。

我当时看到这个配置表的第一反应是:原来VGG16其实就是把一堆卷积层拼成五个块,每块第一层之后池化一次,最后接三个全连接层。搞清楚这个规律之后,看很多后续网络就非常轻松,因为后来的ResNet、DenseNet基本也是“分阶段设计”的套路,只是内部变换方式不同。

2.2 VGG16逐层参数与特征图尺寸变化

以224×224×3的输入为例,VGG16的完整数据流是这样走的:

  • 第一阶段:两组conv3-64,输出224×224×64,然后max-pool,尺寸变为112×112×64。
  • 第二阶段:两组conv3-128,输出112×112×128,然后max-pool,尺寸变为56×56×128。
  • 第三阶段:三组conv3-256,输出56×56×256,然后max-pool,尺寸变为28×28×256。
  • 第四阶段:三组conv3-512,输出28×28×512,然后max-pool,尺寸变为14×14×512。
  • 第五阶段:三组conv3-512,输出14×14×512,然后max-pool,尺寸变为7×7×512。
  • 最后将7×7×512的特征图展开成25088维向量,依次经过4096、4096、1000三个全连接层,最后通过softmax输出分类概率。

整个过程最反直觉的地方是:卷积层几乎不改变特征图长宽,只增加通道数;真正让尺寸逐级减半的是那5个max-pool。一张224×224的图片经过5次池化之后变成7×7,这里刚好是2的5次方关系:224除以32等于7。所以后面换输入大小时,比如改成384×384,那pool四次之后就会变成12×12,结构需要稍微调整一下。我在做目标检测项目时就经常把VGG的输入从224加大到384甚至512,目的就是保留更多空间位置信息。

2.3 参数量统计:全连接层才是吃参数的“哑铃”

VGG16的参数量大约是1.38亿,这个数字惊人,但更惊人的是它的分布。我把卷积层和一个全连接层的参数量拆开算过:

部分参数量占比
全部卷积层约1472万约10.6%
fc6(7×7×512→4096)约1.028亿约74.2%
fc7(4096→4096)约1678万约12.1%
fc8(4096→1000)约410万约3.0%

也就是说,卷积层加起来只占十分之一多一点,光一个fc6就吞掉了整个网络七成以上的参数量。我第一次算完这个表之后,立刻明白为什么后来全局平均池化会流行:把最后的多维特征图直接平均成一个向量,既能保留语义信息,又能省掉那个巨无霸的展开矩阵。VGG之所以慢和占用显存,很大一部分原因就是这三个全连接层在“拖后腿”。

3. 3×3小卷积核:VGG最精妙的两笔账

3.1 等效感受野:为什么两个3×3等于一个5×5

VGG全文的灵魂不是“深”,而是“用什么样的方式深”。它坚持在整条网络里只用3×3卷积核,步长为1,padding为1。这里有一个非常关键的计算:两个3×3卷积堆叠之后,输出特征图上的一个像素,在原图上覆盖的区域等于一个5×5卷积的感受野,也就是25个像素范围;三个3×3卷积堆叠,则等价于一个7×7卷积的感受野,覆盖49个像素范围。

当时我看到这个结论的第一反应是把公式推一遍。假设输入是一个5×5的区域,经过第一次3×3卷积之后,输出图的每个位置对应原图的3×3窗口,再经过第二次3×3卷积,输出图的每个位置对应前一层的3×3窗口,而前一层的3×3窗口又对应原图5×5范围,这样两级映射叠加起来,刚好等效于一次5×5卷积。如果把中间特征图忽略掉,只看输入输出关系,两个小卷积核确实“拼”成了一个大卷积核。

这个等效关系就是小卷积核能撑起深度的底层逻辑:网络并没有因为换用3×3而丢失感受野,反而因为多了中间层,获得了更丰富的非线性表达能力。

3.2 参数账与非线性账:49对比27,1对比3

用7×7卷积替代三个3×3卷积,从参数量上算一笔账。假设输入输出通道数都为C,一个7×7卷积的参数是7×7×C×C=49C²;三个3×3卷积的参数是3×(3×3×C×C)=27C²。也就是说,在感受野完全等价的前提下,小卷积核堆叠能节省大约45%的参数。这个比例不依赖C的具体值,是一个通用的结论。

如果只看参数节省,可能有人觉得“少一点又怎样”,但VGG真正赢在非线性层数。一个大7×7卷积只有一层卷积加一层ReLU,而三个3×3卷积堆叠起来是三层卷积加三层ReLU。神经网络的深度意义就在于这些非线性层的堆叠,更多非线性让网络能够拟合更复杂的函数曲面。这就好比一个三人小组各自观察一小片信息然后层层汇总,比一个人拿大扫把看一大片区域要更细致,中间多出来的“讨论环节”很重要。

我后来在看很多轻量化网络的论文时发现,它们都在反复使用这个思想:用更小的卷积核叠加替代大卷积核,换取参数效率和非线性能力,VGG在这方面是祖师爷级别的存在。

3.3 为什么1×1卷积不够,也不直接上大卷积核

论文里有一个配置C,是在部分层中用1×1卷积代替3×3卷积。1×1卷积可以在不改变特征图空间尺寸的情况下做通道混合和增加非线性,参数量也更低,但实验结果显示C配置的精度明显不如D配置,也就是同样深度下3×3卷积比1×1卷积更有效。

我当时反复想这段实验,结论是:1×1卷积完全不会做空间信息交互,每个输出像素只看到输入对应位置那个像素的通道信息,等于把画面剪成无数个点再逐点处理,缺少邻域上下文。而3×3卷积每次都整合了周围3×3邻域的信息,虽然网络设计上“每一点只看到一小块”,但正是这些小块的层层传递,才让感受野逐步扩大。

再看“为什么不直接用7×7卷积”:参数多、非线性少是一个原因,另一个原因是7×7大卷积核感受野过大时,提取的特征相对粗糙,而三个3×3卷积相当于在同样的视觉范围内做了三次逐步精炼的特征提取,精度自然更稳。VGG的核心思路其实是把“加大感受野”的动作拆成三次小的移动,这让网络能够以更细腻的方式理解图像。

4. 论文里的消融实验与训练细节:那些被忽略的参考值

4.1 深度对精度的影响:从11层到19层的台阶

论文里最直接的对比就是不同深度的网络在ImageNet验证集上的表现。只增加深度、保持其他结构基本不变时,性能是随着层数增加逐步提升的。比如A配置大概在11层,B和C在13到16层之间,D和E在16到19层,整体趋势是越深越好,直到19层时收益边际开始变小。这条曲线给了当时整个领域一个非常明确的信号:网络深度还有很大挖掘空间。

但这里有一个容易被人忽略的背景:更深的网络意味着训练困难。论文用“先训练浅层网络,再用浅层网络的权重初始化深层网络”来解决这个问题,这个方法在今天很多任务上仍然好用。我自己在搭大规模分类模型时,如果训练资源不足,也会先跑一个浅层模型,再用它的部分权重去初始化深层模型,收敛速度和最终精度都有改善。

4.2 LRN被否定,1×1不如3×3,消融实验的价值

VGG论文里有一组很经典的对照实验:A-LRN。A-LRN在A的基础上增加了局部响应归一化(LRN),这在AlexNet时代被很多人视为标配。但实验结果非常打脸:加了LRN的A-LRN精度反而比不加LRN的A略低。既然无益且增加计算量,VGG后面的配置全部取消了LRN。

这件事给我的启发比结构本身更大:不要盲从既有的“经验技巧”,任何设计都应该用消融实验去验证。很多后来出现的模块,比如各种归一化层、注意力模块、激活函数变体,在一个新的网络里到底有没有效果,只有通过严格的对比才能下结论。VGG当年就是靠这种扎实的实验态度,把LRN从主流网络里清了出去。

4.3 训练细节清单:初始化、缩放、参数配置

论文在训练细节上的信息量很大,这部分对实际复现非常关键:

  • 权重初始化:先训练配置A获得权重,再用A的浅层权重初始化B、C、D、E的对应层。这比从头随机初始化深层网络稳定得多,收敛也更快。
  • 输入尺寸:训练时把图片短边随机缩放到256到512之间的某个尺度,再从中随机裁剪224×224区域。这种多尺度训练相当于免费的数据增强。
  • 数据增强:随机水平翻转、RGB颜色抖动也是标配。
  • 优化配置:SGD,mini-batch为256,momentum为0.9,权重衰减设为5e-4,前两个全连接层使用dropout且概率为0.5。
  • 学习率策略:初始学习率0.01,当验证集准确率停止提升时,将学习率除以10,整个训练过程大约下降三次学习率。

我复现时发现,如果跳过浅层初始化这一步,直接从深层随机初始化开始训练,模型其实也能收敛,但需要更多的epoch和更小的学习率,前期损失下降非常慢。论文里这个“从浅到深”的思路,本质上是给梯度传播一个更平滑的起点。

4.4 测试阶段的多尺度技巧

测试阶段,VGG团队也没有只做一次中心裁剪。他们用了两种策略叠加:一个是对多尺度缩放的图片做密集评估,也就是让卷积网络在全图上滑动计算;另一个是multi-crop,从不同位置裁剪多块区域分别预测,最后平均所有结果。这些技巧叠加起来能带来一到两个百分点的提升。

放在今天看,这种多尺度测试思路已经演化成了很多竞赛和工业项目的“测试时增强”。不过也要提醒一句,multi-crop会成倍增加推理时间,实际部署时需要在精度和速度之间做取舍。我做在线推理时基本不用多尺度,但离线评测和竞赛时会开,效果确实立竿见影。

5. VGG的工程遗产与教训:迁移价值、参数包袱与后辈改进

5.1 预训练模型的迁移帝国

如果说VGG的论文是技术胜利,那么它开源的预训练权重就是工程胜利。2014年之后很长一段时间,目标检测网络的骨干、语义分割网络的编码器、风格迁移的特征提取器,几乎都能看到VGG16的影子和它的预训练权重。我自己的感觉是,当时用VGG16做backbone比后来用很多新颖网络还要省心:结构简单,预训练模型好找,上下采样路径清晰,fine-tune时调整空间又大。

就算到现在,在某些中小规模数据集上,VGG16的预训练特征依然非常能打。我做过一次对比,把VGG16换成一个“更新”但更轻的网络,在小样本迁移任务上反而是VGG16的表现更稳。原因在于大参数量的模型在预训练阶段已经记住了大量通用视觉模式,迁移到下游任务时只需要小幅度调整。

5.2 VGG的不足之处:显存、参数与全连接层的“重税”

VGG的缺点也和它的成功一样突出:参数量大、计算量大、显存占用高。从上面的参数分布就能看出来,最后三个全连接层贡献了绝大多数参数,而全连接层的参数数量由输入特征图大小决定,一旦输入尺寸变大,fc6的参数还会进一步膨胀。这意味着VGG对输入分辨率非常敏感,想要高分辨率输入几乎等于把显存花在矩阵展开上。

在显存只有几个GB的年代,训练VGG16的batch size不敢设太大。我的经验是,一张224×224的图片在VGG16上做一次前向,激活值占据的显存比模型参数本身还大,尤其最后几个卷积层的特征图,通道数512、尺寸适中,加起来非常吃内存。后来很多网络改用全局平均池化替代全连接层,把参数减少到几百万级别,正是针对这个痛点。残差结构的出现更是让“更深”不再等同于“更难训练”。

5.3 它对后续网络设计留下的三条启示

读完VGG再看后面的ResNet、DenseNet,会发现很多思路都能溯源到这篇论文:

  • 小卷积核是大势所趋,增大感受野不一定要增大卷积核,用层数堆叠更划算。
  • 网络设计要有清晰的阶段性,通道数逐级翻倍、分辨率逐级减半这种瀑布结构,非常有利于梯度流动和工程实现。
  • 每个设计选择都必须经过实验验证,不管是LRN还是1×1卷积,只有数据能说服人。

VGG本身也像一个路标:它证明了深度有潜力,但也暴露了单纯加深网络会出现收敛困难和高参数负担,这就直接催生了后续的残差连接和批量归一化。可以说,没有VGG踩出来的这些坑,后面很多网络不会走得那么顺。

6. 我复现VGG时踩过的几个小坑

6.1 显存不够时的处理顺序

我第一次在单卡上跑VGG16的时候,batch size开到32直接显存溢出。当时急着把batch调小到8,结果训练非常不稳定。后来我养成一个习惯:优先保持batch size在16以上,如果显存不够,先把输入图片从224降到192,或者关闭验证阶段的数据增强,而不是一味缩小batch。batch缩到很小会让BatchNorm(如果加了的话)统计量不准,也会让梯度的噪声变大。VGG本身没有BatchNorm,但后来的变体加了,这时候小batch问题会更明显。

6.2 迁移学习时忘了输入归一化

VGG官方预训练权重在训练时有特定的输入预处理方式,大致是每个像素减去训练集的均值。很多人直接拿来fine-tune时不做这个归一化,结果准确率总差一大截。我第一次犯这个错误时,反复调学习率都没用,最后检查数据流才发现问题。如果你用任何一个现成模型库加载VGG权重,务必先确认对方实现的预处理是否与你使用的数据加载代码一致,这一步出错非常隐蔽。

6.3 用VGG做特征提取器时,冻结层数要谨慎

我的另一个教训是:做小数据集的迁移学习时,不要一开始就把所有层都解冻。比较稳的做法是先把新任务的全连接层从头训练,卷积层暂时冻结,等损失降到一个平台之后再解冻最后一两个卷积块,用小学习率微调。VGG的前半部分提取的是通用边缘、纹理、形状特征,后面几层更偏向任务相关的语义特征,所以解冻顺序应该从最后往前逐渐放开。这样训练速度更快,也不容易把预训练学到的知识冲掉。

6.4 关于训练VGG的一点心得体会

很多人觉得VGG结构老、参数多,已经不值得学了。我的看法恰恰相反,正是因为它的结构规整、问题暴露得明显,它才是一个完美的“实验田”。你可以在VGG上验证数据增强的效果,可以对比不同初始化的影响,可以测试不同池化策略,这些实验结论往往能推广到更新的结构上。如果让我给刚入门的人推荐第一个要反复精读的卷积网络,我大概率还是会把这篇论文和VGG16的代码一起丢过去,读懂了它,再看后来的网络会轻松很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询