卷积神经网络五:GoogleNet

news2024/11/27 22:34:51

在2014年的ImageNet图像识别大赛中,一个名叫GoogleNet的网络架构大放异彩。GoogleNet使用了一种叫作Inception的结构。其实GoogleNet本质上就是一种Inception网络,而一个Inception网络又是由多个Inception模块和少量的汇聚层堆叠而成。

Inception模块

在Inception网络结构中,一个卷积层包含多个不同大小的卷积操作,成为Inception模块。Inception模块使用1x1、3x3、5x5等不同大小的卷积核,并将得到的特征映射在深度上拼接(堆叠)起来作为输出特征映射。

GoogleNet的Inception模块结构如图所示:

Inception模块采用了4组平行的特征抽取方式。分别为1x1、3x3、5x5的卷积和3x3的池化层。同时,为了提高计算效率,减少参数数量,Inception模块在进行3x3和5x5的卷积之前和3x3的池化层后进行一次1x1卷积来减少特征映射的深度,其实本质上就是通过1x1卷积来改变输出的通道数量。最后所谓特征映射在深度上拼接其实就是在通道维度上进行合并。

下面我们来实现一下GoogleNet的Inception模块:

import torch.nn as nn
import torch
import torch.optim as optim
import torchvision.transforms as transforms
from torch.nn import functional as F

# 构建inception模块
class Inception(nn.Module):
    # c1,c2,c3,c4代码每一路输出的通道数,这些通道最后进行拼接成为最终输出
    def __init__(self, in_channels, c1, c2, c3, c4) -> None:
        super(Inception, self).__init__()
        # 线路1,1x1卷积层
        self.p1 = nn.Sequential(nn.Conv2d(in_channels, c1, kernel_size=1), nn.ReLU())
        # 线路2,1x1卷积层
        self.p2_1 = nn.Sequential(nn.Conv2d(in_channels, c2[0], kernel_size=1), nn.ReLU())
        # 线路2,3x3卷积层,填充1
        self.p2_2 = nn.Sequential(nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1), nn.ReLU())
        # 线路3,1x1卷积层
        self.p3_1 = nn.Sequential(nn.Conv2d(in_channels, c3[0], kernel_size=1), nn.ReLU())
        # 线路3,5x5卷积层
        self.p3_2 = nn.Sequential(nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2), nn.ReLU())
        # 线路4,3x3池化层
        self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1) # MaxPool2d的stride默认跟kernel_size一样
        # 线路4,1x1卷积层
        self.p4_2 = nn.Sequential(nn.Conv2d(in_channels, c4, kernel_size=1), nn.ReLU())

    def forward(self, x):
        p1 = self.p1(x)
        p2 = self.p2_2(self.p2_1(x))
        p3 = self.p3_2(self.p3_1(x))
        p4 = self.p4_2(self.p4_1(x))
        print("p1.shape:{},p2.shape:{},p3.shape:{},p4.shape:{}".format(p1.shape, p2.shape, p3.shape, p4.shape))
        # 在通道维度上拼接
        return torch.cat((p1, p2, p3, p4), dim=0)

if __name__ == '__main__':
    x = torch.zeros(3, 224, 224)
    m = Inception(3, 192, (96,208), (16,48), 64)
    print(m(x).shape)
# 输出:
p1.shape:torch.Size([192, 224, 224]),p2.shape:torch.Size([208, 224, 224]),p3.shape:torch.Size([48, 224, 224]),p4.shape:torch.Size([64, 224, 224])
torch.Size([512, 224, 224])

这里我们是完全根据inception模块的结构图来实现的,我们假设线路1的输出通道数192,线路2的输出通道数(96,208),线路3的输出通道数(16,48),线路四的输出通道数64。可以看到输入一个3*224*224的张量,最终输出如下:线路1输出192*224*224,线路2输出208*224*224,线路3输出48*224*224,线路4输出64*224*224,按第0维通道维拼接,结果为512*224*224。

这里要注意两个问题,首先拼接的维度选择,我们这里因为维度0是通道维,所以torch.cat函数中选择dim=0,完整模型代码中,dim=1,这是因为第0维一般是批量数batch_size。第二个问题是,池化层MaxPool2d的stride=1不能忘,因为MaxPool2d默认的stride是和kernel_size一样的,如果不指定,那么线路4的3x3池化层输出会变成3*75*75,[(224-3+2*1)/3+1=75],导致线路四最终输出为64*75*75,如果这样的话,就无法按通道维度拼接了,因为[192,224,224],[208,224,224],[48,224,224]和[64,75,75]前三个张量无法和最后一个张量拼接。

Googlenet网络模型

完整的googlenet网络架构如下:

具体流程如下:

输入为3x224x224的张量

1.经过7x7的卷积层,输出64通道,步幅为2,填充为3,输出大小为64x112x112

2.经过3x3的池化层,通道数不变,步幅为2,填充为1,输出大小为64x56x56

3.经过1x1的卷积层,输出64通道,输出大小为64x56x56

4.经过3x3的卷积层,输出192通道,步幅为1,填充为1,输出大小为192x56x56

5.经过3x3的池化层,通道数不变,步幅为2,填充为1,输出大小为192x2828

6.经过两个Inception模块:

1)第一个Inception,输入通道数192,输出通道数为 64+128+32+32=256,由于Inception模块并不改变特征输出的长宽大小,所以输出大小为256x28x28

2)第二个Inception,输入通道数256,输出通道数为128+192+96+64=480,输出大小为480x28x28

7.经过3x3池化层,通道数不变,步幅为2,填充为1,输出大小为480x14x14

8.经过五个Inception模块:

1)第一个Inception,输入通道数480,输出通道数192+208+48+64=512,输出大小为512x14x14

2)第二个Inception,输入通道数512,输出通道数160+224+64+64=512,输出大小为512x14x14

3)第三个Inception,输入通道数512,输出通道数128+256+64+64=512,输出大小为512x14x14

4)第四个Inception,输入通道数512,输出通道数112+228+64+64=528,输出大小为528x14x14

5)第五个Inception,输入通道数528,输出通道数256+320+128+128=832,输出大小为832x14x14

9.经过3x3池化层,通道数不变,步幅为2,填充为1,输出大小为832x7x7

10.经过两个Inception模块:

1)第一个Inception,输入通道数832,输出通道数256+320+128+128=832,输出大小为832x7x7

2)第二个Inception,输入通道数832,输出通道数384+384+128+128=1024,输出大小为1024x7x7

11.经过全局平均汇聚层,输出1024x1x1

12.全连接层,输出10个分类结果。

下面我们实现一下代码:

class GoogleNet(nn.Module):
    def __init__(self, num_classes) -> None:
        super(GoogleNet, self).__init__()
        b1 = nn.Sequential(nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3), 
                           nn.ReLU(), 
                           nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
        b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1),
                           nn.Conv2d(64, 192, kernel_size=3, stride=1, padding=1),
                           nn.ReLU(),
                           nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
        b3 = nn.Sequential(Inception(192, 64, (96,128), (16,32), 32),
                           Inception(256, 128, (128,192), (32,96), 64),
                           nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
        b4 = nn.Sequential(Inception(480, 192, (96,208), (16,48), 64),
                           Inception(512, 160, (112,224), (24,64), 64),
                           Inception(512, 128, (128,256), (24,64), 64),
                           Inception(512, 112, (144,288), (32,64), 64),
                           Inception(528, 256, (160,320), (32,128), 128),
                           nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
        b5 = nn.Sequential(Inception(832, 256, (160,320), (32,128), 128),
                           Inception(832, 384, (192,384), (48,128), 128),
                           nn.AdaptiveAvgPool2d((1,1)),
                           nn.Flatten())
        self.features = nn.Sequential(b1,b2,b3,b4,b5)
        self.classifier = nn.Sequential(
            nn.Linear(1024, num_classes)
        )

    def forward(self, x):
        x =self.features(x)
        print(x.shape)
        x = self.classifier(x)
        return x
    
if __name__ == '__main__':
    X = torch.rand(size=(1,3,224,224))
    net = GoogleNet(num_classes=10)
    X = net(X)
    print('output shape:', X.shape)
# 输出:
p1.shape:torch.Size([1, 64, 28, 28]),p2.shape:torch.Size([1, 128, 28, 28]),p3.shape:torch.Size([1, 32, 28, 28]),p4.shape:torch.Size([1, 32, 28, 28])
p1.shape:torch.Size([1, 128, 28, 28]),p2.shape:torch.Size([1, 192, 28, 28]),p3.shape:torch.Size([1, 96, 28, 28]),p4.shape:torch.Size([1, 64, 28, 28])
p1.shape:torch.Size([1, 192, 14, 14]),p2.shape:torch.Size([1, 208, 14, 14]),p3.shape:torch.Size([1, 48, 14, 14]),p4.shape:torch.Size([1, 64, 14, 14])
p1.shape:torch.Size([1, 160, 14, 14]),p2.shape:torch.Size([1, 224, 14, 14]),p3.shape:torch.Size([1, 64, 14, 14]),p4.shape:torch.Size([1, 64, 14, 14])
p1.shape:torch.Size([1, 128, 14, 14]),p2.shape:torch.Size([1, 256, 14, 14]),p3.shape:torch.Size([1, 64, 14, 14]),p4.shape:torch.Size([1, 64, 14, 14])  
p1.shape:torch.Size([1, 112, 14, 14]),p2.shape:torch.Size([1, 288, 14, 14]),p3.shape:torch.Size([1, 64, 14, 14]),p4.shape:torch.Size([1, 64, 14, 14])  
p1.shape:torch.Size([1, 256, 14, 14]),p2.shape:torch.Size([1, 320, 14, 14]),p3.shape:torch.Size([1, 128, 14, 14]),p4.shape:torch.Size([1, 128, 14, 14])
p1.shape:torch.Size([1, 256, 7, 7]),p2.shape:torch.Size([1, 320, 7, 7]),p3.shape:torch.Size([1, 128, 7, 7]),p4.shape:torch.Size([1, 128, 7, 7])
p1.shape:torch.Size([1, 384, 7, 7]),p2.shape:torch.Size([1, 384, 7, 7]),p3.shape:torch.Size([1, 128, 7, 7]),p4.shape:torch.Size([1, 128, 7, 7])
torch.Size([1, 1024])
output shape: torch.Size([1, 10])

可以看到,每一路的大小都输出来了,最终得到分类结果。这里要注意一点,之前的Inception模块中的按通道拼接操作,由于这里加入了batch_size,所以合并的torch.cat函数dim=1,也就是按维度1来进行拼接。

下面我们来测试一下,尝试一下该数据集在cifar-10上的训练效果:

根据我们上一次在VGG中阐述的批量归一化层的作用,我们同样在Inception模块中引入批量归一化层:

# 构建inception模块
class Inception(nn.Module):
    # c1,c2,c3,c4代码每一路输出的通道数,这些通道最后进行拼接成为最终输出
    def __init__(self, in_channels, c1, c2, c3, c4) -> None:
        super(Inception, self).__init__()
        # 线路1,1x1卷积层
        self.p1 = nn.Sequential(nn.Conv2d(in_channels, c1, kernel_size=1), nn.BatchNorm2d(c1), nn.ReLU())
        # 线路2,1x1卷积层
        self.p2_1 = nn.Sequential(nn.Conv2d(in_channels, c2[0], kernel_size=1), nn.BatchNorm2d(c2[0]), nn.ReLU())
        # 线路2,3x3卷积层,填充1
        self.p2_2 = nn.Sequential(nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1), nn.BatchNorm2d(c2[1]), nn.ReLU())
        # 线路3,1x1卷积层
        self.p3_1 = nn.Sequential(nn.Conv2d(in_channels, c3[0], kernel_size=1), nn.BatchNorm2d(c3[0]), nn.ReLU())
        # 线路3,5x5卷积层
        self.p3_2 = nn.Sequential(nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2), nn.BatchNorm2d(c3[1]), nn.ReLU())
        # 线路4,3x3池化层
        self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1) # MaxPool2d的stride默认跟kernel_size一样
        # 线路4,1x1卷积层
        self.p4_2 = nn.Sequential(nn.Conv2d(in_channels, c4, kernel_size=1), nn.BatchNorm2d(c4), nn.ReLU())

    def forward(self, x):
        p1 = self.p1(x)
        p2 = self.p2_2(self.p2_1(x))
        p3 = self.p3_2(self.p3_1(x))
        p4 = self.p4_2(self.p4_1(x))
        #print("p1.shape:{},p2.shape:{},p3.shape:{},p4.shape:{}".format(p1.shape, p2.shape, p3.shape, p4.shape))
        # 在通道维度上拼接
        return torch.cat((p1, p2, p3, p4), dim=1)

然后,用cifar-10数据集进行训练,我将cifar-10数据集放大至224x224进行训练。

# 加载cifar数据集
import torchvision.datasets as datasets
from torchvision.transforms.functional import InterpolationMode
transform = transforms.Compose([transforms.Resize((224, 224), interpolation=InterpolationMode.BICUBIC),
                            transforms.ToTensor(),
                            transforms.Normalize(mean=[0.4914, 0.4822, 0.4465], std=[0.247, 0.2435, 0.2616]) # 此为训练集上的均值与方差
                            ])
train_dataset_cifar = datasets.CIFAR10('./data', train=True, transform=transform, download=True)
test_dataset_cifar = datasets.CIFAR10('./data', train=False, transform=transform, download=True)
train_loader_cifar = torch.utils.data.DataLoader(dataset=train_dataset_cifar, batch_size=24, shuffle=True)
test_loader_cifar = torch.utils.data.DataLoader(dataset=test_dataset_cifar, batch_size=24, shuffle=False)
train(net, train_loader_cifar, test_loader_cifar, lr=0.01, epochs=10)

训练和测试结果如下:

Epoch 1, Loss: 1.4117, Time 00:04:44
Epoch 2, Loss: 0.8174, Time 00:04:42
Epoch 3, Loss: 0.5929, Time 00:05:07
Epoch 4, Loss: 0.4710, Time 00:05:05
Epoch 5, Loss: 0.3846, Time 00:05:13
Epoch 6, Loss: 0.3080, Time 00:05:18
Epoch 7, Loss: 0.2601, Time 00:05:03
Epoch 8, Loss: 0.2079, Time 00:04:58
Epoch 9, Loss: 0.1662, Time 00:04:50
Epoch 10, Loss: 0.1411, Time 00:04:50

Test Accuracy: 83.74%

可以看到,仅仅训练了10个epoch,测试精度就达到了83.74%。下次我们来看看微软亚洲研究院何凯明大神等人提出的一个里程碑式的卷积神经网络:残差网络ResNet。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1530090.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTML_CSS学习:超链接、列表、表格、表格常用属性

一、超链接_唤起指定应用 1.相关代码 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><title>超链接_唤起指定应用</title> </head> <body><a href"tel:10010">电话联…

docker入门(四)—— docker常用命令详解

docker 常用命令 基本命令 # 查看 docker 版本 docker version # 查看一些 docker 的详细信息 docker info 帮助命令&#xff08;–help&#xff09;&#xff0c;linux必须要会看帮助文档 docker --help[rootiZbp15293q8kgzhur7n6kvZ /]# docker --helpUsage: docker [OPTI…

dockerfile文件编写

文章目录 dockerfile是什么Dockerfile常用指令1. FROM2. MAINTAINER3. WORKDIR4.COPY5.ADD6.ENV7.RUN8.CMD9.ENTRYPOINT dockerfile是什么 Dockerfile是一个文本配置文件&#xff0c;用于自动化构建Docker镜像。 Dockerfile是由一系列命令和参数构成的脚本&#xff0c;它指导D…

在Ubuntu20.04(原为cuda12.0, gcc9.几版本和g++9.几版本)下先安装cuda9.0后再配置gcc-5环境

因为自己对Linux相关操作不是很熟悉&#xff0c;所以因为之前的代码报错之后决定要安cuda9.0&#xff0c;于是先安装了cuda9.0。里面用到的一些链接&#xff0c;链接文件夹时直接去copy它的路径&#xff0c;就不那么容易错了。 今天运行程序之后发现gcc环境不太匹配cuda9.0&am…

杰发科技AC7801——Keil编译的Hex大小如何计算

编译结果是Keil里面前三个数据的总和&#xff1a; 即CodeRoDataRWData的总和。 通过ATCLinkTool工具查看内存&#xff0c;发现最后一个字节正好是5328 注意读内存数据时候需要强转成32位&#xff0c;加1000的 增加1024的地址只需要加256即可

【Unity投屏总结】投屏方案总结

【背景】 想方便自己在VR中工作&#xff0c;打算做一个能够挂多个屏幕的远程控制VR桌面。研究下来发现细分场景有很多&#xff0c;有点鱼和熊掌不可兼得的意味&#xff0c;细分如下。 【投屏场景与解决方案】 希望多人能够同时观看我的屏幕&#xff0c;也就是一屏投多屏&…

备战蓝桥杯---0/1Trie模板

最近学校作业有点多被迫参加学校的仪仗队当帕鲁&#xff0c;有许多题还没有补&#xff08;尤其是牛客&#xff0c;寒假时没有怎么管&#xff0c;现在后悔了qaq),蓝桥杯也快来了&#xff0c;一下子事情多了起来&#xff0c;反而不知道要看什么了&#xff0c;在此先立个flag----蓝…

C#,图论与图算法,计算无向连通图中长度为n环的算法与源代码

1 无向连通图中长度为n环 给定一个无向连通图和一个数n,计算图中长度为n的环的总数。长度为n的循环仅表示该循环包含n个顶点和n条边。我们必须统计存在的所有这样的环。 为了解决这个问题,可以有效地使用DFS(深度优先搜索)。使用DFS,我们可以找到特定源(或起点)的长度…

Acrobat Pro DC 2023:PDF编辑与管理的全新体验

Acrobat Pro DC 2023是一款功能强大且全面的PDF编辑和管理软件&#xff0c;旨在为用户提供卓越的PDF处理体验。以下是关于Acrobat Pro DC 2023软件功能特色的详细介绍&#xff1a; PDF编辑和管理&#xff1a;Acrobat Pro DC 2023拥有强大的PDF编辑功能&#xff0c;可以对PDF文…

Grok-1:参数量最大的开源大语言模型

Grok-1&#xff1a;参数量最大的开源大语言模型 项目简介 由马斯克领衔的大型模型企业 xAI 正式公布了一项重要动作&#xff1a;开源了一个拥有 3140 亿参数的混合专家模型&#xff08;MoE&#xff09;「Grok-1」&#xff0c;连同其模型权重和网络架构一并公开。 此举将 Gro…

南京大学AI考研,宣布改考408!

官网还没通知 附上南大与同层次学校近四年的分数线对比&#xff0c;整体很难 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 如果确定要冲南大的话建议提早调整自己的复习路线&…

Java 学习和实践笔记(41):API 文档以及String类的常用方法

JDK 8用到的全部类的文档在这里下载&#xff1a; Java Development Kit 8 文档 | Oracle 中国

十一、MYSQL 基于MHA的高可用集群

目录 一、MHA概述 1、简介 2、MHA 特点 3、MHA 工作原理&#xff08;流程&#xff09; 二、MHA高可用结构部署 1、环境准备 2、安装MHA 监控manager 3、在manager管理机器上配置管理节点&#xff1a; 4、编master_ip_failover脚本写 5、在master上创建mha这个用户来访…

Android和IOS Flutter应用开发使用 Provider.of 时,可以使用 listen: false 来避免不必要的重建

文章目录 listen: false解释示例 listen: false 使用 Provider.of 时&#xff0c;可以使用 listen: false 来避免不必要的重建 解释 当您使用 Provider.of 获取状态对象时&#xff0c;默认情况下&#xff0c;该对象每次发生变化时都会触发重建该对象所在的组件。这在大多数情…

综合知识篇11-系统性能评价、系统测试考点(2024年软考高级系统架构设计师冲刺知识点总结系列文章)

专栏系列文章: 2024高级系统架构设计师备考资料(高频考点&真题&经验)https://blog.csdn.net/seeker1994/category_12593400.html案例分析篇00-【历年案例分析真题考点汇总】与【专栏文章案例分析高频考点目录】(2024年软考高级系统架构设计师冲刺知识点总结-案例…

百度交易中台之系统对账篇

作者 | 天空 导读 introduction 百度交易中台作为集团移动生态战略的基础设施&#xff0c;面向收银交易与清分结算场景&#xff0c;赋能业务、提供高效交易生态搭建。目前支持百度体系内多个产品线&#xff0c;主要包括&#xff1a;度小店、小程序、地图打车、文心一言等。本文…

【保姆级教程】如何拥有GPT?(Proton邮箱版)

OnlyFans 订阅教程移步&#xff1a;【保姆级】2024年最新Onlyfans订阅教程 Midjourney 订阅教程移步&#xff1a; 【一看就会】五分钟完成MidJourney订阅 GPT-4.0 升级教程移步&#xff1a;五分钟开通GPT4.0 如果你需要使用Wildcard开通GPT4、Midjourney或是Onlyfans的话&am…

HCIA ——VLAN实验

一 、 实验需求 1.PC1和PC3所在接口为access接口&#xff1b;属于vlan 2 PC2-4-5-6处于同一网段&#xff1b;其中PC2可以访问PC4-5-6 PC4可以访问PC5不能访问PC6 PC5不能访问PC6 3.PC1-PC3与PC2-4-5-6不在同一个网段 4.所有PC均使用DHCP获取IP地址&#xff0c;且PC1可以正常访问…

Linux 进程管理工具top ps

概述 top 和 ps 是 Linux 系统中两个非常重要的用于管理和监控进程的命令工具。以下是它们的主要功能和区别&#xff1a; top&#xff1a; 动态视图&#xff1a;top 提供了一个实时动态更新的视图&#xff0c;能够持续显示系统中当前正在运行的进程信息及其资源占用情况。 系统…

【蓝桥杯-单片机】基于定时器的倒计时程序设计

基于定时器的倒计时程序 题目如下所示&#xff1a; 实现过程中遇到的一些问题 01 如何改变Seg_Buf数组的值数码管总是一致地显示0 1 2 3 4 5 首先这个问题不是在main.c中关于数码管显示部分的逻辑错误&#xff0c;就是发生在数码管的底层错误。 检查了逻辑部分&#xff…