模型结构

VGG网络是计算机视觉领域一种主流的特征提取的主干网络
它最早来自牛津大学视觉组的论文《VERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE-SCALE IMAGE RECOGNITION》
【Pytorch深度学习开发实践学习】【VGG】经典算法复现-Pytorch实现VGG主干网络(1)model.py-LMLPHP
【Pytorch深度学习开发实践学习】【VGG】经典算法复现-Pytorch实现VGG主干网络(1)model.py-LMLPHP
【Pytorch深度学习开发实践学习】【VGG】经典算法复现-Pytorch实现VGG主干网络(1)model.py-LMLPHP
【Pytorch深度学习开发实践学习】【VGG】经典算法复现-Pytorch实现VGG主干网络(1)model.py-LMLPHP

他们利用3*3卷积核、最大池化和全连接层构建了5种类型的VGG神经网络,
下面就具体实现一下这些类型的VGG神经网络

模型部分代码实现

VGG类

我们首先定义一个VGG类,这个类就是定义和实现网络结构

class VGG(nn.Module):
	def __init__(self,features,num_classes=1000,init_weights=False):
		super(VGG,self).__init()
		self.features = features
		self.classifier = nn.Sequential(
			nn.Linear(7*7*512,4096),
			nn.Relu(True),
			nn.Dropout(p=0.5),
			nn.Linear(4096,4096),
			nn.Relu(True),
			nn.Dropout(p=0.5),
			nn.Linear(4096,num_classes)
		)
		if init_weights:
			self._initialize.weights()
	def forward(self,x):
		x = self.features(x)
		x = torch.flatten(x,start_dim=1)
		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)
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_uniform_(m.weight)
                # nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)

逐行解释上面的代码
我们首先定义一个叫做VGG的类,它继承了nn.Module类
我们定义了VGG类的初始化函数 init ,它接受了三个参数:
①是feature,这个我们后面会提到,这是VGG的特征提取部分的网络
②是num_classes,这是VGG最后一个全连接层的输出的维数,它表示要进行分类的图像一共有多少个类
③是init_weights,它表示我们要不要初始化权重,因为我们要使用预训练权重,所有这个我们在类初始化函数里面

super(VGG,self).init()
super()是一个内置函数,用于临时地替换当前的类,从而可以调用父类的方法。VGG是当前的子类,self是子类的一个实例,super(VGG,self)返回了一个临时对象,该对象绑定了VGG的父类,并允许调用其方法。
init()这是父类的初始化方法,当我们调用super(VGG,self).init()时,我们是调用父类的__init__()方法。

self.features = features
这是将传入的features参数赋值给类的features属性。
self.classifier = nn.Sequential()
这个代码定义了一个顺序模型,它是pytorch定义的一个包含多个层的容器,这些层会按照它们被添加到容器中的顺序被应用,具体来说,这个分类器包括了以下层:
nn.Linear(77512,4096),
nn.Relu(True),
nn.Dropout(p=0.5),
nn.Linear(4096,4096),
nn.Relu(True),
nn.Dropout(p=0.5),
nn.Linear(4096,num_classes)
首先是一个全连接层,它的输入是77512,这是最后一个卷积层输出的维度,然后输出是4096,
然后是一个ReLU激活函数
后面是一个Dropout,
然后是一个全连接层,它的输入是4096,输出是4096,
然后是一个ReLU激活函数
后面是一个Dropout,
最后一层还是全连接层,它的输入是4096,输出就是num_classes,这个参数是我们在VGG类的初始化函数中指定的,根据实际任务来定。

def forward(self, x):
        x = self.features(x)
        x = torch.flatten(x, start_dim=1)
        x = self.classifier(x)
        return x

forward函数定义了整个网络的前向计算过程
首先是我们定义的features函数,它的输入是预处理的图像
维度是(batch_size,3,224,224)
经过特征提取网络输出的维度 (batch_size,512,7,7)
因为要输入到全连接层,我们要将网络展开
x = torch.flatten(x, start_dim=1) 这里是把(batch_size,512,7,7)从第1维进行展开,注意是我们是从第0维开始计算的,第1维是第2个,就是512
所以展开以后就是(batch_size,51277)
展平操作将多维张量转换为一维向量,以便可以传递给全连接层
最后将展平后的x传递给classifier,最后输出的是类别

make_features方法和cfg字典



def make_features(cfgs:list):
	layers = []
	in_channels =3
	for v in cfgs:
		if v == 'M':
			layers += [nn.Maxpool2d(kersize=2,stride=2)]
		else:
			layers +=[nn.conv2d(in_channels,v,kernel_size =3,padding=1),nn.Relu(True)]
			in_channels = v
	return nn.Sequential(*layers)
			
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']
}
    

这里我们定义了一个叫cfgs的字典,这个字典的键是模型的名字,11、13、16、19分别表现VGG的权重层数,这里的权重层数指的是有可学习权重的层,具体来说就是卷积层和池化层。
64、128、256、512这都是通道数,VGG都是使用的33的卷积核
我们以VGG11为例,大家可以结合最上面的图来看,首先是一个3
364的卷积层,然后跟一个最大池化层,再接着是1个33128的卷积层,再是一个最大池化层,接着是2个33256的卷积层,接着一个最大池化层,再是2个33256的卷积层,接着一个最大池化层,再是2个33*256的卷积层,最后接着一个最大池化层。

这个make_features函数接受一个cfgs的参数,我们指定这个参数的类型是一个列表。代码中的冒号:在类型提示中用来指定参数的类型。类型提示是python3.5以及更高版本的一个特性。它允许你为函数参数和返回值提供预期类型的信息。这有助于代码的可读性和维护性,但是并不强制执行类型检查。

我们首先初始化了一个空列表layers,
我们指定初始的输入通道数是3,in_channels
然后遍历列表,如果读取的是‘M’,那我们向layers列表中加入最大池化层
如果不是,我们加入卷积层和Relu激活层。
最后我们通过nn.Sequential通过非关键字参数的形式将layers输入到Sequential中,形成我们的特征提取网络
为什么要使用非关键字参数传递的形式呢?
我们可以看看Sequential是如何定义的

【Pytorch深度学习开发实践学习】【VGG】经典算法复现-Pytorch实现VGG主干网络(1)model.py-LMLPHP
【Pytorch深度学习开发实践学习】【VGG】经典算法复现-Pytorch实现VGG主干网络(1)model.py-LMLPHP
给Sequential传递参数有两种形式,一种就是我们顺序地填入非关键字参数,另外一种就是我们顺序地定义一个字典

为了帮助大家更好地理解这个
我们实例化一个vgg19的实例,断点调试一下
【Pytorch深度学习开发实践学习】【VGG】经典算法复现-Pytorch实现VGG主干网络(1)model.py-LMLPHP
【Pytorch深度学习开发实践学习】【VGG】经典算法复现-Pytorch实现VGG主干网络(1)model.py-LMLPHP
可以看到layer实际上就是一个列表

这个返回的nn.Sequential实际上就是我们前面定义的VGG类中初始化函数的接受的属性features,那么后面的实例化VGG类的时候,我们就需要调用make_features类去得到一个feature并把这个传给VGG类

vgg函数

def vgg(model_name="vgg16", **kwargs):
    assert model_name in cfgs, "Warning: model number {} not in cfgs dict!".format(model_name)
    cfg = cfgs[model_name]

    model = VGG(make_features(cfg), **kwargs)
    return model

那么问题来了,根据权重层数的不同,VGG有四种类型的网络,我们在实例化VGG类的时候到底是使用那一类呢?聪明的同学肯定已经想到了,我们必须还要写一个函数去指定我们使用那一类的网络模型

所以这里我们定义一个vgg函数,它接受一个model_name的参数,这个参数就是我们指定的网络的名称

接下来是一个断言,确定模型名称是不是在我们建立的cfgs的字典中,
如果在,我们去获取这个名称对应的值
这个值就是作为一个列表提供给make_feature函数
然后我们实例化一个VGG类,调用make_feature函数生成features提供给实例化的VGG

model.py的完整代码

那么model.py全部的代码如下:

import torch.nn as nn
import torch

# official pretrain weights
model_urls = {
    'vgg11': 'https://download.pytorch.org/models/vgg11-bbd30ac9.pth',
    'vgg13': 'https://download.pytorch.org/models/vgg13-c768596a.pth',
    'vgg16': 'https://download.pytorch.org/models/vgg16-397923af.pth',
    'vgg19': 'https://download.pytorch.org/models/vgg19-dcbb9e9d.pth'
}


class VGG(nn.Module):
    def __init__(self, features, num_classes=1000, init_weights=False):
        super(VGG, self).__init__()
        self.features = features
        self.classifier = nn.Sequential(
            nn.Linear(512*7*7, 4096),
            nn.ReLU(True),
            nn.Dropout(p=0.5),
            nn.Linear(4096, 4096),
            nn.ReLU(True),
            nn.Dropout(p=0.5),
            nn.Linear(4096, num_classes)
        )
        if init_weights:
            self._initialize_weights()

    def forward(self, 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)
        # 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)
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_uniform_(m.weight)
                # nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)


def make_features(cfg: list):
    layers = []
    in_channels = 3
    for v in cfg:
        if v == "M":
            layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
        else:
            conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
            layers += [conv2d, nn.ReLU(True)]
            in_channels = v
    return nn.Sequential(*layers)


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 vgg(model_name="vgg16", **kwargs):
    assert model_name in cfgs, "Warning: model number {} not in cfgs dict!".format(model_name)
    cfg = cfgs[model_name]

    model = VGG(make_features(cfg), **kwargs)
    return model


03-12 10:55