使用Pytorch实现VGGNet(含VGGNet特征整理)
2023-12-14 05:36:09
知识点整理
VGGNet 的主要特点:
- 采用3x3的小卷积核
- 将模型提升到11-19层
- 进一步提升了模型的泛化能力
- 模型结构相对简洁
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+Dropout
nn.Linear(512 * 7 * 7, 4096),
nn.ReLU(inplace=True),
nn.Dropout(),
# 全连接层 + ReLU + Dropout
nn.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)), # 随机比例裁剪并进行resize
transforms.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)), #resize
transforms.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 = 0
total_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()
#记录训练集loss
total_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+Dropout
nn.Linear(512 * 7 * 7, 4096),
nn.ReLU(inplace=True),
nn.Dropout(),
# 全连接层 + ReLU + Dropout
nn.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)), # 随机比例裁剪并进行resize
transforms.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)), #resize
transforms.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 = 0
total_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()
#记录训练集loss
total_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])
文章来源:https://blog.csdn.net/qq_43858783/article/details/134912746
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!