【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
相关推荐
世人万千丶1 天前
鸿蒙日志体系高级应用:HiLog分级输出/隐私脱敏/远程日志采集/线上问题精准溯源方案
学习·harmonyos·鸿蒙
奔跑中的小象1 天前
统信UOS + 天数AI卡部署SGLang服务手册
人工智能·uos·sglang·天数智芯
DevSecOps选型指南1 天前
中国版Mythos,为何是悬镜安全灵脉CodeAI?
人工智能·安全
Shockang1 天前
LangGraph 状态机实战
人工智能
刹那芳华19921 天前
循环神经网络的从零开始实现(RNN)
人工智能·rnn·深度学习
阿童木写作1 天前
跨境图片翻译工具多合一,批量图片视频字幕翻译加智能抠图
人工智能·python·音视频·语音识别
科技之门1 天前
AI3D从建模到贴图、绑骨和动画的完整流程怎么做?V2Fun完整工作流指南
人工智能·3d·贴图
宇宙第一小趴菜1 天前
二、机器学习的应用领域和发展史
人工智能·机器学习
前端开发江鸟1 天前
我写过 MCP Server,却一直以为 MCP 只有 Tool
人工智能
天国梦1 天前
自习室智能化升级避坑指南:天学网AI智习室方案实测与选型建议
大数据·人工智能