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

相关文章

Tomcat的下载安装与使用教程

《Tomcat的下载安装与使用教程》本文介绍了Tomcat的下载、安装和使用方法,包括在本机和云服务器上部署Tomcat的过程,以及解决启动失败问题的方法... 目录Tomcat的下载安装与使用Tomcat的下载与安装Tomcat在本机运行使用Tomcat在php云服务器上的使用总结Tomcat的下载安装与

SpringBoot基于沙箱环境实现支付宝支付教程

《SpringBoot基于沙箱环境实现支付宝支付教程》本文介绍了如何使用支付宝沙箱环境进行开发测试,包括沙箱环境的介绍、准备步骤、在SpringBoot项目中结合支付宝沙箱进行支付接口的实现与测试... 目录一、支付宝沙箱环境介绍二、沙箱环境准备2.1 注册入驻支付宝开放平台2.2 配置沙箱环境2.3 沙箱

Mysql中InnoDB与MyISAM索引差异详解(最新整理)

《Mysql中InnoDB与MyISAM索引差异详解(最新整理)》InnoDB和MyISAM在索引实现和特性上有差异,包括聚集索引、非聚集索引、事务支持、并发控制、覆盖索引、主键约束、外键支持和物理存... 目录1. 索引类型与数据存储方式InnoDBMyISAM2. 事务与并发控制InnoDBMyISAM

StarRocks索引详解(最新整理)

《StarRocks索引详解(最新整理)》StarRocks支持多种索引类型,包括主键索引、前缀索引、Bitmap索引和Bloomfilter索引,这些索引类型适用于不同场景,如唯一性约束、减少索引空... 目录1. 主键索引(Primary Key Index)2. 前缀索引(Prefix Index /

Nginx实现高并发的项目实践

《Nginx实现高并发的项目实践》本文主要介绍了Nginx实现高并发的项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录使用最新稳定版本的Nginx合理配置工作进程(workers)配置工作进程连接数(worker_co

python中列表list切分的实现

《python中列表list切分的实现》列表是Python中最常用的数据结构之一,经常需要对列表进行切分操作,本文主要介绍了python中列表list切分的实现,文中通过示例代码介绍的非常详细,对大家... 目录一、列表切片的基本用法1.1 基本切片操作1.2 切片的负索引1.3 切片的省略二、列表切分的高

基于Python实现一个PDF特殊字体提取工具

《基于Python实现一个PDF特殊字体提取工具》在PDF文档处理场景中,我们常常需要针对特定格式的文本内容进行提取分析,本文介绍的PDF特殊字体提取器是一款基于Python开发的桌面应用程序感兴趣的... 目录一、应用背景与功能概述二、技术架构与核心组件2.1 技术选型2.2 系统架构三、核心功能实现解析

Python使用PIL库将PNG图片转换为ICO图标的示例代码

《Python使用PIL库将PNG图片转换为ICO图标的示例代码》在软件开发和网站设计中,ICO图标是一种常用的图像格式,特别适用于应用程序图标、网页收藏夹图标等场景,本文将介绍如何使用Python的... 目录引言准备工作代码解析实践操作结果展示结语引言在软件开发和网站设计中,ICO图标是一种常用的图像

使用Java发送邮件到QQ邮箱的完整指南

《使用Java发送邮件到QQ邮箱的完整指南》在现代软件开发中,邮件发送功能是一个常见的需求,无论是用户注册验证、密码重置,还是系统通知,邮件都是一种重要的通信方式,本文将详细介绍如何使用Java编写程... 目录引言1. 准备工作1.1 获取QQ邮箱的SMTP授权码1.2 添加JavaMail依赖2. 实现

MyBatis与其使用方法示例详解

《MyBatis与其使用方法示例详解》MyBatis是一个支持自定义SQL的持久层框架,通过XML文件实现SQL配置和数据映射,简化了JDBC代码的编写,本文给大家介绍MyBatis与其使用方法讲解,... 目录ORM缺优分析MyBATisMyBatis的工作流程MyBatis的基本使用环境准备MyBati