深度学习-一篇入门

【模型】一篇入门之-VGG卷积神经网络

作者 : 老饼 发表日期 : 2023-07-28 10:51:30 更新日期 : 2024-11-01 18:49:55
本站原创文章,转载请说明来自《老饼讲解-深度学习》www.bbbdata.com



VGGNet是2014年ILSVRC定位任务第一名、分类任务第二名的CNN模型,它开启了3×3小卷积的应用之路

本文讲解VGGNet的模型结构,以及对其中的VGG16、VGG19模型配置进行详细讲解,并展示VGG模型的具体代码实现

通过本文,可以快速了解VGG卷积神经网络是什么,以及如何使用VGG神经网络来对图像进行类别识别





    01.VGG卷积神经网络是什么     




本节介绍VGG卷积神经网络的模型特色以及模型结构,快速了解VGG是什么





      VGGNet卷积神经网络简介       


VGGNet是Visual Geometry Group和Google DeepMind公司在2014年共同研发的卷积神经网络
  VGGNet在ImageNet大型视觉识别挑战ILSVRC2014中定位任务获得第一名和分类任务第二名
VGGNet原文为《Very Deep Convolutional Networks for Large-Scale Image Recognition
 VGGNet是什么
  VGGNet是基于AlexNet的来试验3x3卷积在CNN中的效果而诞生的一系列CNN模型
在VGGNet的整个实验过程,一共做了六组模型(A、A-LRN、B、C、D、E模型)
最终的D模型(16层)和E模型(19层)效果最好(两个模型后来又被称为VGG16和VGG19)
  VGGNet的核心思路-3x3卷积
在VGGNet之前比较喜欢用7×7之类的卷积,但VGGNet提出了用多个小卷积来替代大卷积效果会更好
在VGGNet之后,基本就很少见5×5、7×7这些卷积层了,取而代之的是连续使用多个3×3卷积层 
     VGGNet模型的六组模型分别如下:   
VGG主要用于图片类别识别
 VGG的输入是:3×224×224的图片                         
 VGG的输出是:1000×1的向量P,Pi代表样本属于第i类的概率 
   VGG共包括6个模型,各个模型的配置如下:
 VGG各个模型的配置
                 conv3-128:指的是128个3×3×k的填充1、步幅1的卷积核(k与输入通道数保持一致)
maxpool  :指的是使用窗口Size为2×2、步幅为2的Max池化方法       

整个实验是对AlexNet的改进,所以整体结构和输入、输出都与AlexNet保持一致
6组模型都保持AlexNet的5大池化层,3个全连接层的结构,只是将卷积层中的大核换成几个小的卷积核层
VGGNet六组模型的设计思路如下:

 1. A模型:A模型是整个实验中的Base模型                                                                                     
  它只是将AlexNet中的卷积层用几层小核卷积层进行替换(总层数为11)             
                  1、2卷积层换成一个3×3的小核卷积层,3、4、5层换成两个3×3的小核卷积层          
 
2. A-LRN模型:在A的C2层加入LRN(局部归一化)得到模型B,发现没什么用(总层数为11)                
 3. B模型:抛弃LRN,直接在A上加深C1,C2层,(总层数为13)                                                  
 4. C模型:在C模型基础上,在C3、C4、C5上各增加一个核Size为1的卷积层(总层数为16)               
 5. D模型:在C模型基础上,在C3、C4、C5上各增加一个核Size为3的卷积层(总层数为16)               
 6. E模型:在C模型基础上,在C3、C4、C5上各增加两个核Size为3的卷积层(总层数为19)               
最后发现,D(VGG-16)和E(VGG-19)的效果不错,但如果再加深效果就不好了
  总的来说,VGGNet就是由VGG团队在AlexNet的基础上进行的一系列对CNN加深实验中得到的一系列模型
整个实验过程共有6个模型,其中D模型VGG-16(16层)和E模型VGG-19(19层)是6个模型中最有价值的两个模型







     02. VGG-16与VGG-19模型结构详述    




本节详细讲解VGG-16模型结构和VGG-16的具体运算流程





     VGG-16/19模型结构描述    


VGG-16模型结构详述
 VGG的卷积、池化配置是统一的,如下
 卷积配置:3×3的卷积核,填充1,步幅1                                  
 池化配置:2×2的池化窗口,步幅2,池化方式:max                  
VGG-16各层明细如下:
输入:224×244×3的图像                                                                                               
说明:以下“卷积(a,b)”中的a,b代表卷积的输入和输出通道                          

C1:卷积(3,64)->ReLu->卷积(64,64)->ReLu->池化                                                       
     输出Size:112×112×64                                                       
C2:卷积(64,128)->ReLu->卷积(128,128)->ReLu->池化                                               
     输出Size:56×56×128                                                        
C3:卷积(128,256)->ReLu->卷积(256,256)->ReLu->卷积(256,256)->ReLu->池化        
     输出Size:28×28×256                                                        
C4:卷积(256,512)->ReLu->卷积(512,512)->ReLu->卷积(512,512)->ReLu->池化        
     输出Size:14×14×512                                                        
C5:卷积(512,512)->ReLu->卷积(512,512)->ReLu->卷积(512,512)->ReLu->池化        
     输出Size:7×7×512                                                          
F6:全连接层,神经元个数4096,激活函数ReLu(使用p=0.5的Dropout)                        
F7:全连接层,神经元个数4096,激活函数ReLu(使用p=0.5的Dropout)                        
F8:全连接层,神经元个数1000(即类别的个数)                                                              
VGG-16的输出:将F8的输出进行soft-max就是最终的输出                                              
为方便直观了解VGG-16的模型的整体结构以及运算流程,
笔者绘制了VGG-16的模型结构图如下

 VGG-16的模型结构图 
        VGG-19模型结构详述       
VGG-19的结构只是在VGG-16的C3、C4、C5层各加一个卷积层,其它一切与VGG-16一致
 VGG-19的C3、C4、C5配置具体如下:
 C3:卷积(128,256)->ReLu->卷积(256,256)->ReLu->卷积(256,256)->ReLu->卷积(256,256)->ReLu->池化          
     输出Size:28×28×256                                                                                 
 C4:卷积(256,512)->ReLu->卷积(512,512)->ReLu->卷积(512,512)->ReLu->卷积(512,512)->ReLu->池化          
     输出Size:14×14×512                                                                                 
 C5:卷积(512,512)->ReLu->卷积(512,512)->ReLu->卷积(512,512)->ReLu->卷积(512,512)->ReLu->池化          
     输出Size:7×7×512                                                                                   
 
简单来说,VGG-19就是在VGG-16的C3,C4,C5各加一层,共加三层,就变成了VGG-19
为避免内容过于重复,这里不再展示VGG-19的完整结构图,参考VGG-16即可






     什么是VGG模型转换    


VGG论文中提供了模型转换方法,以兼容不同的输入
VGG的原始模型,只支持输入Size为224×224的(或者其它固定尺寸)的图片
 为了让VGG模型在应用时去掉这一限制,原文中对训练好的VGG模型进行转换,使它适用于任何尺寸的输入
 VGG模型转换
VGG原模型不支持其它Size的主要原因在于,C5的输出Size会随着输入层Size的改变而改变
当C5的输出Size改变时,就会与第6大层(全连接层)的Size不匹配,导致无法继续前馈运算

VGG将模型转换为适用于任何输入Size的方法主要是将全连接层转为卷积层
利用卷积运算可以兼容不同Size的特性,从而保障VGG模型每层都能够前馈而不出错
具体步骤如下:

1. 将第一个全连接层F6转为7×7的卷积层                        
2. 将最后两个全连接层F7、F8转为1×1的卷积层              
 最终F6、F7、F8转换为C6、C7、C8,其结构如下:
C6:卷积->ReLu                                                         
  卷积核:4096个512×7×7                      
C7:卷积(4096个4096*1*1)->ReLu                             
 卷积核:4096个4096×1×1                    
C8:卷积                                                                     
  卷积核:1000(类别个数)个4096×1×1         
 
最后将C8的输出池化为1000×1的size   再进行soft-max就得到最终属于每类别的概率







     03. VGG卷积神经网络-代码实现      




本节展示如何使用代码实现VGG卷积神经网络





      pytorch中的VGG模型      


进一步地,可以将pytorch中的VGG模型结构打印出来
 具体如下:
  import torchvision                                                                                          
  model = torchvision.models.vgg16()                                # 初始化VGG模型  
  print('\n VGG的模块:\n',dict(model.named_children()) )    # 打印VGG的模块  

运行结果如下:
 
pytorch中的VGG模型结构 
可看到pytorch中VGG16的模型结构基本与原文基本是一致的






     VGGNet代码实现      


下面展示如何训练一个VGG16卷积神经网络用于图片类别识别
 具体代码如下:
import torchvision
from   torch import nn
import torch
from   torch.utils.data   import DataLoader
import numpy as np
model = torchvision.models.vgg16()            # 初始化VGG模型,并使用VGG16默认的预训练参数
model.features[0] =  nn.Conv2d(1,64, kernel_size=3,stride=1,padding=1)  # 修改模型的输入层
model.classifier[6] = nn.Linear(4096,10)                           # 修改模型的输出层
# print('\n VGG的模块:\n',dict(model.named_children()) ) 
# 训练函数                                                                                     
def train(dataloader,valLoader,model,epochs,goal,device):                                      
    for epoch in range(epochs):                                                                
        err_num  = 0                                                                           # 本次epoch评估错误的样本
        eval_num = 0                                                                           # 本次epoch已评估的样本
        print('-----------当前epoch:',str(epoch),'----------------')                           
        for batch, (imgs, labels) in enumerate(dataloader):                                    
		    # -----训练模型-----                                                               
            x, y = imgs.to(device), labels.to(device)                                          # 将数据发送到设备
            optimizer.zero_grad()                                                              # 将优化器里的参数梯度清空
            py   = model(x)                                                                    # 计算模型的预测值   
            loss = lossFun(py, y)                                                              # 计算损失函数值
            loss.backward()                                                                    # 更新参数的梯度
            optimizer.step()                                                                   # 更新参数
			# ----计算错误率----                                                               
            idx      = torch.argmax(py,axis=1)                                                 # 模型的预测类别
            eval_num = eval_num + len(idx)                                                     # 更新本次epoch已评估的样本
            err_num  = err_num +sum(y != idx)                                                  # 更新本次epoch评估错误的样本
            if(batch%10==0):                                                                   # 每10批打印一次结果
                print('err_rate:',err_num/eval_num)                                            # 打印错误率
        # -----------验证数据误差---------------------------                                   
        model.eval()                                                                           # 将模型调整为评估状态
        val_acc_rate = calAcc(model,valLoader,device)                                          # 计算验证数据集的准确率
        model.train()                                                                          # 将模型调整回训练状态
        print("验证数据的准确率:",val_acc_rate)                                                # 打印准确率    
        if((err_num/eval_num)<=goal):                                                          # 检查退出条件
            break                                                                              
    print('训练步数',str(epoch),',最终训练误差',str(err_num/eval_num))                         

# 计算数据集的准确率                                                                           
def calAcc(model,dataLoader,device):                                                           
    py = np.empty(0)                                                                           # 初始化预测结果
    y  = np.empty(0)                                                                           # 初始化真实结果
    for batch, (imgs, labels) in enumerate(dataLoader):                                        # 逐批预测
        cur_py =  model(imgs.to(device))                                                       # 计算网络的输出
        cur_py = torch.argmax(cur_py,axis=1)                                                   # 将最大者作为预测结果
        py     = np.hstack((py,cur_py.detach().cpu().numpy()))                                 # 记录本批预测的y
        y      = np.hstack((y,labels))                                                         # 记录本批真实的y
    acc_rate = sum(y==py)/len(y)                                                               # 计算测试样本的准确率
    return acc_rate                                                                               

# -------模型参数初始化----------------------
def init_param(model):
    param_dict = dict(model.named_parameters())       # 获取模型的参数字典
    for key in  param_dict:                           # 历遍每个参数,对其初始化
        param_name = key.split(".")[-1]               # 获取参数的尾缀作为名称
        if (param_name=='weight'):                    # 如果是权重
            torch.nn.init.normal_(param_dict[key])    # 则正态分布初始化
        elif (param_name=='bias'):                    # 如果是阈值
            torch.nn.init.zeros_(param_dict[key])     # 则初始化为0
            
            
#--------------------------主流程脚本----------------------------------------------       
#-------------------加载数据--------------------------------
trainsform =torchvision.transforms.Compose([
    torchvision.transforms.Resize([224, 224]),
    torchvision.transforms.ToTensor(),
    ]
    )
train_data = torchvision.datasets.MNIST(
    root       = 'D:\pytorch\data'                                                             # 路径,如果路径有,就直接从路径中加载,如果没有,就联网获取
    ,train     = True                                                                          # 获取训练数据
    ,transform = trainsform                                                                    # 转换数据
    ,download  = True                                                                          # 是否下载,选为True,就下载到root下面
    ,target_transform= None)                                                                   
val_data = torchvision.datasets.MNIST(
    root       = 'D:\pytorch\data'                                                             # 路径,如果路径有,就直接从路径中加载,如果没有,就联网获取
    ,train     = False                                                                         # 获取测试数据
    ,transform = trainsform                                                                    # 转换数据
    ,download  = True                                                                          # 是否下载,选为True,就下载到root下面
    ,target_transform= None)                                                                   
                                                                                               
#-------------------模型训练--------------------------------                                   
trainLoader = DataLoader(train_data, batch_size=30, shuffle=True)                              # 将数据装载到DataLoader
valLoader   = DataLoader(val_data  , batch_size=30)                                            # 将验证数据装载到DataLoader 
device      = torch.device('cuda' if torch.cuda.is_available() else 'cpu')                     # 设置训练设备  
init_param(model)                                                                              # 初始化模型参数
model       = model.to(device)                                                                 # 发送到设备  
lossFun     = torch.nn.CrossEntropyLoss()                                                      # 定义损失函数为交叉熵损失函数
optimizer   = torch.optim.SGD(model.parameters(), lr=0.01,momentum =0.9)                       # 初始化优化器
train(trainLoader,valLoader,model,1000,0.01,device)                                            # 训练模型,训练100步,错误低于1%时停止训练

# -----------模型效果评估--------------------------- 
model.eval()                                                                                   # 将模型切换到评估状态(屏蔽Dropout)
train_acc_rate = calAcc(model,trainLoader,device)                                              # 计算训练数据集的准确率
print("训练数据的准确率:",train_acc_rate)                                                       # 打印准确率
val_acc_rate = calAcc(model,valLoader,device)                                                  # 计算验证数据集的准确率
print("验证数据的准确率:",val_acc_rate)                                                         # 打印准确率
备注:代码未亲测,仅供参考







好了,以上就是VGG卷积神经网络的模型结构与代码实现了~









 End 






联系老饼