【Pytorch深度学习开发实践学习】【VGG】经典算法复现-Pytorch实现VGG主干网络(1)model.py

模型结构

VGG网络是计算机视觉领域一种主流的特征提取的主干网络

它最早来自牛津大学视觉组的论文《VERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE-SCALE IMAGE RECOGNITION》



他们利用3*3卷积核、最大池化和全连接层构建了5种类型的VGG神经网络,

下面就具体实现一下这些类型的VGG神经网络

模型部分代码实现

VGG类

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

python 复制代码
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(77 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)

首先是一个全连接层,它的输入是77 512,这是最后一个卷积层输出的维度,然后输出是4096,

然后是一个ReLU激活函数

后面是一个Dropout,

然后是一个全连接层,它的输入是4096,输出是4096,

然后是一个ReLU激活函数

后面是一个Dropout,

最后一层还是全连接层,它的输入是4096,输出就是num_classes,这个参数是我们在VGG类的初始化函数中指定的,根据实际任务来定。

python 复制代码
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,5127 7)

展平操作将多维张量转换为一维向量,以便可以传递给全连接层

最后将展平后的x传递给classifier,最后输出的是类别

make_features方法和cfg字典

python 复制代码
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个3 3128的卷积层,再是一个最大池化层,接着是2个3 3256的卷积层,接着一个最大池化层,再是2个3 3256的卷积层,接着一个最大池化层,再是2个33*256的卷积层,最后接着一个最大池化层。

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

我们首先初始化了一个空列表layers,

我们指定初始的输入通道数是3,in_channels

然后遍历列表,如果读取的是'M',那我们向layers列表中加入最大池化层

如果不是,我们加入卷积层和Relu激活层。

最后我们通过nn.Sequential通过非关键字参数的形式将layers输入到Sequential中,形成我们的特征提取网络

为什么要使用非关键字参数传递的形式呢?

我们可以看看Sequential是如何定义的


给Sequential传递参数有两种形式,一种就是我们顺序地填入非关键字参数,另外一种就是我们顺序地定义一个字典

为了帮助大家更好地理解这个

我们实例化一个vgg19的实例,断点调试一下

可以看到layer实际上就是一个列表

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

vgg函数

python 复制代码
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全部的代码如下:

python 复制代码
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
相关推荐
攸攸太上9 分钟前
JMeter学习
java·后端·学习·jmeter·微服务
Ljubim.te38 分钟前
Linux基于CentOS学习【进程状态】【进程优先级】【调度与切换】【进程挂起】【进程饥饿】
linux·学习·centos
深度学习实战训练营42 分钟前
基于keras的停车场车位识别
人工智能·深度学习·keras
乔代码嘚1 小时前
AI2.0时代,普通小白如何通过AI月入30万
人工智能·stable diffusion·aigc
墨@#≯1 小时前
机器学习系列篇章0 --- 人工智能&机器学习相关概念梳理
人工智能·经验分享·机器学习
Elastic 中国社区官方博客1 小时前
Elasticsearch:使用 LLM 实现传统搜索自动化
大数据·人工智能·elasticsearch·搜索引擎·ai·自动化·全文检索
yngsqq1 小时前
031集——文本文件按空格分行——C#学习笔记
笔记·学习·c#
_.Switch1 小时前
Python机器学习模型的部署与维护:版本管理、监控与更新策略
开发语言·人工智能·python·算法·机器学习
XiaoLiuLB1 小时前
ChatGPT Canvas:交互式对话编辑器
人工智能·自然语言处理·chatgpt·编辑器·aigc
Hoper.J1 小时前
PyTorch 模型保存与加载的三种常用方式
人工智能·pytorch·python