GoogleNet网络介绍及代码撰写详解(总结2)

news2024/10/6 15:31:15

可以从本人以前的文章中可以看出作者以前从事的是嵌入式控制方面相关的工作,是一个机器视觉小白,之所以开始入门机器视觉的学习只要是一个idea,想把机器视觉与控制相融合未来做一点小东西。废话不多说开始正题。(如有侵权立即删稿)

摘要

本文是介绍GoogleNet网络,个人对其的知识总结,网络设计的知识点,以及代码如何撰写,基于pytorch编写代码。作为一个刚入门的小白怎么去学习别人的代码,一步一步的去理解每一行代码,怎么将网络设计变成代码,模仿大佬的代码去撰写。作为小白如有不足之处请批评指正哈。
注:池化窗口全文称为池化核

机器视觉基础知识

在此之前,本人学习了机器视觉的基础知识,基础不牢地动山摇,以下是本人学习时的链接希望对你有所帮助。
卷积神经网络(CNN)
计算机视觉与深度学习 北京邮电大学 鲁鹏 清晰版合集(完整版)

GoogleNet

在网络设计之前需要明白什么是GoogleNet。
以下是我借鉴的文章的参考链接:

经典CNN模型(四):GoogLeNet(PyTorch详细注释版)

GoogLeNet网络


在这里插入图片描述

GoogleNet(也称为Inception v1)是由Google在2014年提出的一种深度卷积神经网络架构。它在ImageNet图像分类挑战赛中表现出色,并引入了一些创新的设计思想。以下是GoogleNet的一些主要特点:

1x1卷积:1x1卷积被广泛用于减少模型的计算量和参数数量,同时保持网络的非线性能力。它用于调整通道数,使得后续层的计算更加高效。

Inception模块:GoogleNet的核心是Inception模块,它通过并行的卷积操作来提取多尺度特征。这些模块使用不同大小的卷积核(如1x1、3x3和5x5)以及最大池化层,以便捕捉图像中的不同特征。

全局平均池化:在最后的分类层,GoogleNet使用全局平均池化代替传统的全连接层,从而减少了模型的参数数量,并降低了过拟合的风险。

辅助分类器:GoogleNet在多个层级添加了辅助分类器,这不仅有助于缓解梯度消失问题,还可以作为正则化手段,提高训练效果。

本人也看了许多人的文章,认为在论文结合方面的讲解不够深入。本文将结合论文进行讲解。对GoogleNet中以上4个创新点进行讲解。

1x1卷积

在这里插入图片描述
1x1卷积的作用将结合上图流程分析它的优势。假设Previous layer输出的是28×28×192

(a)5x5卷积核的参数计算
每个5x5卷积核的参数:
1.卷积核大小:5 × 5
2.深度:192
3.每个卷积核的参数数量 = 5 × 5 × 192 = 4800
4.卷积核的数量:使用32个卷积核,因为输出为32层。
总参数量 = 4800 × 32 = 153600
(b)1x1卷积核的参数计算
1x1卷积核的参数:
1.卷积核大小:1x1
2.深度:192
3.输出个数为16,因此需要16个1x1卷积核
4.总参数量 = 192 × 16 = 3072

后续5x5卷积核的参数:
1.假设5x5卷积核的输入深度已经是经过1x1卷积处理后的深度,这里深度为16
2.每个5x5卷积核参数:5 × 5 × 16 = 400
3.总共使用32个5x5卷积核,则参数量 = 400 × 32 = 12800
4.总参数量将1x1卷积的参数和5x5卷积的参数相加:
总参数量 = 3072 + 12800 = 15872

比较参数量:参数量比 = 15872 ÷ 153600 ≈ 0.1035,计算参数为原来的10%。
这就展示了使用1x1卷积核的优势,可以大幅度减少参数量,减少计算成本。

Inception模块

在这里插入图片描述
论文中是这么概述它的,Inception网络是由上述类型的模块相互堆叠组成的网络,偶尔会有步幅为2的最大池层,以将网格的分辨率减半。出于技术原因(训练过程中的内存效率),仅在较高层开始使用Inception模块,同时以传统的卷积方式保持较低的层似乎是有益的。这并不是绝对必要的,只是反映了我们目前实施中的一些基础设施效率低下。这种架构的主要优点之一是,它允许在每个阶段显著增加单元的数量,而不会在计算复杂性方面出现不受控制的爆炸。降维的普遍使用允许将最后一级的大量输入滤波器屏蔽到下一层,首先降低它们的维度,然后用大的补丁大小对其进行卷积。这种设计的另一个实际有用的方面是,它与视觉信息应该在各种尺度上处理然后聚合的直觉相一致,以便下一阶段可以同时从不同尺度上提取特征。

很迷茫是不是,还是不知道是干嘛的,好!结合论文例子来分析。

在这里插入图片描述
就拿GoogleNet输入的第一个inception (3a)来分析。

在这里插入图片描述
图片输入采用224×224,采用均值相减的RGB颜色通道。#3×3reduce和#5×5reduce代表在3×3和5×5卷积之前使用的1×1个卷积。

在这里插入图片描述
可以从论文的表Table1中看出max_pool后的输出(output_size)为 5 × 5 × 192(这也是上文为什么那它举例),inception (3a) output_size为28× 28 × 256,1 × 1的卷积输出为28 × 28 × 64,在3 × 3之前的1 × 1卷积网络输出结果为28 × 28 × 96,再然后输入到3 × 3卷积中,然后输出 28 × 28 × 128的网络。在5 × 5之前的1 × 1卷积网络输出结果为28 × 28 × 16,再然后输入到5 × 5卷积中,然后输出 28 × 28 × 32的网络。旁边的Max_Pool经过1 × 1的卷积后输出28 × 28 × 32的网络。最终将四个网络拼接(28 × 28 × 64)+(28 × 28 × 128)+(28 × 28 × 32)+(28 × 28 × 32)=28 × 28 × 256这就是inception (3a) output_size的由来。

经过 inception3a,输入的 28×28×192特征图,通过各个分支的卷积和池化操作,最终输出为 28×28×256 的特征图。因此,Inception 模块有效地增加了特征图的通道数,同时保持了空间维度不变。

全局平均池化

没啥好讲的,就是池化。
在这里插入图片描述
在这里插入图片描述

辅助分类器

在这里插入图片描述
在GoogleNet中辅助分类器有两个,主要讲解第1个,第一个明白了,第二个自然就明白了。
在这里插入图片描述
论文中的意思也就是,分类器1的输入为inception4a,分类器2的输入为inception4d。

在这里插入图片描述
由上图得知分类器1的输入为inception4a=14×14×512,分类器2的输入为inception4d=14×14×528
首先得知道卷积计算的输出尺寸公式为:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

论文中说明卷积核采用1×1×128,第一个FC输出为1024,第二个FC输出也就是1000种种类。(本文将其改成了3种蚂蚁、蜜蜂、狗)
首先inception4a=14×14×512经过平均池化层,步长为3,没有零填充,池化核大小为5,代入公式也就是(14-5+0×2)/3+1=4所以池化后的输出网络为4×4×512。再然后进入1×1的卷积中步长为1,没有零填充,代入公式得(4-1+0)/1+1=4。输出网络为4×4×128。再然后将其变成向量,4×4×128=2048×1,经过第一个FC后输出为1024×1,经过第二个FC后输出为1000×1(num_class ×1)。以上就是辅助器分类的具体计算过程。
在这里插入图片描述
最终的损失输出也就是 loss = 辅助分类器1×0.3+辅助分类器2×0.3+ 主损失。

GoogleNet

了解GoogleNet的重要组成部分后开始对其主干进行讲解。结构设计:GoogLeNet 是基于 Inception 模块的,其中包含多种大小的卷积核(1x1、3x3、5x5)并行处理输入特征图。每个卷积层后面一般会加上 ReLU 激活函数,以引入非线性特性。
在这里插入图片描述
在这里插入图片描述
基于以上开始分析。首先我的输入是224x224x3的RGB图像。

1.经过大小为7x7步长为2,零填充为3的卷积核,总共有64个卷积核,(224-7+2x3)/2+1 =112.5输出舍去小数后为112,输出网络为112x112x64。
2.经过大小为3x3步长为2,零填充为0的池化核,(112-3+0/2+1 =55.5输出保留小数后为56,输出网络为56x56x64。
3.经过大小为1x1步长为1,零填充为0的卷积核,总共有64个卷积核,(56-1+0/1+1 =56,输出网络为56x56x64。
4.经过大小为3x3步长为1,零填充为1的卷积核,总共有192个卷积核,(56-3+2x1)/1+1 =56,输出网络为56x56x192。
5.经过大小为3x3步长为2,零填充为0的池化核,(56-3+0/2+1 =27.5输出保留小数后为28,输出网络为28x28x192。
6.接下来的分析就是上文的inception3a的分析和辅助分类器,此部分省略。
7.后续步骤为机械重复以上步骤,不在详细描述

在这里插入图片描述
对照论文中的此图表Table1验证计算是否正确。

软件代码构思

有了以上理论基础后,开始构建代码思路,整体构建思路如下图所示,写代码之前一定要构思好大致思路,代码永远是为你思路框架服务的。
在这里插入图片描述

Model代码撰写


1.初始化,标准参数初始化

这一部分的代码的作用是,首先是关键参数以及头文件的初始化,没有什么好初始化的,只添加了一个cuda初始化。

import argparse
import torch.nn as nn
import torch
import torch.nn.functional
import argparse
from pathlib import Path
# ------------------------------1.初始化,标准参数初始化-------------------------------
def parse_opt():
    parser = argparse.ArgumentParser()
    # --device "cuda:0,cuda:1" 启用多个设备
    parser.add_argument('--device', default='cuda:0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')  # --device cuda:1
    opt = parser.parse_args()
    return opt

opt = parse_opt()  # 调用解析函数
device = torch.device(opt.device)

2.构建卷积激活函数

之所以构建这个函数,本人也是看过他人的代码后才知道GoogLeNet(也称为Inception v1)采用卷积层结合ReLU激活函数的设计。GoogLeNet通过构建卷积层与ReLU激活函数的组合,不仅提升了模型的表达能力和训练效率,还通过稀疏激活机制提高了模型的泛化能力。这些因素共同推动了其在图像分类等任务中的优异表现。
但最终relu效果不是那么理想本人将其换成了LeakyReLU。

# ------------------------2.构建卷积激活函数-----------------------------
class BasicConv2d(nn.Module):
    def __init__(self, in_channels, out_channels, **kwargs):
        super(BasicConv2d, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, **kwargs)
        self.relu = nn.LeakyReLU(inplace=True)

    def forward(self, x):
        x = self.conv(x)
        x = self.relu(x)
        return x

3.构建Inception结构模板

根据上文理论分析,这部分主要分成4块,branch1=卷积1,branch2=卷积1+卷积3,branch3=卷积1+卷积5,branch4 = 池化+卷积1。每一个Inception的输入网络高宽确定(大小同输入图片),但是深度是不确定的,用一个in_channels来存储输入网络深度。由上图也可以看出branch1、2、3、4的输出深度也不一样。所以用ch1x1, ch1x1_3x3(3x3卷积核前的1x1), ch3x3, ch1x1_5x5(5x5卷积核前的1x1), ch5x5, pool_out_channels分别指定输出深度。

#  -------------------------3.构建Inception结构模板------------------
class Inception(nn.Module):
    def __init__(self, in_channels, ch1x1, ch1x1_3x3, ch3x3, ch1x1_5x5, ch5x5, pool_out_channels):
        super(Inception, self).__init__()
        self.branch1 = BasicConv2d(in_channels, ch1x1, kernel_size=1)
        self.branch2 = nn.Sequential(
            BasicConv2d(in_channels, ch1x1_3x3, kernel_size=1),
            BasicConv2d(ch1x1_3x3, ch3x3, kernel_size=3, padding=1)  # 保证输出大小等于输入大小
        )
        self.branch3 = nn.Sequential(
            BasicConv2d(in_channels, ch1x1_5x5, kernel_size=1),
            BasicConv2d(ch1x1_5x5, ch5x5, kernel_size=5, padding=2)  # 保证输出大小等于输入大小
        )
        self.branch4 = nn.Sequential(
            nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
            BasicConv2d(in_channels,pool_out_channels, 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合并四个输出,合并维度为1,即按照通道维度合并
        result = torch.cat(outputs, 1)
        #print("Output shape:", result.shape)  # 打印输出形状
        return result

4.构建InceptionAux辅助分类器模板
# ------------------------4.构建InceptionAux辅助分类器模板-----------------------
class InceptionAux(nn.Module):
    def __init__(self, in_channels, num_classes):
        super(InceptionAux, self).__init__()
        self.averagePool = nn.AvgPool2d(kernel_size=5, stride=3)
        self.conv = BasicConv2d(in_channels, 128, kernel_size=1)  # output[batch, 128, 4, 4]
        self.fc1 = nn.Linear(2048, 1024)
        self.fc2 = nn.Linear(1024, num_classes)

    def forward(self, x):
        x = self.averagePool(x)
        x = self.conv(x)
        x = torch.flatten(x, 1)
        x = torch.nn.functional.dropout(x, 0.4, training=self.training)  #统一用论文中的0.4
        x = torch.nn.functional.relu(self.fc1(x), inplace=True)
        x = torch.nn.functional.dropout(x, 0.4, training=self.training)
        x = self.fc2(x)
        #   N x num_classes
        return x

5.定义GoogLeNet网络

对照上文中的表格以及论文中的网络设计图设计。这一部分注意加入一个判断语句如果在训练模式和aux_logits为1的情况下,返回x, aux2, aux1三个值。self.training and self.aux_logits: return x, aux2, aux1。测试模式是没有aux2, aux1的只有x的输出。

class GoogLeNet(nn.Module):
    def __init__(self, num_classes=1000, aux_logits=True, init_weight=False):
        super(GoogLeNet, self).__init__()
        self.aux_logits = aux_logits

        self.conv1 = BasicConv2d(3, 64, kernel_size=7, stride=2, padding=3)
        self.maxpool1 = nn.MaxPool2d(3, stride=2, ceil_mode=True)  # ceil_mode=True 计算为小数时,向上取整
        self.conv2 = BasicConv2d(64, 64, kernel_size=1)
        self.conv3 = BasicConv2d(64, 192, kernel_size=3, padding=1)
        self.maxpool2 = nn.MaxPool2d(3, stride=2, ceil_mode=True)

        self.inception3a = Inception(192, 64, 96, 128, 16, 32, 32)
        self.inception3b = Inception(256, 128, 128, 192, 32, 96, 64)
        self.maxpool3 = nn.MaxPool2d(3, stride=2, ceil_mode=True)

        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, 160, 320, 32, 128, 128)
        self.maxpool4 = nn.MaxPool2d(2, stride=2, ceil_mode=True)

        self.inception5a = Inception(832, 256, 160, 320, 32, 128, 128)
        self.inception5b = Inception(832, 384, 192, 384, 48, 128, 128)

        #   辅助分类器
        if aux_logits:
            self.aux1 = InceptionAux(512, num_classes)
            self.aux2 = InceptionAux(528, num_classes)

        #   AdaptiveAvgPool2d 自适应全局平均池化
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.dropout = nn.Dropout(0.4)
        self.fc = nn.Linear(1024, num_classes)
        if init_weight:
            self._initialize_weights()

    def forward(self, x):
        # N x 3 x 224 x 224
        x = self.conv1(x)
        # N x 64 x 112 x 112
        x = self.maxpool1(x)
        # N x 64 x 56 x 56
        x = self.conv2(x)
        # N x 64 x 56 x 56
        x = self.conv3(x)
        # N x 192 x 56 x 56
        x = self.maxpool2(x)

        # N x 192 x 28 x 28
        x = self.inception3a(x)
        # N x 256 x 28 x 28
        x = self.inception3b(x)
        # N x 480 x 28 x 28
        x = self.maxpool3(x)
        # N x 480 x 14 x 14
        x = self.inception4a(x)
        # N x 512 x 14 x 14
        if self.training and self.aux_logits:  # eval model lose this layer
            aux1 = self.aux1(x)

        x = self.inception4b(x)
        # N x 512 x 14 x 14
        x = self.inception4c(x)
        # N x 512 x 14 x 14
        x = self.inception4d(x)
        # N x 528 x 14 x 14
        if self.training and self.aux_logits:  # eval model lose this layer
            aux2 = self.aux2(x)

        x = self.inception4e(x)
        # N x 832 x 14 x 14
        x = self.maxpool4(x)
        # N x 832 x 7 x 7
        x = self.inception5a(x)
        # N x 832 x 7 x 7
        x = self.inception5b(x)
        # N x 1024 x 7 x 7

        x = self.avgpool(x)
        # N x 1024 x 1 x 1
        x = torch.flatten(x, 1)
        # N x 1024
        x = self.dropout(x)
        x = self.fc(x)
        # N x 1000 (num_classes)
        if self.training and self.aux_logits:  # eval model lose this layer
            return x, aux2, aux1
        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')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)


6.权重初始化,偏置初始化

    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')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)

model整体代码

import argparse

import torch.nn as nn
import torch
import torch.nn.functional

import argparse
from pathlib import Path
# ------------------------------1.初始化,标准参数初始化-------------------------------
def parse_opt():
    parser = argparse.ArgumentParser()
    # --device "cuda:0,cuda:1" 启用多个设备
    parser.add_argument('--device', default='cuda:0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')  # --device cuda:1
    opt = parser.parse_args()
    return opt

opt = parse_opt()  # 调用解析函数
device = torch.device(opt.device)


# ------------------------2.构建卷积激活函数-----------------------------
class BasicConv2d(nn.Module):
    def __init__(self, in_channels, out_channels, **kwargs):
        super(BasicConv2d, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, **kwargs)
        self.relu = nn.LeakyReLU(inplace=True)

    def forward(self, x):
        x = self.conv(x)
        x = self.relu(x)
        return x

#  -------------------------3.构建Inception结构模板------------------
class Inception(nn.Module):
    def __init__(self, in_channels, ch1x1, ch1x1_3x3, ch3x3, ch1x1_5x5, ch5x5, pool_out_channels):
        super(Inception, self).__init__()
        self.branch1 = BasicConv2d(in_channels, ch1x1, kernel_size=1)
        self.branch2 = nn.Sequential(
            BasicConv2d(in_channels, ch1x1_3x3, kernel_size=1),
            BasicConv2d(ch1x1_3x3, ch3x3, kernel_size=3, padding=1)  # 保证输出大小等于输入大小
        )
        self.branch3 = nn.Sequential(
            BasicConv2d(in_channels, ch1x1_5x5, kernel_size=1),
            BasicConv2d(ch1x1_5x5, ch5x5, kernel_size=5, padding=2)  # 保证输出大小等于输入大小
        )
        self.branch4 = nn.Sequential(
            nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
            BasicConv2d(in_channels,pool_out_channels, 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合并四个输出,合并维度为1,即按照通道维度合并
        result = torch.cat(outputs, 1)
        #print("Output shape:", result.shape)  # 打印输出形状
        return result

# ------------------------4.构建InceptionAux辅助分类器模板-----------------------
class InceptionAux(nn.Module):
    def __init__(self, in_channels, num_classes):
        super(InceptionAux, self).__init__()
        self.averagePool = nn.AvgPool2d(kernel_size=5, stride=3)
        self.conv = BasicConv2d(in_channels, 128, kernel_size=1)  # output[batch, 128, 4, 4]
        self.fc1 = nn.Linear(2048, 1024)
        self.fc2 = nn.Linear(1024, num_classes)

    def forward(self, x):
        x = self.averagePool(x)
        x = self.conv(x)
        x = torch.flatten(x, 1)
        x = torch.nn.functional.dropout(x, 0.4, training=self.training)  #统一用论文中的0.4
        x = torch.nn.functional.relu(self.fc1(x), inplace=True)
        x = torch.nn.functional.dropout(x, 0.4, training=self.training)
        x = self.fc2(x)
        #   N x num_classes
        return x

#   ---------------------------------5.定义GoogLeNet网络--------------------------------------
class GoogLeNet(nn.Module):
    def __init__(self, num_classes=1000, aux_logits=True, init_weight=False):
        super(GoogLeNet, self).__init__()
        self.aux_logits = aux_logits

        self.conv1 = BasicConv2d(3, 64, kernel_size=7, stride=2, padding=3)
        self.maxpool1 = nn.MaxPool2d(3, stride=2, ceil_mode=True)  # ceil_mode=True 计算为小数时,向上取整
        self.conv2 = BasicConv2d(64, 64, kernel_size=1)
        self.conv3 = BasicConv2d(64, 192, kernel_size=3, padding=1)
        self.maxpool2 = nn.MaxPool2d(3, stride=2, ceil_mode=True)

        self.inception3a = Inception(192, 64, 96, 128, 16, 32, 32)
        self.inception3b = Inception(256, 128, 128, 192, 32, 96, 64)
        self.maxpool3 = nn.MaxPool2d(3, stride=2, ceil_mode=True)

        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, 160, 320, 32, 128, 128)
        self.maxpool4 = nn.MaxPool2d(2, stride=2, ceil_mode=True)

        self.inception5a = Inception(832, 256, 160, 320, 32, 128, 128)
        self.inception5b = Inception(832, 384, 192, 384, 48, 128, 128)

        #   辅助分类器
        if aux_logits:
            self.aux1 = InceptionAux(512, num_classes)
            self.aux2 = InceptionAux(528, num_classes)

        #   AdaptiveAvgPool2d 自适应全局平均池化
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.dropout = nn.Dropout(0.4)
        self.fc = nn.Linear(1024, num_classes)
        if init_weight:
            self._initialize_weights()

    def forward(self, x):
        # N x 3 x 224 x 224
        x = self.conv1(x)
        # N x 64 x 112 x 112
        x = self.maxpool1(x)
        # N x 64 x 56 x 56
        x = self.conv2(x)
        # N x 64 x 56 x 56
        x = self.conv3(x)
        # N x 192 x 56 x 56
        x = self.maxpool2(x)

        # N x 192 x 28 x 28
        x = self.inception3a(x)
        # N x 256 x 28 x 28
        x = self.inception3b(x)
        # N x 480 x 28 x 28
        x = self.maxpool3(x)
        # N x 480 x 14 x 14
        x = self.inception4a(x)
        # N x 512 x 14 x 14
        if self.training and self.aux_logits:  # eval model lose this layer
            aux1 = self.aux1(x)

        x = self.inception4b(x)
        # N x 512 x 14 x 14
        x = self.inception4c(x)
        # N x 512 x 14 x 14
        x = self.inception4d(x)
        # N x 528 x 14 x 14
        if self.training and self.aux_logits:  # eval model lose this layer
            aux2 = self.aux2(x)

        x = self.inception4e(x)
        # N x 832 x 14 x 14
        x = self.maxpool4(x)
        # N x 832 x 7 x 7
        x = self.inception5a(x)
        # N x 832 x 7 x 7
        x = self.inception5b(x)
        # N x 1024 x 7 x 7

        x = self.avgpool(x)
        # N x 1024 x 1 x 1
        x = torch.flatten(x, 1)
        # N x 1024
        x = self.dropout(x)
        x = self.fc(x)
        # N x 1000 (num_classes)
        if self.training and self.aux_logits:  # eval model lose this layer
            return x, aux2, aux1
        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')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)


#---------------------------6.权重初始化,偏置初始化-----------------------------------
    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')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)

Train代码撰写


1.初始化,标准参数初始化

这一部分的代码的作用是,首先是关键参数以及头文件的初始化,再然后就是读取jpg文件,以及txt文件的内容,将其分为3类,蚂蚁,蜜蜂,狗,后续品种可以自己添加,jpg,txt训练数据多少都可以由自己添加,但是文件的命名格式必须为1.jpg,1.txt,2.jpg,2.txt才能读取文件。
这一部分的详细描述见:VGG16网络介绍及代码撰写详解(总结1)

import os
import parser
import time
import numpy as np
import torch
import torchvision.transforms as transforms
import PIL
from PIL import Image
from scipy.stats import norm
from sklearn.datasets import images
from torch import nn
from torch.utils.data import Dataset, random_split, DataLoader, ConcatDataset
import torchvision.datasets
from torch import nn
from torch.nn import MaxPool2d, Flatten

import argparse
from pathlib import Path

from  model import GoogLeNet, InceptionAux, Inception, BasicConv2d
# ------------------------------1.初始化,标准参数初始化-------------------------------
def parse_opt():
    parser = argparse.ArgumentParser()  # 创建 ArgumentParser 对象
    parser.add_argument('--epochs', type=int, default=5, help='total training epochs')  # 添加参数
    parser.add_argument('--batch_size', type=int, default=8, help='size of each batch')  # 添加批次大小参数
    parser.add_argument('--learning_rate', type=int, default=0.0002, help='size of learning_rate')  # 添加批次大小参数
    #--device "cuda:0,cuda:1" 启用多个设备
    parser.add_argument('--device', default='cuda:0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')  #--device cuda:1
    # 解析参数
    opt = parser.parse_args()
    return opt

opt = parse_opt()  # 调用解析函数
epochs = opt.epochs              # 训练的轮数
batch_size = opt.batch_size      # 每个批次的样本数量
learning_rate = opt.learning_rate
device = torch.device(opt.device)

class CustomDataset(Dataset):
    def __init__(self, img_dir, label_dir, transform=None):
        self.img_dir = img_dir
        self.label_dir = label_dir
        self.transform = transform
        self.img_labels = self.load_labels()

    def load_labels(self):
        label_map = {'ants': 0, 'bees': 1,'dogs': 2}    #字典label_map 来定义标签与数字之间的映射关系
        labels = []                                     #创建一个空列表 labels,用来存储从文件中读取到的标签的数字编码
        for label_file in os.listdir(self.label_dir):
            with open(os.path.join(self.label_dir, label_file), 'r') as f:  #对于每个标签文件,使用 open() 打开文件并读取第一行内容readline()
                line = f.readline().strip()         #strip() 方法用于去掉行首和行尾的空白字符(包括换行符)
                #从label_map 中获取当前行的标签对应的数字编码。如果当前行的内容不在 label_map 中,则返回-1
                labels.append(label_map.get(line, -1))
        return labels

    def __len__(self):
        return len(self.img_labels)

    def __getitem__(self, idx):
        #使用 os.path.join 将图像目录路径 (self.img_dir) 和图像文件名拼接起来。文件名格式为 idx + 1(即从1开始计数),加上 .jpg 后缀。
        #例如,如果idx为0,则img_path会是 D:\\Pycharm\\Pytorch_test\\数据集\\练手数据集\\train\\ants_image\\1.jpg
        img_path = os.path.join(self.img_dir, str(idx+1) + '.jpg')
        image = Image.open(img_path).convert('RGB')  #打开指定路径的图像文件,并将其转换为 RGB 模式。
        label = self.img_labels[idx]   #列表中获取当前图像的标签,idx 是当前图像的索引

        if self.transform:   #transform变量是在CustomDataset中最终调用的transforms.Compose实现
            image = self.transform(image)

        return image, label

# 定义转换,包括调整大小
transform = transforms.Compose([
    transforms.Resize((224, 224)),  # 调整为 224x224 大小
    transforms.ToTensor(),          # 转换为 Tensor
    torchvision.transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    # 可以添加其他转换,例如归一化等
])

# 初始化数据集
ants_data = CustomDataset(
    img_dir='D:\\Pycharm\\GoogleNet\\dataset\\train\\ants_image',
    label_dir='D:\\Pycharm\\GoogleNet\\dataset\\train\\ants_label',
    transform=transform
)


bees_data = CustomDataset(
    img_dir='D:\\Pycharm\\GoogleNet\\dataset\\train\\bees_image',
    label_dir='D:\\Pycharm\\GoogleNet\\dataset\\train\\bees_label',
    transform=transform
)

dogs_data = CustomDataset(
    img_dir='D:\\Pycharm\\GoogleNet\\dataset\\train\\dogs_image',
    label_dir='D:\\Pycharm\\GoogleNet\\dataset\\train\\dogs_label',
    transform=transform
)

2.将数据集总和并随机打乱并分成训练集测试集

这一部分的详细描述见:VGG16网络介绍及代码撰写详解(总结1)

# 合并数据集
total_data = ants_data+bees_data
total_data = total_data+dogs_data
# 计算训练集和验证集的大小
total_size = len(total_data)
train_size = int(0.7 * total_size)  # 70%
val_size = total_size - train_size    # 30%

train_data_size = train_size
val_data_size = val_size

print("训练集长度:{}".format(train_data_size))
print("测试集长度:{}".format(val_data_size))
# 随机分割数据集
train_data, val_data = random_split(total_data, [train_size, val_size])

# 创建 DataLoader,分成小批量(batches),以便于进行训练和验证
train_dataloader = DataLoader(train_data, batch_size, shuffle=True) #shuffle=True可以随机打乱数据
val_dataloader = DataLoader(val_data, batch_size, shuffle=False)
# 打印数据集大小
print("训练集大小:{}".format(len(train_data)))
print("验证集大小:{}".format(len(val_data)))


3.创建网络模型

#----------------------------3.创建网络模型--------------------------------
My_GoogleNet = GoogLeNet(num_classes=3,aux_logits=True,init_weight=True)              #调用类
My_GoogleNet = My_GoogleNet.to(device)

直接调用即可。


4.训练加测试

这一部分和常规的网络训练步骤一致,但是它的损失将三个损失相加,得到最终损失 loss = loss0 + loss1 * 0.3 + loss2 * 0.3。开启训练模式后,开启了分类器 logits, aux_logits2, aux_logits1 = My_GoogleNet(imgs) 丛网络中直接提取输出。

#------------------------------------4.训练加测试-----------------------
import matplotlib
matplotlib.use('TkAgg')  # 或者尝试 'Qt5Agg',有这行代码会多一个弹窗显示
import matplotlib.pyplot as plt
import torch.nn as nn
import pandas as pd
def train():
    best_accuracy = 0.0
    # (1).损失函数构建
    loss_fn = nn.CrossEntropyLoss()  #计算预测值与真实标签之间的差异
    loss_fn = loss_fn.to(device)     #将模型和数据都放在同一个设备上,GPU

    # (2).优化器
    # #随机梯度下降(Stochastic Gradient Descent)优化器的一种实现。SGD 是一种常见的优化算法
    optimizer = torch.optim.SGD(My_GoogleNet.parameters(), lr=learning_rate)

    # 用于存储损失和准确率
    train_loss = []
    accuracies = []
    test_loss = []

    for i in range(epochs):
        loss_temp = 0  # 临时变量
        print("--------第{}轮训练开始--------".format(i + 1))
        # 训练阶段
        My_GoogleNet.train()  # 设置为训练模式,用来管理Dropout方法:训练时使用Dropout方法,验证时不使用Dropout方法
    # data有两个部分分别是(8,3,224,224),(8,) 一定要注意顺序
        for data in train_dataloader:
            imgs, targets = data
            imgs = imgs.to(device)
            targets = targets.to(device)
            #   将历史损失梯度清零
            optimizer.zero_grad()
            #   参数更新
            #   因为采用了辅助分类器,得到了三个输出(主输出和两个辅助输出)
            logits, aux_logits2, aux_logits1 = My_GoogleNet(imgs)  #训练模式,且开启了分类器 fail
            outputs_GoogleNet = My_GoogleNet(imgs)
            #print("Outputs:", outputs_GoogleNet)
            #   计算三个损失
            loss0 = loss_fn(logits, targets.to(device))
            loss1 = loss_fn(aux_logits1, targets.to(device))
            loss2 = loss_fn(aux_logits2, targets.to(device))
            #   将三个损失相加,得到最终损失
            loss = loss0 + loss1 * 0.3 + loss2 * 0.3

            # 优化器优化模型
            loss.backward()  # 反向传播
            optimizer.step()  # 梯度更新

            # 将当前损失添加到 train_loss 列表
            train_loss.append(loss.item())

#-----------------------测试阶段---------------------------------
        # 测试阶段
        My_GoogleNet.eval()  # 设置为评估模式,关闭Dropout
        total_test_loss = 0
        total_accuracy = 0

        #   验证过程中不计算损失梯度
        with torch.no_grad():
            # 初始化 test_loss 列表
            for data in val_dataloader:
                imgs, targets = data
                imgs = imgs.to(device)
                targets = targets.to(device)
                outputs = My_GoogleNet(imgs)
                loss = loss_fn(outputs, targets)
                # 将当前损失添加到 test_loss 列表
                test_loss.append(loss.item())
                # 找到每个样本的预测类别,然后与真实标签进行比较
                accuracy = (outputs.argmax(1) == targets).sum().item()  # 使用.item()将Tensor转换为Python数值
                total_accuracy += accuracy  # 计算正确预测的数量并累加到 total_accuracy


5.保存数据至.pth文件

                #   如果当前测试集准确率大于历史最优准确率
                if (total_accuracy / val_data_size) > best_accuracy:
                    #   更新历史最优准确率
                    best_accuracy = (total_accuracy / val_data_size)
                    #   保存当前权重
                    torch.save(My_GoogleNet, "GoogleNet_{}.pth".format(2))
                    print("模型已保存")

        # 记录验证损失和准确率
        accuracies.append(total_accuracy / val_data_size)

        print("整体测试集上的正确率:{}".format(total_accuracy / val_data_size))
        print("测试集上的Loss:{}".format(test_loss[-1]))


6.保存损失及正确率至xlsx

方便未来查找数据,打印数据绘制图像,输出至xlsx的数据为accuracies,test_loss,train_loss,随着训练次数的增加,输出个数也在增加。

#-------------------------6.保存损失及正确率至xlsx----------------------

    # 创建一个字典,用于存储数据
    data = {
        'accuracies': accuracies,
        'test_loss': test_loss,
        'train_loss': train_loss
    }

    # 将字典转换为 DataFrame
    df = pd.DataFrame(dict([(k, pd.Series(v)) for k, v in data.items()]))

    # 保存到 Excel 文件
    df.to_excel('GoogleNet.xlsx', index=False)

在这里插入图片描述
在这里插入图片描述
如图所示test_loss随着训练次数不断增加从54变成了60。

读取数据,绘制图像

#读取xlsx数据,xlsx中保存数据方式为列,第一列第一个为accuracies,第二列第一个为test_loss,第三列第一个train_loss,内容分别在列的下方
#读取任意长度内容并分别绘制曲线图片
import pandas as pd
import matplotlib
matplotlib.use('TkAgg')  # 或者尝试 'Qt5Agg'
import matplotlib.pyplot as plt

# 读取 Excel 文件
file_path = 'GoogleNet.xlsx'
data = pd.read_excel(file_path)

# 提取数据
accuracies = data.iloc[1:, 0].astype(float).values  # 第一列,跳过标题
test_loss = data.iloc[1:, 1].astype(float).values    # 第二列,跳过标题
train_loss = data.iloc[1:, 2].astype(float).values   # 第三列,跳过标题

# 创建图形和子图
plt.figure(figsize=(10, 6))

# 绘制 accuracies 曲线
plt.subplot(3, 1, 1)
plt.plot(accuracies, label='Accuracies', color='b')
plt.title('Accuracies over Epochs')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()

# 绘制 test_loss 曲线
plt.subplot(3, 1, 2)
plt.plot(test_loss, label='Test Loss', color='r')
plt.title('Test Loss over Epochs')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

# 绘制 train_loss 曲线
plt.subplot(3, 1, 3)
plt.plot(train_loss, label='Train Loss', color='g')
plt.title('Train Loss over Epochs')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

# 调整布局以避免重叠
plt.tight_layout()

# 显示图形
plt.show()

在这里插入图片描述
在这里插入图片描述
打印的训练数据如图所示。

7.绘制损失和准确率曲线

    # ----------------------------7.绘制损失和准确率曲线---------------------------
    if len(train_loss)>=len(test_loss):
        epochs_range = range(1, len(test_loss) + 1)
    else:
        epochs_range = range(1, len(train_loss)+1)

    plt.figure(figsize=(12, 5))  # 创建一个新的图形窗口,设置图形的大小

    # 训练和验证损失
    plt.subplot(1, 2, 1)  # 第一个图像框
    plt.plot(epochs_range, train_loss[:len(epochs_range)], label='Training Loss')
    plt.plot(epochs_range, test_loss[:len(epochs_range)], label='Validation Loss')
    plt.title('Loss vs Epochs')
    plt.xlabel('Epochs')
    plt.ylabel('Loss')
    plt.legend()

    # 准确率
    plt.subplot(1, 2, 2)
    plt.plot(epochs_range[:len(accuracies)], accuracies[:len(accuracies)], label='Validation Accuracy')
    plt.title('Accuracy vs Epochs')
    plt.xlabel('Epochs')
    plt.ylabel('Accuracy')
    plt.legend()
    plt.tight_layout()
    plt.show()

在这里插入图片描述
输出5次训练图像如图所示。
在这里插入图片描述
也有训练不如意的时候。最好训练次数保持在20次,本人是为了演示训练效果节省时间所以只训练了5次,之所以第5次训练正确率下降本人猜测大概率是学习率的问题。

Train整体代码

import os
import parser
import time
import numpy as np
import torch
import torchvision.transforms as transforms
import PIL
from PIL import Image
from scipy.stats import norm
from sklearn.datasets import images
from torch import nn
from torch.utils.data import Dataset, random_split, DataLoader, ConcatDataset
import torchvision.datasets
from torch import nn
from torch.nn import MaxPool2d, Flatten

import argparse
from pathlib import Path

from  model import GoogLeNet, InceptionAux, Inception, BasicConv2d
# ------------------------------1.初始化,标准参数初始化-------------------------------
def parse_opt():
    parser = argparse.ArgumentParser()  # 创建 ArgumentParser 对象
    parser.add_argument('--epochs', type=int, default=5, help='total training epochs')  # 添加参数
    parser.add_argument('--batch_size', type=int, default=8, help='size of each batch')  # 添加批次大小参数
    parser.add_argument('--learning_rate', type=int, default=0.0002, help='size of learning_rate')  # 添加批次大小参数
    #--device "cuda:0,cuda:1" 启用多个设备
    parser.add_argument('--device', default='cuda:0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')  #--device cuda:1
    # 解析参数
    opt = parser.parse_args()
    return opt

opt = parse_opt()  # 调用解析函数
epochs = opt.epochs              # 训练的轮数
batch_size = opt.batch_size      # 每个批次的样本数量
learning_rate = opt.learning_rate
device = torch.device(opt.device)

class CustomDataset(Dataset):
    def __init__(self, img_dir, label_dir, transform=None):
        self.img_dir = img_dir
        self.label_dir = label_dir
        self.transform = transform
        self.img_labels = self.load_labels()

    def load_labels(self):
        label_map = {'ants': 0, 'bees': 1,'dogs': 2}    #字典label_map 来定义标签与数字之间的映射关系
        labels = []                                     #创建一个空列表 labels,用来存储从文件中读取到的标签的数字编码
        for label_file in os.listdir(self.label_dir):
            with open(os.path.join(self.label_dir, label_file), 'r') as f:  #对于每个标签文件,使用 open() 打开文件并读取第一行内容readline()
                line = f.readline().strip()         #strip() 方法用于去掉行首和行尾的空白字符(包括换行符)
                #从label_map 中获取当前行的标签对应的数字编码。如果当前行的内容不在 label_map 中,则返回-1
                labels.append(label_map.get(line, -1))
        return labels

    def __len__(self):
        return len(self.img_labels)

    def __getitem__(self, idx):
        #使用 os.path.join 将图像目录路径 (self.img_dir) 和图像文件名拼接起来。文件名格式为 idx + 1(即从1开始计数),加上 .jpg 后缀。
        #例如,如果idx为0,则img_path会是 D:\\Pycharm\\Pytorch_test\\数据集\\练手数据集\\train\\ants_image\\1.jpg
        img_path = os.path.join(self.img_dir, str(idx+1) + '.jpg')
        image = Image.open(img_path).convert('RGB')  #打开指定路径的图像文件,并将其转换为 RGB 模式。
        label = self.img_labels[idx]   #列表中获取当前图像的标签,idx 是当前图像的索引

        if self.transform:   #transform变量是在CustomDataset中最终调用的transforms.Compose实现
            image = self.transform(image)

        return image, label

# 定义转换,包括调整大小
transform = transforms.Compose([
    transforms.Resize((224, 224)),  # 调整为 224x224 大小
    transforms.ToTensor(),          # 转换为 Tensor
    torchvision.transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    # 可以添加其他转换,例如归一化等
])

# 初始化数据集
ants_data = CustomDataset(
    img_dir='D:\\Pycharm\\GoogleNet\\dataset\\train\\ants_image',
    label_dir='D:\\Pycharm\\GoogleNet\\dataset\\train\\ants_label',
    transform=transform
)


bees_data = CustomDataset(
    img_dir='D:\\Pycharm\\GoogleNet\\dataset\\train\\bees_image',
    label_dir='D:\\Pycharm\\GoogleNet\\dataset\\train\\bees_label',
    transform=transform
)

dogs_data = CustomDataset(
    img_dir='D:\\Pycharm\\GoogleNet\\dataset\\train\\dogs_image',
    label_dir='D:\\Pycharm\\GoogleNet\\dataset\\train\\dogs_label',
    transform=transform
)
#-----------------------2.将数据集总和并随机打乱并分成训练集测试集-------------------
# 合并数据集
total_data = ants_data+bees_data
total_data = total_data+dogs_data
# 计算训练集和验证集的大小
total_size = len(total_data)
train_size = int(0.7 * total_size)  # 70%
val_size = total_size - train_size    # 30%

train_data_size = train_size
val_data_size = val_size

print("训练集长度:{}".format(train_data_size))
print("测试集长度:{}".format(val_data_size))
# 随机分割数据集
train_data, val_data = random_split(total_data, [train_size, val_size])

# 创建 DataLoader,分成小批量(batches),以便于进行训练和验证
train_dataloader = DataLoader(train_data, batch_size, shuffle=True) #shuffle=True可以随机打乱数据
val_dataloader = DataLoader(val_data, batch_size, shuffle=False)
# 打印数据集大小
print("训练集大小:{}".format(len(train_data)))
print("验证集大小:{}".format(len(val_data)))

#----------------------------3.创建网络模型--------------------------------
My_GoogleNet = GoogLeNet(num_classes=3,aux_logits=True,init_weight=True)              #调用类
My_GoogleNet = My_GoogleNet.to(device)

#------------------------------------4.训练加测试-----------------------
import matplotlib
matplotlib.use('TkAgg')  # 或者尝试 'Qt5Agg',有这行代码会多一个弹窗显示
import matplotlib.pyplot as plt
import torch.nn as nn
import pandas as pd
def train():
    best_accuracy = 0.0
    # (1).损失函数构建
    loss_fn = nn.CrossEntropyLoss()  #计算预测值与真实标签之间的差异
    loss_fn = loss_fn.to(device)     #将模型和数据都放在同一个设备上,GPU

    # (2).优化器
    # #随机梯度下降(Stochastic Gradient Descent)优化器的一种实现。SGD 是一种常见的优化算法
    optimizer = torch.optim.SGD(My_GoogleNet.parameters(), lr=learning_rate)

    # 用于存储损失和准确率
    train_loss = []
    accuracies = []
    test_loss = []

    for i in range(epochs):
        loss_temp = 0  # 临时变量
        print("--------第{}轮训练开始--------".format(i + 1))
        # 训练阶段
        My_GoogleNet.train()  # 设置为训练模式,用来管理Dropout方法:训练时使用Dropout方法,验证时不使用Dropout方法
    # data有两个部分分别是(8,3,224,224),(8,) 一定要注意顺序
        for data in train_dataloader:
            imgs, targets = data
            imgs = imgs.to(device)
            targets = targets.to(device)
            #   将历史损失梯度清零
            optimizer.zero_grad()
            #   参数更新
            #   因为采用了辅助分类器,得到了三个输出(主输出和两个辅助输出)
            logits, aux_logits2, aux_logits1 = My_GoogleNet(imgs)  #训练模式,且开启了分类器 fail
            outputs_GoogleNet = My_GoogleNet(imgs)
            #print("Outputs:", outputs_GoogleNet)
            #   计算三个损失
            loss0 = loss_fn(logits, targets.to(device))
            loss1 = loss_fn(aux_logits1, targets.to(device))
            loss2 = loss_fn(aux_logits2, targets.to(device))
            #   将三个损失相加,得到最终损失
            loss = loss0 + loss1 * 0.3 + loss2 * 0.3

            # 优化器优化模型
            loss.backward()  # 反向传播
            optimizer.step()  # 梯度更新

            # 将当前损失添加到 train_loss 列表
            train_loss.append(loss.item())

#-----------------------测试阶段---------------------------------
        # 测试阶段
        My_GoogleNet.eval()  # 设置为评估模式,关闭Dropout
        total_test_loss = 0
        total_accuracy = 0

        #   验证过程中不计算损失梯度
        with torch.no_grad():
            # 初始化 test_loss 列表
            for data in val_dataloader:
                imgs, targets = data
                imgs = imgs.to(device)
                targets = targets.to(device)
                outputs = My_GoogleNet(imgs)
                loss = loss_fn(outputs, targets)
                # 将当前损失添加到 test_loss 列表
                test_loss.append(loss.item())
                # 找到每个样本的预测类别,然后与真实标签进行比较
                accuracy = (outputs.argmax(1) == targets).sum().item()  # 使用.item()将Tensor转换为Python数值
                total_accuracy += accuracy  # 计算正确预测的数量并累加到 total_accuracy

                # -----------------------5.保存数据至.pth文件---------------------------
                #   如果当前测试集准确率大于历史最优准确率
                if (total_accuracy / val_data_size) > best_accuracy:
                    #   更新历史最优准确率
                    best_accuracy = (total_accuracy / val_data_size)
                    #   保存当前权重
                    torch.save(My_GoogleNet, "GoogleNet_{}.pth".format(2))
                    print("模型已保存")

        # 记录验证损失和准确率
        accuracies.append(total_accuracy / val_data_size)

        print("整体测试集上的正确率:{}".format(total_accuracy / val_data_size))
        print("测试集上的Loss:{}".format(test_loss[-1]))

#-------------------------6.保存损失及正确率至xlsx----------------------

    # 创建一个字典,用于存储数据
    data = {
        'accuracies': accuracies,
        'test_loss': test_loss,
        'train_loss': train_loss
    }

    # 将字典转换为 DataFrame
    df = pd.DataFrame(dict([(k, pd.Series(v)) for k, v in data.items()]))

    # 保存到 Excel 文件
    df.to_excel('GoogleNet.xlsx', index=False)


    # ----------------------------7.绘制损失和准确率曲线---------------------------
    if len(train_loss)>=len(test_loss):
        epochs_range = range(1, len(test_loss) + 1)
    else:
        epochs_range = range(1, len(train_loss)+1)

    plt.figure(figsize=(12, 5))  # 创建一个新的图形窗口,设置图形的大小

    # 训练和验证损失
    plt.subplot(1, 2, 1)  # 第一个图像框
    plt.plot(epochs_range, train_loss[:len(epochs_range)], label='Training Loss')
    plt.plot(epochs_range, test_loss[:len(epochs_range)], label='Validation Loss')
    plt.title('Loss vs Epochs')
    plt.xlabel('Epochs')
    plt.ylabel('Loss')
    plt.legend()

    # 准确率
    plt.subplot(1, 2, 2)
    plt.plot(epochs_range[:len(accuracies)], accuracies[:len(accuracies)], label='Validation Accuracy')
    plt.title('Accuracy vs Epochs')
    plt.xlabel('Epochs')
    plt.ylabel('Accuracy')
    plt.legend()


    plt.tight_layout()
    plt.show()

if __name__ == '__main__':
    parse_opt()
    train()

Detect


1.参数数据初始化

import torch
import torchvision
from PIL import Image
from torch import nn
from torch.nn import MaxPool2d
import argparse
from pathlib import Path
from model import GoogLeNet, InceptionAux, Inception, BasicConv2d
#--------------------------1.参数数据初始化-----------------------------------
def parse_opt():
    parser = argparse.ArgumentParser()  # 创建 ArgumentParser 对象
    parser.add_argument('--Dropout_Val', type=int, default=0.5, help='Dropout_Val number')  # 添加参数
    #--device "cuda:0,cuda:1" 启用多个设备
    parser.add_argument('--device', default='cuda:0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')  #--device cuda:1
    # 解析参数
    opt = parser.parse_args()
    return opt

opt = parse_opt()  # 调用解析函数
Dropout_Val = opt.Dropout_Val
device = torch.device(opt.device)

image_path = "D:\\Pycharm\\GoogleNet\\dataset\\22.jpg"
image = Image.open(image_path)   #注意格式为RGBA
print(image)

image = image.convert("RGB")     #将格式变成RGB
print(image)

#确保你的图像预处理步骤(如归一化)与模型训练时使用的步骤相匹配。通常,GoogleNet 需要将图像归一化到 [0, 1] 范围内,
# 并可能需要减去均值和除以标准差
transform = torchvision.transforms.Compose([
    torchvision.transforms.Resize((224, 224)),
    torchvision.transforms.ToTensor(),
    torchvision.transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

image = transform(image)
print(image.shape)

# 类别标签
class_names = ['ant', 'bee', 'dog']  # 根据实际情况填写


2.搭建GoogleNet网络结构

#-------------------------------------2.搭建GoogleNet网络结构-----------------------------
model = GoogLeNet(num_classes=3,aux_logits=False)              #调用类
model = model.to(device)


3.输出测试结果

#---------------------------3.输出测试结果-----------------------------
model = torch.load("GoogleNet_2.pth")
#模型的权重在GPU上,而输入张量在CPU上。要解决这个问题,你可以将输入张量移动到GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
image = image.to(device)
#模型的权重在CPU上,而输入张量在GPU上。要解决这个问题,你可以将输入张量移动到CPU
#model = torch.load("tudui_0.pth",map_location=torch.device('cpu'))
image = torch.reshape(image,(1,3,224,224))
model.eval()
with torch.no_grad():
    output = model(image)
predicted_index = output.argmax(1).item()  # 获取预测的索引
print(output.argmax(1))
print(output)   #输出结果预测的是第六个类别
print("Predicted index:", predicted_index)
# 输出对应的类别名称
print("Predicted class:", class_names[predicted_index])

model整体代码

import torch
import torchvision
from PIL import Image
from torch import nn
from torch.nn import MaxPool2d
import argparse
from pathlib import Path
from model import GoogLeNet, InceptionAux, Inception, BasicConv2d
#--------------------------1.参数数据初始化-----------------------------------
def parse_opt():
    parser = argparse.ArgumentParser()  # 创建 ArgumentParser 对象
    parser.add_argument('--Dropout_Val', type=int, default=0.5, help='Dropout_Val number')  # 添加参数
    #--device "cuda:0,cuda:1" 启用多个设备
    parser.add_argument('--device', default='cuda:0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')  #--device cuda:1
    # 解析参数
    opt = parser.parse_args()
    return opt

opt = parse_opt()  # 调用解析函数
Dropout_Val = opt.Dropout_Val
device = torch.device(opt.device)

image_path = "D:\\Pycharm\\GoogleNet\\dataset\\22.jpg"
image = Image.open(image_path)   #注意格式为RGBA
print(image)

image = image.convert("RGB")     #将格式变成RGB
print(image)

#确保你的图像预处理步骤(如归一化)与模型训练时使用的步骤相匹配。通常,GoogleNet 需要将图像归一化到 [0, 1] 范围内,
# 并可能需要减去均值和除以标准差
transform = torchvision.transforms.Compose([
    torchvision.transforms.Resize((224, 224)),
    torchvision.transforms.ToTensor(),
    torchvision.transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

image = transform(image)
print(image.shape)

# 类别标签
class_names = ['ant', 'bee', 'dog']  # 根据实际情况填写

#-------------------------------------2.搭建GoogleNet网络结构---------------------------------------------
model = GoogLeNet(num_classes=3,aux_logits=False)              #调用类
model = model.to(device)

#---------------------------3.输出测试结果-----------------------------
model = torch.load("GoogleNet_2.pth")
#模型的权重在GPU上,而输入张量在CPU上。要解决这个问题,你可以将输入张量移动到GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
image = image.to(device)
#模型的权重在CPU上,而输入张量在GPU上。要解决这个问题,你可以将输入张量移动到CPU
#model = torch.load("tudui_0.pth",map_location=torch.device('cpu'))
image = torch.reshape(image,(1,3,224,224))
model.eval()
with torch.no_grad():
    output = model(image)
predicted_index = output.argmax(1).item()  # 获取预测的索引
print(output.argmax(1))
print(output)   #输出结果预测的是第六个类别
print("Predicted index:", predicted_index)
# 输出对应的类别名称
print("Predicted class:", class_names[predicted_index])

实验结果

在这里插入图片描述

问题总结

训练时刚进入卷积时正常。
在这里插入图片描述
但是出卷积后输出不正常为nan。
在这里插入图片描述

进入卷积前输出为 nan(Not a Number)通常表示在计算过程中发生了数值不稳定或错误。
学习率过高:
• 如果学习率设置得过高,可能会导致梯度爆炸,进而产生 nan 值。
• 解决方法:尝试降低学习率,比如将其减半。
综上将学习率减小就没问题了。
以上就是本人的心得与总结,如有不足之处请多多包涵。
百度网盘链接代码权值及训练图片: https://pan.baidu.com/s/17U5L0KRWLcDPoV5I9Ihfeg?pwd=5sw9
提取码: 5sw9

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

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

相关文章

思维导图工具,轻松搞定复杂问题!

一提到思维导图,想必大家都不会陌生;它能帮助我们更好地梳理思路,让复杂的想法变得清晰可见;而随着互联网的普及,在线思维导图工具更是成为了我们日常工作和学习的得力助手;今天,我就来给大家推…

深入解析DPU:AI时代的芯片分工与超级网卡

引言 在当今AI时代,计算需求的爆炸性增长使得传统数据中心的架构面临着巨大的挑战。数据处理任务越来越复杂,尤其是在大型AI模型如GPT和BERT的应用中,如何提高数据中心的计算和传输效率成为关键问题。传统的CPU(中央处理器&#…

从零开始讲PCIe(10)——事务层介绍

一、事务层概述 事务层在响应软件层的请求时,会生成出站数据包。同时,它也会检查入站数据包,并将其中包含的信息传递到软件层。事务层支持非发布事务的分割事务协议,能够将入站的完成数据包与之前传输的非发布请求相关联。该层处理…

开发人员智能助手

据说AI可以代替开发人员,目前看暂时不用担心,不仅不担心,还要主动使用它。 过去有了问题需要自己各网站搜索,真真假假的东西太多,很难找到正确答案,现在可以问智能体了: 1、JAVA开发大师 htt…

【C++】输入输出缺省参数

大家好,我是苏貝,本篇博客带大家了解C的缺省参数,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️ 目录 一. 输入&输出二. 缺省参数1 缺省参数概念2 缺省参数分类a. 全缺省参数b. 半缺省参数…

Top4免费音频剪辑软件大比拼,2024年你选哪一款?

现在我们生活在一个数字化的时代,音频内容对我们来说很重要。不管是给自己拍的视频配背景音乐、整理开会时的录音,还是自己写歌,有个好用的音频剪辑软件都特别重要。今天,我要给大家介绍几款特别好用的音频剪辑软件免费的&#xf…

Vue82 路由器的两种工作模式 以及 node express 部署前端

笔记 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。hash模式: 地址中永远带着#号,不美观 。若以后将地址通过第三方手机app分享…

教育技术革新:SpringBoot在线教育系统开发

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及,互联网成为人们查找信息的重要场所,二十一世纪是信息的时代,所以信息的管理显得特别重要。因此,使用计算机来管理微服务在线教育系统的相关信息成为必然。开…

【性能测试】使用JMeter性能工具做测试的基本过程及案例分析

前言 通过精心设计的性能测试,我们不仅能够了解服务端的综合效能与承载极限,更能前瞻性地评估现有业务架构在面对当前运营需求及未来业务扩张时的适应性与弹性,从而精准规划服务配置的优化路径。 使用jmeter一般用于以下两种类型的性能测试…

Dart基础入门

Dart 是一种由 Google 开发的开源编程语言,它可以用来构建服务器、Web 应用和移动应用。Dart 语言设计得既现代化又易于学习,它支持面向对象编程(OOP)、函数式编程风格,并且具有类似于 Java 或 C# 的语法。Dart 语言和…

性能测试学习2:常见的性能测试策略(基准测试/负载测试/稳定性测试/压力测试/并发测试)

一.基准测试 1)概念 狭义上讲:就是单用户测试。测试环境确定后,对业务模型中的重要业务做单独的测试,获取单用户运行时的各项性能指标。 广义上:是一种测量和评估软件性能指标的活动。可以在某个时刻通过基准测试建立…

[MASM] masm32的下载及详细安装使用过程(附有下载文件)

目录 下载 配置环境 编译 链接 masm的安装和环境配置 下载链接在文末 下载 下载安装包后复制到虚拟机win7系统的桌面 win7安装到虚拟机的步骤: [win7] win7系统的下载及在虚拟机中详细安装过程(附有下载文件)-CSDN博客 双击压缩包&…

微信消息转发(微信消息转发方案的取舍)

机构可能会有很多的粉丝群,老师有很多家长群,比如发送通知 如果一个个的群发送那么就很繁琐。所以就有一键群发的需求。只需要在一个群发送内容主动同步到其他群。 微信消息转发方案取舍 方案1. 因为微信有本地数据库SQLite, 消息会保存到本…

Redis安装RedisBloom插件

Redis安装RedisBloom插件 1. 下载RedisBloom2. 安装RedisBloom3. Redis 安装RedisBloom4. 验证是否安装成功5. 其他安装方法5.1 使用 Docker 安装 RedisBloom5.2 通过 RedisStack 安装 RedisBloom 是一个 Redis 模块,它提供了一种高效的方式来存储和检索大数据集中的…

频繁full gc问题排查及解决

为什么我们要对频繁full gc的情况进行处理---》频繁full gc会导致stw,影响用户体验。 (1)先进行问题的排查 如果频繁full gc 会报警,公司有自己的监控平台,可以查看full gc的情况 如果公司没有自己的监控平台&#…

爬虫案例——爬取情话网数据

需求: 1.爬取情话网站中表白里面的所有句子(表白词_表白的话_表白句子情话大全_情话网) 2.利用XPath来进行解析 3.使用面向对象形发请求——创建一个类 4.将爬取下来的数据保存在数据库中 写出对应解析语法 //div[class"box labelbo…

【实战篇】自增主键为什么不是连续的?

背景 由于自增主键可以让主键索引尽量地保持递增顺序插入,避免了页分裂,因此索引更紧凑。 之前我见过有的业务设计依赖于自增主键的连续性,也就是说,这个设计假设自增主键是连续的。但实际上,这样的假设是错的&#…

Linux高阶——Github本地仓库与云端仓库关联

1、安装代理软件 steam 选择Github和系统代理模式,一键加速即可 2、 安装Git 3、访问Github网站,创建新用户 4、Github探索 (1)Explore探索标签 (2)工程结构 用户名/仓库名 自述文件,用markdo…

C语言复习概要(三)

本文 使用Visual Studio进行调试的技巧与函数递归详解1. 引言2. Visual Studio 调试技巧2.1. 断点的使用2.1.1. 基本断点示例:设置基本断点 2.1.2. 条件断点示例:条件断点 2.2. 逐步执行代码示例:逐步执行代码 2.3. 监视变量使用监视窗口 2.4…

希捷8T硬盘exfat变0字节的恢复方法

最近流行的3.5寸大容量台式硬盘移动盒子是一种性价比较高的组合,为了方便如涉及到跨平台(win和mac),大多数此类组合选择了exfat文件系统。下边这个案例就是我们经常遇到的exfat变0字节。 故障存储: ST8000HKVS002 8T/exfat 文件…