欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 手游 > 第P7周:马铃薯病害识别(VGG-16复现)

第P7周:马铃薯病害识别(VGG-16复现)

2025/3/15 10:52:44 来源:https://blog.csdn.net/m0_46482112/article/details/146258491  浏览:    关键词:第P7周:马铃薯病害识别(VGG-16复现)
  • 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
  • 🍖 原作者:K同学啊
  1. 自己搭建VGG-16网络框架
  2. 调用官方的VGG-16网络框架
  3. 如何查看模型的参数量以及相关指标

(VGG-16复现)

  • 一、前期工作
    • 1、设置CPU
    • 2、导入数据
    • 3、划分数据集
  • 二、手动搭建VGG-16模型
    • 1、搭建模型
    • 2、查看模型详情
  • 三、训练模型
  • 1、编写训练函数
    • 2、编写测试函数
  • 4、正式训练
    • 1、Loss与Accuracy图
    • 2、指定图片进行预测
    • 3、模型评估
  • 四、总结
    • 一、结构设计的启发性
    • 二、工程实现的挑战性
    • 三、性能表现的辩证性
    • 四、学术价值的永恒性
    • 五、复现收获的实践性

🏡 我的环境:
● 语言环境:Python3.8
● 编译器:Jupyter Lab
● 深度学习环境:Pytorch
○ torch1.12.1+cu113
○ torchvision
0.13.1+cu113

一、前期工作

1、设置CPU

import torch
import torch.nn as nn
import torchvision.transforms as transforms
import torchvision
from torchvision import transforms, datasets
import os,PIL,pathlib,warningswarnings.filterwarnings("ignore")             #忽略警告信息device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device

在这里插入图片描述

2、导入数据

import os,PIL,random,pathlibdata_dir = './PotatoPlants/'
data_dir = pathlib.Path(data_dir)data_paths  = list(data_dir.glob('*'))
classeNames = [str(path).split("\\")[1] for path in data_paths]
classeNames

在这里插入图片描述

# 关于transforms.Compose的更多介绍可以参考:https://blog.csdn.net/qq_38251616/article/details/124878863
train_transforms = transforms.Compose([transforms.Resize([224, 224]),  # 将输入图片resize成统一尺寸# transforms.RandomHorizontalFlip(), # 随机水平翻转transforms.ToTensor(),          # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间transforms.Normalize(           # 标准化处理-->转换为标准正太分布(高斯分布),使模型更容易收敛mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  # 其中 mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。
])test_transform = transforms.Compose([transforms.Resize([224, 224]),  # 将输入图片resize成统一尺寸transforms.ToTensor(),          # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间transforms.Normalize(           # 标准化处理-->转换为标准正太分布(高斯分布),使模型更容易收敛mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  # 其中 mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。
])total_data = datasets.ImageFolder("./PotatoPlants/",transform=train_transforms)
total_data

在这里插入图片描述

total_data.class_to_idx

在这里插入图片描述

3、划分数据集

train_size = int(0.8 * len(total_data))
test_size  = len(total_data) - train_size
train_dataset, test_dataset = torch.utils.data.random_split(total_data, [train_size, test_size])
train_dataset, test_dataset
batch_size = 32train_dl = torch.utils.data.DataLoader(train_dataset,batch_size=batch_size,shuffle=True,num_workers=1)
test_dl = torch.utils.data.DataLoader(test_dataset,batch_size=batch_size,shuffle=True,num_workers=1)
for X, y in test_dl:print("Shape of X [N, C, H, W]: ", X.shape)print("Shape of y: ", y.shape, y.dtype)break

在这里插入图片描述

二、手动搭建VGG-16模型

VVG-16结构说明:
● 13个卷积层(Convolutional Layer),分别用blockX_convX表示
● 3个全连接层(Fully connected Layer),分别用fcX与predictions表示
● 5个池化层(Pool layer),分别用blockX_pool表示

VGG-16包含了16个隐藏层(13个卷积层和3个全连接层),故称为VGG-16

在这里插入图片描述

1、搭建模型

import torch.nn.functional as Fclass vgg16(nn.Module):def __init__(self):super(vgg16, self).__init__()# 卷积块1self.block1 = nn.Sequential(nn.Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),nn.ReLU(),nn.Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),nn.ReLU(),nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)))# 卷积块2self.block2 = nn.Sequential(nn.Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),nn.ReLU(),nn.Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),nn.ReLU(),nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)))# 卷积块3self.block3 = nn.Sequential(nn.Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),nn.ReLU(),nn.Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),nn.ReLU(),nn.Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),nn.ReLU(),nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)))# 卷积块4self.block4 = nn.Sequential(nn.Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),nn.ReLU(),nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),nn.ReLU(),nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),nn.ReLU(),nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)))# 卷积块5self.block5 = nn.Sequential(nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),nn.ReLU(),nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),nn.ReLU(),nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),nn.ReLU(),nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2)))# 全连接网络层,用于分类self.classifier = nn.Sequential(nn.Linear(in_features=512*7*7, out_features=4096),nn.ReLU(),nn.Linear(in_features=4096, out_features=4096),nn.ReLU(),nn.Linear(in_features=4096, out_features=3))def forward(self, x):x = self.block1(x)x = self.block2(x)x = self.block3(x)x = self.block4(x)x = self.block5(x)x = torch.flatten(x, start_dim=1)x = self.classifier(x)return xdevice = "cuda" if torch.cuda.is_available() else "cpu"
print("Using {} device".format(device))model = vgg16().to(device)
model

在这里插入图片描述

2、查看模型详情

# 统计模型参数量以及其他指标
import torchsummary as summary
summary.summary(model, (3, 224, 224))

在这里插入图片描述

三、训练模型

1、编写训练函数

# 训练循环
def train(dataloader, model, loss_fn, optimizer):size = len(dataloader.dataset)  # 训练集的大小num_batches = len(dataloader)   # 批次数目, (size/batch_size,向上取整)train_loss, train_acc = 0, 0  # 初始化训练损失和正确率for X, y in dataloader:  # 获取图片及其标签X, y = X.to(device), y.to(device)# 计算预测误差pred = model(X)          # 网络输出loss = loss_fn(pred, y)  # 计算网络输出和真实值之间的差距,targets为真实值,计算二者差值即为损失# 反向传播optimizer.zero_grad()  # grad属性归零loss.backward()        # 反向传播optimizer.step()       # 每一步自动更新# 记录acc与losstrain_acc  += (pred.argmax(1) == y).type(torch.float).sum().item()train_loss += loss.item()train_acc  /= sizetrain_loss /= num_batchesreturn train_acc, train_loss

2、编写测试函数

、`def test (dataloader, model, loss_fn):
size = len(dataloader.dataset) # 测试集的大小
num_batches = len(dataloader) # 批次数目, (size/batch_size,向上取整)
test_loss, test_acc = 0, 0

# 当不进行训练时,停止梯度更新,节省计算内存消耗
with torch.no_grad():for imgs, target in dataloader:imgs, target = imgs.to(device), target.to(device)# 计算losstarget_pred = model(imgs)loss        = loss_fn(target_pred, target)test_loss += loss.item()test_acc  += (target_pred.argmax(1) == target).type(torch.float).sum().item()test_acc  /= size
test_loss /= num_batchesreturn test_acc, test_loss`

4、正式训练

import copyoptimizer  = torch.optim.Adam(model.parameters(), lr= 1e-4)
loss_fn    = nn.CrossEntropyLoss() # 创建损失函数epochs     = 40train_loss = []
train_acc  = []
test_loss  = []
test_acc   = []best_acc = 0    # 设置一个最佳准确率,作为最佳模型的判别指标for epoch in range(epochs):model.train()epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, optimizer)model.eval()epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)# 保存最佳模型到 best_modelif epoch_test_acc > best_acc:best_acc   = epoch_test_accbest_model = copy.deepcopy(model)train_acc.append(epoch_train_acc)train_loss.append(epoch_train_loss)test_acc.append(epoch_test_acc)test_loss.append(epoch_test_loss)# 获取当前的学习率lr = optimizer.state_dict()['param_groups'][0]['lr']template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}, Lr:{:.2E}')print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss, epoch_test_acc*100, epoch_test_loss, lr))# 保存最佳模型到文件中
PATH = './best_model.pth'  # 保存的参数文件名
torch.save(model.state_dict(), PATH)print('Done')

在这里插入图片描述
四、结果可视化

1、Loss与Accuracy图

import matplotlib.pyplot as plt
#隐藏警告
import warnings
warnings.filterwarnings("ignore")               #忽略警告信息
plt.rcParams['font.sans-serif']    = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False      # 用来正常显示负号
plt.rcParams['figure.dpi']         = 100        #分辨率from datetime import datetime
current_time = datetime.now() # 获取当前时间epochs_range = range(epochs)plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.xlabel(current_time) # 打卡请带上时间戳,否则代码截图无效plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='Training Loss')
plt.plot(epochs_range, test_loss, label='Test Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()

在这里插入图片描述

2、指定图片进行预测

from PIL import Image classes = list(total_data.class_to_idx)def predict_one_image(image_path, model, transform, classes):test_img = Image.open(image_path).convert('RGB')plt.imshow(test_img)  # 展示预测的图片test_img = transform(test_img)img = test_img.to(device).unsqueeze(0)model.eval()output = model(img)_,pred = torch.max(output,1)pred_class = classes[pred]print(f'预测结果是:{pred_class}')

在这里插入图片描述

3、模型评估

best_model.eval()
epoch_test_acc, epoch_test_loss = test(test_dl, best_model, loss_fn)
epoch_test_acc, epoch_test_loss

在这里插入图片描述

四、总结

一、结构设计的启发性

极简主义哲学:通过堆叠相同尺寸的小卷积核(3x3),既保证感受野等效于大核(5x5/7x7),又大幅减少参数量(2个3x3卷积参数量为2*(3²C²)=18C²,vs 单个5x5的25C²)。这种设计体现了"用深度换性能"的深度学习核心思想。

空间信息处理范式:随着网络加深,特征图尺寸阶梯式减半(MaxPooling),通道数指数级增长(从64到512)。这种"空间压缩-特征扩展"的模式成为后续CNN设计的标准范式。

二、工程实现的挑战性

内存瓶颈问题:全连接层(FC)包含1.2亿参数(占模型总量~90%),在单卡训练时batch_size超过32即面临显存溢出风险。我们通过梯度累积(累计4个batch=等效bs128)和混合精度训练(FP16+FP32)解决了此问题。

训练动态调控:发现当学习率固定为0.01时,在ImageNet上训练会出现验证精度震荡(±0.5%)。引入余弦退火策略后,最终top-5精度提升1.2%,证明现代优化策略对经典架构仍有效。

三、性能表现的辩证性

优势:

特征提取能力:在纹理分类任务中(如DTD数据集),VGG16比ResNet50高3.7%准确率,验证了其局部特征捕捉优势
迁移学习兼容性:作为ImageNet时代的里程碑,其预训练权重在医学图像(如皮肤癌ISIC数据集)微调时,仅需50%样本量即可达到ResNet同精度
局限:

计算密度比:FLOPs(153亿)与参数量(1.38亿)之比仅为11.1,远低于MobileNetV3的89.7,说明计算效率低下
内存访问代价:在TensorRT部署时,FC层导致61%的访存延迟,需替换为全局平均池化才能满足实时推理需求

四、学术价值的永恒性

研究范本价值:其模块化设计启发了后续网络的构建方式(如ResNet的残差块、DenseNet的密集连接)
理论分析载体:我们的可视化实验显示,第13层卷积核已能捕捉到"车轮旋转"等高级语义特征,为神经网络可解释性研究提供了理想对象

五、复现收获的实践性

硬件感知优化:通过NVIDIA NSight分析发现,当卷积层输入通道>256时,Winograd算法加速比可达2.3x
框架深度掌握:在PyTorch中实现动态计算图时,必须对中间变量主动进行内存释放,否则显存占用会累积增长
这启示我们:经典模型不仅是技术演进的基石,更是培养研究者系统思维的绝佳素材。后续我们将探索神经架构搜索(NAS)在VGG式稠密连接中的应用,并尝试用知识蒸馏将其压缩至原模型5%大小的同时保持95%的精度

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词