【计算机视觉CV-图像分类】06 - VGGNet的鲜花分类实现:从数据预处理到模型优化的完整实战!

embedded/2024/12/26 10:19:57/

目录

  1. 引言

  2. VGGNet概述

  3. VGGNet的网络架构

  4. 基于预训练VGGNet的五类鲜花分类实现

    • 4.1 数据准备与预处理

    • 4.2 模型实例化与参数调整

    • 4.3 模型训练与保存最优模型

    • 4.4 模型导入与预测

    • 4.5 训练过程的可视化

  5. 模型优化与防止过拟合

  6. 总结与展望

  7. 参考文献


引言

计算机视觉领域,图像分类是一个基础且关键的任务。随着深度学习技术的迅猛发展,卷积神经网络(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的主要特点

  1. 统一的卷积核尺寸:VGGNet全部采用3×3的小卷积核。这种设计不仅减少了参数数量,还能通过堆叠多个卷积层来增加网络的深度和非线性表达能力。

  2. 深层网络结构:VGGNet通过增加网络的深度(层数),增强了模型的特征提取能力,相较于早期的网络如AlexNet,VGGNet在图像分类任务中表现更加出色。

  3. 重复使用简单的模块:VGGNet通过重复使用相同的卷积和池化模块,使得网络结构简洁且易于扩展,便于理解和实现。

  4. 全连接层:在卷积层之后,VGGNet使用多个全连接层进行高层次的特征组合和分类任务,增强了模型的分类能力。

VGGNet的版本

VGGNet有多个不同深度的版本,最常用的是VGG-16和VGG-19,分别包含16层和19层深度。这些版本在不同的任务中表现出色,尤其是在图像特征提取方面,被广泛应用于各种计算机视觉任务中。


VGGNet的网络架构

VGGNet的网络架构以其统一的小卷积核和深层结构著称。以VGG-16为例,其详细架构如下:

图片来源:Wikipedia

VGG-16详细架构

from torchsummary import summary
# 通过net.summay()查看网络的形状
summary(model=model,input_size=(3,224,224),batch_size=1,device='cpu')

  1. 输入层:224×224 RGB图像。

  2. 卷积层:

    • 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个通道

  3. 池化层:每两个或三个卷积层后接一个2×2的最大池化层,进行空间降维。

  4. 全连接层:

    • 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的尺寸,并进行标准化处理。此外,为了提高模型的泛化能力,我们还可以应用数据增强技术,如随机水平翻转和随机旋转。

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(),                             # 转换为Tensortransforms.Normalize([0.485, 0.456, 0.406],       # 标准化[0.229, 0.224, 0.225])
])val_transforms = transforms.Compose([transforms.Resize((224, 224)),                     # 调整图像大小transforms.ToTensor(),                             # 转换为Tensortransforms.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()  # 显示图像窗口

结果展示:

图片示例来自自定义数据集

代码解析
  1. 导入必要的库:包括torchtorchvision及其相关模块。

  2. 设置设备:判断是否有GPU可用,以加速训练过程。

  3. 定义数据预处理:

    • 训练集:包括随机水平翻转和随机旋转的数据增强,以提高模型的泛化能力。

    • 验证集:仅调整图像大小和标准化,确保评估时数据的一致性。

  4. 加载数据集:使用ImageFolder读取指定路径下的图像数据,并应用相应的预处理。

  5. 创建数据加载器:使用DataLoader批量加载数据,设置适当的批次大小和是否打乱数据。

  6. 获取类别名称:从训练集中提取类别标签,便于后续的结果解读。

  7. 可视化数据:通过matplotlib展示一个批次的图像,帮助理解数据分布。

4.2 模型实例化与参数调整

接下来,我们将实例化预训练的VGG-16模型,并根据五类鲜花分类任务进行调整。具体步骤包括加载预训练权重、冻结卷积层参数、调整全连接层以适应五分类任务等。

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)
代码解析
  1. 加载预训练模型:

    • 使用vgg16函数,并指定weights=VGG16_Weights.IMAGENET1K_V1来加载在ImageNet数据集上预训练的权重。

  2. 冻结卷积层参数:

    • 通过设置param.requires_grad = False,冻结卷积层的参数,避免在训练过程中更新。这有助于减少训练时间和防止过拟合,尤其在数据集较小时效果显著。

  3. 调整全连接层:

    • VGG-16的全连接层classifier的最后一层(classifier[6])原本输出为1000类。我们将其替换为输出5类,以适应五类鲜花分类任务。

  4. 移动模型到设备:

    • 将模型移动到GPU(如果可用)或CPU,确保后续的训练和推理过程在指定设备上进行。

4.3 模型训练与保存最优模型

在完成模型实例化和参数调整后,我们将定义训练过程,包括前向传播、损失计算、反向传播和参数更新。此外,我们还将实现保存验证集上表现最优的模型,以便在训练完成后进行加载和预测。

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)
# 调用训练函数,返回训练完成的模型和训练过程的记录

输出: 

代码解析
  1. 定义损失函数和优化器:

    • 使用交叉熵损失函数(CrossEntropyLoss)适用于多分类任务。

    • 采用Adam优化器,仅优化全连接层的参数,学习率设为1e-4。

  2. 训练函数train_model

    • 参数:

      • model:待训练的模型。

      • criterion:损失函数。

      • optimizer:优化器。

      • train_loaderval_loader:训练和验证数据加载器。

      • num_epochs:训练轮数。

    • 过程:

      • 初始化最佳模型权重和最佳准确率。

      • 遍历每个epoch,包含训练和验证两个阶段。

      • 在训练阶段,设置模型为训练模式,并进行前向传播、计算损失、反向传播和参数更新。

      • 在验证阶段,设置模型为评估模式,仅进行前向传播和损失计算。

      • 记录每个阶段的损失和准确率,并在验证准确率提升时保存模型权重。

    • 返回:

      • 训练完成后的最佳模型和训练过程中的记录(损失和准确率)。

  3. 保存最优模型:

    • 当验证集准确率提升时,使用torch.save保存当前模型权重为

      saved_models/best_vgg16_flower_lr0.001_bs32.pth
      
  4. 训练过程的记录:

    • 通过history字典记录每个epoch的训练和验证损失与准确率,便于后续的可视化分析。

4.4 模型导入与预测

在完成模型训练并保存最优模型后,我们将加载该模型,并对新图像进行预测。

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}%')
# - 打印预测结果,包括类别名称和置信度百分比

输出打印:

代码解析
  1. 实例化模型结构:

    • 重新实例化VGG-16模型,并调整全连接层以适应5类分类任务。

  2. 加载最优模型权重:

    • 使用torch.load加载保存的最优模型权重best_vgg16_flower_lr0.001_bs32.pth

    • 将模型移动到设备,并设置为评估模式(model.eval())。

  3. 定义预测函数predict_image

    • 接受图像路径、模型、预处理方法和类别名称作为输入。

    • 读取并预处理图像,添加批次维度后移动到设备。

    • 通过模型进行前向传播,获取预测结果。

    • 使用torch.softmax计算置信度分数,并返回预测类别及其置信度。

  4. 定义预测时的预处理方式:

    • 与训练时保持一致,确保图像的尺寸和标准化方式相同。

  5. 进行预测:

    • 指定测试图像路径,调用预测函数,输出预测类别及其置信度。

4.5 训练过程的可视化

为了更直观地理解模型的训练过程,我们将可视化训练和验证阶段的损失与准确率变化趋势。这有助于判断模型是否出现过拟合或欠拟合,并指导进一步的模型优化。

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)

 

代码解析
  1. 定义绘图函数plot_loss

    • 接受history字典,绘制训练和验证阶段的损失曲线。

    • X轴为epoch,Y轴为损失值。

    • 添加图例、标题和标签以增强可读性。

  2. 定义绘图函数plot_accuracy

    • 接受history字典,绘制训练和验证阶段的准确率曲线。

    • X轴为epoch,Y轴为准确率。

    • 添加图例、标题和标签以增强可读性。

  3. 调用绘图函数:

    • 使用训练过程中记录的损失和准确率数据,生成可视化图表。

结果展示:

损失曲线示例

准确率曲线示例

可视化分析

通过损失和准确率曲线,我们可以观察到模型在训练过程中的表现:

  • 损失曲线:训练损失逐渐下降,验证损失在初期下降后趋于平稳,甚至可能出现轻微上升,提示模型可能开始过拟合。

  • 准确率曲线:训练准确率逐渐提高,验证准确率在初期上升后趋于稳定,验证集准确率的提升放缓,进一步提示过拟合的风险。

这些观察结果有助于我们决定是否需要进一步优化模型,如引入正则化、调整学习率或增加数据量等。


模型优化与防止过拟合

在前面的训练过程中,我们可能会观察到模型在训练集上表现良好,但在验证集上表现欠佳,或验证集准确率不再提升,甚至下降。这通常是由于模型过于复杂或数据不足导致的过拟合现象。为了解决这些问题,我们可以采取以下几种优化方法:

1. 使用更深层的预训练模型

虽然VGGNet已经表现出色,但在某些任务中,使用更深层或更先进的模型(如ResNet、DenseNet等)可能会带来更好的性能。然而,考虑到本文的教学目的,我们继续使用VGGNet。

2. 数据增强

数据增强通过对训练图像进行随机变换,增加数据的多样性,提升模型的泛化能力。我们已经在数据预处理中应用了一些基本的数据增强技术,如随机水平翻转和随机旋转。根据需要,还可以添加更多的数据增强方法,如随机裁剪、颜色抖动等。

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层。

# 修改全连接层,调整Dropout概率
model.classifier = nn.Sequential(nn.Linear(25088, 4096),nn.ReLU(),nn.Dropout(0.5),  # Dropout概率调整为0.5nn.Linear(4096, 4096),nn.ReLU(),nn.Dropout(0.5),nn.Linear(4096, 5)
)
model = model.to(device)

5. 早停法(Early Stopping)

早停法通过监控验证集的性能,在验证集性能不再提升时提前停止训练,防止模型过拟合。

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}')  # 打印当前 epochprint('-' * 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, historyprint()  # 每个 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的基本知识和实现方法,还了解了如何在实际项目中优化模型,提升其性能。希望本文能为您的深度学习之路提供有力的支持和指导。


参考文献

  1. Simonyan, K., & Zisserman, A. (2014). Very Deep Convolutional Networks for Large-Scale Image Recognition. arXiv:1409.1556

  2. PyTorch Documentation. torchvision.models.VGG. Retrieved from Models and pre-trained weights — Torchvision 0.20 documentation

  3. Wikipedia. VGGNet. Retrieved from https://en.wikipedia.org/wiki/VGG_net

  4. CSDN博客. VGGNet详解与实现. Retrieved from https://blog.csdn.net


如果觉得文章有用,辛苦点赞,收藏,转发,关注一波,如果有其他不懂的可以私信我哈~

你想用深度学习解决哪个现实世界的那些问题?在评论区告诉我们


http://www.ppmy.cn/embedded/148869.html

相关文章

Scratch教学作品 | 鲁道夫与雪人——温馨圣诞动画,享受节日魔法! ✨

今天为大家推荐一款充满圣诞氛围的Scratch动画作品——《鲁道夫与雪人》!由SumitraKan制作,这款作品以轻松愉快的节日旋律和童趣动画,为你呈现一个温馨的圣诞世界。跟着鲁道夫和雪人一起踏上奇妙的节日旅程吧!✨ 更棒的是&#xf…

BFS 解决拓扑排序_ 课程表_火星词典

什么是拓扑排序 拓扑排序的基本要求 有向无环图(DAG):拓扑排序只适用于没有环的有向图。如果图中有环,无法进行拓扑排序。 唯一性:对于一个DAG,拓扑排序不一定是唯一的,可能有多种有效的拓扑排序…

链原生 Web3 AI 网络 Chainbase 推出 AVS 主网, 拓展 EigenLayer AVS 场景

在 12 月 4 日,链原生的 Web3 AI 数据网络 Chainbase 正式启动了 Chainbase AVS 主网,同时发布了首批 20 个 AVS 节点运营商名单。Chainbase AVS 是 EigenLayer AVS 中首个以数据智能为应用导向的主网 AVS,其采用四层网络架构,其中…

RPC入门教学(一) ———— RPC介绍与protobuf的介绍与使用

什么是RPC RPC(Remote Procedure Call,远程过程调用)是一种允许程序调用另一台计算机上的子程序或函数的协议,而无需程序员显式地进 行底层网络编程。RPC的目标是让开发者在编写分布式应用时,可以像调用本地函数一样简…

PostgreSQL编译安装教程

下载安装 1.在家目录创建一个文件夹放下载安装包 mkdir softwarecd software 2.下载文件压缩包 wget https://ftp.postgresql.org/pub/source/v16.0/postgresql-16.0.tar.gz 3.解压 tar -xzvf postgresql-16.0.tar.gz 4.编译 在software/postgresql-16.0下 cd software…

电脑文件wlanapi.dll有什么用?找不到wlanapi.dll文件四种详细解决方法

在使用Windows操作系统时,可能会遇到“找不到wlanapi.dll”的错误提示,这通常意味着系统缺少一个关键的动态链接库文件,该文件对于无线网络功能至关重要。本文将为您提供关于wlanapi.dll的详细介绍,分析其缺失对电脑的影响&#x…

Java 中的 7 种重试机制

随着互联网的发展项目中的业务功能越来越复杂,有一些基础服务我们不可避免的会去调用一些第三方的接口或者公司内其他项目中提供的服务,但是远程服务的健壮性和网络稳定性都是不可控因素。在测试阶段可能没有什么异常情况,但上线后可能会出现…

React Native 集成原生Android功能

React Native 集成原生功能完整指南 前言 在 React Native 开发中,我们经常需要使用设备的原生功能,比如蓝牙、打印机等。本文将以集成打印机功能为例,详细介绍如何在 React Native 项目中集成 Android 原生功能。 集成步骤概述 创建原生…