本节摘要:VGG 把架构设计压缩成一条规则——所有卷积一律 3×3、步幅 1、填充 1,池化一律 2×2,然后把深度堆到十六层。它用 1.38 亿参数换来了极致的结构规整,也把"卷积参数其实很少、全连接才是吞金兽"这条规律摆上了台面。本节论证小核叠深既省参数又不丢视野,并逐块对账 VGG-16。
AlexNet 里 11×11、7×7、5×5、3×3 的核混着用,读结构图像读菜谱。VGG 的回答是把菜谱烧掉:只留 3×3。理由是笔算得清的两条。第一条关于视野:两层 3×3 串联,第二层每个输出能看到第一层的 3×3,而第一层各自覆盖原始输入的 3×3,合起来正好 5×5;三层叠出 7×7。第二条关于参数:同样造出 7×7 的视野,一层 7×7 核要 C×C×49 个参数,三层 3×3 只要 3×C×C×9 = 27C²,省下四成半,还多白赚两次非线性变换。小核叠深在数学上是笔划算买卖。
阅读完本节,你应当能够:
VGG-16 的十三个卷积层组成五个块:前两个块各两层 64、128 通道,后三个块各三层 256、512、512 通道;每块收尾一个 2×2 最大池化,尺寸减半。通道翻倍与尺寸减半交替出现,整条流水线的形状可以口算:224 进,五次减半后 7×7,通道 512,摊平 25,088 维。
import torch import torch.nn as nn def vgg_block(cin, cout, layers): """一个 VGG 块:layers 层 3×3 same 卷积 + 一次 2×2 池化""" seq, c = [], cin for _ in range(layers): seq += [nn.Conv2d(c, cout, kernel_size=3, padding=1), nn.ReLU()] c = cout seq.append(nn.MaxPool2d(2, 2)) return nn.Sequential(*seq) trunk = nn.Sequential( vgg_block(3, 64, 2), vgg_block(64, 128, 2), vgg_block(128, 256, 3), vgg_block(256, 512, 3), vgg_block(512, 512, 3), ) x = torch.randn(1, 3, 224, 224) feat = trunk(x) print("卷积主干出口:", tuple(feat.shape)) # (1, 512, 7, 7) n_conv = sum(p.numel() for p in trunk.parameters()) print("卷积主干参数:", f"{n_conv:,}") # 14,714,688 head = nn.Sequential(nn.Flatten(), nn.Linear(512 * 7 * 7, 4096), nn.ReLU(), nn.Linear(4096, 4096), nn.ReLU(), nn.Linear(4096, 1000)) n_fc = sum(p.numel() for p in head.parameters()) print("分类头参数:", f"{n_fc:,}") # 123,642,856 print("总参数:", f"{n_conv + n_fc:,}") # 138,357,544
账目摊开后规律刺眼:卷积主干只有 1,471 万,三个全连接吃掉 1.236 亿(89.3%)。VGG 的"大"基本是分类头的大——这条诊断直接催生了后两节的故事:GoogLeNet 换轻头,把参数砍回百万级;后来的 ResNet 干脆用全局平均池化把头拆了。
把"视野等价"的账算实:
# 感受野累加(2.2 节公式):同尺寸下比较两种配方 r, j = 1, 1 for _ in range(3): # 三层 3×3 步幅 1 r = r + 2 * j j = j * 1 print("三层 3×3 的感受野:", r) # 7 C = 256 p_7x7 = C * C * 7 * 7 # 一层 7×7 的参数 p_3x3x3 = 3 * (C * C * 3 * 3) # 三层 3×3 的参数 print("一层 7×7:", f"{p_7x7:,}") # 3,215,872 print("三层 3×3:", f"{p_3x3x3:,}") # 1,769,472 print("节省:", f"{round((1 - p_3x3x3 / p_7x7) * 100)}%") # 45%
同样 256 通道的场合,一层 7×7 要 321 万参数,三层 3×3 只要 176.9 万,省 45%,还多两次 ReLU 弯折。两层 3×3 对一层 5×5 同理:18C² 对 25C²,省 28%。"用小核的堆叠换大核的视野"从此成为行业默认,你在今天任何主流网络里看到的 3×3 海洋,源头都是 VGG。
它的遗产不止于核尺寸。块状封装("同通道重复 N 层 + 池化收尾")让结构表变得可复制、可加深,后来的 ResNet 直接继承这套语法;结构规整带来的均匀特征尺寸,让 VGG 长期霸榜迁移学习的骨干位——直到今天,风格迁移、特征提取的老代码库里还能撞见它。代价也照单全收:1.38 亿参数、单图上百亿次的乘加、训练慢且吃显存。轻量化的事,3.4 节与 3.6 节各自给出一派答案。
💡 关键直觉:看 VGG 别数层数,数"块"。五个块就是五次"细看再粗收",每一块出口的特征图都是下一块的合格原料——把块当成积木单元,一切现代 CNN 的结构表都会突然变得好读。