dl学习笔记(11):VGG,NIN,GooleNet经典架构pytorch实现

news/2025/2/22 5:37:27

(1)VGG16:

先给出架构图:

实现:

class VGG16(nn.Module):
    def __init__(self):
        super().__init__()
        self.feature_part = nn.Sequential(
            nn.Conv2d(3,64,3,padding=1),nn.ReLU(inplace=True),
            nn.Conv2d(64,64,3,padding=1),nn.ReLU(inplace=True),
            nn.MaxPool2d(2),

            nn.Conv2d(64,128,3,padding=1),nn.ReLU(inplace=True),
            nn.Conv2d(128,128,3,padding=1),nn.ReLU(inplace=True),
            nn.MaxPool2d(2),

            nn.Conv2d(128,256,3,padding=1),nn.ReLU(inplace=True),
            nn.Conv2d(256,256,3,padding=1),nn.ReLU(inplace=True),
            nn.Conv2d(256,256,3,padding=1),nn.ReLU(inplace=True),
            nn.MaxPool2d(2),

            nn.Conv2d(256,512,3,padding=1),nn.ReLU(inplace=True),
            nn.Conv2d(512,512,3,padding=1),nn.ReLU(inplace=True),
            nn.Conv2d(512,512,3,padding=1),nn.ReLU(inplace=True),
            nn.MaxPool2d(2),

            nn.Conv2d(512,512,3,padding=1),nn.ReLU(inplace=True),
            nn.Conv2d(512,512,3,padding=1),nn.ReLU(inplace=True),
            nn.Conv2d(512,512,3,padding=1),nn.ReLU(inplace=True),
            nn.MaxPool2d(2))
        
        self.clf_part = nn.Sequential(
            nn.Dropout(0.5),
            nn.Linear(512*7*7,4096),nn.ReLU(inplace=True),
            nn.Dropout(0.5),
            nn.Linear(4096,4096),nn.ReLU(inplace=True),
            nn.Linear(4096,1000),nn.Softmax(dim=1))
    
    def forward(self,x):
        x = self.feature_part(x) 
        x = x.view(-1,512*7*7) 
        output = self.clf_part(x)
        return output                               
            

这里与前面的实现方式略有不同,这次使用sequential函数统一装起来实现。把全连接层前面的卷积层和池化层的重复定义为特征提取部分,后面定义为分类输出部分。其他部分没什么太大区别,按照架构图上的参数填就可以了。

下面是用torchinfo的结果:

(2)NIN:

class NiN(nn.Module):
    def __init__(self):
        super().__init__()
        self.block1 = nn.Sequential(
            nn.Conv2d(3,192,5,padding=2),nn.ReLU(inplace=True),
            nn.Conv2d(192,160,1),nn.ReLU(inplace=True),
            nn.Conv2d(160,96,1),nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3,stride=2),
            nn.Dropout(0.25))
        
        self.block2 = nn.Sequential(
            nn.Conv2d(96,192,5,padding=2),nn.ReLU(inplace=True),
            nn.Conv2d(192,192,1),nn.ReLU(inplace=True),
            nn.Conv2d(192,192,1),nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3,stride=2),
            nn.Dropout(0.25))
        
        self.block3 = nn.Sequential(
            nn.Conv2d(192,192,3,padding=1),nn.ReLU(inplace=True),
            nn.Conv2d(192,192,1),nn.ReLU(inplace=True),
            nn.Conv2d(192,10,1),nn.ReLU(inplace=True),
            nn.AvgPool2d(7,stride=1),
            nn.Softmax(dim=1))
        
    def forward(self,x):
        output = self.block3(self.block2(self.block1(x)))
        return output
            

torchinfo结果:

从参数量可以看出NIN并不算一个性能很优秀的架构,但是NIN最大的优点就是让人们看到了1*1卷积核和用卷积层去代替线性层的可能性,为后面的GoogleNet提供了灵感来源。

(3)GoogleNet:

前面的架构都很优秀,但是下面我们要花比较多的篇幅来讲googlenet。

我们前面讲到的VGG非常优秀,但它在比赛上拿到的最高名次是亚军,而力压群雄拿到冠军的是
在ImageNet数据集上达到6.7%错误率的GoogLeNet。GoogLeNet由谷歌团队与众多大学合作研发,发表于论文《Going deeper with convolutions》。受到NiN网络的启发,谷歌引入了一种全新的网络架构:Inception block,并将使用Inception V1的网络架构称为GoogLeNet。Inception是电影《盗梦空间》的英文名称,很有趣的一点是该篇论文第一篇参考文献就是盗梦空间表情包出处。
在具体讲Inception块的理念之前,我们先回顾一下当时主流方法都有什么弊端。我们还是拿VGG鞭尸举例子,最让人老生常谈的就是VGG的参数量太多,已经到达一亿多的级别,各层之间的连接过于稠密,计算量太大很容易过拟合。为了解决这个问题,当时主流方法有下面几种:
1.使用分组卷积,用卷积代替全连接层等很多技巧办法用来减少神经元或者特征图之间的联系,让网络变得更加稀疏,不再变得这么稠密。
2.通过引入dropout的随机性来减少过拟合的情况,从某种角度讲也是一种随机的稀疏性
3.通过使用GPU来解决计算量大的问题
AlexNet和VGG都主要使用了2和3,NIN主要使用了1,但是都没有完全解决。使用分组卷积虽然能让网络变得稀疏,但是网络的学习能力会由于丢掉信息从而出现学习率波动甚至下降。使用dropout引入随机的稀疏和现代GPU架构的计算原理是冲突的,会造成大量的数据查找和缓存缺失带来的时间延迟。现代计算机天然的适合计算密集型分布类似的数据,所以其实并不适合稀疏性。
所以为了解决稀疏性与稠密的矛盾,谷歌给出的思路就是既要又要,顾名思义就是既要结构稀疏,又要计算稠密:用普通卷积层,池化层这些稠密元素组成 的块去逼近 一个稀疏架构,从而构造一种参数量与稀疏网络相似的稠密网络。
基于这样的理念,在经历过很多试验后首先搞出一个初始版本inception块如下:
但是这样的话,参数由于最后还要并在一起就会越来越厚,并不可行,这时候就受到了NIN网络的启发,他们看到1*1卷积核的妙用,又搞出了第二个版本:
其中具体的结构如下,其中图片的大小并不是28*28,这里只是举例:
下面来分析一下这种块有什么优势:
1.使用不同大小的卷积核可以提取到各个类型层次的信息,你也不需要纠结用什么尺寸的卷积核了,谷歌说我都要用。
2.原来串联的过程现在变成了并联,充分释放了计算效率。
3.最后就是第二个版本和前面版本的区别:1*1卷积核,那么这个卷积核是如何工作的,如下图:
具体的来说有以下几个好处:
1.能够很轻松的调整通道数,让各个层之间的连接变得更加自然
2.能够减少参数量,就像前面NIN使用的那样,我们可以先降下来卷积最后再升上去
3.能够跨通道信息交融,如上图最后生成的每一个小方块都是前一层每一个通道加权得来
4.由于不改变特征图的尺寸,所以还能够增加模型深度提高非线性表达能力。
最后我们给出完整的架构图:
下面开始实现部分:
由于模型结构过于复杂,所以我们需要定义基本元素单位,下面我们先定义卷积包和inception块
卷积包:
class BasicConv(nn.Module):
    def __init__(self,in_channels, out_channels,**kwargs):
        super().__init__()
        self.conv = nn.Sequential(nn.Conv2d(in_channels, out_channels, bias=False, **kwargs),
                                 nn.BatchNorm2d(out_channels),
                                 nn.ReLU(inplace=True))
    def forward(self,x):
        x = self.conv(x)
        return x

由于当时那时候batchnormalization技术还没有出来,但是后来都陆续使用上了,所以这里我们也用上,另外由于后续还有可能使用到其他参数,所以加上了**kwargs。

下面定义inception块:

class Inception(nn.Module):
    def __init__(self
                 ,in_channels : int
                 ,ch1x1 : int
                 ,ch3x3red : int
                 ,ch3x3 : int
                 ,ch5x5red : int
                 ,ch5x5 : int
                 ,pool_proj : int
                ):
        super().__init__()
        #1x1
        self.branch1 = BasicConv(in_channels,ch1x1,kernel_size=1)
        #1x1 + 3x3
        self.branch2 = nn.Sequential(BasicConv(in_channels, ch3x3red, kernel_size=1)
                                     ,BasicConv(ch3x3red, ch3x3, kernel_size=3,padding=1))
        #1x1 + 5x5
        self.branch3 = nn.Sequential(BasicConv(in_channels, ch5x5red, kernel_size=1)
                                     ,BasicConv(ch5x5red, ch5x5, kernel_size=5, padding=2))
        #pool + 1x1
        self.branch4 = nn.Sequential(nn.MaxPool2d(kernel_size=3,stride=1, padding=1,ceil_mode=True)
                                    ,BasicConv(in_channels,pool_proj,kernel_size=1))
    def forward(self,x):
        branch1 = self.branch1(x) 
        branch2 = self.branch2(x) 
        branch3 = self.branch3(x) 
        branch4 = self.branch4(x) 
        outputs = [branch1, branch2, branch3, branch4]
        return torch.cat(outputs, 1) #合并

需要说明的是传入的参数在开始那张表中都有,我们写的时候只需要照着抄数字就行。

另外还有一点没有提及的是,googlenet架构有三个输出,其中还有两个辅助分类器,最后再按照一定的权重输出的,所以我们还需要定义一个辅助分类器:

class AuxClf(nn.Module):
    def __init__(self,in_channels : int, num_classes : int, **kwargs):
        super().__init__()
        self.feature_ = nn.Sequential(nn.AvgPool2d(kernel_size=5,stride=3)
                                     ,BasicConv(in_channels,128, kernel_size=1))
        self.clf_ = nn.Sequential(nn.Linear(4*4*128, 1024)
                                 ,nn.ReLU(inplace=True)
                                 ,nn.Dropout(0.7)
                                 ,nn.Linear(1024,num_classes))
    def forward(self,x):
        x = self.feature_(x)
        x = x.view(-1,4*4*128)
        x = self.clf_(x)
        return x

最后定义GooleNet大类:

class GoogLeNet(nn.Module):
    def __init__(self,num_classes: int = 1000):
        super().__init__()
        
        #block1
        self.conv1 = BasicConv(3,64,kernel_size=7,stride=2,padding = 3)
        self.maxpool1 = nn.MaxPool2d(kernel_size=3,stride=2,ceil_mode = True)
        
        #block2
        self.conv2 = BasicConv(64,64,kernel_size=1)
        self.conv3 = BasicConv(64,192,kernel_size=3, padding = 1)
        self.maxpool2 = nn.MaxPool2d(kernel_size=3,stride=2,ceil_mode = True)
        
        #block3
        self.inception3a = Inception(192,64,96,128,16,32,32)
        self.inception3b = Inception(256,128,128,192,32,96,64)
        self.maxpool3 = nn.MaxPool2d(kernel_size=3,stride=2,ceil_mode = True)

        
        #block4 
        self.inception4a = Inception(480,192,96,208,16,48,64)
        self.inception4b = Inception(512,160,112,224,24,64,64)
        self.inception4c = Inception(512,128,128,256,24,64,64)
        self.inception4d = Inception(512,112,144,288,32,64,64)
        self.inception4e = Inception(528,256,150,320,32,128,128)
        self.maxpool4 = nn.MaxPool2d(kernel_size=3,stride=2,ceil_mode = True)
        
        #block5
        self.inception5a = Inception(832,256,160,320,32,128,128)
        self.inception5b = Inception(832,384,192,384,48,128,128)
        
        #clf
        self.avgpool = nn.AdaptiveAvgPool2d((1,1)) 
        self.dropout = nn.Dropout(0.4)
        self.fc = nn.Linear(1024,num_classes)
        
        #auxclf
        self.aux1 = AuxClf(512, num_classes) #4a
        self.aux2 = AuxClf(528, num_classes) #4d
    
    def forward(self,x):
        #block1
        x = self.maxpool1(self.conv1(x))
        
        #block2
        x = self.maxpool2(self.conv3(self.conv2(x)))
        
        #block3
        x = self.inception3a(x)
        x = self.inception3b(x)
        x = self.maxpool3(x)
        
        #block4
        x = self.inception4a(x)
        aux1 = self.aux1(x)
        
        x = self.inception4b(x)
        x = self.inception4c(x)
        x = self.inception4d(x)
        aux2 = self.aux2(x)
        
        x = self.inception4e(x)
        x = self.maxpool4(x)
        
        #block5
        x = self.inception5a(x)
        x = self.inception5b(x)
        
        #clf
        x = self.avgpool(x) 
        x = torch.flatten(x,1)
        x = self.dropout(x)
        x = self.fc(x)
        
        return x, aux2, aux1

torchinfo结果:


http://www.niftyadmin.cn/n/5858208.html

相关文章

高效率:转换效率高达 96%,可有效减少能源损耗

WD5030 的特点 高效率:转换效率高达 96%,可有效减少能源损耗,降低设备发热,提高能源利用效率,延长电池供电设备的续航时间135。 精准输出电压:内置可调线路补偿和可调输出电压功能,输出电压精度…

SpringAI系列 - RAG篇(三) - ETL

目录 一、引言二、组件说明三、集成示例一、引言 接下来我们介绍ETL框架,该框架对应我们之前提到的阶段1:ETL,主要负责知识的提取和管理。ETL 框架是检索增强生成(RAG)数据处理的核心,其将原始数据源转换为结构化向量并进行存储,确保数据以最佳格式供 AI 模型检索。 …

使用GitLab和GitLab-Runner建立CICD流水线

1.安装部署 使用docker-compose来部署gitlab系统,创建一个用于存放gitlab的目录: # 创建gitlab存储目录 mkdir -p /opt/docker/gitlab # 进入到存储目录中 cd /opt/docker/gitlab # 创建docker-compose.yml文件 touch docker-compose.yml在docker-compose.yml中加入以下配…

Spring Boot 自动装配原理深度剖析

一、引言 在 Java 开发领域,Spring 框架无疑是中流砥柱。而 Spring Boot 的出现,更是极大地简化了 Spring 应用的搭建和开发过程。其中,自动装配原理是 Spring Boot 的核心亮点之一,它让开发者无需手动编写大量繁琐的配置代码&am…

基于微信小程序的宿舍报修管理系统设计与实现,SpringBoot(15500字)+Vue+毕业论文+指导搭建视频

运行环境 jdkmysqlIntelliJ IDEAmaven3微信开发者工具 项目技术SpringBoothtmlcssjsjqueryvue2uni-app 宿舍报修小程序是一个集中管理宿舍维修请求的在线平台,为学生、维修人员和管理员提供了一个便捷、高效的交互界面。以下是关于这些功能的简单介绍: …

汽车迷你Fakra连接器市场报告:未来几年年复合增长率CAGR为21.3%

汽车微型 Fakra 连接器是汽车行业的专用连接器,用于连接汽车内的各种电子控制单元 (ECU)、传感器和通信系统。Fakra 连接器以其坚固耐用的设计而著称,这对于抵御汽车环境中的高温、振动、潮湿和化学物质等恶劣环境至关重要。 据QYResearch调研团队最新报…

微信小程序---计划时钟设计与实现

微信小程序-计划时钟已上线,欢迎各位小伙伴的测试和使用~(微信小程序搜计划时钟即可使用) 在这篇博客中,我们将探讨如何在微信小程序中设计和实现一个任务管理功能,该功能允许用户添加、删除和查看任务。任务管理系统的核心是基于日期和时间的任务管理,可以设置任务的开…

进阶——第十六届蓝桥杯嵌入式熟练度练习(eeprom的读写)

在MX中开启PB6,PB7 读函数 uint8_t eeprom_read(uint8_t addr) {I2CStart();I2CSendByte(0xa0);I2CWaitAck();I2CSendByte(addr);I2CWaitAck();I2CStart();I2CSendByte(0xa1);I2CWaitAck();dataI2CReceiveByte();I2CSendNotAck();I2CStop();return data; } 写函数 void eep…