目录
[二、VGG16 的核心设计思想](#二、VGG16 的核心设计思想)
摘要
VGG16 是牛津大学 Visual Geometry Group 在 2014 年提出的深度卷积网络,凭借简洁统一的"堆叠小卷积核"设计,至今仍是理解 CNN 特征提取机制的最佳范本之一。即便在 ResNet、ViT 大行其道的今天,VGG16 预训练模型仍被广泛用作目标检测、风格迁移和医学影像任务的骨干网络。
一、背景
2012 年 AlexNet 在 ImageNet 上取得突破后,VGG作者受他的影响,VGG 保留了 AlexNet 确立的基本骨架,包括"卷积层堆叠 + 全连接层分类"的整体范式。它沿用了 AlexNet 的 5 个卷积阶段(stage)划分,以及最终接入 3 个全连接层(其中包含 4096 维的中间层)的设计。在训练策略上,VGG 也基本沿袭了 AlexNet 的做法,例如同样使用 ReLU 激活函数和 Dropout 来防止过拟合。
AlexNet 在第一层使用了 11×11 的大卷积核,后续层也包含 5×5 卷积核。VGG 则完全放弃了这些大核,统一替换为连续的 3×3 小卷积核
VGG团队参加 ImageNet Challenge 2014 的基础。在该竞赛中,VGG 团队在定位任务上获得第一名,分类任务上获得第二名(分类冠军是 GoogLeNet)。
二、VGG16 的核心设计思想
VGG16 的名字来源于它的深度:16 个带权重的层(13 个卷积层 + 3 个全连接层)。它的设计可以用一句话概括:用多个 3×3 小卷积核的堆叠,替代大卷积核,在保持感受野的同时减少参数量并增加非线性。
两个 3×3 卷积堆叠的感受野等效于一个 5×5 卷积,三个 3×3 堆叠等效于 7×7。但参数量上,3 个 3×3(输入输出通道均为 C)仅需 3 × (3×3×C×C) = 27C²,而一个 7×7 需要 49C²。更重要的是,每次卷积后都跟 ReLU,三次非线性变换让决策函数更具判别力。

VGG16 的完整结构分为 5 个卷积块,每块后接最大池化将空间尺寸减半:
|--------|---------------------------------------|----------------------------|
| 阶段 | 配置 | 输出尺寸(输入 224×224) |
| Block1 | 2 × Conv3-64 | 224×224×64 → 池化 112×112×64 |
| Block2 | 2 × Conv3-128 | 112×112×128 → 池化 56×56×128 |
| Block3 | 3 × Conv3-256 | 56×56×256 → 池化 28×28×256 |
| Block4 | 3 × Conv3-512 | 28×28×512 → 池化 14×14×512 |
| Block5 | 3 × Conv3-512 | 14×14×512 → 池化 7×7×512 |
| 分类头 | Flatten → FC-4096 → FC-4096 → FC-1000 | 1000 类输出 |
所有卷积均为 kernel_size=3, padding=1, stride=1,池化为 2×2, stride=2。最终特征图展平后维度为 512×7×7 = 25088,接入两个 4096 维全连接层,最后输出 1000 类(ImageNet 标准)
三、VGG16网络实现
若是自己根据论文中的结构搭建网络如下:
在进入全连接层之前,使用 AdaptiveAvgPool2d((7, 7)) 而非直接 Flatten,这使得模型能够接受不小于 32×32 的任意尺寸输入。
python
import torch
import torch.nn as nn
class VGG16(nn.Module):
def __init__(self, num_classes=1000):
super(VGG16, self).__init__()
# 卷积特征提取部分
self.features = nn.Sequential(
# Block 1
nn.Conv2d(3, 64, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(64, 64, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
# Block 2
nn.Conv2d(64, 128, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(128, 128, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
# Block 3
nn.Conv2d(128, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
# Block 4
nn.Conv2d(256, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
# Block 5
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
)
# 自适应池化:确保任意输入尺寸都能得到 7×7 特征图
self.avgpool = nn.AdaptiveAvgPool2d((7, 7))
# 分类头
self.classifier = nn.Sequential(
nn.Linear(512 * 7 * 7, 4096),
nn.ReLU(inplace=True),
nn.Dropout(p=0.5),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Dropout(p=0.5),
nn.Linear(4096, num_classes),
)
def forward(self, x):
x = self.features(x)
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.classifier(x)
return x
参数量约 1.38 亿,其中绝大部分集中在分类头(约 1.24 亿),卷积层仅约 1470 万。这也是 VGG16 被诟病"参数效率低"的原因。
四、直接使用VGG16预训练模型
实际项目中,从零训练 VGG16 需要大量数据和算力。更常见的做法是加载 ImageNet 数据集预训练权重进行微调或特征提取。
python
import torchvision.models as models
from torchvision.models import VGG16_Weights
# 加载预训练 VGG16(权重在 ImageNet-1K 上训练)
weights = VGG16_Weights.IMAGENET1K_V1
model = models.vgg16(weights=weights)
# 查看预处理要求
preprocess = weights.transforms()
print(preprocess)
# 输出包含:Resize(256) → CenterCrop(224) → ToTensor → Normalize(mean, std)
官方预训练权重在 ImageNet-1K 上的指标为:Top-1 准确率 71.592%,Top-5 准确率 90.382%,模型文件约 528 MB。
微调时示例:将 classifier[6] 最后一层输出替换为自己的类别数:
python
num_classes = 10 # 例如 CIFAR-10
model.classifier[6] = nn.Linear(4096, num_classes)
# 可选:冻结特征层
for param in model.features.parameters():
param.requires_grad = False
# 只训练分类头
optimizer = torch.optim.SGD(
model.classifier.parameters(),
lr=0.001, momentum=0.9
)
五、VGG19
VGG19 是 VGG16 的加深版本,与VGG16没有太大区别,使用方法一样。它的核心变化在于:在最后两个卷积块中各多堆叠了一层 3×3 卷积,使带权重的层数从 16 增加到 19(16 个卷积层 + 3 个全连接层)
与 VGG16 的结构差异
两者的整体骨架完全一致:5 个卷积块、每块后接最大池化、统一的 3×3 卷积核、最后接 3 个全连接层。区别只在于卷积层的堆叠数量:
|--------|---------------|---------------|
| 阶段 | VGG16 | VGG19 |
| Block1 | 2 × Conv3-64 | 2 × Conv3-64 |
| Block2 | 2 × Conv3-128 | 2 × Conv3-128 |
| Block3 | 3 × Conv3-256 | 4 × Conv3-256 |
| Block4 | 3 × Conv3-512 | 4 × Conv3-512 |
| Block5 | 3 × Conv3-512 | 4 × Conv3-512 |
也就是说,VGG19 在 Block3、Block4、Block5 各多了一层卷积,其余配置不变。
直接调用, ImageNet 1k 数据集上预训练:
python
import torchvision.models as models
from torchvision.models import VGG19_Weights
model = models.vgg19(weights=VGG19_Weights.IMAGENET1K_V1)
六、VGG的局限与改进方向
VGG16 的优雅之处在于结构规整,但它的缺陷同样明显:
参数冗余严重。全连接层占据约 90% 的参数量,而现代网络(如 ResNet)通过全局平均池化将分类头压缩到极小的规模。VGG16 的卷积层本身其实相当轻量。
训练收敛较慢。原始 VGG16 没有 Batch Normalization,深层网络的梯度流动不够稳定。改进方案 VGG16-BN 在每个卷积后加入 BN 层,收敛速度和最终精度均有提升。
内存占用高。第一个全连接层 25088 → 4096 的权重矩阵就有约 1 亿参数,推理时也需要相应内存。如果部署在边缘设备,通常只保留 features 部分作为特征提取器。
VGG19-BN
与 VGG16 一样,torchvision 也提供了带 Batch Normalization 的版本 vgg19_bn。加入 BN 后,ImageNet Top-1 提升到 74.218%,Top-5 为 91.842%,参数量约 143.68M。如果要在 VGG19 上做微调或从零训练,优先用 vgg19_bn 会更稳。
参考来源:Simonyan & Zisserman, Very Deep Convolutional Networks for Large-Scale Image Recognition(arXiv:1409.1556, 2014);PyTorch 官方 VGG 源码与 VGG16_Weights 文档;ImageNet 预训练指标与预处理参数;VGG16-BN 改进思路。
论文网址:++1409.1556 Very Deep Convolutional Networks for Large-Scale Image Recognition++