在 PyTorch 中实现 VGGNet 非常直观,因为它的结构极其规整 ------ 全是 3×3 卷积层和 2×2 池化层的组合,就像搭积木一样按固定模式堆叠。我们以最经典的 VGG-16 为例,一步步实现,保证零基础你能看懂每一行代码的作用。
一、先明确 VGG-16 的核心结构(复习)
VGG-16 的结构可以用一句话概括:5 个卷积块(每个块含多个 3×3 卷积层)+3 个全连接层,每个卷积块后都接 1 个 2×2 的最大池化层。具体来说:
输入(224×224彩色图) →
卷积块1(2个3×3卷积) → 池化 →
卷积块2(2个3×3卷积) → 池化 →
卷积块3(3个3×3卷积) → 池化 →
卷积块4(3个3×3卷积) → 池化 →
卷积块5(3个3×3卷积) → 池化 →
全连接层1(4096) → 全连接层2(4096) → 输出层(1000类)
二、PyTorch 实现 VGG-16 的步骤
步骤 1:导入必要的库
和之前实现 LeNet、AlexNet 一样,先准备好工具:
python
import torch # 核心库
import torch.nn as nn # 神经网络层
import torch.optim as optim # 优化器
from torch.utils.data import DataLoader # 数据加载器
from torchvision import datasets, transforms # 图像数据处理
步骤 2:定义 VGG-16 网络结构
VGG 的精髓是 "用配置列表定义卷积块",避免重复写代码。我们先定义每个卷积块的 "卷积层数量" 和 "输出通道数",再用循环构建网络。
python
class VGG16(nn.Module):
def __init__(self, num_classes=1000):
super(VGG16, self).__init__()
# 定义卷积块的配置:(卷积层数量, 输出通道数)
# 5个卷积块对应VGG-16的结构
config = [
(2, 64), # 卷积块1:2个卷积层,输出64通道
(2, 128), # 卷积块2:2个卷积层,输出128通道
(3, 256), # 卷积块3:3个卷积层,输出256通道
(3, 512), # 卷积块4:3个卷积层,输出512通道
(3, 512) # 卷积块5:3个卷积层,输出512通道
]
# 构建卷积部分
layers = []
in_channels = 3 # 输入是3通道彩色图
for (num_convs, out_channels) in config:
# 每个卷积块内堆叠num_convs个3×3卷积层
for _ in range(num_convs):
layers.append(nn.Conv2d(
in_channels=in_channels,
out_channels=out_channels,
kernel_size=3, # 固定3×3卷积核
padding=1 # 边缘填充1像素,保证卷积后尺寸不变
))
layers.append(nn.ReLU(inplace=True)) # ReLU激活
in_channels = out_channels # 更新输入通道数为当前输出通道数
# 每个卷积块后接1个2×2最大池化层(步长2,尺寸减半)
layers.append(nn.MaxPool2d(kernel_size=2, stride=2))
# 把卷积层组合成Sequential
self.features = nn.Sequential(*layers)
# 构建全连接部分
self.classifier = nn.Sequential(
# 全连接层1:输入是7×7×512(池化后特征图),输出4096
nn.Linear(512 * 7 * 7, 4096),
nn.ReLU(inplace=True),
nn.Dropout(p=0.5), # Dropout防止过拟合
# 全连接层2:输入4096,输出4096
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Dropout(p=0.5),
# 输出层:输入4096,输出类别数
nn.Linear(4096, num_classes)
)
# 定义数据流向
def forward(self, x):
x = self.features(x) # 经过卷积部分
x = x.view(x.size(0), 512 * 7 * 7) # 拉平特征图(batch_size, 512×7×7)
x = self.classifier(x) # 经过全连接部分
return x
关键解释:
- 配置列表 config :用元组
(卷积层数量, 输出通道数)
定义每个卷积块,让代码更简洁(改数字就能调整网络结构)。 - 3×3 卷积 + padding=1:保证卷积后特征图尺寸不变(比如 224×224 的图经过 3×3 卷积 + padding=1 后还是 224×224)。
- 池化层作用:每个卷积块后用 2×2 池化(步长 2),让特征图尺寸减半(224→112→56→28→14→7),最终得到 7×7×512 的特征图。
步骤 3:准备数据(用 CIFAR-10 演示)
VGG 原本用于 ImageNet(1000 类),我们用小型的 CIFAR-10(10 类)演示,预处理流程类似:
python
# 数据预处理:缩放+裁剪+翻转+标准化
transform = transforms.Compose([
transforms.Resize(256), # 缩放为256×256
transforms.RandomCrop(224), # 随机裁剪成224×224(VGG输入尺寸)
transforms.RandomHorizontalFlip(), # 随机水平翻转(数据增强)
transforms.ToTensor(), # 转为Tensor
# 标准化(用ImageNet的均值和标准差,通用做法)
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
# 加载CIFAR-10数据集
train_dataset = datasets.CIFAR10(
root='./data', train=True, download=True, transform=transform
)
test_dataset = datasets.CIFAR10(
root='./data', train=False, download=True, transform=transform
)
# 批量加载数据(VGG参数量大,batch_size设小一点,这里用64)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=4)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=4)
步骤 4:初始化模型、损失函数和优化器
- 模型:我们定义的 VGG16(CIFAR-10 是 10 类,输出层设为 10)。
- 损失函数:交叉熵损失(分类任务标配)。
- 优化器:SGD + 动量(VGG 原文推荐的优化器)。
python
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # 用GPU加速(必须!否则太慢)
model = VGG16(num_classes=10).to(device) # 初始化模型,输出10类
criterion = nn.CrossEntropyLoss() # 交叉熵损失
# 优化器:学习率0.001,动量0.9,权重衰减5e-4(防止过拟合)
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9, weight_decay=5e-4)
注意:VGG-16 参数量达 1.38 亿,必须用 GPU 训练,否则单轮训练可能要几小时甚至几天。
步骤 5:训练模型
训练逻辑和 AlexNet 类似,但 VGG 更深,需要更多轮次才能收敛(这里示例训练 20 轮):
python
def train(model, train_loader, criterion, optimizer, epoch):
model.train() # 训练模式
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device) # 数据放GPU
optimizer.zero_grad() # 清空梯度
output = model(data) # 模型预测
loss = criterion(output, target) # 计算损失
loss.backward() # 反向传播
optimizer.step() # 更新参数
# 打印进度(每200个batch打印一次)
if batch_idx % 200 == 0:
print(f'Epoch {epoch}, Batch {batch_idx}, Loss: {loss.item():.6f}')
步骤 6:测试模型效果
用测试集评估准确率:
python
def test(model, test_loader):
model.eval() # 评估模式(关闭Dropout)
correct = 0
total = 0
with torch.no_grad(): # 不计算梯度,节省内存
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
_, predicted = torch.max(output.data, 1) # 取概率最大的类别
total += target.size(0)
correct += (predicted == target).sum().item()
print(f'Test Accuracy: {100 * correct / total:.2f}%')
步骤 7:开始训练和测试
python
# 训练20轮(实际需要更多轮次,比如50-100轮才能达到较高准确率)
for epoch in range(1, 21):
train(model, train_loader, criterion, optimizer, epoch)
test(model, test_loader)
训练过程中,你会看到损失逐渐下降,准确率逐渐上升。在 CIFAR-10 上,VGG-16 训练充分后准确率通常能达到 90% 左右。
三、完整代码总结
python
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
# 1. 定义VGG-16网络结构
class VGG16(nn.Module):
def __init__(self, num_classes=1000):
super(VGG16, self).__init__()
# 卷积块配置:(卷积层数量, 输出通道数)
config = [
(2, 64), # 卷积块1
(2, 128), # 卷积块2
(3, 256), # 卷积块3
(3, 512), # 卷积块4
(3, 512) # 卷积块5
]
# 构建卷积部分
layers = []
in_channels = 3 # 输入为3通道彩色图
for (num_convs, out_channels) in config:
# 堆叠num_convs个3×3卷积层
for _ in range(num_convs):
layers.append(nn.Conv2d(
in_channels=in_channels,
out_channels=out_channels,
kernel_size=3,
padding=1
))
layers.append(nn.ReLU(inplace=True))
in_channels = out_channels
# 每个卷积块后接池化层
layers.append(nn.MaxPool2d(kernel_size=2, stride=2))
self.features = nn.Sequential(*layers)
# 构建全连接部分
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 = x.view(x.size(0), 512 * 7 * 7) # 拉平特征图
x = self.classifier(x)
return x
# 2. 准备CIFAR-10数据
transform = transforms.Compose([
transforms.Resize(256),
transforms.RandomCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
train_dataset = datasets.CIFAR10(
root='./data', train=True, download=True, transform=transform
)
test_dataset = datasets.CIFAR10(
root='./data', train=False, download=True, transform=transform
)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=4)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=4)
# 3. 初始化模型、损失函数和优化器
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = VGG16(num_classes=10).to(device) # CIFAR-10是10类
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9, weight_decay=5e-4)
# 4. 训练函数
def train(model, train_loader, criterion, optimizer, epoch):
model.train()
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
if batch_idx % 200 == 0:
print(f'Epoch {epoch}, Batch {batch_idx}, Loss: {loss.item():.6f}')
# 5. 测试函数
def test(model, test_loader):
model.eval()
correct = 0
total = 0
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
_, predicted = torch.max(output.data, 1)
total += target.size(0)
correct += (predicted == target).sum().item()
print(f'Test Accuracy: {100 * correct / total:.2f}%')
# 6. 开始训练和测试
for epoch in range(1, 21):
train(model, train_loader, criterion, optimizer, epoch)
test(model, test_loader)
四、关键知识点回顾
- 结构设计 :用配置列表
config
定义卷积块,通过循环堆叠 3×3 卷积层,结构极其规整,修改config
即可得到 VGG-11、VGG-19 等其他版本。 - 核心技巧:3×3 卷积 + padding=1 保证尺寸不变,2×2 池化实现尺寸减半,Dropout 防止过拟合。
- 实现注意 :VGG 参数量极大(1.38 亿),必须用 GPU 训练;全连接层输入维度是
512×7×7
(最后一次池化后的特征图尺寸),别算错。
运行这段代码,你就能亲手实现这个 "结构标准化典范" 模型,感受 "小卷积核 + 深网络" 的强大特征提取能力!