一位深度学习小萌新的学渣笔记(三)VGG网络介绍及代码详解

VGG网络结构详解与模型的搭建

简单介绍VGG

模型结构


  • 经常用的是16层结构:point_up_2:13层个卷积层以及3个全连接层

亮点

  • 网络亮点:通过堆叠多个3x3 的卷积核来以替代大尺度卷积核(减少所需参数)

感受野

概念

例子

在这里插入图片描述

感受野计算公式


也就是说我们特征层3中的一个单元就相当于我们原图中的5*5的感受视野

为什么论文中说堆叠两个3x3的卷积核代替5x5的卷积核,堆叠三个3x3的卷积核替代7x7的卷积核?
在VGG网络中卷积核的步距(stride)是默认为 1 的
可以经过计算得到
在这里插入图片描述
目的:减少网络中训练参数的个数
同样可以通过计算证明


一个C是卷积核的深度就是有多层卷积,还有一个C是卷积核的个数,(因为这里假设的输入输出channel相同,所以输出的特征矩阵也是C

VGG16

参数使用

在这里插入图片描述

卷积层

通过这个参数设定的卷积层输出的高度和宽度不变:
由计算可以算得
在这里插入图片描述
我们设定的卷积核的大小就是 3*3
out =( in - 3 + 2 )/ 1 + 1 = in

下采样层

通过这个参数设定的下采样层输出的高度和宽度直接缩小为原来的一半:
out =( in - 2 + 0 )/ 2 + 1 = in / 2

基本结构

16 weight layers
Input (224x224 RGB images)

两层3x3的卷积核+ReLU
Maxpool最大下采样层

两层3x3的卷积核+ReLU
Maxpool最大下采样层

三层3x3的卷积核+ReLU
Maxpool最大下采样层

三层3x3的卷积核+ReLU
Maxpool最大下采样层

连接两个全连接层+ReLU
一层全连接层

加上一个soft-max处理进行激活


(2层)由于采用的卷积核conv3-64 的深度是64 所以输出的特征矩阵 宽和高不变 深度变成64
224 224 64
由于采用的下采样层maxpool 将特征矩阵 宽和高缩减为原来的一半 深度不变还是64 112 112 64
(2层)后面又是卷积核conv3-128的深度是128 所以输出的特征矩阵 宽和高不变 深度变成128
112 112 128
下采样层maxpool 将特征矩阵 宽和高缩减为原来的一半 深度不变还是128
56 56 128
(三层)卷积核conv3-256的深度是256 所以输出的特征矩阵 宽和高不变 深度变成256
56 56 256
下采样层maxpool 将特征矩阵 宽和高缩减为原来的一半 深度不变还是256
28 28 256
(三层)卷积核conv3-512的深度是512 所以输出的特征矩阵 宽和高不变 深度变成512
28 28 512
下采样层maxpool 将特征矩阵 宽和高缩减为原来的一半 深度不变还是512
14 14 512
(三层)卷积核conv3-512的深度是512 所以输出的特征矩阵 宽和高不变 深度变成512
14 14 512
下采样层maxpool 将特征矩阵 宽和高缩减为原来的一半 深度不变还是512
7 7 512
(三层)全连接层
FC-4096(ReLU)
FC-4096(ReLU)
FC-1000

model.py中代码解读

提取特征网络结构

卷积层+下采样层

cfgs = {
    'vgg11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    #所有的卷积核大小都是3*3  64个卷积核  最大池化下采样层  128个卷积核 最大池化层  256个卷积核   256个卷积核  下采样层  512个卷积核  512个卷积核   下采样层   512个卷积核  512个卷积核  下采样层  
    '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_features(cfg: list):#传入一个配置变量 是一个list的类型 传入定义配置的列表
    layers = [] #定义一个空列表来存放我们创建的每一层结构
    in_channels = 3 #输入通道是3
    for v in cfg: #由for循环遍历我们的配置列表就可以得到  可以得到一个有卷积操作和池化操作做组成一个列表
        if v == "M": #如果是一个最大池化层的话
            layers += [nn.MaxPool2d(kernel_size=2, stride=2)] 
            #创建一个最大池化下采样2d 已经规定了池化核的大小都是2, 步长也是2
        else:  #否则就是一个卷积核
            conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
            #创建一个卷积层  输入的彩色图像的深度是3 输出的特征矩阵的深度也就是对应着我们卷积核的个数,卷积核大小是3*3 padding=1     stride默认是等于1
            layers += [conv2d, nn.ReLU(True)]
            #每一个卷积层都是采用ReLU激活函数 定义在一起 并添加到layers的空列表中
            in_channels = v
            #讲in channels改变 使得上一层的输出变成下一层的输入
    return nn.Sequential(*layers)
    #Sequential函数将我们的列表通过非关键字参数的形式传入  
    #Sequential

分类网络结构

全连接层

class VGG(nn.Module):#继承自nn.Module的父类
    def __init__(self, features, num_classes=1000, init_weights=False):
    #初始化函数中  features是我们通过makefeatures函数生成的特征网络结构  所需要分类的类别个数
     #是否对我们网络进行权重初始化= False 
        super(VGG, self).__init__()
        self.features = features #是我们刚刚生成的网络features
        self.classifier = nn.Sequential(    #同样是利用sequential函数来生成分类网络结构  
            nn.Dropout(p=0.5), #目的是为了减少过拟合 0.5概率随机失活神经元
            nn.Linear(512*7*7, 2048), #输入节点个数就是我们展平之后所得到的一维向量的元素个数
            #全连接层的输出节点个数原论文中是4096 为了减少训练参数就是设置为2048
            nn.ReLU(True),#激活函数
            nn.Dropout(p=0.5),
            nn.Linear(2048, 2048),#输入节点个数是上一层的输出节点个数  
             #全连接层的输出节点个数原论文中是4096 为了减少训练参数就是设置为2048
            nn.ReLU(True),
            nn.Linear(2048, num_classes)
            #输入节点个数是上一层的输出节点个数  输出节点个数是对应我们分类的类别个数
            
        )
        if init_weights: #如果有初始化的参数的话
            self._initialize_weights() #进入到提前定义好的初始化权重函数当中

    def forward(self, x): #forward正向传播的过程 x是我们输入的图像数据
        # N x 3 x 224 x 224
        x = self.features(x) #通过我们的特征网络结构
        # N x 512 x 7 x 7
        x = torch.flatten(x, start_dim=1) #通过一个展平处理 第0个维度是我们的batch维度 从channel开始展平         
        # N x 512*7*7
        x = self.classifier(x) #通过我们的分类网络结构
        return x

    def _initialize_weights(self):
        for m in self.modules(): #遍历我们网络的每一个子模块
            if isinstance(m, nn.Conv2d):#如果遍历的当前层是一个卷积层的话
                # nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                nn.init.xavier_uniform_(m.weight)
                #用我们的xavier的初始化方法去初始化我们的卷积层的权重
                if m.bias is not None: #如果采用了偏置的话
                    nn.init.constant_(m.bias, 0) #将我们的偏置初始化为0
            elif isinstance(m, nn.Linear):#如果遍历的当前层是一个全连接层的话
                nn.init.xavier_uniform_(m.weight)
                #用我们的xavier的初始化方法去初始化我们的全连接层权重
                # nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)
				#将我们的偏置初始化为0

实例化我们的VGG网络

def vgg(model_name="vgg16", **kwargs):
#model_name可以实例化的我们所给定的网络模型 
    try:
        cfg = cfgs[model_name] #得到我们的配置列表
    except:
        print("Warning: model number {} not in cfgs dict!".format(model_name))
        exit(-1)
    model = VGG(make_features(cfg), **kwargs) #通过VGG的类来实例化我们VGG网络
    #第一个参数 make_features是一个函数 feature的参数是所对应的配置文件, **kwargs可变长度的字典变量,包括了class_num=1000.init_weights=False.
    return model

train.py代码解读

和训练AlexNet基本上一样

predict.py

和训练AlexNet基本上一样

粤 ICP 备 2020080455 号