使用Pytorch实现VGGNet(含VGGNet特征整理)

2023-12-11 05:15

本文主要是介绍使用Pytorch实现VGGNet(含VGGNet特征整理),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

知识点整理

VGGNet 的主要特点:

  1. 采用3x3的小卷积核
  2. 将模型提升到11-19层
  3. 进一步提升了模型的泛化能力
  4. 模型结构相对简洁

VGGNet主要解决了以下几个问题:

首先在当时的卷积神经网络中网络结构越深网络表现的性能越好,但同时也会带来较大的复杂度和较大的模型参数问题,VGGNet通过设计一种网络模型更深,但是参数较少的卷积网络很好地解决上述问题。
然后,在卷积神经网络训练中容易出现过拟合问题,导致模型泛化能力较差,VGGNet通过设计合理的网络结构和小卷积核的方式提升了模型的泛化能力。
最后,在计算机视觉任务中网络的计算效率是一个非常重要的问题,VGGNet通过使用小卷积核和简单的网络结构在保证较高的计算精度的同时提高了模型的计算效率。

VGG的结构如下图所示:
标出的为常用网络
它是一种典型的卷积神经网络模型,它由若干个卷积层和全连接层组成,在表中每一列是一种网络,共有A、A-LRN、B、C、D、E六种网络架构。由上到下就是每种网络结构对应的组成。
VGGNet的六种形式代表了6种不同深度的网络,最左边的A有11个weight layers,到最后的E为19 weight layers,但不管是哪一种它们在整体上主要分为三大种组成部分:

  • 卷积层(卷积核大小均为3x3)
  • 池化层(VGGNet在卷积层之后采用最大池化层来缩小图像的尺寸,通过池化层的下采样来减少计算复杂度)、全连接层(VGGNet)的输出层包括2个4096维
  • 全连接层和输出层,上述的FC为1000维,该维度决定于输出的维度大小)
    需要注意的是C网络和D网络都是16层,主要区别是C网络在B网络的基础上增加了3个1x1的卷积核,这样增加了网络非线性的提升效果;而D网络则是采用3x3的卷积核实现了更好的效果,所以一般意义上的VGG16都是指D模型

可以看到VGG一般都是采用较小的3x3的卷积核,那为什么会采用这种小卷积核呢?
主要原因在于两个3x3的卷积层串联,相当与一个7x7的卷积层,但是3x3的卷积层的参数量只有7x7的一半左右,同时前者可以有2种非线性操作,而后者只有一种非线性操作。因此采用3x3的小卷积核对于特征的学习能力更强,同时参数反而更少

模型复现

导包

import torch
import torch.nn as nn
from torchinfo import summary

网络模型定义

class VGG(nn.Module):def __init__(self, features, num_classes=1000):super(VGG, self).__init__()# 卷积层直接使用传入的结构,后面有专门构建这部分的内容self.features = features# 定义全连接层self.classifier = nn.Sequential(# 全连接层+ReLU+Dropoutnn.Linear(512 * 7 * 7, 4096),nn.ReLU(inplace=True),nn.Dropout(),# 全连接层 + ReLU + Dropoutnn.Linear(4096, 4096),nn.ReLU(inplace=True),  # inplace = True ,会改变输入数据的值,节省反复申请与释放内存的空间与时间,只是将原来的地址传递,效率更好nn.Dropout(),# 全连接层nn.Linear(4096, num_classes),)# 定义前向传播函数def forward(self, x):# 先经过feature提取特征,flatten后送入全连接层x = self.features(x)x = torch.flatten(x, 1)x = self.classifier(x)return x

定义配置项

# 定义相关配置项,其中M表示池化层,数值完全和论文中保持一致
cfgs = {'vgg11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],'vgg13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],'vgg16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],'vgg19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M']
}

拼接卷积层

# 根据传入的配置项拼接卷积层
def make_layers(cfg):layers = []in_channels = 3  # 初始通道数为3# 遍历传入的配置项for v in cfg:if v == 'M':  # 如果是池化层,则直接新增MaxPool2d即可layers += [nn.MaxPool2d(kernel_size=2, stride=2)]else:  # 如果是卷积层,则新增3x3卷积 + ReLU非线性激活conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)layers += [conv2d, nn.ReLU(inplace=True)]in_channels = v  # 记录通道数,作为下一次的in_channels *******# 返回使用Sequential构造的卷积层return nn.Sequential(*layers)

封装函数

# 封装函数,依次传入对应的配置项
def vgg11(num_classes=1000):return VGG(make_layers(cfgs['vgg11']), num_classes=num_classes)def vgg13(num_classes=1000):return VGG(make_layers(cfgs['vgg13']), num_classes=num_classes)def vgg16(num_classes=1000):return VGG(make_layers(cfgs['vgg16']), num_classes=num_classes)def vgg19(num_classes=1000):return VGG(make_layers(cfgs['vgg19']), num_classes=num_classes)

查看网络结构

# 网络结构
# 查看模型结构即参数数量,input_size  表示示例输入数据的维度信息
summary(vgg16(), input_size=(1, 3, 224, 224))

使用torchvision定义模型

# 简单实现 ---> 利用torch.vision
# 查看torchvision自带的模型结构即参数量
from torchvision import models
summary(models.vgg16(),input_size=(1,3,224,224))

模型训练与评估

导入必要的库

import torch.optim as optim
from torch.utils.data  import DataLoader
from torchvision import datasets,transforms,models
from tqdm import *
import numpy as np
import sys

设备检测

# 设备检测,若未检测到cuda设备则在CPU上运行
device = torch.device("cuda:2" if torch.cuda.is_available() else "cpu")
print("device:",device)

设置随机数种子

# 设置随机数种子
torch.manual_seed(0)

定义模型、优化器、损失函数

# 定义模型、优化器、损失函数
model = vgg11(num_classes=102).to(device)
optimizer = optim.SGD(model.parameters(),lr=0.002,momentum=0.9)
criterion = nn.CrossEntropyLoss()

设置训练集的数据转换

trainform_train = transforms.Compose([transforms.RandomRotation(30),# 随机旋转-30到30度之间transforms.RandomResizedCrop((224,224)), # 随机比例裁剪并进行resizetransforms.RandomHorizontalFlip(p=0.5), #随机垂直翻转transforms.ToTensor(), # 将数据转换成张量# 对三通道数据进行归一化(均值,标准差),数值是冲ImageNet数据集上的百万张图片中随机抽样计算得到transforms.Normalize(mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225])])

设置测试集的数据变换

#设置测试集的数据变换,不进行数据增强,仅仅使用resize和归一化操作
transform_test = transforms.Compose([transforms.Resize((224,224)), #resizetransforms.ToTensor(),#将数据转换成张量形式# 对三通道数据进行归一化(均值,标准差),数值是冲ImageNet数据集上的百万张图片中随机抽样计算得到transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

加载训练数据

# 加载训练数据,需要特别注意的是Flowers102的数据集,test簇的数据集较多一些,所以这里使用test作为训练集
train_dataset = datasets.Flowers102(root='./data/flowers102',split="test",download=True,transform=trainform_train)

实例化训练数据加载器

train_loader = DataLoader(train_dataset,batch_size=64,shuffle=True,num_workers=4)

加载测试数据

test_dataset = datasets.Flowers102(root='./data/flowers102',split="train",download=True,transform=transform_test)

实例化测试数据加载器

test_loader = DataLoader(test_dataset,batch_size=64,shuffle=False,num_workers=4)

设置epochs并进行训练

# 设置epochs并进行训练
num_epochs = 200 # 设置epoch数
loss_history = [] # 创建损失历史记录表
acc_history = [] # 创建准确率历史记录表
# tqdm用于显示进度条并评估任务时间开销
for epoch in tqdm(range(num_epochs),file=sys.stdout):# 记录损失和预测正确数total_loss = 0total_correct = 0# 批量训练model.train()for inputs,labels in train_loader:# 将数据转移到指定计算资源设备上inputs = inputs.to(device)labels = labels.to(device)# 预测、损失函数、反向传播optimizer.zero_grad()outputs = model(inputs)loss = criterion(outputs,labels)loss.backward()optimizer.step()#记录训练集losstotal_loss += loss.item()# 测试模型,不计算梯度model.eval()with torch.no_grad():for inputs,labels in test_loader:inputs = inputs.to(device)labels = labels.to(device)# 预测outputs = model(inputs)# 记录测试集预测正确数total_correct  += (outputs.argmax(1) == labels).sum().item()# 记录训练集损失和测试集准确率loss_history.append(np.log10(total_loss)) # 将损失加入到损失历史记录列表中,由于数值有时比较大所以这里取了对数acc_history.append(total_correct/len(test_dataset)) # 将准确率加入到准确率历史记录列表# 打印中间值if epoch % 10 == 0:tqdm.write("Epoch:{0} Loss:{1} ACC:{2}".format(epoch,loss_history[-1],acc_history[-1]))

使用Matplotlib绘制损失和准确率的曲线图

import matplotlib.pyplot as plt
plt.plot(loss_history,label='loss')
plt.plot(acc_history,label='accuracy')
plt.legend()
plt.show()

输出准确率

print("Accuracy:",acc_history[-1])

结果

结果

省流版-全部代码

"""
VGGNet 的主要特点:
1、采用3x3的小卷积核
2、将模型提升到11-19层
3、进一步提升了模型的泛化能力
4、模型结构相对简洁VGGNet主要解决了以下几个问题,首先在当时的卷积神经网络中网络结构越深网络表现的性能越好,但同时也会带来较大的复杂度和较大的模型参数问题,VGGNet通过
设计一种网络模型更深,但是参数较少的卷积网络很好地解决上述问题。然后,在卷积神经网络训练中容易出现过拟合问题,导致模型泛化能力较差,VGGNet通过设计合理
的网络结构和小卷积核的方式提升了模型的泛化能力。最后,在计算机视觉任务中网络的计算效率是一个非常重要的问题,VGGNet通过使用小卷积核和简单的网络结构在保证
较高的计算精度的同时提高了模型的计算效率。VGG的结构如下图所示:它是一种典型的卷积神经网络模型,它由若干个卷积层和全连接层组成,在表中每一列是一种网络,共有A、A-LRN、B、C、D、E六种网络架构。由上到下就是每种网络结构对应的组成。
VGGNet的六种形式代表了6种不同深度的网络,最左边的A有11个weight layers,到最后的E为19 weight layers,但不管是哪一种它们在整体上主要分为三大种组成部分:
卷积层(卷积核大小均为3x3)、池化层(VGGNet在卷积层之后采用最大池化层来缩小图像的尺寸,通过池化层的下采样来减少计算复杂度)、全连接层(VGGNet)的输出层包括2个4096
维全连接层和输出层,上述的FC为1000维,该维度决定于输出的维度大小)需要注意的是C网络和D网络都是16层,主要区别是C网络在B网络的基础上增加了3个1x1的卷积核,这样增加了网络非线性的提升效果;而D网络则是采用3x3的卷积核实现了更好的效果,所以
一般意义上的VGG16都是指D模型。可以看到VGG一般都是采用较小的3x3的卷积核,那为什么会采用这种小卷积核呢?
主要原因在于两个3x3的卷积层串联,相当与一个7x7的卷积层,但是3x3的卷积层的参数量只有7x7的一半左右,同时前者可以有2种非线性操作,而后者只有一种非线性操作。因此采用3x3的小卷积核
对于特征的学习能力更强,同时参数反而更少
"""
# 导包
import torch
import torch.nn as nn
from torchinfo import summary# 网络结构定义
class VGG(nn.Module):def __init__(self, features, num_classes=1000):super(VGG, self).__init__()# 卷积层直接使用传入的结构,后面有专门构建这部分的内容self.features = features# 定义全连接层self.classifier = nn.Sequential(# 全连接层+ReLU+Dropoutnn.Linear(512 * 7 * 7, 4096),nn.ReLU(inplace=True),nn.Dropout(),# 全连接层 + ReLU + Dropoutnn.Linear(4096, 4096),nn.ReLU(inplace=True),  # inplace = True ,会改变输入数据的值,节省反复申请与释放内存的空间与时间,只是将原来的地址传递,效率更好nn.Dropout(),# 全连接层nn.Linear(4096, num_classes),)# 定义前向传播函数def forward(self, x):# 先经过feature提取特征,flatten后送入全连接层x = self.features(x)x = torch.flatten(x, 1)x = self.classifier(x)return x# 定义配置项# 定义相关配置项,其中M表示池化层,数值完全和论文中保持一致
cfgs = {'vgg11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],'vgg13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],'vgg16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],'vgg19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M']
}# 拼接卷积层
# 根据传入的配置项拼接卷积层
def make_layers(cfg):layers = []in_channels = 3  # 初始通道数为3# 遍历传入的配置项for v in cfg:if v == 'M':  # 如果是池化层,则直接新增MaxPool2d即可layers += [nn.MaxPool2d(kernel_size=2, stride=2)]else:  # 如果是卷积层,则新增3x3卷积 + ReLU非线性激活conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)layers += [conv2d, nn.ReLU(inplace=True)]in_channels = v  # 记录通道数,作为下一次的in_channels *******# 返回使用Sequential构造的卷积层return nn.Sequential(*layers)# 封装函数
# 封装函数,依次传入对应的配置项
def vgg11(num_classes=1000):return VGG(make_layers(cfgs['vgg11']), num_classes=num_classes)def vgg13(num_classes=1000):return VGG(make_layers(cfgs['vgg13']), num_classes=num_classes)def vgg16(num_classes=1000):return VGG(make_layers(cfgs['vgg16']), num_classes=num_classes)def vgg19(num_classes=1000):return VGG(make_layers(cfgs['vgg19']), num_classes=num_classes)# 网络结构
# 查看模型结构即参数数量,input_size  表示示例输入数据的维度信息
summary(vgg16(), input_size=(1, 3, 224, 224))# 简单实现 ---> 利用torch.vision
# 查看torchvision自带的模型结构即参数量
from torchvision import models
summary(models.vgg16(),input_size=(1,3,224,224))# 模型训练
# 导入必要的库
import torch.optim as optim
from torch.utils.data  import DataLoader
from torchvision import datasets,transforms,models
from tqdm import *
import numpy as np
import sys# 设备检测,若未检测到cuda设备则在CPU上运行
device = torch.device("cuda:2" if torch.cuda.is_available() else "cpu")
print("device:",device)
# 设置随机数种子
torch.manual_seed(0)# 定义模型、优化器、损失函数
model = vgg11(num_classes=102).to(device)
optimizer = optim.SGD(model.parameters(),lr=0.002,momentum=0.9)
criterion = nn.CrossEntropyLoss()# 设置训练集的数据变换,进行数据增强
trainform_train = transforms.Compose([transforms.RandomRotation(30),# 随机旋转-30到30度之间transforms.RandomResizedCrop((224,224)), # 随机比例裁剪并进行resizetransforms.RandomHorizontalFlip(p=0.5), #随机垂直翻转transforms.ToTensor(), # 将数据转换成张量# 对三通道数据进行归一化(均值,标准差),数值是冲ImageNet数据集上的百万张图片中随机抽样计算得到transforms.Normalize(mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225])])#设置测试集的数据变换,不进行数据增强,仅仅使用resize和归一化操作
transform_test = transforms.Compose([transforms.Resize((224,224)), #resizetransforms.ToTensor(),#将数据转换成张量形式# 对三通道数据进行归一化(均值,标准差),数值是冲ImageNet数据集上的百万张图片中随机抽样计算得到transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])# 加载训练数据,需要特别注意的是Flowers102的数据集,test簇的数据集较多一些,所以这里使用test作为训练集
train_dataset = datasets.Flowers102(root='./data/flowers102',split="test",download=True,transform=trainform_train)
# 实例化训练数据加载器
train_loader = DataLoader(train_dataset,batch_size=64,shuffle=True,num_workers=4)
# 加载测试数据,使用“train”作为测试集
test_dataset = datasets.Flowers102(root='./data/flowers102',split="train",download=True,transform=transform_test)
# 实例化测试数据加载器
test_loader = DataLoader(test_dataset,batch_size=64,shuffle=False,num_workers=4)# 设置epochs并进行训练
num_epochs = 200 # 设置epoch数
loss_history = [] # 创建损失历史记录表
acc_history = [] # 创建准确率历史记录表# tqdm用于显示进度条并评估任务时间开销
for epoch in tqdm(range(num_epochs),file=sys.stdout):# 记录损失和预测正确数total_loss = 0total_correct = 0# 批量训练model.train()for inputs,labels in train_loader:# 将数据转移到指定计算资源设备上inputs = inputs.to(device)labels = labels.to(device)# 预测、损失函数、反向传播optimizer.zero_grad()outputs = model(inputs)loss = criterion(outputs,labels)loss.backward()optimizer.step()#记录训练集losstotal_loss += loss.item()# 测试模型,不计算梯度model.eval()with torch.no_grad():for inputs,labels in test_loader:inputs = inputs.to(device)labels = labels.to(device)# 预测outputs = model(inputs)# 记录测试集预测正确数total_correct  += (outputs.argmax(1) == labels).sum().item()# 记录训练集损失和测试集准确率loss_history.append(np.log10(total_loss)) # 将损失加入到损失历史记录列表中,由于数值有时比较大所以这里取了对数acc_history.append(total_correct/len(test_dataset)) # 将准确率加入到准确率历史记录列表# 打印中间值if epoch % 10 == 0:tqdm.write("Epoch:{0} Loss:{1} ACC:{2}".format(epoch,loss_history[-1],acc_history[-1]))
# 使用Matplotlib绘制损失和准确率的曲线图
import matplotlib.pyplot as plt
plt.plot(loss_history,label='loss')
plt.plot(acc_history,label='accuracy')
plt.legend()
plt.show()# 输出准确率
print("Accuracy:",acc_history[-1])

这篇关于使用Pytorch实现VGGNet(含VGGNet特征整理)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/479551

相关文章

Java中使用Java Mail实现邮件服务功能示例

《Java中使用JavaMail实现邮件服务功能示例》:本文主要介绍Java中使用JavaMail实现邮件服务功能的相关资料,文章还提供了一个发送邮件的示例代码,包括创建参数类、邮件类和执行结... 目录前言一、历史背景二编程、pom依赖三、API说明(一)Session (会话)(二)Message编程客

Java中List转Map的几种具体实现方式和特点

《Java中List转Map的几种具体实现方式和特点》:本文主要介绍几种常用的List转Map的方式,包括使用for循环遍历、Java8StreamAPI、ApacheCommonsCollect... 目录前言1、使用for循环遍历:2、Java8 Stream API:3、Apache Commons

C++中使用vector存储并遍历数据的基本步骤

《C++中使用vector存储并遍历数据的基本步骤》C++标准模板库(STL)提供了多种容器类型,包括顺序容器、关联容器、无序关联容器和容器适配器,每种容器都有其特定的用途和特性,:本文主要介绍C... 目录(1)容器及简要描述‌php顺序容器‌‌关联容器‌‌无序关联容器‌(基于哈希表):‌容器适配器‌:(

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一

使用Python实现高效的端口扫描器

《使用Python实现高效的端口扫描器》在网络安全领域,端口扫描是一项基本而重要的技能,通过端口扫描,可以发现目标主机上开放的服务和端口,这对于安全评估、渗透测试等有着不可忽视的作用,本文将介绍如何使... 目录1. 端口扫描的基本原理2. 使用python实现端口扫描2.1 安装必要的库2.2 编写端口扫

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

MySQL分表自动化创建的实现方案

《MySQL分表自动化创建的实现方案》在数据库应用场景中,随着数据量的不断增长,单表存储数据可能会面临性能瓶颈,例如查询、插入、更新等操作的效率会逐渐降低,分表是一种有效的优化策略,它将数据分散存储在... 目录一、项目目的二、实现过程(一)mysql 事件调度器结合存储过程方式1. 开启事件调度器2. 创

使用Python实现操作mongodb详解

《使用Python实现操作mongodb详解》这篇文章主要为大家详细介绍了使用Python实现操作mongodb的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、示例二、常用指令三、遇到的问题一、示例from pymongo import MongoClientf

SQL Server使用SELECT INTO实现表备份的代码示例

《SQLServer使用SELECTINTO实现表备份的代码示例》在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误,在SQLServer中,可以使用SELECTINT... 在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误。在 SQL Server 中,可以使用 SE

使用Python合并 Excel单元格指定行列或单元格范围

《使用Python合并Excel单元格指定行列或单元格范围》合并Excel单元格是Excel数据处理和表格设计中的一项常用操作,本文将介绍如何通过Python合并Excel中的指定行列或单... 目录python Excel库安装Python合并Excel 中的指定行Python合并Excel 中的指定列P