经典卷积模型(三)VGGNet代码解析

  • Post author:
  • Post category:其他




VGGNet

VGGNet主要解决了CNN中的深度问题。相比较之前的卷积网络使用的11×11,7×7的卷积核,VGGNet使用多层更小的卷积核来替代。例如用两层卷积核为3×3的卷积层来替代一层卷积核为5×5的卷积层,用三层卷积核为3×3的卷积层来替代一层卷积核为7×7的卷积层,这样网络就会变深。之所以这样做是因为:

  • 增加层数后可以增加模型非线性。
  • 网络层数的增长并不会带来参数量上的爆炸,因为参数量主要集中在最后三个全连接层中。
  • 2个3×3卷积堆叠等于1个5×5卷积,3个3×3堆叠等于1个7×7卷积,采用更多次更小的卷积核可以带来更多非线性因素,利于函数的拟合,减少参数数量。
  • 1×1卷积核配合ReLU()函数可以增加更多非线性因素,减少参数数量。



模型

VGGNet把网络分成了5段,每段都把多个3*3的卷积网络串联在一起,每段卷积后面接一个最大池化层,最后面是3个全连接层和一个softmax层。如图

在这里插入图片描述



初始化方法

上图展示了VGGNet中有多种模型,网络层数由11层逐渐发展到19层。但在深层网络中参数初始化值的选定很重要,因为深层网络的梯度很不稳定,糟糕的模型初始化会使得模型效果不好。因此,VGGNet的发明者们

先用较浅的网络模型(11层,随机初始化)进行训练(浅网络随机初始化不易出问题)

,得到稳定的参数,

再将这些参数直接赋予更深网络模型作为初始值

(主要赋给前四层卷积层和后三层全连接层,中间层随机初始化)。



代码

以VGG16为例

import torch.nn as nn
import math
class VGG16(nn.Module):
    def __init__(self,num_classes=1000):
        super(VGG16,self).__init__()
        self.features = nn.Sequential(
        	#1
            nn.Conv2d(3,64,kernel_size=3,padding=1),
            nn.ReLU(True),
            nn.Conv2d(64,64,kernel_size=3,padding=1),
            nn.ReLU(True),
            nn.MaxPool2d(kernel_size=2,stride=2),
			#2
            nn.Conv2d(64,128,kernel_size=3,padding=1),
            nn.ReLU(True),
            nn.Conv2d(128,128,kernel_size=3,padding=1),
            nn.ReLU(True),
            nn.MaxPool2d(kernel_size=2,stride=2),
			#3
            nn.Conv2d(128,256,kernel_size=3,padding=1),
            nn.ReLU(True),
            nn.Conv2d(256,256,kernel_size=3,padding=1),
            nn.ReLU(True),
            nn.Conv2d(256,256,kernel_size=3,padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2,stride=2),
			#4
            nn.Conv2d(256,512,kernel_size=3,padding=1),
            nn.ReLU(True),
            nn.Conv2d(512,512,kernel_size=3,padding=1),
            nn.ReLU(True),
            nn.Conv2d(512,512,kernel_size=3,padding=1),
            nn.ReLU(True),
            nn.MaxPool2d(kernel_size=2,stride=2),
			#5
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )
        self.classifier = nn.Sequential(
        	#6
            nn.Linear(512*7*7,4096),
            nn.ReLU(True),
            #7
            nn.Linear(4096,4096),
            nn.ReLU(True),
            nn.Dropout(),
            #8
            nn.Linear(4096,num_classes),
        )
    def forward(self,x):
        x = self.features(x)
        x = x.view(x.size(0),-1)
        x = self.classifier(x)
        return x

输入图片长宽为224*224,通道数为3,则维度为(224,224,3),以下忽略激活函数:

  1. 使用两个3*3卷积核(感受野与1个5*5卷积核相等),填充数都为1,第一个卷积核将通道3扩展到64,第二个卷积核保持通道数不变,因为卷积核大小3*3且填充数为1,因此特征输出的大小不变((224-3+2*1/)1+1=224),之后再经过两倍下采样的最大池化层,所以(224,224,3)->(224,224,64)->(224,224,64)->(112,112,64)。

    nn.Conv2d(3,64,kernel_size=3,padding=1),
    nn.ReLU(True),
    nn.Conv2d(64,64,kernel_size=3,padding=1),
    nn.ReLU(True),
    nn.MaxPool2d(kernel_size=2,stride=2),
    
  2. 使用两个3*3卷积核,填充数都为1,第一个卷积核将通道64扩展到128,第二个卷积核保持通道数不变,特征输出的大小保持不变。之后再经过两倍下采样的最大池化层。(112,112,64)->(112,112,128)->(112,112,128)->(56,56,128)。

    nn.Conv2d(64,128,kernel_size=3,padding=1),
    nn.ReLU(True),
    nn.Conv2d(128,128,kernel_size=3,padding=1),
    nn.ReLU(True),
    nn.MaxPool2d(kernel_size=2,stride=2),
    
  3. 使用三个3*3卷积核,填充数都为1,第一个卷积核将通道128扩展到256,第二个第三个卷积核保持通道数不变,特征输出的大小保持不变。之后再经过两倍下采样的最大池化层。(56,56,128)->(56,56,256)->(56,56,256)->(56,56,256)->(28,28,256)。

    nn.Conv2d(128,256,kernel_size=3,padding=1),
    nn.ReLU(True),
    nn.Conv2d(256,256,kernel_size=3,padding=1),
    nn.ReLU(True),
    nn.Conv2d(256,256,kernel_size=3,padding=1),
    nn.ReLU(),
    nn.MaxPool2d(kernel_size=2,stride=2),
    
  4. 使用三个3*3卷积核,填充数都为1,第一个卷积核将通道256扩展到512,第二个第三个卷积核保持通道数不变,特征输出的大小保持不变。之后再经过两倍下采样的最大池化层。(28,28,256)->(28,28,512)->(28,28,512)->(28,28,512)->(14,14,512)。

    nn.Conv2d(256,512,kernel_size=3,padding=1),
    nn.ReLU(True),
    nn.Conv2d(512,512,kernel_size=3,padding=1),
    nn.ReLU(True),
    nn.Conv2d(512,512,kernel_size=3,padding=1),
    nn.ReLU(True),
    nn.MaxPool2d(kernel_size=2,stride=2),
    
  5. 使用三个3*3卷积核,填充数都为1,第一个第二个第三个卷积核保持通道数不变都为512,特征输出的大小保持不变。之后再经过两倍下采样的最大池化层。(14,14,512)->(14,14,512)->(14,14,512)->(14,14,512)->(7,7,512)。

    nn.Conv2d(512, 512, kernel_size=3, padding=1),
    nn.ReLU(True),
    nn.Conv2d(512, 512, kernel_size=3, padding=1),
    nn.ReLU(True),
    nn.Conv2d(512, 512, kernel_size=3, padding=1),
    nn.ReLU(True),
    nn.MaxPool2d(kernel_size=2, stride=2),
    
  6. 经过第一个全连接层,在此之前打平操作

    x = x.view(x.size(0),-1)

    ,将(7,7,512)->(7*7*512)。然后经过第一个全连接层,将(7*7*512)->(4096)。

    nn.Linear(512*7*7,4096),
    nn.ReLU(True),
    
  7. 经过第二个全连接层,(4096)->(4096)。

    nn.Linear(4096,4096),
    nn.ReLU(True),
    nn.Dropout(),
    
  8. 经过第三个全连接层,即输出层,输出num_classes个分类,(4096)->(num_classes)。

    nn.Linear(4096,num_classes),
    



思考

  1. 使用比较大的,例如11*11的卷积核,或者7*7,5*5的卷积核,不如使用多个3*3卷积核,一来可以与relu搭配增加非线性,还可以减少参数计算量。在感受野方面,7*7等于3个3*3,5*5的卷积核等于2个3*3。
  2. 加入1*1大小的卷积核和relu搭配,进一步加深网络的非线性,减少参数数量。
  3. 只要看到多层通道数不变的卷积步骤,或者全连接层步骤,一定要搭配好激活函数,例如relu,来加深网络的非线性。
  4. 网络参数量大的网络,要注意预训练微调步骤。可先训练VGG11,再固定共有参数层,去训练VGG16,VGG19的其他层。



版权声明:本文为sleepinghm原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。