VGG:深度网络的探索之旅
VGG:深度网络的探索之旅
引言
在AlexNet证明了深度学习的威力之后,研究人员开始思考一个问题:网络的深度对性能有多大影响? VGG团队对这个问题进行了系统性的研究,并给出了经典的答案。
成绩:ILSVRC 2014 定位任务第一名,分类任务第二名 [1]
研究背景
随着硬件技术的进步,研究人员有能力构建足够深、足够大的卷积神经网络来做分类任务。事实证明,卷积神经网络在深度上的提升,带来了分类效果的极大改善。
VGG团队决定系统性地研究网络深度的影响,他们构建了11层、13层、16层和19层的不同深度模型进行对比实验 [1]。
网络结构

结构特点
VGG网络具有以下鲜明特点:
- 统一使用3×3卷积核
- 统一使用2×2最大池化
- 模块化的网络设计
- 逐层加倍的通道数(64 → 128 → 256 → 512 → 512)
主要贡献
系统研究网络深度
VGG第一次深入研究了网络的深度对模型效果的影响,分别对11层、13层、16层和19层的模型进行训练。
实验结果表明:
- 深度的增加能显著提升性能
- 16层和19层效果最好
- 后来人们常用的是VGG16和VGG19
模块化网络设计
VGG将卷积神经网络模块化定义为不同的Stage,提出了可以通过重复使用简单的基础块来构建深度模型的思路。
这一思想深刻影响了后续网络的设计,包括ResNet、DenseNet等都采用了类似的模块化设计理念。
感受野理论的实践验证
VGG团队深入讨论了模型的感受野问题,得出了重要结论 [1]:
- 两层3×3卷积核 ≈ 一个5×5卷积核(感受野相同)
- 三层3×3卷积核 ≈ 一个7×7卷积核(感受野相同)
但是使用多层小卷积核有以下优势:
- 参数更少:3×3×3 = 27 < 49 = 7×7
- 非线性更强:多层带来更多的ReLU激活
- 鲁棒性更好:更深的网络更难过拟合
感受野分析
让我们用数学来理解这个重要的发现:
单层卷积的感受野
输入:7×7
卷积核:3×3
输出:5×5
感受野:3×3
两层叠加的感受野
第一层:7×7 → 5×5 (3×3卷积)
第二层:5×5 → 3×3 (3×3卷积)
总感受野:5×5
参数量对比
# 一个5×5卷积核
params_5x5 = 5 * 5 * C * C = 25C²
# 两个3×3卷积核
params_3x3_x2 = (3 * 3 * C * C) * 2 = 18C²
# 节省参数:
reduction = (25C² - 18C²) / 25C² = 28%
三层3×3 vs 一个7×7
# 一个7×7卷积核
params_7x7 = 7 * 7 * C * C = 49C²
# 三个3×3卷积核
params_3x3_x3 = (3 * 3 * C * C) * 3 = 27C²
# 节省参数:
reduction = (49C² - 27C²) / 49C² = 45%
graph LR
subgraph SINGLE["单个 7×7 卷积"]
S_IN[Input] --> S_CONV["7×7 Conv<br/>49C² 参数"]
S_CONV --> S_OUT[Output]
S_NL["1× ReLU 非线性"]
end
subgraph STACKED["3个 3×3 卷积 · VGG"]
T_IN[Input] --> T_C1["3×3 Conv → ReLU"]
T_C1 --> T_C2["3×3 Conv → ReLU"]
T_C2 --> T_C3["3×3 Conv → ReLU"]
T_C3 --> T_OUT[Output]
T_P["27C² 参数 · 45% 更少<br/>3× 非线性"]
end
S_OUT -.->|"相同 7×7 感受野"| T_OUT
style SINGLE fill:#fce4ec,stroke:#c62828
style STACKED fill:#e8f5e9,stroke:#2e7d32
网络配置
VGG提供了多种深度的配置方案:
| 模型 | 层数 | 参数量 | Top-1错误率 | Top-5错误率 |
|---|---|---|---|---|
| VGG11 | 11 | 132M | 28.7% | 9.9% |
| VGG13 | 13 | 133M | 28.5% | 9.8% |
| VGG16 | 16 | 138M | 27.0% | 8.8% |
| VGG19 | 19 | 144M | 27.3% | 9.0% |
VGG16架构详解
让我们详细看看最常用的VGG16:
import torch.nn as nn
class VGG16(nn.Module):
def __init__(self, num_classes=1000):
super(VGG16, self).__init__()
# Stage 1: 64 channels
self.stage1 = nn.Sequential(
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)
)
# Stage 2: 128 channels
self.stage2 = nn.Sequential(
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)
)
# Stage 3: 256 channels
self.stage3 = nn.Sequential(
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)
)
# Stage 4: 512 channels
self.stage4 = nn.Sequential(
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)
)
# Stage 5: 512 channels
self.stage5 = nn.Sequential(
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)
)
# Classifier
self.classifier = nn.Sequential(
nn.Linear(512 * 7 * 7, 4096),
nn.ReLU(inplace=True),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Dropout(0.5),
nn.Linear(4096, num_classes)
)
def forward(self, x):
x = self.stage1(x)
x = self.stage2(x)
x = self.stage3(x)
x = self.stage4(x)
x = self.stage5(x)
x = x.view(x.size(0), -1)
x = self.classifier(x)
return x
VGG的优缺点
优点
- 结构简单统一:只使用3×3卷积和2×2池化
- 效果优秀:在当时达到了SOTA水平
- 泛化能力强:预训练的VGG特征在其他任务上表现优异
- 易于理解和实现:模块化设计清晰明了
缺点
- 参数量巨大:VGG16有138M参数
- 计算量大:主要计算量在全连接层
- 内存消耗高:需要大量GPU内存
- 训练时间长:深度和参数量导致训练慢
参数分布分析
VGG16的参数主要分布在哪里?
# 卷积层参数 ≈ 15M
# 全连接层参数 ≈ 123M
# 也就是说,约90%的参数都在全连接层!
这一发现启发了后续网络(如GoogLeNet)使用全局平均池化替代全连接层。
实践经验
在复现VGG的过程中,我获得了以下经验:
内存管理
# 使用混合精度训练减少内存占用
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()
with autocast():
outputs = model(inputs)
loss = criterion(outputs, labels)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
批量大小调整
由于VGG参数量大,需要适当减小批量大小:
# AlexNet可能用 batch_size=256
# VGG建议用 batch_size=32 或 64
学习率策略
# VGG适合使用学习率衰减策略
scheduler = torch.optim.lr_scheduler.StepLR(
optimizer,
step_size=30, # 每30个epoch
gamma=0.1 # 学习率乘以0.1
)
模型复现
我在PyTorch平台上复现了VGG模型:
- 平台:PyTorch
- 主要库:torchvision, torch, matplotlib
- 数据集:Oxford Flower102花分类数据集
- 代码地址:GitHub - DeepLearning/model_classification/VGG
VGG的影响
VGG虽然不是ImageNet 2014的冠军,但其影响力可能超过了当年的冠军GoogLeNet [1]:
- 简单性:结构简洁,易于理解和实现
- 可复用性:VGG特征在迁移学习中广泛使用
- 理论贡献:感受野理论的实践验证
- 设计思想:模块化设计影响深远
总结
VGG网络的主要贡献和启示:
- 深度很重要:系统性地证明了网络深度对性能的重要性
- 小卷积核更好:3×3卷积核的优势(参数少、非线性强)
- 模块化设计:可复用的设计模块简化了网络构建
- 简单即美:统一的结构设计带来良好的可扩展性
VGG为后续更深的网络(如ResNet)铺平了道路,同时其简洁的设计思想至今仍被广泛借鉴。
参考文献
- Simonyan, K. & Zisserman, A. Very Deep Convolutional Networks for Large-Scale Image Recognition. International Conference on Learning Representations (ICLR), 2015. arXiv: 1409.1556