GoogLeNet:图像分类神经网络的深度剖析与实践

文章目录


前言

在当今的人工智能领域,图像分类是一个至关重要的研究方向,其在安防监控、医学影像分析、自动驾驶等众多领域都有着广泛的应用。为了实现更精准、高效的图像分类,各种神经网络架构不断涌现。其中,GoogLeNet作为一款具有里程碑意义的网络模型,于2014年由Google团队在ImageNet挑战赛中首次提出,并一举斩获第一名。它独特的设计理念和卓越的性能,为后续的神经网络发展奠定了坚实的基础。本文将深入剖析GoogLeNet的网络结构、创新点、不足之处,并给出相应的代码示例,希望能帮助读者全面了解这一经典的图像分类网络。


GoogLeNet

一、网络基本介绍

GoogLeNet由Google团队于2014年在ImageNet挑战赛中首次提出,在该次图像分类比赛中斩获第一名。它是一种常用于图像分类任务的流行神经网络架构。

其主要特点如下:

  • Inception模块:采用了名为"Inception"的模块结构,该模块将网络结构划分为多个并行分支,每个分支用于捕捉不同尺度的特征。这种设计使得GoogLeNet能更有效地学习图像特征,同时还能减小网络体积。
  • 平均池化技术:运用"平均池化"技术,可在不改变图像尺寸的情况下对图像进行降采样。这有助于GoogLeNet更快地处理图像,还能提高网络的鲁棒性。

1.1 论文信息


1.2 网络结构的表

在论文中给出了网络结构的表,表中包含以下信息:

  • type:每一层的结构。
  • patch size/stride:卷积或者池化的核大小/步长。
  • output size:本层的输出尺寸。
  • depth:这一层的卷积深度,即卷积层数。
  • param:本层的参数量。
  • ops:运算量。

特殊说明:

  • "#1×1" 表示 1×1 卷积核的数量。
  • "#3×3" 表示 3×3 卷积核的数量。
  • "#5×5" 表示 5×5 卷积核的数量。
  • "#3×3 reduce" 和 "#5×5 reduce" 表示在 3×3 和 5×5 卷积之前的降维层中 1×1 滤波器(卷积核)的数量。

Inception 结构中的对应卷积的参数可与 Inception 结构对照查看。


1.3 网络结构图

给出了 GoogLeNet 网络的网络结构图。


二、网络的创新

2.1 Inception结构

Inception结构的思想与之前的卷积思想不同。之前的模型是将不同的卷积层通过串联方式连接,而Inception结构采用串联 + 并联的方式连接卷积层。

Inception结构会对输入图像并行地执行多个卷积运算或池化操作,然后将所有输出结果拼接为一个深度较大的特征图。不同大小卷积核的卷积运算能够获取图像中的不同信息,对这些信息进行处理可以得到更好的图像特征。其结构示例图如下

Inception结构常用于图像分类和识别任务,因为它能有效捕捉图像中的细节信息。其主要优势在于可以高效处理大量数据,且模型参数量相对较少,适合在不同设备上运行。


2.2 1x1卷积核进行降维

Inception结构的四个分支中,每个分支得到的特征矩阵宽和高必须相同,之后将四个分支的特征矩阵沿着深度方向进行拼接。

其中三个分支采用1x1卷积的原因是其具有降维作用。例如,有一个输入特征矩阵为 n × n × 512 n \times n \times 512 n×n×512:

  • 直接卷积情况 :若直接经过64个5x5的卷积核进行卷积,所需参数量为 5 × 5 × 512 × 64 = 819200 5\times5\times512\times64 = 819200 5×5×512×64=819200。
  • 降维卷积情况 :若先经过1x1的卷积(假设1x1卷积核个数为24个)再经过64个5x5的卷积,所需参数量为 1 × 1 × 512 × 24 + 5 × 5 × 24 × 64 = 50688 1\times1\times512\times24+5\times5\times24\times64 = 50688 1×1×512×24+5×5×24×64=50688。

对比可知,经过1x1的卷积降维后,参数量明显减少。


2.3 辅助分类器

辅助分类器通常与主要的分类器结合使用,有助于模型更好地理解图像中的细节和复杂模式,提高模型的泛化能力,使其更准确地预测未知图像。

在GoogLeNet中有两个辅助分类器,分别位于Inception4a和Inception4d。这两个辅助分类器仅用于训练,目的是解决梯度消失问题。分类器的损失值将乘以0.3的权重后加到最终损失中。

两个辅助分类器的结构完全一致,具体如下:

  1. 平均池化层:池化核大小为5x5,步长为3。
  2. 卷积层:128个卷积核,大小为1x1,步长为1。
  3. 全连接层:节点个数为1024个。
  4. Dropout层:在全连接之后接一个dropout,比例为70%,即随机失活70%的神经元。
  5. 全连接输出层:由于数据集是ImageNet,所以输出层有1000个节点,然后经过softmax函数。

需要注意的是,辅助分类器仅在训练时使用,预测时不使用。


三、GoogLeNet的缺点

  1. 计算复杂度高:采用Inception模块致使网络计算复杂度大幅提升,训练速度慢,不适用于对实时性要求较高的应用场景。
  2. 网络结构复杂:网络结构本身较难理解,且辅助分类器的存在增加了调试和优化网络的难度。

综上所述,计算复杂度高和网络结构复杂是GoogLeNet的主要短板。


四、代码示例

python 复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim


# 定义一个名为Inception的类,该类继承自nn.Module,用于构建Inception模块
class Inception(nn.Module):

    # 定义类的构造函数,初始化Inception模块的各个分支
    # in_channels:输入通道数
    # ch1x1:1x1卷积的输出通道数
    # ch3x3red:3x3卷积前的压缩通道数
    # ch3x3:3x3卷积的输出通道数
    # ch5x5red:5x5卷积前的压缩通道数
    # ch5x5:5x5卷积的输出通道数
    # pool_proj:池化投影后的输出通道数
    def __init__(self, in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj):
        # 调用父类的构造函数
        super(Inception, self).__init__()
        # 定义第一个分支,使用1x1卷积
        self.branch1 = BasicConv2d(in_channels=in_channels, out_channels=ch1x1, kernel_size=1)
        # 定义第二个分支,先使用1x1卷积进行通道压缩,再使用3x3卷积
        self.branch2 = nn.Sequential(
                BasicConv2d(in_channels=in_channels, out_channels=ch3x3red, kernel_size=1),
                # 保证输出大小等于输入大小
                BasicConv2d(in_channels=ch3x3red, out_channels=ch3x3, kernel_size=3, padding=1)
                )
        # 定义第三个分支,先使用1x1卷积进行通道压缩,再使用5x5卷积
        self.branch3 = nn.Sequential(
                BasicConv2d(in_channels=in_channels, out_channels=ch5x5red, kernel_size=1),
                # 在官方的实现中,其实是3x3的kernel并不是5x5
                BasicConv2d(in_channels=ch5x5red, out_channels=ch5x5, kernel_size=5, padding=2)  # 保证输出大小等于输入大小
                )
        # 定义第四个分支,先使用3x3最大池化,再使用1x1卷积
        self.branch4 = nn.Sequential(
                nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
                BasicConv2d(in_channels=in_channels, out_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]
        # 使用torch.cat函数将outputs列表中的张量在通道维度(维度1)拼接在一起
        return torch.cat(outputs, 1)


# 定义辅助分类器 InceptionAux类,用于在训练过程中提供额外的监督信息
class InceptionAux(nn.Module):

    # 定义构造函数,初始化辅助分类器的各个层
    # in_channels:输入通道数
    # num_classes:类别数
    def __init__(self, in_channels, num_classes):
        # 调用父类的构造函数
        super(InceptionAux, self).__init__()
        # 定义一个2D平均池化层,池化窗口大小为5x5,步长为3
        self.averagePool = nn.AvgPool2d(kernel_size=5, stride=3)
        # 定义一个基本的2D卷积层,输入通道数为in_channels,输出通道数为128,卷积窗口大小为1x1
        self.conv = BasicConv2d(in_channels, 128, kernel_size=1)
        # 定义一个全连接层,输入特征数为2048,输出特征数为1024
        self.fc1 = nn.Linear(2048, 1024)
        # 定义第二个全连接层,输入特征数为1024,输出特征数为num_classes(类别数)
        self.fc2 = nn.Linear(1024, num_classes)

    # 定义前向传播函数,将输入数据经过各个层得到分类结果
    def forward(self, x):
        # 对输入的x进行平均池化
        x = self.averagePool(x)
        # 对池化后的x进行卷积
        x = self.conv(x)
        # 将卷积后的x按照第1维进行展平
        x = torch.flatten(x, 1)
        # 进行dropout操作,丢弃概率为0.5,防止过拟合
        x = F.dropout(x, 0.5, training=self.training)
        # 使用ReLU进行激活
        x = F.relu(self.fc1(x))
        # 再次进行dropout操作,丢弃概率为0.5
        x = F.dropout(x, 0.5, training=self.training)
        # 通过全连接层得到最终的分类结果
        x = self.fc2(x)
        return x


# 定义一个名为BasicConv2d的类,用于构建基本的卷积层,包含卷积和ReLU激活函数
class BasicConv2d(nn.Module):

    # 定义构造函数,初始化卷积层和ReLU激活函数
    # in_channels:输入通道数
    # out_channels:输出通道数
    # **kwargs:可变关键字参数,用于传递卷积层的其他参数,如卷积核大小、步长等
    def __init__(self, in_channels, out_channels, **kwargs):
        # 调用父类的构造函数
        super(BasicConv2d, self).__init__()
        # 定义一个2D卷积层,输入通道为in_channels,输出通道为out_channels,其他参数通过**kwargs传入
        self.conv = nn.Conv2d(in_channels, out_channels, **kwargs)
        # 定义一个ReLU激活函数
        self.relu = nn.ReLU(inplace=True)

    # 定义前向传播函数,将输入数据经过卷积和ReLU激活
    def forward(self, x):
        # 数据通过卷积层
        x = self.conv(x)
        # 对卷积后的结果进行ReLU激活
        x = self.relu(x)
        return x


# 定义GoogleNet类,构建完整的GoogleNet模型
class GoogleNet(nn.Module):

    # 定义构造函数,初始化GoogleNet模型的各个层
    # num_classes:类别数,默认为1000
    # aux_logits:是否使用辅助分类器,默认为True
    def __init__(self, num_classes=1000, aux_logits=True):
        # 调用父类的构造函数
        super(GoogleNet, self).__init__()
        # 是否使用辅助分类器的标志
        self.aux_logits = aux_logits
        # 第一层卷积层,使用BasicConv2d模块,输入通道数为3,输出通道数为64,卷积核大小为7,步长为2,填充为3
        self.conv1 = BasicConv2d(3, 64, kernel_size=7, stride=2, padding=3)
        # 第一层最大池化层,池化窗口大小为3,步长为2,采用ceil方式计算输出大小
        self.maxpool1 = nn.MaxPool2d(3, stride=2, ceil_mode=True)
        # 第二层卷积层,输入通道数为64,输出通道数为64,卷积核大小为1
        self.conv2 = BasicConv2d(64, 64, kernel_size=1)
        # 第三层卷积层,输入通道数为64,输出通道数为192,卷积核大小为3,填充为1
        self.conv3 = BasicConv2d(64, 192, kernel_size=3, padding=1)
        # 第二层最大池化层,池化窗口大小为3,步长为2,采用ceil方式计算输出大小
        self.maxpool2 = nn.MaxPool2d(3, stride=2, ceil_mode=True)
        # 第四层卷积层,使用Inception模块,输入通道数为192,输出通道数分别为64、96、128、16、32、32
        self.inception3a = Inception(192, 64, 96, 128, 16, 32, 32)
        # 第五层卷积层,使用Inception模块,输入通道数为256,输出通道数分别为128、128、192、32、96、64
        self.inception3b = Inception(256, 128, 128, 192, 32, 96, 64)
        # 第三层最大池化层,池化窗口大小为3,步长为2,采用ceil方式计算输出大小
        self.maxpool3 = nn.MaxPool2d(3, stride=2, ceil_mode=True)
        # 第六层卷积层,使用Inception模块,输入通道数为480,输出通道数分别为192、96、208、16、48、64
        self.inception4a = Inception(480, 192, 96, 208, 16, 48, 64)
        # 第七层卷积层,使用Inception模块,输入通道数为512,输出通道数分别为160、112、224、24、64、64
        self.inception4b = Inception(512, 160, 112, 224, 24, 64, 64)
        # 第八层卷积层,使用Inception模块,输入通道数为512,输出通道数分别为128、128、256、24、64、64
        self.inception4c = Inception(512, 128, 128, 256, 24, 64, 64)
        # 第九层卷积层,使用Inception模块,输入通道数为512,输出通道数分别为112、144、288、32、64、64
        self.inception4d = Inception(512, 112, 144, 288, 32, 64, 64)
        # 第十层卷积层,使用Inception模块,输入通道数为528,输出通道数分别为256、160、320、32、128、128
        self.inception4e = Inception(528, 256, 160, 320, 32, 128, 128)
        # 第四层最大池化层,池化窗口大小为3,步长为2,采用ceil方式计算输出大小
        self.maxpool4 = nn.MaxPool2d(3, stride=2, ceil_mode=True)
        # 第十一层卷积层,使用Inception模块,输入通道数为832,输出通道数分别为256、160、320、32、128、128
        self.inception5a = Inception(832, 256, 160, 320, 32, 128, 128)
        # 第十二层卷积层,使用Inception模块,输入通道数为832,输出通道数分别为384、192、384、48、128、128
        self.inception5b = Inception(832, 384, 192, 384, 48, 128, 128)
        # 如果使用辅助分类器,则添加两个InceptionAux模块,分别输入512和528通道的张量,输出num_classes个结果
        if self.aux_logits:
            self.aux1 = InceptionAux(512, num_classes)
            self.aux2 = InceptionAux(528, num_classes)
        # 使用自适应平均池化层,将输入张量的大小调整为(1, 1)
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        # Dropout层,将输入张量的40%元素设为0,防止过拟合
        self.dropout = nn.Dropout(0.4)
        # 全连接层,输入大小为1024,输出大小为num_classes
        self.fc = nn.Linear(1024, num_classes)

    # 定义前向传播函数,将输入数据经过各个层得到最终的分类结果
    def forward(self, x):
        # 数据通过第一层卷积层
        x = self.conv1(x)
        # 对卷积后的x进行最大池化
        x = self.maxpool1(x)
        # 数据通过第二层卷积层
        x = self.conv2(x)
        # 数据通过第三层卷积层
        x = self.conv3(x)
        # 对卷积后的x进行最大池化
        x = self.maxpool2(x)
        # 数据通过第一个Inception模块
        x = self.inception3a(x)
        # 数据通过第二个Inception模块
        x = self.inception3b(x)
        # 对Inception后的x进行最大池化
        x = self.maxpool3(x)
        # 数据通过第三个Inception模块
        x = self.inception4a(x)
        # 如果在训练阶段并且使用辅助分类器,则通过辅助分类器得到辅助分类结果aux1
        if self.training and self.aux_logits:
            aux1 = self.aux1(x)
        # 数据通过第四个Inception模块
        x = self.inception4b(x)
        # 数据通过第五个Inception模块
        x = self.inception4c(x)
        # 数据通过第六个Inception模块
        x = self.inception4d(x)
        # 如果在训练阶段并且使用辅助分类器,则通过辅助分类器得到辅助分类结果aux2
        if self.training and self.aux_logits:
            aux2 = self.aux2(x)
        # 数据通过第七个Inception模块
        x = self.inception4e(x)
        # 对Inception后的x进行最大池化
        x = self.maxpool4(x)
        # 数据通过第八个Inception模块
        x = self.inception5a(x)
        # 数据通过第九个Inception模块
        x = self.inception5b(x)
        # 对特征图进行全局平均池化,将其变为一维向量
        x = self.avgpool(x)
        # 将二维特征图展平为一维向量
        x = torch.flatten(x, 1)
        # 在向量的维度上添加dropout操作,随机丢弃部分神经元以防止过拟合
        x = self.dropout(x)
        # 通过全连接层得到最终的输出结果,通常用于分类任务
        x = self.fc(x)
        # 如果在训练阶段并且使用了辅助分类器,则返回最终输出、辅助分类结果aux2和aux1
        if self.training and self.aux_logits:
            return x, aux2, aux1
        # 如果不是在训练阶段或者没有使用辅助分类器,则只返回最终输出
        return x


# 实例化GoogleNet模型
model = GoogleNet()
# 创建一个随机输入张量,模拟输入数据
input_tensor = torch.rand(5, 3, 224, 224)
# 设置模型为训练模式
model.train()
# 进行前向传播,得到最终输出和辅助分类结果
output, aux_output2, aux_output1 = model(input_tensor)
# 打印最终输出、辅助分类结果
print(output, aux_output2, aux_output1)
# 创建标签张量,模拟真实标签
labels = torch.tensor([1, 0, 2, 0, 1])
# 定义交叉熵损失函数
criterion = nn.CrossEntropyLoss()
# 定义Adam优化器,用于更新模型的参数
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 计算损失,包括最终输出的损失和辅助分类结果的损失
loss = criterion(output, labels) + 0.3 * criterion(aux_output1, labels) + 0.3 * criterion(aux_output2, labels)
# 打印损失值
print(loss)

总结

本文围绕GoogLeNet展开了全面且深入的探讨。首先介绍了GoogLeNet的基本信息,包括其提出背景、主要特点,如Inception模块和平均池化技术,还展示了论文信息、网络结构表以及网络结构图。接着详细阐述了该网络的创新之处,如Inception结构采用串联 + 并联的方式连接卷积层,能有效捕捉图像细节;1x1卷积核可实现降维,大幅减少参数量;辅助分类器有助于解决梯度消失问题,提高模型泛化能力。然而,GoogLeNet也存在计算复杂度高和网络结构复杂的缺点,这限制了它在实时性要求高的场景中的应用。最后,文章给出了GoogLeNet的代码示例,帮助读者更好地理解其实现方式。通过本文的介绍,读者能够对GoogLeNet有一个系统、清晰的认识。

相关推荐
NAGNIP6 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab7 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab7 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP11 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年11 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼11 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS11 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区13 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈13 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang13 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx