VGGNet深度解析:小卷积核堆叠与CNN经典架构原理

📅 发布时间:2026/10/16 1:14:55
VGGNet深度解析:小卷积核堆叠与CNN经典架构原理
2014年深度学习圈子还远没有今天这么喧闹。做图像识别的人最关心的一个词是“深度”而把“深度”真正做成标杆的模型就是VGGNet。它来自某支欧洲视觉研究团队用非常规整的卷积结构把公开图像分类任务的错误率压到了一个新的高度几乎是一夜之间成了学术界和工业界最常见的CNN骨干之一。VGGNet能干什么一句话说它是一个极其稳定、好迁移的卷积特征提取器。图像分类、目标检测、语义分割、风格迁移只要你不是对速度和模型体积极度敏感它的预训练权重都能顶一把。适合谁来读如果你刚开始接触CNN想知道“网络到底怎么堆才合理”或者你已经在用残差网络想回头看看经典结构为什么经典这篇文章都值得往下翻。1. 先说清楚VGGNet在CNN历史上到底解决了什么问题在VGGNet出现之前CNN的深度其实还停留在“浅尝辄止”的阶段。更早的经典网络证明了卷积池化这种组合能做图像分类但大家对“增加深度”这件事是既兴奋又谨慎的。兴奋的是更深的特征似乎能表达更抽象的概念谨慎的是网络一旦变深训练就变得不稳定参数也急剧膨胀很多人并不清楚收益到底在哪里。VGGNet做的事情本质上是把这个模糊的直觉变成了一个结论把网络按“卷积块池化”的方式不断加深只要堆叠的方式足够规整模型的表达能力就会稳定地往上涨。它给出了一个非常清晰的规律——从11层到19层逐级增加网络的深度效果是一路提升的。这个结论看起来简单但当时很少有人如此系统地把“深度”作为单独变量去控制、去验证所以它在研究社区里的说服力非常强。它有三大贡献值得单独拎出来第一用实验确认了深度的价值。同一套设计逻辑网络越深分类准确率越高。这对后来的研究是一个很强的信号与其花时间精心设计非常复杂的单层算子不如先把网络的深度提上去。第二确立了“小卷积核堆叠”这个设计范式。之前的网络喜欢用较大的卷积核比如11x11、7x7这种。VGGNet告诉大家用一堆3x3的小卷积核逐层堆叠不仅能获得与大卷积核同样的感受野参数量还更少非线性也更强。这个思想后来被很多网络继承直到现在3x3卷积依然是大量模型的基础组件。第三模型的规整性极佳。从头到尾都遵循“输入空间分辨率减半、通道数翻倍”的节奏这个规律非常容易记忆也特别适合做迁移学习。很多做工程的人拿到VGGNet的预训练模型直接砍掉最后的全连接层把中间的卷积特征图拿来用效果都非常好。这种“开箱即用”的特性让它成为那个年代最重要的预训练模型来源之一。2. 核心设计拆解为什么三个3x3卷出7x7的视野VGGNet最出名的设计就是整个网络的所有卷积核几乎都是3x3。表面上看这是很无聊的重复实际上这一层选择背后藏着一笔很清楚的账。2.1 感受野的数学账三层小核等于一层大核先解释一下感受野。感受野指的是特征图上一个点往回看原始图像能覆盖多大的区域。卷积神经网络是层层堆叠的高层特征图上每个位置其实对应了底层多个像素的综合信息。对3x3卷积来说如果输入和输出尺寸保持不变padding为1、stride为1那么堆叠两层之后第二层特征图上的一个点就能看到原始图像上5x5的区域堆叠三层之后这个范围就变成7x7。这是一个可以计算的规律当所有卷积的stride为1时每多一层kernel尺寸为k的卷积感受野就增加k-1。3x3卷积每加一层额外增加2。所以第一层3x3感受野是3x3第二层3x3感受野是5x5第三层3x3感受野是7x7。所以三层3x3卷积堆叠和一个7x7卷积的感受野完全等价。两层3x3堆叠则和一个5x5卷积等价。VGGNet没有用大卷积核但在需要大感受野的位置用小卷积核把它“叠”出来了。这个设计妙在哪里不只是参数变少更重要的是它让网络的表达方式更灵活。7x7卷积是一步到位地提取局部纹理而三层3x3是逐级地先看到细节再看到局部结构再看到更大范围的模式。每一级都经过一次非线性激活中间特征的抽象层次更丰富。论文里也隐晦地提到堆叠小卷积核具有某种正则化效果——参数少了表达方式更受约束反而让模型不容易过拟合。2.2 参数省了一半一笔很实在的成本账假设输入和输出的通道数都是C看两种结构一种是一个7x7卷积参数数量是7x7xCxC也就是49C²。另一种是三层3x3卷积总参数数量是3x3xCxC再乘以3也就是27C²。算一下省了多少(49-27)/49约等于44.9%。接近一半的参数换来相同的感受野。更长的网络路径更强的非线性。这笔交易对训练时间、显存占用、过拟合风险都是正面的。所以小卷积核堆叠看似简单实际上是一个在设计之初就把“效果”和“代价”都算清楚的选择。2.3 非线性更强每一次卷积都跟着一个ReLU被很多人忽略的细节是大卷积核换成三个小卷积核还间接增加了非线性层数。7x7卷积后面通常只跟一个激活函数而三个3x3卷积后面可以跟三个激活函数。CNN的表达能力很大程度来自这些非线性层的组合更深的非线性让网络能学到的特征边界更复杂。这也是VGGNet虽然结构简单但表现力并不差的原因之一。我自己的体会是这个“用深度换非线性”的思路其实至今仍然有效。后面很多结构复杂的新模型不管怎么加注意力机制或多分支结构底层依然保留了大量连续的小卷积核堆叠就是在吃VGGNet留下的红利。3. 网络结构逐个看从224x224到1000类VGGNet的整体结构非常对称适合拿来做教学案例。你只要记住一个规律每过一个阶段图像尺寸减半通道数翻倍。剩下的就是用3x3卷积不断堆叠。3.1 五大卷积块与三层全连接以最常用的VGG16为例输入是一张224x224的RGB图像也就是224x224x3。整个网络分为5个卷积段然后接3层全连接第一段连续两个3x3卷积通道64图像尺寸还是224x224然后做一次最大池化变成112x112第二段连续两个3x3卷积通道128池化后变成56x56第三段连续三个3x3卷积通道256池化后变成28x28第四段连续三个3x3卷积通道512池化后变成14x14第五段连续三个3x3卷积通道512池化后变成7x7最后把7x7x512的特征图展平接上4096、4096、1000三层全连接最后一层是Softmax输出1000个类别的概率。每个卷积层后面都跟一个ReLU激活函数。池化用的是2x2、stride为2的最大池化没有重叠也不带参数。整体设计就是干净利落的“卷一段、池一下卷一段、池一下”。我平时看网络结构最关心的其实是特征图的尺寸变化。VGGNet这一点非常友好你只需要从224开始每过一个池化层就除以2最后到7x7整个前向过程心里就完全有数了不会出现维度对不上的问题。3.2 六种配置怎么选原论文并不是只提了VGG16一个结构而是给出了A、A-LRN、B、C、D、E整整六种不同配置。区别主要在于卷积层的层数A每个卷积段的层数分别是1、1、2、2、2总深度11层A-LRN在A的某些卷积层后面加了LRN局部响应归一化其他结构一样B每个卷积段的层数是2、2、2、2、2总深度13层C前两段2层后三段3层总深度16层但第三段、第四段、第五段多出来的那一层用的是1x1卷积D前两段2层后三段3层总深度16层多出来的层用3x3卷积这就是VGG16E前两段2层后三段4层总深度19层这就是VGG19。D和E是实际使用最频繁的两个配置。C和D的对比还揭示了一个小结论在同样的深度下3x3卷积比1x1卷积效果更好。1x1卷积虽然能增加非线性但它没有空间信息的交互表达力天然受限。这个表把关键差异列出来配置五段卷积层数分布总深度特点A1-1-2-2-211层最轻量A-LRN1-1-2-2-211层A加LRN最终被弃用B2-2-2-2-213层开始系统堆3x3C2-2-3-3-316层多出层用1x1卷积D2-2-3-3-316层多出层用3x3即VGG16E2-2-4-4-419层即VGG193.3 参数量分布全连接层才是“大胃王”VGG16的参数量一共约1.38亿放到今天看也是一个偏大的模型。但有意思的是这1.38亿里卷积部分只贡献了大约1400万剩下的1.24亿基本都在最后三个全连接层上。最后一个7x7x512的特征图要映射到4096维这一层矩阵运算的规模就超过1亿参数。全连接层为什么这么贵全连接的本质是“所有输入节点连所有输出节点”。特征图拉平后有7x7x51225088个值要变成4096个值权重矩阵就是25088x4096大约1.03亿个参数。加上后面两层总量自然就上去了。这也间接推动了后来很多模型的设计变化要么把全连接层换成全局平均池化要么干脆只用卷积特征做下游任务。如果今天你的任务不是非得输出固定类别数我通常建议直接把VGGNet最后三层全连接砍掉只保留前五个卷积段。这样模型体积会小很多而且卷积部分的特征依然非常有效。4. 训练一台VGGNet论文里没细说的关键经验VGGNet在当年之所以这么难训是因为没有现在这些成熟的训练技巧。批归一化当时还没普及残差连接也没有出现网络一旦深了梯度传播很容易出问题。论文里有一些细节回头看都是宝贵的工程经验。4.1 初始化策略先训浅的再训深的当时的Pytorch、TensorFlow都还没有今天这么完善也没有那么多初始化算法可以直接调用。研究团队训练深层网络时遇到的最直接问题就是随机初始化之后深网络根本不收敛。他们的解决办法非常朴素但很有效先训练一个较浅的配置A11层。浅网络相对容易收敛训练好之后用A网络的前面几层参数去初始化更深网络的对应层。其他没有被覆盖到的层再用均值为0、方差0.01的高斯分布随机初始化。这个做法的逻辑很直观浅层网络可以稳定地提取边缘、纹理这些基础特征把这些已经学好的特征“继承”给深网络深网络的训练就从更合理的起点出发后期只需要专心学更高层的语义特征。这种“由浅入深”的参数继承思路在今天的很多竞赛项目里依然有人用。比如你训练一个大模型之前先在小尺寸或者轻量版本上把参数跑热再搬到大模型上继续优化收敛速度会明显快很多。4.2 数据增强里的“随机缩放”是精髓VGGNet训练的时候输入图像统一裁剪成224x224但裁剪之前的尺寸并不是固定的。论文用了两种做法一种是先把图像最短边缩放到固定值比如256或384再随机裁剪出224x224区域另一种更进阶的做法是每次在256到512之间随机取一个缩放值对图像进行缩放然后再随机裁剪。后一种做法叫尺度抖动scale jittering它比单纯固定缩放强在哪里它让网络在训练阶段就见过了各种尺度的物体有的物体在裁剪后只占很小一块区域有的则充满了整个画面。这样网络在推理阶段对物体大小就不那么敏感了泛化性能会好很多。除了随机裁剪还有水平翻转、RGB通道上的颜色扰动。整体数据增强策略总结下来就是“空间上多变、颜色上扰动”这套组合拳在之后的很多代模型里都被保留下来了。4.3 训练超参这些数字本身就有参考价值如果你要复现VGGNet的训练论文里的这些超参可以作为起点优化器用SGD带动量动量系数0.9batch size为256权重衰减系数设为5e-4学习率初始0.01当验证集准确率不再上升时就把学习率除以10前两个全连接层后面接Dropout概率0.5迭代大约37万次约74个epoch学习率一共衰减过3次。我实际做训练的时候发现这些数字至少有两个地方值得注意。第一权重衰减5e-4在今天的标准看属于偏大的如果没有大规模数据很容易把模型压得太狠。第二学习率从0.01开始只对SGD这种带动量的优化器合理如果你换成Adam或AdamW初始学习率要大幅调低。另外论文里有个反直觉的结论局部响应归一化LRN完全没有提升精度还额外增加了内存和计算开销。A和A-LRN两种配置对了一下差别可忽略甚至加了LRN还差了一点点。所以后续所有配置都把LRN删掉了。这个例子提醒我们论文里的“有人用了某技巧”不代表“这个技巧有用”最终还是要靠实验说话。5. 推理与工程落地让VGGNet真正跑起来训练出一个VGGNet只是第一步真到工程落地阶段还有几个技巧值得掌握。我敢说很多今天还在用VGG做骨干网络的人也未必把这些细节全吃透了。5.1 多尺度测试让网络“多看一眼”VGGNet在推理阶段有一个很实用的技巧多尺度测试。也就是测试一张图的时候不只用单一缩放比例而是把图像分别缩放到三个不同的尺寸比如把最短边缩放到256、384、512每一份都完整跑一遍前向把输出的Softmax概率加起来平均作为最终预测。这个做法的原理很简单物体在图像中的大小本来就千差万别单一只用一个尺度去推理遇到小物体或者超大物体时特征可能提取不够充分。多尺度测试相当于用多个“视角”去看同一张图最终结果更稳健。实现上难度很低就是多了几次前向换来几个千分点的提升是很划算的。5.2 全连接转卷积一次前向处理整张图VGGNet训练时输入是224x224但推理时如果来了一张大图很多初学者会直接裁剪成224x224分块送进去非常浪费计算。更聪明的做法是把全连接层改写成等价的卷积层。具体来说原先第一个全连接层接收7x7x512的特征图输出4096维对应可以改成一个卷积和尺寸为7x7的卷积层让特征图直接变成1x1x4096第二个全连接层改成1x1卷积输出还是4096通道第三个全连接层改成1x1卷积输出1000通道。这样改完之后整个网络不带展平操作任意尺寸的输入图像都能直接一次前向通过。特征图最后是一个空间维度大于1的全局概率图你只需要在空间维度上做平均池化就能得到整个图像属于每个类别的分数。用代码示意一下这个改写# 推理时保留的卷积形式VGG16 全连接层转卷积 # 输入特征图来自第五个卷积段7x7x512 fc1_as_conv nn.Conv2d(512, 4096, (7, 7)) fc2_as_conv nn.Conv2d(4096, 4096, (1, 1)) fc3_as_conv nn.Conv2d(4096, 1000, (1, 1)) # 前向一次得到 N x 1000 x h x w 的分数图 # 后续对h、w做平均池化得到 N x 1000这个技巧在今天看来依然经典。很多做检测和分割的人其实就是从这条思路上延伸出来的与其在全连接层上做滑窗不如让网络保留空间位置信息为后续的逐像素预测做准备。5.3 作为预训练骨干的常规操作现在很少会有人从零开始训练一个完整的VGG16更多是把公开的预训练权重拿过来砍掉全连接层只保留前五段卷积作为骨干网络再接自己的分类头、检测头或分割头。这里有一个经验将VGG用作backbone时一般会保留从第一段到第五段的全部卷积特征。前两段特征更偏局部纹理适合细粒度细节后几段特征更偏语义结构适合做分类、定位。如果你做的是细粒度分类不妨把第三段、第四段的特征也拉出来一起用多层特征融合往往比只用最后一层效果更好。实际部署中还要注意一个坑预训练权重里如果带批归一化图层不能随便丢掉否则特征分布会乱掉。VGGNet本身没有BN层但很多第三方实现会自己加BN用这些版本时要小心最好连BN的均值方差参数一起保留。6. 复现与实战中的常见坑最后把我在复现和使用VGGNet时踩过的坑、看到别人踩过的坑集中整理一下都是非常具体的问题。6.1 显存爆炸问题VGGNet的显存占用在同代模型里不算低原因在于前几层卷积的特征图尺寸较大。显存不足时优先不要动模型结构先把batch size降下来比如从64降到32、16这是最直接有效的方法。如果还想进一步压显存可以把输入分辨率从224降到192或160对很多任务影响不大。现在主流框架支持混合精度训练开FP16之后显存基本能减半也能大幅加快训练速度。6.2 训练不收敛如果你非要从零开始训练VGGNet最可能遇到的问题就是不收敛。前面提到的“先训浅层再训深层”是一种解法更现代的解法是加批归一化。在每一层卷积后面加BN基本能根治梯度不稳定问题。另一个常见原因是学习率设置过高从0.01开始的SGD对数据量有要求小数据集上最好从0.001起步。6.3 过拟合VGGNet容量大在小数据集上非常容易过拟合。除了Dropout和数据增强还有一个容易忽略的操作把网络最后的全连接层全部去掉换成全局平均池化。这个改变能大幅减少可训练参数几乎就等于一个强正则项。很多实践项目里这种轻量化改法比调半天Dropout概率还管用。6.4 部署速度和体积VGGNet作为预训练模型好用但部署到移动端或实时服务里性能和体积都是一个坎。剪枝和量化是最常用的两种手段。卷积层的冗余度其实比想象中高对VGGNet做结构化剪枝比如按照通道重要程度裁掉一部分卷积通道通常能压缩不少体积精度损失却不大。如果只想快速瘦身可以先做INT8量化再把全连接砍掉模型体积能从数百MB降到几十MB。常见问题表现处理思路显存不足Out of Memory降batch size、开FP16、降低输入分辨率不收敛损失不降或NaN加BN、降低学习率、参数继承过拟合验证集掉点加Dropout、全局池化代替全连接、加强数据增强模型过大部署困难通道剪枝、INT8量化、去除全连接层关于这套结构的几点个人体会踩过几次坑之后我对VGGNet的看法已经很明确了它不是一个在“前沿性能”上让你惊艳的模型但绝对是最适合用来理解CNN底层逻辑的骨架之一。它的设计没有花哨的分支、没有注意力机制就是纯粹地堆卷积反而让人一眼能看清深度、宽度、参数、感受野这些基本元素之间的关系。如果你正在自己的数据集上测试新想法我个人的建议是不要一上来就照搬VGG16在大型公开数据集上的完整配置。先拿VGG11或减半通道的版本把流程跑通确认数据加载、损失计算、验证逻辑都没有问题再逐步加深到VGG16、VGG19。这样调试一轮的时间成本会低很多。最后分享一个我自己常用的VGG特征使用技巧做主分类任务时不要只用最后一个池化层前的特征。把第四段卷积输出和第五段卷积输出做一个简单的空间加权融合再送进分类头在很多中小数据集上都能看到稳定的小幅提升。这种多尺度特征融合的做法其实就是从VGGNet规整的分段结构里天然延伸出来的也是它留给后来者的一个重要启发。