卷积神经网络之VGG

目录

摘要

一、背景

[二、VGG16 的核心设计思想](#二、VGG16 的核心设计思想)

三、VGG16网络实现

四、直接使用VGG16预训练模型

五、VGG19

六、VGG的局限与改进方向


摘要

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++

相关推荐
Flynn Liang40 分钟前
Spring AI 2.0 深度解析:ChatClient、Advisor 链与 MCP——Java 工程师的 AI 落地最优解
java·人工智能·spring·大模型·架构设计·spring ai
Martina_03211 小时前
AI生成3D模型一合并网格,材质就串色?用6步检查材质槽、UV与图集重映射
图像处理·人工智能·3d·材质·uv·游戏策划·1024程序员节
蓝鲨硬科技1 小时前
Token之后,物理AI需要什么样的度量衡?
大数据·人工智能
人工智能AI技术1 小时前
Agent架构新思路:多Agent正在过时,单循环Main Loop才是新方向
人工智能
GlobalInfo1 小时前
管道检测机器人行业调查、市场规模、排名分析报告2026-2032
大数据·人工智能·机器人
小郎君6581 小时前
Vue 多标签页的加载态设计:为什么我没有用 Suspense 实现 PageLoading
vue.js·算法
johnsong1 小时前
AI前沿日报 | 2026-11-03 — 基因突变:当AI开始重写神经网络
人工智能·深度学习·神经网络
AICoder码农王1 小时前
AgentScope Java v2 第9课,Harness 总架构:13 个中间件从哪来
人工智能
FPGA小迷弟1 小时前
FPGA 千兆以太网帧格式全解:从 IEEE 802.3 帧结构到 Verilog 收发实现
人工智能·5g·fpga开发·架构·硬件架构