使用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

相关文章

Spring Boot整合Redis注解实现增删改查功能(Redis注解使用)

《SpringBoot整合Redis注解实现增删改查功能(Redis注解使用)》文章介绍了如何使用SpringBoot整合Redis注解实现增删改查功能,包括配置、实体类、Repository、Se... 目录配置Redis连接定义实体类创建Repository接口增删改查操作示例插入数据查询数据删除数据更

Java Lettuce 客户端入门到生产的实现步骤

《JavaLettuce客户端入门到生产的实现步骤》本文主要介绍了JavaLettuce客户端入门到生产的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 目录1 安装依赖MavenGradle2 最小化连接示例3 核心特性速览4 生产环境配置建议5 常见问题

使用python生成固定格式序号的方法详解

《使用python生成固定格式序号的方法详解》这篇文章主要为大家详细介绍了如何使用python生成固定格式序号,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考一下... 目录生成结果验证完整生成代码扩展说明1. 保存到文本文件2. 转换为jsON格式3. 处理特殊序号格式(如带圈数字)4

Java使用Swing生成一个最大公约数计算器

《Java使用Swing生成一个最大公约数计算器》这篇文章主要为大家详细介绍了Java使用Swing生成一个最大公约数计算器的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下... 目录第一步:利用欧几里得算法计算最大公约数欧几里得算法的证明情形 1:b=0情形 2:b>0完成相关代码第二步:加

linux ssh如何实现增加访问端口

《linuxssh如何实现增加访问端口》Linux中SSH默认使用22端口,为了增强安全性或满足特定需求,可以通过修改SSH配置来增加或更改SSH访问端口,具体步骤包括修改SSH配置文件、增加或修改... 目录1. 修改 SSH 配置文件2. 增加或修改端口3. 保存并退出编辑器4. 更新防火墙规则使用uf

Java 的ArrayList集合底层实现与最佳实践

《Java的ArrayList集合底层实现与最佳实践》本文主要介绍了Java的ArrayList集合类的核心概念、底层实现、关键成员变量、初始化机制、容量演变、扩容机制、性能分析、核心方法源码解析、... 目录1. 核心概念与底层实现1.1 ArrayList 的本质1.1.1 底层数据结构JDK 1.7

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.