GoogLeNet网络

学习up主:炮哥带你学

网络诞生背景

在这里插入图片描述
该网络的创新点在于Inception模块,学懂了该模块,整个网络就是paper tiger了,不要被它的庞大所吓到了,越大,越有一定的规律隐含其中。

网络架构介绍

池化操作,通道数不改变,改变特征图大小
inception,不改变特征图大小,改变通道数

详解Incption块

在这里插入图片描述
之所以在4个路径中通过填充,步长来让输出特征图的宽,高一致,就是为了后续的通道合并操作。否则便没有办法进行合并,在这里先埋一个小伏笔,后续实战的时候,会提到这个宽高不一致的情况下,它会不会报错呢?

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
弄明白了Inception块,后续直接进行调用即可

全局平均池化层

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
这里注意一下,这一网络层叫做全局平均池化层(GAP)但pytorch框架下,却没有这个函数,用的而是
在PyTorch中,全局平均池化层通常指的是torch.nn.AdaptiveAvgPool2d层,当输出尺寸设置为(1, 1)时,它将执行全局平均池化。这个层可以自动适应输入特征图的尺寸,无论输入特征图的大小如何,最终都会输出一个大小为(1, 1)的特征图,这个特征图是输入特征图上所有元素的平均值。没有下面这个哦
GlobalAveragePoolingLayer(GAP)

因为信息丢失比较严重,后续该部分在网络中用的比较少

网络参数详解

不难发现:11的卷积核都是步长为1,填充为0,最大池化层做了个下采样(感受野大小为33,步幅为2,填充为1)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
既然有这么多的Inception,一个一个去写是不是很麻烦呢,那直接定义一个块,然后调用岂不是非常的快呢?

Inception模块代码
##搭建Inception,定义一个Inceptin模块,后续可反复调用
class Inception(nn.Module):
    #之所以加in_chanels 是因为基本结构都一样,不一样就在输入的通道数不同,c1表示卷积核的个数
    def __init__(self,in_channels,c1,c2,c3,c4):
        super(Inception,self).__init__()
        self.ReLU = nn.ReLU()

        ##定义剩下的网络各层
        #路径1 单个1*1卷积层(步幅为1,填充为0,都是默认参数,可以不用去写)p1_1中的_1表示的第几个卷积
        self.p1_1 = nn.Conv2d(in_channels=in_channels,out_channels=c1,kernel_size=1)

        #路径2 单1*1卷积和单3*3卷积
        self.p2_1 = nn.Conv2d(in_channels=in_channels,out_channels=c2[0],kernel_size=1)
        self.p2_2 = nn.Conv2d(in_channels=c2[0], out_channels=c2[1], kernel_size=3,padding=1)

        # 路径3 单1*1卷积和单5*5卷积
        self.p3_1 = nn.Conv2d(in_channels=in_channels, out_channels=c3[0], kernel_size=1)
        self.p3_2 = nn.Conv2d(in_channels=c3[0], out_channels=c3[1], kernel_size=5,padding=2)

        #路径4 单最大池化层3*3,单1*1卷积层,池化层不改变通道数,因此不需要有输出通道参数,只需要关注感受野大小,步长和填充情况
        self.p4_1 = nn.MaxPool2d(kernel_size=3,padding=1)
        self.p4_2 = nn.Conv2d(in_channels=in_channels, out_channels=c4, kernel_size=1)
        #定义前向传播过程,将各个网络串在一起,和之前的VGG,LeNet,AlexNet是单路线(x直接一下到底),注意区别,独立变量进行保存每条支路的输出
    def forward(self,x):
        #p1为路径1的输出,p2为路径2的输出,后续同理,最大池化层后是没有激活函数的
        p1 = self.ReLU(self.p1_1(x))
        p2 = self.ReLU(self.p2_2(self.ReLU(self.p2_1(x))))
        p3 = self.ReLU(self.p3_2(self.ReLU(self.p3_1(x))))
        p4 = self.ReLU(self.p4_2(self.p4_1(x)))
        #返回通道融合之后的结果:dim=1,表示按照通道维度进行融合
        return torch.cat((p1, p2, p3, p4), dim=1)
这里埋一个小问题,后续进行介绍

权重初始化

在之前VGG16的网络模型中说过,由于VGG网络过深的原因,因此进行了权重初始化,使得避免梯度消失,爆炸的情况,使得过拟合出现。

#进行权重初始化
        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)
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)

在PyTorch中,nn.init.kaiming_normal_是一种权重初始化方法,它是He初始化(也称为Xavier初始化)的一个变种。这种初始化方法特别适用于使用ReLU(Rectified Linear Unit)激活函数的神经网络层。以下是这行代码的详细解释:
● m.weight:这是要初始化的模型层的权重参数。m是一个神经网络层的实例,weight属性是该层的权重矩阵。
● mode=‘fan_out’:这个参数指定了计算扇出(fan-out)的方式。扇出是指每个输入单元连接到多少个输出单元。在’fan_out’模式下,初始化的方差与输出单元的数量成正比,这有助于保持网络中前向传播的信号强度。
● nonlinearity=‘relu’:这个参数指定了激活函数的类型。在这里,'relu’表示使用ReLU激活函数。Kaiming初始化会根据激活函数的类型调整权重的分布,以确保在训练开始时网络的隐藏层有适当的激活水平。
这行代码的作用是,使用Kaiming初始化方法,为模型层m的权重参数weight进行初始化,使其更适合与ReLU激活函数配合使用。这通常有助于加速收敛并提高训练效果。
在这里插入图片描述
总结来说,如果你的神经网络使用了ReLU或其变体作为激活函数,Kaiming初始化是一个更合适的选择。而如果你的网络使用了tanh或sigmoid这样的线性激活函数,Xavier初始化可能更合适。然而,值得注意的是,即使参数初始化得当,模型在训练过程中仍然可能出现梯度不均衡的问题,这就需要其他策略来进一步处理。

GoogLeNet网络总结

在这里插入图片描述

实战部分

GoogLeNet模型搭建

##导入基本的库
import torch
from torch import nn
from torchsummary import summary

##搭建Inception,定义一个Inceptin模块,后续可反复调用
class Inception(nn.Module):
    #之所以加in_chanels 是因为基本结构都一样,不一样就在输入的通道数不同,c1表示卷积核的个数
    def __init__(self,in_channels,c1,c2,c3,c4):
        super(Inception,self).__init__()
        self.ReLU = nn.ReLU()
        ##定义剩下的网络各层
        #路径1 单个1*1卷积层(步幅为1,填充为0,都是默认参数,可以不用去写)p1_1中的_1表示的第几个卷积
        self.p1_1 = nn.Conv2d(in_channels=in_channels,out_channels=c1,kernel_size=1)

        #路径2 单1*1卷积和单3*3卷积
        self.p2_1 = nn.Conv2d(in_channels=in_channels,out_channels=c2[0],kernel_size=1)
        self.p2_2 = nn.Conv2d(in_channels=c2[0], out_channels=c2[1], kernel_size=3,padding=1)

        # 路径3 单1*1卷积和单5*5卷积
        self.p3_1 = nn.Conv2d(in_channels=in_channels, out_channels=c3[0], kernel_size=1)
        self.p3_2 = nn.Conv2d(in_channels=c3[0], out_channels=c3[1], kernel_size=5,padding=2)

        #路径4 单最大池化层3*3,单1*1卷积层,池化层不改变通道数,因此不需要有输出通道参数,只需要关注感受野大小,步长和填充情况
        #MaxPool2d的stride 不写的话,默认是3,使得结果出错
        self.p4_1 = nn.MaxPool2d(kernel_size=3,padding=1)
        self.p4_2 = nn.Conv2d(in_channels=in_channels, out_channels=c4, kernel_size=1)

    #定义前向传播过程,将各个网络串在一起,和之前的VGG,LeNet,AlexNet是单路线(x直接一下到底),注意区别,独立变量进行保存每条支路的输出
    def forward(self,x):
        #p1为路径1的输出,p2为路径2的输出,后续同理,最大池化层后是没有激活函数的
        p1 = self.ReLU(self.p1_1(x))
        p2 = self.ReLU(self.p2_2(self.ReLU(self.p2_1(x))))
        p3 = self.ReLU(self.p3_2(self.ReLU(self.p3_1(x))))
        p4 = self.ReLU(self.p4_2(self.p4_1(x)))
        #返回通道融合之后的结果:dim=1, 看一下各通道的尺寸情况,最后两个表示特征图的大小,只有特征图大小一致,才进行通道融合呢
        #print (p1.shape,p2.shape,p3.shape,p4.shape),这一步单纯是看能进行通道融合的不

        return torch.cat((p1, p2, p3, p4), dim=1)
##搭建GoogLeNet模型
##模型初始化,局部归一化忽略掉,
class GoogLeNet(nn.Module):
    ##注意这块需要将Inception输入进去,方便进行调用
    def __init__(self,Inception):
        super(GoogLeNet,self).__init__()
        #定义第一块,我们的示例是灰度图,池化层只关注感受野大小,步长和填充,卷积层之后的激活函数不要忘
        self.b1 = nn.Sequential(
            nn.Conv2d(in_channels=1,out_channels=64,kernel_size=7,stride=2,padding=3),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
        # 定义第二块
        self.b2 = nn.Sequential(
            nn.Conv2d(in_channels=64, out_channels=64, kernel_size=1),
            nn.ReLU(),
            nn.Conv2d(in_channels=64, out_channels=192, kernel_size=3,padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
        # 定义第三块,两个Inception块的串接+最大池化层,注意的是输入对应的参数即可
        ##Inception块是这样定义的def __init__(self,in_channels,c1,c2,c3,c4):
        #需要我们输入5个参数,需要注意的是c2,c3是一个元胞数组,in_channels表示输入的特征图的通道数,c1是路径1的(输出通道数)卷积核个数
        ##c2[0]表示路径2的第一层的输出通道数,c2[1]表示路径2的第二层输出通道数,c3也是如此,c4表示路径4的卷积层输出通道数,通道数只和卷积核个数有关,池化层不改变通道数,因此原理还是需要好好学的
        #通道融合作为下一个Inception的输入通道数,参数找卷积核个数即可
        self.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))
        #定义第4块,5个Inception块+一个最大池化层
        self.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, (128, 288), (32, 64), 64),
            Inception(528, 256, (160, 320), (32, 128), 128),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
        #定义第5块,2个Inception模块+全局平均池化模块(AdaptiveAvgPool2d)   GlobalAveragePoolingLayer(GAP)
        self.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(),
            nn.Linear(1024,10))

        #进行权重初始化
        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)
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
    #前向传播,注意对齐,缩进
    def forward(self,x):
       x = self.b1(x)
       x = self.b2(x)
       x = self.b3(x)
       x = self.b4(x)
       x = self.b5(x)
       return x
##进行模型的测试
if __name__ == "__main__":
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    #模型实例化,与其他的有所不同,注意区别,模型定义的时候是这样的:def __init__(self,Inception):,因此需要传入参数一起放在设备中
    model = GoogLeNet(Inception).to(device)
    print(summary(model,(1,224,224)))

不难发现,当Inception块定义好了之后,只需要关注卷积核个数就可以,然后输入参数即可。

注意有了新加的模块,网络定义初始化的写法,后续有别的模块,也可以按照类似的方法去写
 def __init__(self,Inception):
model = GoogLeNet(Inception).to(device)

模型训练

#加载数据,并进行可视化,loss,准确率等的展示
import copy
import time

import pandas as pd
import torch
from torchvision.datasets import FashionMNIST
from torchvision import transforms
import torch.utils.data as Data
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
#从model.py导入我们的模型,Inception 也需要导入进来
from model import GoogLeNet,Inception

#训练集和验证集的定义,名称尽可能一目了然,用于处理训练集和验证集
def train_val_data_process():
    ##数据加载进来,transforms.Compose((transforms.Resize(size=28),大小设置按照模型的输入要求来,AlexNet网络的大小是227
    train_data = FashionMNIST(root='./data',
                              train=True,
                              transform=transforms.Compose((transforms.Resize(size=224), transforms.ToTensor())),
                              download=True)
    #把数据集划分为训练集和验证集=8:2
    train_data,val_data = Data.random_split(train_data,[round(0.8*len(train_data)),round(0.2*len(train_data))])
    #先放入数据,一批次数量(数据量比较小,6G运存应该是够的),shuffle是否打乱数据,batch size看GPU情况,这个参数比较少应该可以的。
    train_dataloader = Data.DataLoader(dataset=train_data,
                                       batch_size=32,
                                       shuffle=True,
                                       num_workers=2)
    ##最好训练和验证集的操作是一样的
    val_dataloader = Data.DataLoader(dataset=val_data,
                                       batch_size=32,
                                       shuffle=True,
                                       num_workers=2)
    return train_dataloader , val_dataloader   #返回训练集和验证集

    #模型训练过程,传入模型,训练,验证集,训练的轮次
def train_model_process(model,train_dataloader,val_dataloader,num_epochs):
    #设定训练所用到的设备,有GPU用GPU,没有GPU用CPU
    #告诉设备情况,这一步用到了torch,就需要在前面进行导入,就是这么一个过程,一开始不知道都用那些库,写着需要了就进行导入
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    #使用Adam优化器,便于后续的参数进行更新的,lr表示为学习率,Adam可以理解是梯度下降法的一种优化改良
    optimizer = torch.optim.Adam(model.parameters(),lr=0.001)
    #定义损失函数,在分类问题中一般损失函数用交叉熵损失函数,回归一般用均方差损失函数,来利用损失值来更新参数w和b
    criterion = nn.CrossEntropyLoss()
    #将模型放入到训练设备中
    model = model.to(device)

    #复制当前模型的参数,w,b会有一个初始化的值,随着反向传播的进行,w,b在进行不断的更新。后续把 best_model_wts保存下来(也即是权重)后续测试时可以进行加载

    best_model_wts = copy.deepcopy(model.state_dict())
    ##初始化参数
    ##最高准确度,初始化为0,后续会覆盖掉
    best_acc = 0.0

    #训练集损失值列表,改的地方
    # 训练集损失值列表
    train_loss_all = []
    # 验证集损失值列表
    val_loss_all = []
    # 训练集精度值列表
    train_acc_all = []
    # 验证集精度值/准确度列表
    val_acc_all = []
    #保存当前时间,方便知道一轮会用时多久,time.time()不要自己全部手敲,会报错,就time.等弹出选择即可
    since = time.time()

    #利用循环进行训练,打印信息的阶段
    for epoch in range(num_epochs):
        print("Epoch{}/{}".format(epoch,num_epochs-1))   #减1的原因在于,是从0开始99结束,共100轮(如果以100为例情况下)
        print("-"*10)

    #初始化参数,方便每一轮的开始值都是0,从而可以计算每一轮的loss和准确率,需要计算平均loss值,就需要定义样本数量
        #训练集损失函数
        train_loss = 0.0
        #训练集准确度
        train_corrects = 0
        # 验证集损失函数
        val_loss = 0.0
        # 验证集准确度
        val_corrects = 0

        #定义该轮次的训练集样本数量
        train_num = 0
        #验证集样本数量
        val_num = 0

        #取数据过程,模型和数据需要放在同一个设备,否则会出错
        #对每一个mini-batch训练和计算
        for step, (b_x, b_y) in enumerate(train_dataloader):
            #将特征放入到训练设备中
            b_x = b_x.to(device)
            #将标签值放入到训练设备中
            b_y = b_y.to(device)
            #设置模型的训练模式
            model.train()

            #前向传播过程,输入为一个batch,输出为一个batch中对应的预测,output不是最终的结果而是一个向量
            output = model(b_x)

            #使用softmax取概率最大的作为标签,查找每一行中最大值对应的行标(类别)
            pre_lab = torch.argmax(output,dim=1)
            #利用模型的输出和标签来计算损失,不是通过softmax之后的结果和标签计算哦
            loss = criterion(output,b_y)

            #每一轮开始前将梯度初始化为0,防止梯度累计从而对参数更新产生干扰,只有这样每一轮结束之后才能更新参数w和b
            optimizer.zero_grad()
            #反向传播计算
            loss.backward()
            #根据网络反向传播的梯度信息来更新网络参数,以起到降低loss值函数计算值的作用,利用Adam优化器进行更新参数
            optimizer.step()
            #对损失函数进行累加
            train_loss += loss.item() * b_x.size(0)
            #如果预测正确,则准确度train_corrects+1
            train_corrects += torch.sum(pre_lab == b_y.data)
            #当前该轮次用于训练的样本数量
            train_num += b_x.size(0)

            #模型验证代码部分撰写,基本和训练过程是差不多的操作,是不参与模型的训练,单纯计算loss和准确度,没有反向传播过程
            # 对每一个mini-batch训练和计算,直到最后一批次结束,意味着一轮结束。
        for step, (b_x, b_y) in enumerate(val_dataloader):
            #将特征放入到验证设备中
             b_x = b_x.to(device)
           #将标签值放入到验证设备中
             b_y = b_y.to(device)
            #设置模型的评估模式
             model.eval()
            #前向传播过程,输入为一个batch,输出为一个batch中对应的预测
             output = model(b_x)
              # 使用softmax取概率最大的作为标签,查找每一行中最大值对应的行标(类别)
             pre_lab = torch.argmax(output, dim=1)
             # 利用模型的输出和标签来计算损失,不是通过softmax之后的结果和标签计算哦
            ##相比较于训练过程,是没有反向传播,梯度更新,参数更新的过程。
             loss = criterion(output, b_y)
              #对损失函数进行累加,批次的平均loss*批次数量
             val_loss += loss.item() * b_x.size(0)
             #如果预测正确,则准确度train_corrects+1
             val_corrects += torch.sum(pre_lab == b_y.data)
             #当前用于评估/评估的样本数量
             val_num += b_x.size(0)

              #计算并保存每一次迭代(轮次epoch?)的平均loss和准确度,后续需要进行打印或者绘图
        train_loss_all.append(train_loss / train_num)
        #计算并保存训练集的准确率
        train_acc_all.append(train_corrects.double().item() / train_num)
        # 计算并保存验证集的loss值
        val_loss_all.append(val_loss / val_num)
        # 计算并保存验证集的准确率
        val_acc_all.append(val_corrects.double().item() / val_num)

              ##将上述结果进行打印[-1]表示取当前轮次的数值,print缩进的话,把内部训练的每一次都会进行打印的。
        print('{} Train Loss: {:.4f} Train Acc: {:.4f}'.format(epoch,train_loss_all[-1],train_acc_all[-1]))
        print('{} Val Loss: {:.4f} Val  Acc: {:.4f}'.format(epoch,val_loss_all[-1], val_acc_all[-1]))

        #寻找最高准确度(当前准确度和最佳准确度进行比较,有点排序算法的感觉)
        if val_acc_all[-1] > best_acc:
              #保存当前的最高准确度
               best_acc = val_acc_all[-1]
               #在当前最高的准确度情况下,保存对应的模型权重
               best_model_wts = copy.deepcopy(model.state_dict())
            #训练耗费的时间
        time_use = time.time() - since
        print("训练和验证耗费的时间{:.0f}m{:.0f}s".format(time_use//60,time_use%60))

    #选择最优参数
    #加载最高准确率下的模型参数,并进行保存到一个文件地址,项目文件夹下产生一个best_model.pth,.pth是权重的独特后缀
    #model.load_state_dict(best_model_wts),这行代码是没有用的。下面的两种保存权重方法都是可以的,权重的路径改一下,别一下跑到LeNet的去了
    torch.save(best_model_wts, 'C:/Users/mcg/Desktop/GoogLeNet/best_model.pth')
    #torch.save(model.state_dict(best_model_wts), 'C:/Users/mcg/Desktop/LeNet/best_model.pth')
    #原先的,这样是不行的,torch.save(model.load_state_dict(best_model_wts),'C:/Users/mcg/Desktop/LeNet/best_model.pth')

    #将训练的数值保存为一种文件格式,最重要的就是训练完之后,会保存最优模型的权重。
    train_process = pd.DataFrame(data={"epoch":range(num_epochs),
                                       "train_loss_all":train_loss_all,
                                       "val_loss_all":val_loss_all,
                                       "train_acc_all":train_acc_all,
                                       "val_acc_all": val_acc_all})
    return train_process
##定义一个绘制loss和acc的绘制图的函数,将train_process作为输入传进去
def matplot_acc_loss(train_process):
    plt.figure(figsize=(12,4))     #设置图形的大小
    plt.subplot(1,2,1)        #子图可以按照matlab的画法进行理解
    plt.plot(train_process["epoch"], train_process.train_loss_all, 'ro-' , label = "train loss")    #绘制训练集损失曲线,并加上曲线等设置
    plt.plot(train_process["epoch"], train_process.val_loss_all, 'bs-', label="val loss")  # 绘制验证集损失曲线,并加上曲线等设置
    plt.legend()  #标签
    plt.xlabel("epoch")
    plt.ylabel("loss")

    plt.subplot(1, 2, 2)  # 子图可以按照matlab的画法进行理解
    plt.plot(train_process["epoch"], train_process.train_acc_all, 'ro-', label="train acc")  # 绘制训练集损失曲线,并加上曲线等设置
    plt.plot(train_process["epoch"], train_process.val_acc_all, 'bs-', label="val acc")  # 绘制验证集损失曲线,并加上曲线等设置
    plt.legend()  # 图例
    plt.xlabel("epoch")
    plt.ylabel("acc")
    plt.legend()  # 图例
    plt.show()    #绘制

#对数据开始训练
#主函数,开始运行所有的代码   注意点1:双等号。2.要有冒号:
if __name__ == "__main__":
     #将模型进行实例化,如果后续是别的模型呢?from model import LeNet,先进行搭建我们的model.py,进而将model导入进去,然后在下面进行实例化。将模型放到训练代码就可以了。有个别的模型可能数据的预处理需要改一点。
     GoogLeNet = GoogLeNet(Inception)
     #加载数据集,分为训练集和验证集
     train_dataloader,val_dataloader = train_val_data_process()
     #将训练集,验证集,模型,和训练次数放进训练代码中,
     train_process = train_model_process(GoogLeNet,train_dataloader,val_dataloader,20)    #自己进行设定训练的次数,假如这里的训练次数是20轮,并保留中间值
     ##绘制损失和准确率的图形
     matplot_acc_loss(train_process)

注意模型的导入部分,与之前的模型训练代码有所不同
from model import GoogLeNet,Inception

在模型训练的时候,出现了这样的错误
在这里插入图片描述
错误信息 RuntimeError: Sizes of tensors must match except in dimension 1. Expected size 28 but got size 10 for tensor number 3 in the list. 指出在进行一个涉及多个张量的运算时,除了维度1之外,其他所有维度的大小必须匹配。在这个具体的例子中,某个张量在非维度1上的预期大小是28,但实际得到的大小是10。
这种类型的错误通常发生在以下几种情况:

  1. 矩阵乘法:在进行矩阵乘法时,如果两个矩阵的内积维度不匹配,就会发生这种错误。例如,A 的形状是 [MxN],B 的形状是 [NxP],那么 N 必须与 A 的列数和 B 的行数相匹配。
  2. 张量拼接:使用 torch.cat 等函数拼接张量时,除了拼接的维度之外,其他所有维度的大小必须相同。
  3. 卷积层输出尺寸计算:在使用卷积神经网络时,如果输入张量的尺寸与卷积核、步长或填充不兼容,也可能导致这种错误。
    随之将Inception模块的4个通道情况进行了打印,发现路径4与其他的特征图大小不一致
    在检查了其他的部分没问题的情况下,就考虑是不是特征图大小有问题
    用print (p1.shape,p2.shape,p3.shape,p4.shape),结果就发现特征图大小不一致(p4出现了问题)
    通道融合是需要建立在特征图大小一致的前提下
    在这里插入图片描述
    这时就回应了前面提到的小伏笔,就是通道融合必须是特征图大小是一样的才可以。那么是哪里的问题呢
    通过查看最大池化和卷积操作的函数定义发现
    多嘴一句,那么函数的定义怎么进去呢?
    我的电脑中,想进某个函数的定义按快捷键(ctrl+F4进入函数定义页面)
    或者,直接点右下角的铅笔处,会弹出函数的定义部分
    在这里插入图片描述
    最大池化:
    在这里插入图片描述
    MaxPool2d 主要参数如下:
    ● kernel_size (int 或 tuple): 池化窗口的大小。
    ● stride (int 或 tuple, 可选): 池化窗口移动的步长。若未指定,则默认值与 kernel_size 相同。
    ● padding (int 或 tuple, 可选): 输入数据周围填充的大小。默认值为 0。

卷积操作
在这里插入图片描述
卷积操作:步长默认是为1的

以后在写最大池化层的时候,要注意一下,以防犯错。

因此将上面模型的中的代码进行调整

 self.p4_1 = nn.MaxPool2d(kernel_size=3,padding=1)
 改为 self.p4_1 = nn.MaxPool2d(kernel_size=3,padding=1,stride=1)

需要改的地方‘

from model import GoogLeNet,Inception

transform=transforms.Compose((transforms.Resize(size=224), transforms.ToTensor()))
#视GPU而定
batch_size=32,
#改路径
torch.save(best_model_wts, 'C:/Users/mcg/Desktop/GoogLeNet/best_model.pth')
GoogLeNet = GoogLeNet(Inception)
train_process = train_model_process(GoogLeNet,train_dataloader,val_dataloader,20)

模型测试和推理

模型测试

import torch   #用不用都先导入进来
import torch.utils.data as Data    #导入数据处理的库
from torchvision import transforms
from torchvision.datasets import FashionMNIST    #导入我们所用的数据集
from model import GoogLeNet,Inception #导入需要测试的模型及自己所定义的模块

##数据就处理好了。
##导入数据进行模型的测试,从模型训练的代码拿一部分,改改即可,batch_size=1,希望是一张一张的去进行测试,不需要什么进程,改为num_workers=0即可
def test_data_process():
    ##数据加载进来,transforms.Compose((transforms.Resize(size=224),大小设置按照模型的输入要求来
    test_data = FashionMNIST(root='./data',
                              train=False,
                              transform=transforms.Compose((transforms.Resize(size=224), transforms.ToTensor())),
                              download=True)

    #先放入数据,一批次数量(数据量比较小,6G运存应该是够的),shuffle是否打乱数据,进程是8,电脑垃圾的话,就改小一点
    test_dataloader = Data.DataLoader(dataset=test_data,
                                       batch_size=1,
                                       shuffle=True,
                                       num_workers=0)

    return test_dataloader

##测试,只需要导入模型和数据集,得到一个测试的结果即可
def test_model_process(model,test_dataloader):
    #设定测试用到的设备,有GPU用GPU,没有GPU用CPU
    device = "cuda" if torch.cuda.is_available() else "cpu"
    #将模型放入到测试设备当中
    model = model.to(device)
    #初始化模型参数,准确度即可,虽然已经知道测试的数据集有10000张,但为了后续不知道张数的情况下,也能进行测试。
    test_corrects = 0.0
    test_num = 0

    ##开始模型的测试,不存在反向传播,因此将梯度置为0,只有前向传播,不计算梯度,从而节省内存,加快运行速度
    with torch.no_grad():
        #直接一批次一批次的获取就好了。
        for test_data_x,test_data_y in test_dataloader:
            #将特征放入到测试设备中
            test_data_x = test_data_x.to(device)
            #将标签也放入到测试设备中
            test_data_y = test_data_y.to(device)
            #模型测试状态
            model.eval()
            #前向传播过程,输入为测试数据集,输出为对每个样本的预测值,它其实是一个10*1的矩阵,并不能看出结果来,需要经过一个softmax得到结果。最大概率对应的下标
            output = model(test_data_x)
             #预测的类别,查找每一行中最大值对应的行标
            pre_lab = torch.argmax(output,dim=1)
            #预测的结果和标签一样+1,如果预测正确,则 test_corrects加1,直到循环结束
            test_corrects += torch.sum(pre_lab == test_data_y.data)
            #将所有的测试样本进行累加
            test_num += test_data_x.size(0)

    #计算测试准确率,不在循环里面哦,注意缩进
    test_acc = test_corrects.double().item() / test_num

    print("测试的准确率为:" ,  test_acc)


#主函数进行运行程序
if __name__ == "__main__" :
    #加载模型
    model = GoogLeNet(Inception)
    #torch.load('best_model.pth')加载训练好的模型参数,权重以字符串的形式, model.load_state_dict模型训练化,将实例化之后的模型加载上权重,规定的做法

    model.load_state_dict(torch.load('best_model.pth'))
    #导入测试集的数据集,加载测试数据
    test_dataloader = test_data_process()
    #加载模型测试的函数
    test_model_process(model, test_dataloader)

模型推理

import torch   #用不用都先导入进来
import torch.utils.data as Data    #导入数据处理的库
from torchvision import transforms
from torchvision.datasets import FashionMNIST    #导入我们所用的数据集
from model import GoogLeNet,Inception #导入需要测试的模型

##数据就处理好了。
##导入数据进行模型的测试,从模型训练的代码拿一部分,改改即可,batch_size=1,希望是一张一张的去进行测试,不需要什么进程,改为num_workers=0即可
def test_data_process():
    ##数据加载进来,transforms.Compose((transforms.Resize(size=224),大小设置按照模型的输入要求来
    test_data = FashionMNIST(root='./data',
                              train=False,
                              transform=transforms.Compose((transforms.Resize(size=224), transforms.ToTensor())),
                              download=True)

    #先放入数据,一批次数量(数据量比较小,6G运存应该是够的),shuffle是否打乱数据,进程是8,电脑垃圾的话,就改小一点
    test_dataloader = Data.DataLoader(dataset=test_data,
                                       batch_size=1,
                                       shuffle=True,
                                       num_workers=0)

    return test_dataloader

##测试,只需要导入模型和数据集,得到一个测试的结果即可
def test_model_process(model,test_dataloader):
    #设定测试用到的设备,有GPU用GPU,没有GPU用CPU
    device = "cuda" if torch.cuda.is_available() else "cpu"
    #将模型放入到测试设备当中
    model = model.to(device)
    #初始化模型参数,准确度即可,虽然已经知道测试的数据集有10000张,但为了后续不知道张数的情况下,也能进行测试。
    test_corrects = 0.0
    test_num = 0

    ##开始模型的测试,不存在反向传播,因此将梯度置为0,只有前向传播,不计算梯度,从而节省内存,加快运行速度
    with torch.no_grad():
        #直接一批次一批次的获取就好了。
        for test_data_x,test_data_y in test_dataloader:
            #将特征放入到测试设备中
            test_data_x = test_data_x.to(device)
            #将标签也放入到测试设备中
            test_data_y = test_data_y.to(device)
            #模型测试状态
            model.eval()
            #前向传播过程,输入为测试数据集,输出为对每个样本的预测值,它其实是一个10*1的矩阵,并不能看出结果来,需要经过一个softmax得到结果。最大概率对应的下标
            output = model(test_data_x)
             #预测的类别,查找每一行中最大值对应的行标
            pre_lab = torch.argmax(output,dim=1)
            #预测的结果和标签一样+1,如果预测正确,则 test_corrects加1,直到循环结束
            test_corrects += torch.sum(pre_lab == test_data_y.data)
            #将所有的测试样本进行累加
            test_num += test_data_x.size(0)

    #计算测试准确率,不在循环里面哦,注意缩进
    test_acc = test_corrects.double().item() / test_num

    print("测试的准确率为:" ,  test_acc)


#主函数进行运行程序
if __name__ == "__main__" :
    #加载模型
    model = GoogLeNet(Inception)
    #torch.load('best_model.pth')加载训练好的模型参数,权重以字符串的形式, model.load_state_dict模型训练化,将实例化之后的模型加载上权重,规定的做法

    model.load_state_dict(torch.load('best_model.pth'))
    #导入测试集的数据集,加载测试数据
    test_dataloader = test_data_process()
    #加载模型测试的函数
    #test_model_process(model, test_dataloader)
    #进行模型推理过程
    #设定测试所用到的设备,有GPU用GPU,没有就用CPU
    device = "cuda" if torch.cuda.is_available() else 'cpu'
    model = model.to(device)
    #使用列表取值的形式设置类别
    classes= ['T-shirt/top','Trouser','Pullover','Dress','Coat','Sandal','Shirt','Sneaker','Bag','Ankel boot']
    #测试部分,没有梯度,不存在反向传播
    with torch.no_grad():
        for b_x,b_y in test_dataloader:
            b_x = b_x.to(device)
            b_y = b_y.to(device)

            #设置模型为验证模式
            model.eval()
            output = model(b_x)
            ##10个通过神经元得到数值,经过softmax函数获得对应的概率,从而获得最大值的下标
            #ToTensor()将数据格式转换为张量的形式,只有张量的数据格式,才可以去做梯度运算,反向传播等
            pre_lab = torch.argmax(output, dim=1)
            # pre_lab.item()表示将张量中的数值取出来,以便做其他的操作(比如通过列表设置的类别,取整型方便运算,张量形式是没法进行的。
            result = pre_lab.item()
            label = b_y.item()
            #不仅打印数值,更应该打印出对应的类别,推理之前设置有列表,用下标指向类别
            print("预测值:", classes[result], "------", "真实值:",classes[label])

结果

在这里插入图片描述
在这里插入图片描述
通过图,不难看出验证集基本上是趋于收敛的,而训练集还没有
在这里插入图片描述

说明Inception 块是能够提升识别准确率的,因此可以将该模块用于其他的网络模型中,用于提升准确率。

在这里插入图片描述

我自己的,想说明的是4G显存,上面的代码是可以跑的
训练情况:
0 Train Loss: 0.6625 Train Acc: 0.7558
0 Val Loss: 0.4063 Val  Acc: 0.8425
训练和验证耗费的时间7m34s
1 Train Loss: 0.3690 Train Acc: 0.8619
1 Val Loss: 0.3126 Val  Acc: 0.8818
训练和验证耗费的时间15m6s
2 Train Loss: 0.3152 Train Acc: 0.8824
2 Val Loss: 0.2896 Val  Acc: 0.8965
训练和验证耗费的时间22m39s
待续.....
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值