目录
-
引言
-
VGGNet概述
-
VGGNet的网络架构
-
基于预训练VGGNet的五类鲜花分类实现
-
4.1 数据准备与预处理
-
4.2 模型实例化与参数调整
-
4.3 模型训练与保存最优模型
-
4.4 模型导入与预测
-
4.5 训练过程的可视化
-
-
模型优化与防止过拟合
-
总结与展望
-
参考文献
引言
在计算机视觉领域,图像分类是一个基础且关键的任务。随着深度学习技术的迅猛发展,卷积神经网络(CNN)在图像分类任务中展现出了卓越的性能。VGGNet作为一种经典的深层卷积神经网络,以其简洁而深邃的网络结构,在图像分类任务中取得了显著的成果。本文将详细介绍VGGNet的架构,并通过一个五类鲜花分类的实例,演示如何利用预训练的VGGNet模型进行图像分类。本文使用的工具链为PyTorch 2.5.1与TorchVision 0.20.1,确保代码的兼容性和稳定性。
VGGNet概述
VGGNet由牛津大学视觉几何组(Visual Geometry Group, VGG)和Google DeepMind的研究人员于2014年共同开发。VGGNet在2014年ImageNet大规模视觉识别挑战赛(ILSVRC2014)中表现优异,尤其在图像分类任务中取得了显著的成绩。
VGGNet的主要特点
-
统一的卷积核尺寸:VGGNet全部采用3×3的小卷积核。这种设计不仅减少了参数数量,还能通过堆叠多个卷积层来增加网络的深度和非线性表达能力。
-
深层网络结构:VGGNet通过增加网络的深度(层数),增强了模型的特征提取能力,相较于早期的网络如AlexNet,VGGNet在图像分类任务中表现更加出色。
-
重复使用简单的模块:VGGNet通过重复使用相同的卷积和池化模块,使得网络结构简洁且易于扩展,便于理解和实现。
-
全连接层:在卷积层之后,VGGNet使用多个全连接层进行高层次的特征组合和分类任务,增强了模型的分类能力。
VGGNet的版本
VGGNet有多个不同深度的版本,最常用的是VGG-16和VGG-19,分别包含16层和19层深度。这些版本在不同的任务中表现出色,尤其是在图像特征提取方面,被广泛应用于各种计算机视觉任务中。
VGGNet的网络架构
VGGNet的网络架构以其统一的小卷积核和深层结构著称。以VGG-16为例,其详细架构如下:
图片来源:Wikipedia
VGG-16详细架构
python
from torchsummary import summary
# 通过net.summay()查看网络的形状
summary(model=model,input_size=(3,224,224),batch_size=1,device='cpu')
-
输入层:224×224 RGB图像。
-
卷积层:
-
Conv1: 2个3×3卷积,64个通道
-
Conv2: 2个3×3卷积,128个通道
-
Conv3: 3个3×3卷积,256个通道
-
Conv4: 3个3×3卷积,512个通道
-
Conv5: 3个3×3卷积,512个通道
-
-
池化层:每两个或三个卷积层后接一个2×2的最大池化层,进行空间降维。
-
全连接层:
-
FC1: 4096个神经元
-
FC2: 4096个神经元
-
FC3: 1000个神经元(对应ImageNet的1000类)
-
参数总数
VGG-16共有约138,357,544个参数,其中包括138,357,544个可训练参数。这一庞大的参数量赋予了VGGNet强大的特征表达能力,但也带来了计算和存储的挑战。
基于预训练VGGNet的五类鲜花分类实现
在实际应用中,训练一个深层次的卷积神经网络(如VGGNet)需要大量的计算资源和时间。为此,我们可以利用在大型数据集(如ImageNet)上预训练的模型,进行迁移学习,以便在较小的数据集上快速获得较好的性能。本文将通过一个五类鲜花分类的实例,详细讲解如何利用预训练的VGGNet模型进行图像分类任务。
4.1 数据准备与预处理
首先,我们需要准备好五类鲜花的图像数据集,并对其进行预处理,以适应VGGNet的输入要求。具体步骤包括调整图像大小、数据增强和批量加载。
数据集介绍
假设我们使用的是一个包含五类鲜花的图像数据集,每类约有100张图像。数据集分为训练集和验证集,分别存放在不同的文件夹中。
数据预处理
数据预处理是深度学习模型训练中的关键步骤。对于VGGNet,我们需要将输入图像调整为224×224的尺寸,并进行标准化处理。此外,为了提高模型的泛化能力,我们还可以应用数据增强技术,如随机水平翻转和随机旋转。
python
import torch
import numpy as np # 新增 numpy 导入
from torchvision import transforms
from torch.utils.data import DataLoader
from torchvision.datasets import ImageFolder
import matplotlib.pyplot as plt
# 指定PyTorch和TorchVision的版本兼容性
print(f"PyTorch version: {torch.__version__}")
print(f"TorchVision version: {torch.__version__}") # TorchVision 0.20.1与PyTorch 2.5.1兼容
# 如果 MPS 可用,选择 "mps"。如果 CUDA 可用,选择 "cuda"。如果两者都不可用,选择 "cpu"。
device = torch.device("mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu")
# 将模型移到设备上
model.to(device)
# 指定批次大小
batch_size = 4
# 指定数据集路径
flower_train_path = '../01.图像分类/dataset/flower_datas/train/'
flower_val_path = '../01.图像分类/dataset/flower_datas/val/'
# 定义数据预处理方式
train_transforms = transforms.Compose([
transforms.Resize((224, 224)), # 调整图像大小
transforms.RandomHorizontalFlip(), # 随机水平翻转
transforms.RandomRotation(15), # 随机旋转
transforms.ToTensor(), # 转换为Tensor
transforms.Normalize([0.485, 0.456, 0.406], # 标准化
[0.229, 0.224, 0.225])
])
val_transforms = transforms.Compose([
transforms.Resize((224, 224)), # 调整图像大小
transforms.ToTensor(), # 转换为Tensor
transforms.Normalize([0.485, 0.456, 0.406], # 标准化
[0.229, 0.224, 0.225])
])
# 加载训练集和验证集
flower_train = ImageFolder(root=flower_train_path, transform=train_transforms)
flower_val = ImageFolder(root=flower_val_path, transform=val_transforms)
# 创建数据加载器
train_loader = DataLoader(dataset=flower_train, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(dataset=flower_val, batch_size=batch_size, shuffle=False)
# 获取类别名称
classes = flower_train.classes
print(f"类别名称: {classes}")
# 可视化一个批次的数据
def imshow(inp, title=None):
"""显示一个Tensor图像"""
inp = inp.numpy().transpose((1, 2, 0)) # 转换为 (H, W, C) 格式
mean = np.array([0.485, 0.456, 0.406]) # 转换为 numpy 数组
std = np.array([0.229, 0.224, 0.225]) # 转换为 numpy 数组
inp = std * inp + mean # 反标准化
inp = np.clip(inp, 0, 1) # 将像素值限制在 0 到 1 之间
plt.imshow(inp) # 显示图像
if title:
plt.title(title) # 如果提供了标题,则显示类别名称作为标题
plt.pause(0.001) # 暂停以确保图像显示
# 展示训练集中的一个batch
inputs, classes_idx = next(iter(train_loader)) # 从训练集加载器中获取一个批次的数据
out = torchvision.utils.make_grid(inputs) # 将多个图像拼接成网格
imshow(out, title=[classes[x] for x in classes_idx]) # 显示拼接后的图像,并显示对应的类别名称
plt.show() # 显示图像窗口
结果展示:
图片示例来自自定义数据集
代码解析
-
导入必要的库 :包括
torch
、torchvision
及其相关模块。 -
设置设备:判断是否有GPU可用,以加速训练过程。
-
定义数据预处理:
-
训练集:包括随机水平翻转和随机旋转的数据增强,以提高模型的泛化能力。
-
验证集:仅调整图像大小和标准化,确保评估时数据的一致性。
-
-
加载数据集 :使用
ImageFolder
读取指定路径下的图像数据,并应用相应的预处理。 -
创建数据加载器 :使用
DataLoader
批量加载数据,设置适当的批次大小和是否打乱数据。 -
获取类别名称:从训练集中提取类别标签,便于后续的结果解读。
-
可视化数据 :通过
matplotlib
展示一个批次的图像,帮助理解数据分布。
4.2 模型实例化与参数调整
接下来,我们将实例化预训练的VGG-16模型,并根据五类鲜花分类任务进行调整。具体步骤包括加载预训练权重、冻结卷积层参数、调整全连接层以适应五分类任务等。
python
from torchvision.models import vgg16, VGG16_Weights
import torch.nn as nn
# 1. 实例化模型
# 指定 weights=VGG16_Weights.IMAGENET1K_V1 来获取官方的 ImageNet 预训练权重
model = vgg16(weights=VGG16_Weights.IMAGENET1K_V1)
# 查看模型结构(可选)
#print(model)
# 2. 冻结卷积层的参数,以防止在训练过程中更新
for param in model.features.parameters():
param.requires_grad = False
# 3. 修改全连接层以适应5类分类任务
# VGG-16的classifier包含3个全连接层,最后一层输出为1000类
# 我们将其修改为5类
num_features = model.classifier[6].in_features
model.classifier[6] = nn.Linear(num_features, 5)
# 将模型移动到设备(GPU或CPU)
model = model.to(device)
# 4. 查看修改后的模型结构(可选)
#print(model)
代码解析
-
加载预训练模型:
- 使用
vgg16
函数,并指定weights=VGG16_Weights.IMAGENET1K_V1
来加载在ImageNet数据集上预训练的权重。
- 使用
-
冻结卷积层参数:
- 通过设置
param.requires_grad = False
,冻结卷积层的参数,避免在训练过程中更新。这有助于减少训练时间和防止过拟合,尤其在数据集较小时效果显著。
- 通过设置
-
调整全连接层:
- VGG-16的全连接层
classifier
的最后一层(classifier[6]
)原本输出为1000类。我们将其替换为输出5类,以适应五类鲜花分类任务。
- VGG-16的全连接层
-
移动模型到设备:
- 将模型移动到GPU(如果可用)或CPU,确保后续的训练和推理过程在指定设备上进行。
4.3 模型训练与保存最优模型
在完成模型实例化和参数调整后,我们将定义训练过程,包括前向传播、损失计算、反向传播和参数更新。此外,我们还将实现保存验证集上表现最优的模型,以便在训练完成后进行加载和预测。
python
import torch.optim as optim
from tqdm import tqdm # 用于显示训练进度条
import copy # 用于保存模型的最佳权重
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
# - **损失函数**:交叉熵损失函数,用于多分类问题,衡量预测值与真实标签的差异。
# 只优化最后一层全连接层的参数
optimizer = optim.Adam(model.classifier.parameters(), lr=1e-4)
# - **优化器**:使用 Adam 优化算法,只针对模型的全连接层(`classifier`)进行优化。
# - **学习率 (lr)**:设置为 1e-4,控制每次参数更新的步幅。
# 定义训练和验证的函数
def train_model(model, criterion, optimizer, train_loader, val_loader, num_epochs=25):
"""
训练和验证模型
:param model: 要训练的神经网络模型
:param criterion: 损失函数
:param optimizer: 优化器
:param train_loader: 训练数据加载器
:param val_loader: 验证数据加载器
:param num_epochs: 训练的总轮次
:return: 训练完成后的模型和记录的训练历史
"""
best_model_wts = copy.deepcopy(model.state_dict())
# 保存最佳模型的权重
best_acc = 0.0 # 初始化最佳验证准确率
# 用于记录每个epoch的训练和验证损失与准确率
train_losses = [] # 记录训练集损失
val_losses = [] # 记录验证集损失
train_accuracies = [] # 记录训练集准确率
val_accuracies = [] # 记录验证集准确率
for epoch in range(num_epochs):
print(f'Epoch {epoch+1}/{num_epochs}') # 打印当前轮次
print('-' * 10) # 分隔线
# 每个epoch都有训练和验证两个阶段
for phase in ['train', 'val']:
if phase == 'train':
model.train() # 设置模型为训练模式(启用Dropout和BatchNorm)
dataloader = train_loader # 使用训练集加载器
else:
model.eval() # 设置模型为评估模式(禁用Dropout和BatchNorm)
dataloader = val_loader # 使用验证集加载器
running_loss = 0.0 # 累积损失初始化
running_corrects = 0 # 累积正确预测数初始化
# 迭代数据
for inputs, labels in tqdm(dataloader, desc=f'{phase}'):
inputs = inputs.to(device) # 将输入数据移动到指定设备(GPU或CPU)
labels = labels.to(device) # 将标签移动到指定设备
# 前向传播
with torch.set_grad_enabled(phase == 'train'):
# 如果是训练阶段启用梯度计算,验证阶段关闭梯度计算以加速
outputs = model(inputs) # 模型前向计算,得到输出
_, preds = torch.max(outputs, 1) # 获取预测类别
loss = criterion(outputs, labels) # 计算损失
# 反向传播和优化(仅训练阶段)
if phase == 'train':
optimizer.zero_grad() # 清空梯度
loss.backward() # 反向传播计算梯度
optimizer.step() # 更新模型参数
# 统计损失和正确预测数
running_loss += loss.item() * inputs.size(0) # 累积损失,乘以批次大小
running_corrects += torch.sum(preds == labels.data) # 累积正确预测数
epoch_loss = running_loss / len(dataloader.dataset)
# 平均损失:累积损失除以数据集大小
epoch_acc = running_corrects.float() / len(dataloader.dataset)
# 准确率:累积正确数除以数据集大小
print(f'{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}')
# 输出当前阶段的损失和准确率
# 记录损失和准确率
if phase == 'train':
train_losses.append(epoch_loss)
train_accuracies.append(epoch_acc.item())
else:
val_losses.append(epoch_loss)
val_accuracies.append(epoch_acc.item())
# 深拷贝模型
if epoch_acc > best_acc:
best_acc = epoch_acc # 更新最佳准确率
best_model_wts = copy.deepcopy(model.state_dict())
# 保存当前模型的最佳权重
torch.save(model.state_dict(), 'saved_models/best_vgg16_flower_lr0.001_bs32.pth')
# 保存模型到文件中
print('保存了最优模型')
print() # 换行
print(f'最佳验证准确率: {best_acc:.4f}')
# 打印最佳验证准确率
# 加载最佳模型权重
model.load_state_dict(best_model_wts)
# 恢复为最佳模型的权重
# 返回训练过程的记录
history = {
'train_losses': train_losses,
'val_losses': val_losses,
'train_accuracies': train_accuracies,
'val_accuracies': val_accuracies
}
return model, history
# 开始训练
num_epochs = 25 # 训练的轮次数
model, history = train_model(model, criterion, optimizer, train_loader, val_loader, num_epochs=num_epochs)
# 调用训练函数,返回训练完成的模型和训练过程的记录
输出:
代码解析
-
定义损失函数和优化器:
-
使用交叉熵损失函数(
CrossEntropyLoss
)适用于多分类任务。 -
采用Adam优化器,仅优化全连接层的参数,学习率设为1e-4。
-
-
训练函数
train_model
:-
参数:
-
model
:待训练的模型。 -
criterion
:损失函数。 -
optimizer
:优化器。 -
train_loader
和val_loader
:训练和验证数据加载器。 -
num_epochs
:训练轮数。
-
-
过程:
-
初始化最佳模型权重和最佳准确率。
-
遍历每个epoch,包含训练和验证两个阶段。
-
在训练阶段,设置模型为训练模式,并进行前向传播、计算损失、反向传播和参数更新。
-
在验证阶段,设置模型为评估模式,仅进行前向传播和损失计算。
-
记录每个阶段的损失和准确率,并在验证准确率提升时保存模型权重。
-
-
返回:
- 训练完成后的最佳模型和训练过程中的记录(损失和准确率)。
-
-
保存最优模型:
-
当验证集准确率提升时,使用
torch.save
保存当前模型权重为saved_models/best_vgg16_flower_lr0.001_bs32.pth
-
-
训练过程的记录:
- 通过
history
字典记录每个epoch的训练和验证损失与准确率,便于后续的可视化分析。
- 通过
4.4 模型导入与预测
在完成模型训练并保存最优模型后,我们将加载该模型,并对新图像进行预测。
python
from PIL import Image # 导入 PIL 库,用于打开和处理图像
from torchvision.models import vgg16, VGG16_Weights
import torch.nn as nn
from torchvision import transforms
import torch
# 如果 MPS 可用,选择 "mps"。如果 CUDA 可用,选择 "cuda"。如果两者都不可用,选择 "cpu"。
device = torch.device("mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu")
batch_size=1
# 1. 实例化模型
# 指定 weights=VGG16_Weights.IMAGENET1K_V1 来获取官方的 ImageNet 预训练权重
model = vgg16(weights=VGG16_Weights.IMAGENET1K_V1)
# 1. 实例化模型结构
model = vgg16(weights=VGG16_Weights.IMAGENET1K_V1)
# - 加载预训练的 VGG16 模型,权重为在 ImageNet 数据集上训练的版本 (IMAGENET1K_V1)
num_features = model.classifier[6].in_features
# - 获取 VGG16 最后一层全连接层的输入特征数量
model.classifier[6] = nn.Linear(num_features, 5)
# - 修改最后一层全连接层,将其输出节点数设置为 5,对应 5 个类别
# 2. 加载最优模型权重
model.load_state_dict(torch.load('saved_models/best_vgg16_flower_lr0.001_bs32.pth', weights_only=True))
# - 从文件 'best_vgg16_flower.pth' 中加载之前训练好的模型权重
model = model.to(device)
# - 将模型移动到指定设备(GPU 或 CPU)
# 获取类别名称
classes = ['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']
model.eval()
# - 设置模型为评估模式,禁用 Dropout 和 Batch Normalization 的训练行为
# 3. 定义预测函数
def predict_image(image_path, model, transform, classes):
"""
对单张图像进行预测
:param image_path: 图像文件路径
:param model: 加载了权重的 VGG16 模型
:param transform: 数据预处理方法(与训练一致)
:param classes: 类别名称列表
:return: 预测的类别名称和置信度分数
"""
image = Image.open(image_path).convert('RGB')
# - 打开图像文件,并将其转换为 RGB 模式(确保有 3 个通道)
image = transform(image).unsqueeze(0)
# - 应用数据预处理方法,将图像转换为 Tensor
# - 调用 `unsqueeze(0)` 增加一个批次维度,形状从 (C, H, W) 变为 (1, C, H, W)
image = image.to(device)
# - 将图像数据移动到指定设备(GPU 或 CPU)
with torch.no_grad():
# - 禁用梯度计算(推理阶段不需要计算梯度,节省内存并加速)
output = model(image)
# - 将图像输入模型,获得预测输出
_, predicted = torch.max(output, 1)
# - 获取预测的类别索引,`torch.max` 返回最大值和对应的索引
confidence = torch.softmax(output, 1)[0] * 100
# - 对模型的输出应用 softmax 函数,将其转化为概率分布,并转换为百分比形式
predicted_class = classes[predicted.item()]
# - 使用类别索引从类别列表中获取对应的类别名称
confidence_score = confidence[predicted.item()].item()
# - 获取预测类别对应的置信度分数
return predicted_class, confidence_score
# - 返回预测的类别名称和置信度分数
# 4. 定义与训练时相同的预处理方式
predict_transform = transforms.Compose([
transforms.Resize((224, 224)),
# - 调整图像大小为 224x224 像素
transforms.ToTensor(),
# - 将图像转换为 PyTorch 的张量格式 (C, H, W)
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
# - 使用 ImageNet 数据集的均值和标准差进行标准化
])
# 5. 进行预测
test_image_path = '../01.图像分类/dataset/flower_datas/val/daisy/105806915_a9c13e2106_n.jpg'
# - 指定测试图像的路径,请替换为实际的图像文件路径
predicted_class, confidence_score = predict_image(test_image_path, model, predict_transform, classes)
# - 调用预测函数,获取预测的类别名称和置信度分数
print(f'预测类别: {predicted_class}, 置信度: {confidence_score:.2f}%')
# - 打印预测结果,包括类别名称和置信度百分比
输出打印:
代码解析
-
实例化模型结构:
- 重新实例化VGG-16模型,并调整全连接层以适应5类分类任务。
-
加载最优模型权重:
-
使用
torch.load
加载保存的最优模型权重best_vgg16_flower_lr0.001_bs32.pth
。 -
将模型移动到设备,并设置为评估模式(
model.eval()
)。
-
-
定义预测函数
predict_image
:-
接受图像路径、模型、预处理方法和类别名称作为输入。
-
读取并预处理图像,添加批次维度后移动到设备。
-
通过模型进行前向传播,获取预测结果。
-
使用
torch.softmax
计算置信度分数,并返回预测类别及其置信度。
-
-
定义预测时的预处理方式:
- 与训练时保持一致,确保图像的尺寸和标准化方式相同。
-
进行预测:
- 指定测试图像路径,调用预测函数,输出预测类别及其置信度。
4.5 训练过程的可视化
为了更直观地理解模型的训练过程,我们将可视化训练和验证阶段的损失与准确率变化趋势。这有助于判断模型是否出现过拟合或欠拟合,并指导进一步的模型优化。
python
import matplotlib.pyplot as plt
# 绘制训练和验证损失曲线
def plot_loss(history):
plt.figure(figsize=(10,5))
plt.plot(history['train_losses'], label='训练损失')
plt.plot(history['val_losses'], label='验证损失')
plt.xlabel('Epoch')
plt.ylabel('损失')
plt.title('训练与验证损失曲线')
plt.legend()
plt.show()
# 绘制训练和验证准确率曲线
def plot_accuracy(history):
plt.figure(figsize=(10,5))
plt.plot(history['train_accuracies'], label='训练准确率')
plt.plot(history['val_accuracies'], label='验证准确率')
plt.xlabel('Epoch')
plt.ylabel('准确率')
plt.title('训练与验证准确率曲线')
plt.legend()
plt.show()
# 调用绘图函数
plot_loss(history)
plot_accuracy(history)
代码解析
-
定义绘图函数
plot_loss
:-
接受
history
字典,绘制训练和验证阶段的损失曲线。 -
X轴为epoch,Y轴为损失值。
-
添加图例、标题和标签以增强可读性。
-
-
定义绘图函数
plot_accuracy
:-
接受
history
字典,绘制训练和验证阶段的准确率曲线。 -
X轴为epoch,Y轴为准确率。
-
添加图例、标题和标签以增强可读性。
-
-
调用绘图函数:
- 使用训练过程中记录的损失和准确率数据,生成可视化图表。
结果展示:
损失曲线示例
准确率曲线示例
可视化分析
通过损失和准确率曲线,我们可以观察到模型在训练过程中的表现:
-
损失曲线:训练损失逐渐下降,验证损失在初期下降后趋于平稳,甚至可能出现轻微上升,提示模型可能开始过拟合。
-
准确率曲线:训练准确率逐渐提高,验证准确率在初期上升后趋于稳定,验证集准确率的提升放缓,进一步提示过拟合的风险。
这些观察结果有助于我们决定是否需要进一步优化模型,如引入正则化、调整学习率或增加数据量等。
模型优化与防止过拟合
在前面的训练过程中,我们可能会观察到模型在训练集上表现良好,但在验证集上表现欠佳,或验证集准确率不再提升,甚至下降。这通常是由于模型过于复杂或数据不足导致的过拟合现象。为了解决这些问题,我们可以采取以下几种优化方法:
1. 使用更深层的预训练模型
虽然VGGNet已经表现出色,但在某些任务中,使用更深层或更先进的模型(如ResNet、DenseNet等)可能会带来更好的性能。然而,考虑到本文的教学目的,我们继续使用VGGNet。
2. 数据增强
数据增强通过对训练图像进行随机变换,增加数据的多样性,提升模型的泛化能力。我们已经在数据预处理中应用了一些基本的数据增强技术,如随机水平翻转和随机旋转。根据需要,还可以添加更多的数据增强方法,如随机裁剪、颜色抖动等。
python
train_transforms = transforms.Compose([
transforms.Resize((224, 224)),
# - 调整图像大小为 224x224 像素
# - 目的:统一图像输入尺寸,方便后续模型处理
transforms.RandomHorizontalFlip(),
# - 随机水平翻转图像,概率为 50%
# - 目的:通过翻转图像增强数据集,增加模型的鲁棒性
transforms.RandomRotation(15),
# - 随机旋转图像,旋转角度范围在 ±15 度之间
# - 目的:使模型更具泛化能力,适应旋转视角的变化
transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.2),
# - 颜色抖动,随机调整图像的亮度、对比度、饱和度和色调
# - 参数解释:
# brightness=0.2:亮度随机变化范围为 ±20%
# contrast=0.2:对比度随机变化范围为 ±20%
# saturation=0.2:饱和度随机变化范围为 ±20%
# hue=0.2:色调随机变化范围为 ±20%(值范围 -0.5 到 0.5)
# - 目的:增强数据集的多样性,让模型适应不同光线和色彩条件
transforms.ToTensor(),
# - 将图像转换为 PyTorch 张量(Tensor),并归一化到 [0, 1] 范围
# - 转换格式为 (H, W, C) → (C, H, W),适配 PyTorch 模型输入
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
# - 按照 ImageNet 数据集的均值和标准差对图像进行标准化
# - 均值:[0.485, 0.456, 0.406](分别对应 RGB 三个通道)
# - 标准差:[0.229, 0.224, 0.225](分别对应 RGB 三个通道)
# - 目的:让模型适应 ImageNet 预训练模型的输入要求,加速收敛并提高准确性
])
4. 引入正则化技术
Dropout 是一种有效的正则化技术,通过在训练过程中随机丢弃一部分神经元,防止模型过拟合。VGGNet的全连接层已经包含了Dropout层,但我们可以根据需要调整其概率或在卷积层中引入额外的Dropout层。
python
# 修改全连接层,调整Dropout概率
model.classifier = nn.Sequential(
nn.Linear(25088, 4096),
nn.ReLU(),
nn.Dropout(0.5), # Dropout概率调整为0.5
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 5)
)
model = model.to(device)
5. 早停法(Early Stopping)
早停法通过监控验证集的性能,在验证集性能不再提升时提前停止训练,防止模型过拟合。
python
import torch
import torch.optim as optim
from tqdm import tqdm
import copy
# 定义使用早停法的模型训练函数
def train_model_with_early_stopping(model, criterion, optimizer, train_loader, val_loader, num_epochs=25, patience=5):
"""
使用早停法训练模型
:param model: 神经网络模型
:param criterion: 损失函数
:param optimizer: 优化器
:param train_loader: 训练集数据加载器
:param val_loader: 验证集数据加载器
:param num_epochs: 总训练轮次
:param patience: 早停的耐心值(连续多少个 epoch 验证准确率不提升时停止训练)
:return: 训练完成的模型和训练记录
"""
best_model_wts = copy.deepcopy(model.state_dict())
# 保存最佳模型的权重
best_acc = 0.0 # 初始化最佳验证准确率
epochs_no_improve = 0 # 连续验证准确率未提升的 epoch 数
# 用于记录每个 epoch 的损失和准确率
train_losses = []
val_losses = []
train_accuracies = []
val_accuracies = []
for epoch in range(num_epochs):
print(f'Epoch {epoch+1}/{num_epochs}') # 打印当前 epoch
print('-' * 10) # 分隔线
for phase in ['train', 'val']:
if phase == 'train':
model.train() # 设置模型为训练模式
dataloader = train_loader # 使用训练集数据加载器
else:
model.eval() # 设置模型为评估模式
dataloader = val_loader # 使用验证集数据加载器
running_loss = 0.0 # 累积损失
running_corrects = 0 # 累积正确预测数
# 遍历每个批次
for inputs, labels in tqdm(dataloader, desc=f'{phase}'):
inputs = inputs.to(device) # 将输入数据移动到指定设备(GPU 或 CPU)
labels = labels.to(device) # 将标签移动到指定设备
# 前向传播
with torch.set_grad_enabled(phase == 'train'):
outputs = model(inputs) # 计算模型输出
_, preds = torch.max(outputs, 1) # 获取预测类别
loss = criterion(outputs, labels) # 计算损失
# 反向传播和优化(仅训练阶段)
if phase == 'train':
optimizer.zero_grad() # 清空梯度
loss.backward() # 反向传播计算梯度
optimizer.step() # 更新模型参数
# 累积损失和正确预测数
running_loss += loss.item() * inputs.size(0)
# - `loss.item()` 是批次的平均损失,需要乘以批次大小恢复总损失
running_corrects += torch.sum(preds == labels.data)
# - 统计正确预测的样本数
# 计算每个 epoch 的平均损失和准确率
epoch_loss = running_loss / len(dataloader.dataset)
# - 总损失除以数据集大小,得到平均损失
epoch_acc = running_corrects.double() / len(dataloader.dataset)
# - 正确预测数除以数据集大小,得到准确率
print(f'{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}')
# 打印当前阶段的损失和准确率
# 记录损失和准确率
if phase == 'train':
train_losses.append(epoch_loss)
train_accuracies.append(epoch_acc.item())
else:
val_losses.append(epoch_loss)
val_accuracies.append(epoch_acc.item())
# 检查是否为最佳模型
if epoch_acc > best_acc:
best_acc = epoch_acc # 更新最佳验证准确率
best_model_wts = copy.deepcopy(model.state_dict())
# 保存当前模型的最佳权重
torch.save(model.state_dict(), 'best_vgg16_flower.pth')
print('保存了最优模型')
epochs_no_improve = 0 # 重置耐心计数
else:
epochs_no_improve += 1 # 验证准确率未提升
# 如果连续未提升的 epoch 数达到耐心值,则触发早停
if epochs_no_improve >= patience:
print('早停法触发,停止训练')
model.load_state_dict(best_model_wts)
# 加载最佳模型权重
history = {
'train_losses': train_losses,
'val_losses': val_losses,
'train_accuracies': train_accuracies,
'val_accuracies': val_accuracies
}
return model, history
print() # 每个 epoch 结束后换行
print(f'最佳验证准确率: {best_acc:.4f}')
# 打印最佳验证准确率
model.load_state_dict(best_model_wts)
# 加载最佳模型权重
# 保存训练过程的记录
history = {
'train_losses': train_losses, # 每个 epoch 的训练损失
'val_losses': val_losses, # 每个 epoch 的验证损失
'train_accuracies': train_accuracies, # 每个 epoch 的训练准确率
'val_accuracies': val_accuracies # 每个 epoch 的验证准确率
}
return model, history # 返回最佳模型和训练记录
# 设置优化器为 AdamW
optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-2)
# - 使用 AdamW 优化器
# - 参数:
# - `lr=1e-3`:学习率设置为 0.001
# - `weight_decay=1e-2`:权重衰减因子,用于 L2 正则化,防止过拟合
# 使用早停法进行训练
model, history = train_model_with_early_stopping(
model,
criterion,
optimizer,
train_loader,
val_loader,
num_epochs=25,
patience=5
)
6. 模型优化总结
通过上述优化方法,我们可以显著提升VGGNet在五类鲜花分类任务中的性能,具体包括:
-
利用预训练模型:加速训练过程,提升初始性能。
-
数据增强:增加数据多样性,提升模型泛化能力。
-
学习率调度:动态调整学习率,优化训练过程。
-
正则化技术:防止模型过拟合,提升验证集性能。
-
早停法:避免过度训练,保存最佳模型。
这些优化方法在实际应用中相辅相成,共同提升模型的整体表现。
总结与展望
本文详细介绍了VGGNet的架构及其在图像分类任务中的应用,特别是基于预训练模型进行五类鲜花分类的实战案例。通过数据准备与预处理、模型实例化与参数调整、模型训练与保存最优模型、模型导入与预测以及训练过程的可视化,全面展示了如何在PyTorch 2.5.1与TorchVision 0.20.1环境下高效应用VGGNet。
尽管VGGNet在图像分类任务中表现出色,但其庞大的参数量和计算资源需求也带来了挑战。随着深度学习技术的不断进步,更多高效且精确的网络架构如ResNet、DenseNet等被提出,未来的研究将继续探索更为高效的网络结构和训练方法。
通过本文的学习,读者不仅掌握了VGGNet的基本知识和实现方法,还了解了如何在实际项目中优化模型,提升其性能。希望本文能为您的深度学习之路提供有力的支持和指导。
参考文献
-
Simonyan, K., & Zisserman, A. (2014). Very Deep Convolutional Networks for Large-Scale Image Recognition . arXiv:1409.1556
-
PyTorch Documentation. torchvision.models.VGG . Retrieved from Models and pre-trained weights --- Torchvision 0.20 documentation
-
Wikipedia. VGGNet . Retrieved from https://en.wikipedia.org/wiki/VGG_net
-
CSDN博客. VGGNet详解与实现 . Retrieved from https://blog.csdn.net
如果觉得文章有用,辛苦点赞,收藏,转发,关注一波,如果有其他不懂的可以私信我哈~
你想用深度学习解决哪个现实世界的那些问题?在评论区告诉我们