GoogLeNet网络模型

GoogLeNet网络模型

诞生背景

在2014年的ImageNet图像识别挑战赛中,一个GoogLeNet的网络架构大放异彩,与VGG不同的是,VGG用的是3*3的卷积,而GoogLeNet从1*1到7*7的卷积核都用,也就是使用不同大小的卷积核组合。

网络模型架构

在GoogLeNet中,基本的卷积块被称为Inception块。

Inception模块

输入
1x1卷积
1x1卷积
3x3卷积
1x1卷积
5x5卷积
3x3最大池化
1x1卷积
通道合并

Inception块参数详解

输入为224×224×3三通道的图像。

路径1:

​ (1)输入为224×224×3,卷积核数量为64个;卷 积核的尺寸大小为1×1×3;步幅为1(stride=1), 填充为0(padding=0);卷积后得到shape为 224×224×64的特征图输出。

路径2:

​ (1)输入为224×224×3,卷积核数量为96个;卷 积核的尺寸大小为1×1×3;步幅为1(stride = 1), 填充为0(padding=0);卷积后得到shape为 224×224×64的特征图输出。

​ (2)输入为224×224×64,卷积核数量为128个; 卷积核的尺寸大小为3×3×64;步幅为1(stride = 1),填充为1(padding=1);卷积后得到shape 为224×224×128的特征图输出。

路径3:
(1)输入为224×224×3,卷积核数量为16个;卷积核 的尺寸大小为1×1×3;步幅为1(stride = 1),填充为 0(padding=0);卷积后得到shape为224×224×16的 特征图输出。

​ (2)输入为224×224×16,卷积核数量为32个;卷积 核的尺寸大小为5×5×16;步幅为1(stride = 1),填 充 为 2 ( padding=2 ) ; 卷 积 后 得 到 shape 为 224×224×32的特征图输出。

路径4:

​ (1)输入为224×224×3,池化核的尺寸大小为3×3; 步幅为1(stride = 1),填充为1(padding=1);池 化后得到shape为224×224×3的特征图输出。

​ (2)输入为224×224×3,卷积核数量为32个;卷积核 的尺寸大小为1×1×3;步幅为1(stride = 1),填充为 0(padding=0);卷积后得到shape为224×224×32的 特征图输出。

通道合并:

路径1的到输出为:224×224×64

路径2的到输出为:224×224×128

路径3的到输出为:224×224×32

路径4的到输出为:224×224×32

最终通道合并为64+128+32+32=256,最终的输出为: 224×224×256

整个网络模型架构

阶段层号操作参数 / 配置输出尺寸(H×W×C)对应论文描述
输入层-图像输入224×224 RGB,均值预处理224×224×3摘要 / 引言部分
初始卷积Conv17×7 卷积,步长 2,填充 364 个滤波器112×112×64引言中 “7×7 卷积层”
初始池化Pool13×3 最大池化,步长 2,填充 1-56×56×64引言中 “最大池化层”
降维卷积Conv2并行分支: ① 1×1 卷积 ② 1×1 卷积→3×3 卷积分支①:64 通道 分支②:64→192 通道56×56×256引言中 “3×3 卷积(含 1×1 降维)”
Inception 组 1Inception 2a4 分支并行: ① 1×1 卷积 ② 1×1→3×3 卷积 ③ 1×1→5×5 卷积 ④ 池化→1×1①64 ②96→128 ③16→32 ④32 通道56×56×256论文第 4 节 “第一组 Inception 模块”
Inception 2b4 分支并行(通道数调整)①128 ②128→192 ③32→96 ④64 通道56×56×480同上
降采样Pool23×3 最大池化,步长 2,填充 1-28×28×480组间池化,降低尺寸
Inception 组 2Inception 3a4 分支并行(通道数调整)①192 ②96→208 ③16→48 ④64 通道28×28×512论文第 4 节 “第二组 Inception 模块”
Inception 3b4 分支并行(通道数调整)①160 ②112→224 ③24→64 ④64 通道28×28×512同上
降采样Pool33×3 最大池化,步长 2,填充 1-14×14×512组间池化,降低尺寸
Inception 组 3Inception 4a4 分支并行(通道数调整) 后接辅助分类器 1①128 ②128→256 ③24→64 ④64 通道 辅助分类器:平均池化 + 1×1 卷积 + FC14×14×512论文第 4 节 “第三组 Inception 模块”
Inception 4b4 分支并行(通道数调整) 后接辅助分类器 2①112 ②144→288 ③32→64 ④64 通道 辅助分类器:同上14×14×528同上,辅助分类器缓解梯度消失
Inception 4c4 分支并行(通道数调整)①256 ②256→320 ③32→128 ④128 通道14×14×832同上
降采样Pool43×3 最大池化,步长 2,填充 1-7×7×832组间池化,降低尺寸
Inception 组 4Inception 5a4 分支并行(通道数调整)①256 ②320→384 ③32→128 ④128 通道7×7×896论文第 4 节 “第四组 Inception 模块”
Inception 5b4 分支并行(通道数调整)①384 ②384→448 ③48→128 ④128 通道7×7×1088同上
全局池化GlobalPool全局平均池化-1×1×1024引言中 “全局平均池化替代全连接层”
分类器Dropout随机丢弃 70% 神经元-1×1×1024论文第 5 节 “Dropout 层”
FC全连接层 + Softmax1024→1000 通道(ImageNet 类别数)1×1×1000论文第 5 节 “线性层 + Softmax”

环境准备

首先在本地中的某个盘符新建一个文件夹,就叫GoogLeNet吧,作为项目的根目录。

然后新建一个python文件,就叫plot.py吧,往里面写入以下代码,用于下载数据集:

# FashionMNIST里面包含了许多数据集
from click.core import batch
from spacy.cli.train import train
from torchvision.datasets import FashionMNIST
from torchvision import transforms # 处理数据集,归一化
import torch.utils.data as Data
import numpy as np
import matplotlib.pyplot as plt


# 下载FashionMMIST数据集
train_data = FashionMNIST(
    root="./data", # 指定数据集要下载的路径
    train=True,# 要训练集
    # 将数据进行归一化操作
    transform=transforms.Compose([
        transforms.Resize(size=224), # 调整数据的大小
        transforms.ToTensor() # 将数据转换为tensor
    ]),
    download=True # 开启下载
)

# 加载数据集集
train_loader = Data.DataLoader(
    dataset=train_data,# 要加载的数据集
    batch_size=64 ,# 批量数据大小
    shuffle=True, # 打乱数据顺序
    num_workers=0, # 加载数据线程数量
)

# 绘制出训练集
for step,(b_x,b_y) in enumerate(train_loader):
    if step > 0:
        break
    batch_x = b_x.squeeze().numpy() # 将四维张量移除第一维,将数据转换为numpy格式
    batch_y = b_y.numpy() # 将张量数据转成numpy格式
    class_label = train_data.classes # 训练集标签
print("class_label,",class_label)

# 绘图
plt.figure(figsize=(12,5))
for ii in np.arange(len(batch_y)):
    plt.subplot(4,16,ii+1)
    plt.imshow(batch_x[ii, : , :],cmap=plt.cm.gray)
    plt.title(class_label[batch_y[ii]],size=10)
    plt.axis('off')
    plt.subplots_adjust(wspace=0.05)

plt.show()

执行上述代码后,就会开始下载所需要的数据集文件,只不过下载的速度比较慢,然后下载完成,项目的根目录会多出data文件夹,以下是data的目录结构:

--data
  --FashionMNIST
    --raw # 该文件夹下就存放数据集文件

模型搭建

创建model.py文件,用于构建模型代码。

import torch
from torch import nn
from torchsummary import summary

# 定义一个通用的Inception模块
class Inception(nn.Module):
    """
    in_channels:输入通道数
    c1:路线1的卷积
    c2:元组,路线2中有两层卷积
    c3:元组,路线3中有两层卷积
    c4:路线4的卷积
    """
    def __init__(self,in_channels,c1,c2,c3,c4):
        super(Inception,self).__init__()
        self.ReLU = nn.ReLU() # 定义激活函数 ReLU
        
        # 路线1 单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卷积
        # stride的默认参数与kernel_size的值一致,而kernel_size的默认值是1
        self.p4_1 = nn.MaxPool2d(kernel_size=3,padding=1,stride=1)
        self.p4_2 = nn.Conv2d(in_channels=in_channels,out_channels=c4,kernel_size=1)
        
        
    def forward(self,x):
        # x为输入大小
        p1 = self.ReLU(self.p1_1(x)) # 路线1
        p2 = self.ReLU(self.p2_2(self.ReLU(self.p2_1(x)))) # 路线2
        p3 = self.ReLU(self.p3_2(self.ReLU(self.p3_1(x)))) # 路线3
        p4 = self.ReLU(self.p4_2(self.p4_1(x))) # 路线4
        
        # 最后进行通道融合 dim=1表示在通道的基础上进行融合
        return torch.cat((p1,p2,p3,p4),dim=1)
    

# 定义GoogLeNet网络模型
class GoogLeNet(nn.Module):
    def __init__(self,Inception):
        super(GoogLeNet,self).__init__()
        # 定义第一块网络层,相当于封装了部分网络层
        self.b1 = nn.Sequential(
        	nn.Conv2d(in_channels=3,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)
        )
        
        # 定义第三块网络层
        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)
        )
        
        # 定义第四块网络层
        self.b3 = 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)
        )
        
        # 定义第五块网络层
        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: # 如果偏置b存在
                    nn.init.constant_(m.bias,0) # 将b置为0
                elif isinstance(m,nn.Linear): # 如果当前层是全连接层
                    nn.init.normal_(m.weight,0,0.01) # 使用正太分布初始化,将权重w置为0,标准差为0.01
                    if m.bias is not None: # 如果参数b存在
                    	nn.init.constant_(m.bias,0) # 将b置为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")
    model = GoogLeNet(Inception).to(device)
    print(summary(model,(1,224,224)))
  • 打个简单的比方,训练网络模型,就好比解方程,为了得到这个方程的极值点,训练的过程就好比是找准一个方向,不断的朝这个方向靠近,使得方程的值不断减小,最终达到极值点,而不收敛,就是,不论你怎么跑,方程的解都不减小。即达不到最后的极值点.在loss上就表现为稳定性的比较大。跟迭代不收敛或者系统不稳定差不多,上下波动不能趋近一个定值。

  • 收敛的意思是指某个值一直在往我们所期望的阈值靠,就拿深度学习中loss损失来做示例,如下一张图是loss在每轮训练时的一个曲线图,可以看到loss一直从一开始的1.8在往1.0降,1.0就是我们期望的阈值,而1.8是最开始loss最大损失值。

模型训练

创建一个model_train.py 文件

import copy
import time

import torch
from torchvision.datasets import FashionMNIST
from torchvision import transforms
import torch.nn as nn
import torch.utils.data as Data
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from model import GoogLeNet


# 处理训练集核数据集
def train_val_data_process():
    # 加载数据集
    train_data = FashionMNIST(
        root="./data",# 数据集所在路径
        train=True, # 要训练集
        # 将数据进行归一化操作
        transform=transforms.Compose([
            transforms.Resize(size=224), # 修改数据的大小
            transforms.ToTensor() # 将数据转成Tensor格式
        ]),
        download=True # 开启加载
    )

    # 随机 划分训练集 和 验证集
    train_data,val_data = Data.random_split(
        train_data, # 要划分的数据集
        [
            round(0.8*len(train_data)), # 划分80%给训练集
            round(0.2*len(train_data)) # 划分20%给验证集
        ]
    )

    # 加载训练集数据
    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):
    # model:需要训练的模型,train_dataloader:训练集数据,val_dataloader:验证集数据,num_epochs:训练轮数
    # 指定设备
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    # 定义优化器
    optimizer = torch.optim.Adam(model.parameters(),lr=0.001)
    # 定义交叉熵损失函数
    criterion = nn.CrossEntropyLoss()
    # 将模型放入到设备中进行训练
    model.to(device)
    # 复制当前模型的参数
    best_model_wts = copy.deepcopy(model.state_dict())

    # 初始化参数,记录模型的的精确度和损失值
    best_acc = 0.0 # 最高精确度

    train_loss_all = [] # 训练集的总损失
    train_acc_all = [] # 训练集的总精度

    val_loss_all = [] # 验证集的总损失
    val_acc_all = [] # 验证集的总精度

    since = time.time() # 记录开始训练的时间

    # 开始训练模型 每轮参数
    for epoch in range(num_epochs):
        print("Epoch {}/{}".format(epoch,num_epochs-1))
        print("-"*10)

        # 初始化参数,记录本轮的模型的损失之和精度
        train_loss = 0.0 # 训练的损失
        train_corrects = 0  # 训练的准确度

        val_loss = 0.0 # 验证集的损失
        val_corrents = 0 # 验证集的准确度

        train_num = 0 # 本轮训练集的数量
        val_num = 0 # 本轮验证集的数量

        # 取出每轮中的数据集进行训练
        for step,(b_x,b_y) in enumerate(train_dataloader):
            b_x = b_x.to(device) # 将训练集数据放入到设备当中
            b_y = b_y.to(device) # 将标签数据放入到设备当中

            model.train() # 开启模型训练模式

            # 将每批次中的标签数据放入到模型中,进行前向传播
            output = model(b_x)

            # 查找每一行中最大值对应的行标,即预测值
            pre_lab = torch.argmax(output,dim=1)

            # 计算当前批次的损失值(模型的输出,标签)
            loss = criterion(output,b_y)

            # 每批次训练完后,将梯度初始化成0
            optimizer.zero_grad()

            # 反向传播计算
            loss.backward()

            # 更新参数
            optimizer.step()

            # 本批次损失值的累加
            train_loss += loss.item() * b_x.size(0)

            # 如果模型预测的结果正确,本批次的准确度+1
            train_corrects += torch.sum(pre_lab == b_y.data)

            # 本此次的训练数据累加
            train_num += b_y.size(0)


        # 取出每轮中的数据进行验证
        for step,(b_x,b_y) in enumerate(val_dataloader):
            # 将数据和标签分别放入到设备中
            b_x = b_x.to(device)
            b_y = b_y.to(device)

            model.eval() # 设置模型为评估模式

            # 前向传播,输入一个批次,输出该批次的对应的预测值
            output = model(b_x)

            # 查找每一行中最大值对应的行标,即预测值
            pre_lab = torch.argmax(output,dim=1)

            # 计算本此次的损失函数
            loss = criterion(output,b_y)

            # 本批次的损失函数累加
            val_loss += loss.item() * b_x.size(0)

            # 如果预测正确,那就本批次的精度度累加
            val_corrents += torch.sum(pre_lab==b_y.data)

            # 当前用于验证的样本数累加
            val_num += b_x.size(0)


        # 计算每轮次的损失值和准确率
        train_loss_all.append(train_loss / train_num) # 本轮训练集的loss值
        train_acc_all.append(train_corrects.double().item() / train_num) # 本轮训练集的准确率

        val_loss_all.append(val_loss / val_num) # 本轮验证集的loss值
        val_acc_all.append(val_corrents.double().item() / val_num) # 本轮验证集的准确率

        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))

    # 将最佳的模型参数保存
    torch.save(best_model_wts,"best_model.pth") # .pth是权重文件的后缀,如果相对路径不行,就改为绝对路径

    # 保存训练好的模型参数
    train_process = pd.DataFrame(
        data={
            "epoch":range(num_epochs),
            "train_loss_all":train_loss_all,
            "train_acc_all":train_acc_all,
            "val_loss_all":val_loss_all,
            "val_acc_all":val_acc_all
        }
    )
    return train_process


# 定义绘图的函数,绘制loss和准确度
def matplot_acc_loss(train_process):
    plt.figure(figsize=(12,4))

    # 绘制训练集和验证集的损失值图像
    plt.subplot(1,2,1) # 一行两列,第一列
    plt.plot(train_process['epoch'],train_process.train_loss_all,"ro-",label="train loss")
    plt.plot(train_process['epoch'],train_process.val_acc_all,"bs-",label="val loss")
    plt.legend() # 图例
    plt.xlabel("epoch") # x轴标签
    plt.ylabel("loss") # y轴标签


    # 绘制训练集和验证集的准确度图像
    plt.subplot(1,2,2) # 一行两列,第二列
    plt.plot(train_process['epoch'],train_process.val_loss_all,"ro-",label="train acc")
    plt.plot(train_process['epoch'],train_process.val_acc_all,"bs-",label="va acc")



if __name__ == '__main__':
    # 实例化自定义模型类
    model = GoogLeNet()
    # 加载数据集
    train_dataloader,val_dataloader = train_val_data_process()
    # 训练模型
    train_process = train_model_process(model,train_dataloader,val_dataloader,20)
    # 绘制图像
    matplot_acc_loss(train_process)

模型训练完毕后,会生成最佳模型参数文件:best_model.pth

模型测试

创建一个model_test.py文件,用于模型的测试

import torch
import torch.utils.data as Data
from numpy.random import shuffle
from torchvision import transforms
from torchvision.datasets import FashionMNIST

from model import GoogLeNet,Inception

# 加载要训练的数据
def test_data_process():
    # 加载测试集数据
    test_data = FashionMNIST(
        root="./data",# 指定数据集要下载的路径
        train=False,# 不要训练集数据
        # 数据归一化操作
        transform=transforms.Compose([
            transforms.Resize(size=224), # 将数据转成224*224大小
            transforms.ToTensor(),# 将数据转成Tensor格式
        ]),
        download=True # 加载数据
    )

    # 通过DataLoader加载器 来加载数据
    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):
    # 指定设备
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    # 将模型放入设备中
    model.to(device)

    # 初始化模型训练的每轮参数
    test_correct = 0.0 # 准确度
    test_num = 0 # 测试样本数量

    # 只进行前向传播
    with torch.no_grad(): # 将梯度设置为0
        for test_data_x,test_data_y in enumerate(test_dataloader): # 遍历每轮次
            # 由于上面设置批次为1,所以这里就不需要循环批次了

            test_data_x = test_data_x.to(device) # 将测试数据放入到设备中
            test_data_y = test_data_y.to(device) # 将标签数据放入到设备中

            # 模型切换成评估模式
            model.eval()

            # 前向传播 将测试数据放入到模型中
            output = model(test_data_x)

            # 查找每一行中最大值的行标
            pre_lab = torch.argmax(output,dim=1)

            # 模型预测的结果 将pre_lab 与 标签数据 进行比较
            # 如果预测正确,则加1
            test_correct += torch.sum(pre_lab==test_data_y.data)

            # 测试样本数量累加
            test_num += test_data_y.size(0)

    # 计算最终测试的准确率 每轮的准确度 / 总样本数量
    test_acc = test_correct.double().item() / test_num

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





if __name__ == '__main__':
    # 加载模型
    model = GoogLeNet(Inception)
    # 模型具体的训练过程
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    # 加载训练好的模型最佳参数
    model.load_state_dict(torch.load('best_model.pth',map_location=device))

    # 加载测试的数据集
    test_dataloader = test_data_process()

    # 开始测试
    # test_model_process(model, test_dataloader) # 简略测试

    # 模型具体的训练过程
    # device = torch.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', 'Ankle boot')

    # 梯度设置为0
    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)
            # 获取最大值的行标
            pre_lab = torch.argmax(output,dim=1)
            # 取出张量中的下标
            result = pre_lab.item()
            label = b_y.item()

            print("预测结果为:",classes[result],"标签为:",classes[label])


猫狗分类

利用自己的训练集来进行模型训练,实现猫狗分类

环境搭建

将上面刚刚搭建好的GoogLeNet项目,复制一份,就叫GoogLeNet-1吧。

数据集准备和划分

本地已经准备好了data_cat_dog的自定义数据集文件夹,里面均为图片。执行以下脚本,可以实现训练集的数据划分。

data_cat_dog的自定义数据集获取地址

禁止私自搬运和商用,后果自负,仅限学习使用

新建一个data_partitioning.py文件,并写入以下代码:

import os
from shutil import copy
import random


def mkfile(file):
    if not os.path.exists(file):
        os.makedirs(file)


# 获取data文件夹下所有文件夹名(即需要分类的类名)
file_path = 'data_cat_dog'
flower_class = [cla for cla in os.listdir(file_path)]

# 创建 训练集train 文件夹,并由类名在其目录下创建5个子目录
mkfile('data/train')
for cla in flower_class:
    mkfile('data/train/' + cla)

# 创建 验证集val 文件夹,并由类名在其目录下创建子目录
mkfile('data/test')
for cla in flower_class:
    mkfile('data/test/' + cla)

# 划分比例,训练集 : 测试集 = 9 : 1
split_rate = 0.1

# 遍历所有类别的全部图像并按比例分成训练集和验证集
for cla in flower_class:
    cla_path = file_path + '/' + cla + '/'  # 某一类别的子目录
    images = os.listdir(cla_path)  # iamges 列表存储了该目录下所有图像的名称
    num = len(images)
    eval_index = random.sample(images, k=int(num * split_rate))  # 从images列表中随机抽取 k 个图像名称
    for index, image in enumerate(images):
        # eval_index 中保存验证集val的图像名称
        if image in eval_index:
            image_path = cla_path + image
            new_path = 'data/test/' + cla
            copy(image_path, new_path)  # 将选中的图像复制到新路径

        # 其余的图像保存在训练集train中
        else:
            image_path = cla_path + image
            new_path = 'data/train/' + cla
            copy(image_path, new_path)
        print("\r[{}] processing [{}/{}]".format(cla, index + 1, num), end="")  # processing bar
    print()

print("processing done!")

执行以上代码,就可以划分猫和狗的训练集和测试集,然后本地项目中会多个以下文件:

--data
  --test
     --cat
     --dog
  --train
      --cat
      --dog

数据集加载

重写model_train.py文件中的部分代码

import copy
import time

import torch
from torchvision.datasets import ImageFolder # 加载自己的数据集
from torchvision import transforms
import torch.nn as nn
import torch.utils.data as Data
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from model import GoogLeNet


# 处理训练集核数据集
def train_val_data_process():
    # ======(修改的部分开始)=============================
    
    ROOT_TRAIN = r'data\train' # 数据集的路径
    
     # 定义归一化方法 计算均值和方差 其中的数值来自于下面的数据集归一化部分的mean和variance两个值
    normalize = transforms.Normalize([0.162,0.151,0.138],[0.058,0.052,0.048])
    
    # 定义数据集处理方法变量
    train_transform = transforms.Compose( # 对数据集进行操作
    	[
            transforms.Resize((224,224)),# 修改数据集的大小为 224*224
            transforms.ToTensor(), # 将数据修改为tensor格式
            normalize # 数据归一化
        
        ]  
    )
    # 加载数据集 ImageFolder()为第三方库导入的包
    train_data = ImageFolder(
    	ROOT_TRAIN, # 读取数据集的路径
        transform = train_transform # 处理数据的方法
    )
    
    # ======(修改的部分结束)=============================
    
    

    # 随机 划分训练集 和 验证集
    train_data,val_data = Data.random_split(
        train_data, # 要划分的数据集
        [
            round(0.8*len(train_data)), # 划分80%给训练集
            round(0.2*len(train_data)) # 划分20%给验证集
        ]
    )

    # 加载训练集数据
    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):
    # model:需要训练的模型,train_dataloader:训练集数据,val_dataloader:验证集数据,num_epochs:训练轮数
    # 指定设备
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    # 定义优化器
    optimizer = torch.optim.Adam(model.parameters(),lr=0.001)
    # 定义交叉熵损失函数
    criterion = nn.CrossEntropyLoss()
    # 将模型放入到设备中进行训练
    model.to(device)
    # 复制当前模型的参数
    best_model_wts = copy.deepcopy(model.state_dict())

    # 初始化参数,记录模型的的精确度和损失值
    best_acc = 0.0 # 最高精确度

    train_loss_all = [] # 训练集的总损失
    train_acc_all = [] # 训练集的总精度

    val_loss_all = [] # 验证集的总损失
    val_acc_all = [] # 验证集的总精度

    since = time.time() # 记录开始训练的时间

    # 开始训练模型 每轮参数
    for epoch in range(num_epochs):
        print("Epoch {}/{}".format(epoch,num_epochs-1))
        print("-"*10)

        # 初始化参数,记录本轮的模型的损失之和精度
        train_loss = 0.0 # 训练的损失
        train_corrects = 0  # 训练的准确度

        val_loss = 0.0 # 验证集的损失
        val_corrents = 0 # 验证集的准确度

        train_num = 0 # 本轮训练集的数量
        val_num = 0 # 本轮验证集的数量

        # 取出每轮中的数据集进行训练
        for step,(b_x,b_y) in enumerate(train_dataloader):
            b_x = b_x.to(device) # 将训练集数据放入到设备当中
            b_y = b_y.to(device) # 将标签数据放入到设备当中

            model.train() # 开启模型训练模式

            # 将每批次中的标签数据放入到模型中,进行前向传播
            output = model(b_x)

            # 查找每一行中最大值对应的行标,即预测值
            pre_lab = torch.argmax(output,dim=1)

            # 计算当前批次的损失值(模型的输出,标签)
            loss = criterion(output,b_y)

            # 每批次训练完后,将梯度初始化成0
            optimizer.zero_grad()

            # 反向传播计算
            loss.backward()

            # 更新参数
            optimizer.step()

            # 本批次损失值的累加
            train_loss += loss.item() * b_x.size(0)

            # 如果模型预测的结果正确,本批次的准确度+1
            train_corrects += torch.sum(pre_lab == b_y.data)

            # 本此次的训练数据累加
            train_num += b_y.size(0)


        # 取出每轮中的数据进行验证
        for step,(b_x,b_y) in enumerate(val_dataloader):
            # 将数据和标签分别放入到设备中
            b_x = b_x.to(device)
            b_y = b_y.to(device)

            model.eval() # 设置模型为评估模式

            # 前向传播,输入一个批次,输出该批次的对应的预测值
            output = model(b_x)

            # 查找每一行中最大值对应的行标,即预测值
            pre_lab = torch.argmax(output,dim=1)

            # 计算本此次的损失函数
            loss = criterion(output,b_y)

            # 本批次的损失函数累加
            val_loss += loss.item() * b_x.size(0)

            # 如果预测正确,那就本批次的精度度累加
            val_corrents += torch.sum(pre_lab==b_y.data)

            # 当前用于验证的样本数累加
            val_num += b_x.size(0)


        # 计算每轮次的损失值和准确率
        train_loss_all.append(train_loss / train_num) # 本轮训练集的loss值
        train_acc_all.append(train_corrects.double().item() / train_num) # 本轮训练集的准确率

        val_loss_all.append(val_loss / val_num) # 本轮验证集的loss值
        val_acc_all.append(val_corrents.double().item() / val_num) # 本轮验证集的准确率

        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))

    # 将最佳的模型参数保存
    torch.save(best_model_wts,"best_model.pth") # .pth是权重文件的后缀,如果相对路径不行,就改为绝对路径

    # 保存训练好的模型参数
    train_process = pd.DataFrame(
        data={
            "epoch":range(num_epochs),
            "train_loss_all":train_loss_all,
            "train_acc_all":train_acc_all,
            "val_loss_all":val_loss_all,
            "val_acc_all":val_acc_all
        }
    )
    return train_process


# 定义绘图的函数,绘制loss和准确度
def matplot_acc_loss(train_process):
    plt.figure(figsize=(12,4))

    # 绘制训练集和验证集的损失值图像
    plt.subplot(1,2,1) # 一行两列,第一列
    plt.plot(train_process['epoch'],train_process.train_loss_all,"ro-",label="train loss")
    plt.plot(train_process['epoch'],train_process.val_acc_all,"bs-",label="val loss")
    plt.legend() # 图例
    plt.xlabel("epoch") # x轴标签
    plt.ylabel("loss") # y轴标签


    # 绘制训练集和验证集的准确度图像
    plt.subplot(1,2,2) # 一行两列,第二列
    plt.plot(train_process['epoch'],train_process.val_loss_all,"ro-",label="train acc")
    plt.plot(train_process['epoch'],train_process.val_acc_all,"bs-",label="va acc")



if __name__ == '__main__':
    # 实例化自定义模型类
    model = GoogLeNet()
    # 加载数据集
    train_dataloader,val_dataloader = train_val_data_process()
    # 训练模型
    train_process = train_model_process(model,train_dataloader,val_dataloader,20)
    # 绘制图像
    matplot_acc_loss(train_process)

数据集归一化

在项目的根目录下创建一个py文件,就叫mean_std.py吧,用于计算均值和方差,使数据归一化,符合正太分布

# mean_std.py文件
from PIL import Image
import os
import numpy as np

# 文件夹路径,包含所有图片文件
folder_path = 'data_cat_dog'

# 初始化累积变量
total_pixels = 0
sum_normalized_pixel_values = np.zeros(3)  # 如果是RGB图像,需要三个通道的均值和方差

# 遍历文件夹中的图片文件
for root, dirs, files in os.walk(folder_path):
    for filename in files:
        if filename.endswith(('.jpg', '.jpeg', '.png', '.bmp')):  # 可根据实际情况添加其他格式
            image_path = os.path.join(root, filename)
            image = Image.open(image_path)
            image_array = np.array(image)

            # 归一化像素值到0-1之间
            normalized_image_array = image_array / 255.0

            # print(image_path)
            # print(normalized_image_array.shape)
            # 累积归一化后的像素值和像素数量
            total_pixels += normalized_image_array.size
            sum_normalized_pixel_values += np.sum(normalized_image_array, axis=(0, 1))

# 计算均值和方差
mean = sum_normalized_pixel_values / total_pixels


sum_squared_diff = np.zeros(3)
for root, dirs, files in os.walk(folder_path):
    for filename in files:
        if filename.endswith(('.jpg', '.jpeg', '.png', '.bmp')):
            image_path = os.path.join(root, filename)
            image = Image.open(image_path)
            image_array = np.array(image)
            # 归一化像素值到0-1之间
            normalized_image_array = image_array / 255.0
            # print(normalized_image_array.shape)
            # print(mean.shape)
            # print(image_path)

            try:
                diff = (normalized_image_array - mean) ** 2
                sum_squared_diff += np.sum(diff, axis=(0, 1))
            except:
                print(f"捕获到自定义异常")
            # diff = (normalized_image_array - mean) ** 2
            # sum_squared_diff += np.sum(diff, axis=(0, 1))

variance = sum_squared_diff / total_pixels

print("Mean:", mean)
print("Variance:", variance)

运行起来也是需要一定的时间。运行完毕后会得到mean和variance两个值。

模型测试

修改model_test.py文件中的部分代码

import torch
import torch.utils.data as Data
from numpy.random import shuffle
from torchvision import transforms
from torchvision.datasets import FashionMNIST
from torchvision.datasets import ImageFolder

from model import GoogLeNet,Inception

# 加载要训练的数据
def test_data_process():
    #======================修改的部分开始======================
    ROOT_TRAIN = r'data\train' # 数据集的路径
    
    # 定义归一化方法 计算均值和方差
    normalize = transforms.Normalize([0.162,0.151,0.138],[0.058,0.052,0.048])
    
    # 定义数据集处理方法变量
    test_transform = transforms.Compose( # 对数据集进行操作
    	[
            transforms.Resize((224,224)),# 修改数据集的大小为 224*224
            transforms.ToTensor(), # 将数据修改为tensor格式
            normalize # 数据归一化
        
        ]  
    )
    # 加载数据集 ImageFolder()为第三方库导入的包
    test_data = ImageFolder(
    	ROOT_TRAIN, # 读取数据集的路径
        transform = test_transform # 处理数据的方法
    )
	#======================修改的部分结束======================
    # 通过DataLoader加载器 来加载数据
    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):
    # 指定设备
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    # 将模型放入设备中
    model.to(device)

    # 初始化模型训练的每轮参数
    test_correct = 0.0 # 准确度
    test_num = 0 # 测试样本数量

    # 只进行前向传播
    with torch.no_grad(): # 将梯度设置为0
        for test_data_x,test_data_y in enumerate(test_dataloader): # 遍历每轮次
            # 由于上面设置批次为1,所以这里就不需要循环批次了

            test_data_x = test_data_x.to(device) # 将测试数据放入到设备中
            test_data_y = test_data_y.to(device) # 将标签数据放入到设备中

            # 模型切换成评估模式
            model.eval()

            # 前向传播 将测试数据放入到模型中
            output = model(test_data_x)

            # 查找每一行中最大值的行标
            pre_lab = torch.argmax(output,dim=1)

            # 模型预测的结果 将pre_lab 与 标签数据 进行比较
            # 如果预测正确,则加1
            test_correct += torch.sum(pre_lab==test_data_y.data)

            # 测试样本数量累加
            test_num += test_data_y.size(0)

    # 计算最终测试的准确率 每轮的准确度 / 总样本数量
    test_acc = test_correct.double().item() / test_num

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





if __name__ == '__main__':
    # 加载模型
    model = GoogLeNet(Inception)
    # 模型具体的训练过程
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    # 加载训练好的模型最佳参数
    model.load_state_dict(torch.load('best_model.pth',map_location=device))

    # 加载测试的数据集
    test_dataloader = test_data_process()

    # 开始测试
    # test_model_process(model, test_dataloader) # 简略测试

    # 模型具体的训练过程
    # device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    # 将模型放入到设备当中
    model = model.to(device)

    # 数据的类别
    classes = ['猫','狗']

    # 梯度设置为0
    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)
            # 获取最大值的行标
            pre_lab = torch.argmax(output,dim=1)
            # 取出张量中的下标
            result = pre_lab.item()
            label = b_y.item()

            print("预测结果为:",classes[result],"标签为:",classes[label])


模型的推理

随便从网上下载猫或者狗的图像到项目的根目录下,这里就下载猫的图片吧:

图片名就设置为: 7dd98d1001e939010b1f0c1e537c22e836d19614.jpeg

然后增加和修改model_test.py文件中的代码:

import torch
import torch.utils.data as Data
from numpy.random import shuffle
from torchvision import transforms
from torchvision.datasets import FashionMNIST
from torchvision.datasets import ImageFolder
from PIL import Image # 导入第三方库 增加代码

from model import GoogLeNet,Inception

# 加载要训练的数据
def test_data_process():
    #======================修改的部分开始======================
    ROOT_TRAIN = r'data\train' # 数据集的路径
    
    # 定义归一化方法 计算均值和方差
    normalize = transforms.Normalize([0.162,0.151,0.138],[0.058,0.052,0.048])
    
    # 定义数据集处理方法变量
    test_transform = transforms.Compose( # 对数据集进行操作
    	[
            transforms.Resize((224,224)),# 修改数据集的大小为 224*224
            transforms.ToTensor(), # 将数据修改为tensor格式
            normalize # 数据归一化
        
        ]  
    )
    # 加载数据集 ImageFolder()为第三方库导入的包
    test_data = ImageFolder(
    	ROOT_TRAIN, # 读取数据集的路径
        transform = test_transform # 处理数据的方法
    )
	#======================修改的部分结束======================
    # 通过DataLoader加载器 来加载数据
    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):
    # 指定设备
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    # 将模型放入设备中
    model.to(device)

    # 初始化模型训练的每轮参数
    test_correct = 0.0 # 准确度
    test_num = 0 # 测试样本数量

    # 只进行前向传播
    with torch.no_grad(): # 将梯度设置为0
        for test_data_x,test_data_y in enumerate(test_dataloader): # 遍历每轮次
            # 由于上面设置批次为1,所以这里就不需要循环批次了

            test_data_x = test_data_x.to(device) # 将测试数据放入到设备中
            test_data_y = test_data_y.to(device) # 将标签数据放入到设备中

            # 模型切换成评估模式
            model.eval()

            # 前向传播 将测试数据放入到模型中
            output = model(test_data_x)

            # 查找每一行中最大值的行标
            pre_lab = torch.argmax(output,dim=1)

            # 模型预测的结果 将pre_lab 与 标签数据 进行比较
            # 如果预测正确,则加1
            test_correct += torch.sum(pre_lab==test_data_y.data)

            # 测试样本数量累加
            test_num += test_data_y.size(0)

    # 计算最终测试的准确率 每轮的准确度 / 总样本数量
    test_acc = test_correct.double().item() / test_num

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





if __name__ == '__main__':
    # 加载模型
    model = GoogLeNet(Inception)
    # 指定设备
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    # 加载训练好的模型最佳参数
    model.load_state_dict(torch.load('best_model.pth',map_location=device))
    # 加载测试的数据集
    # test_dataloader = test_data_process()
    
    # 开始测试
    # test_model_process(model, test_dataloader) # 简略测试
    
    # 指定设备
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    # 将模型放入到设备当中
    model = model.to(device)

    # 数据的类别
    classes = ['猫','狗']

    # =================新增代码部分开始,到底直接结束=================
    # 读取刚刚下载到本地猫的图片
	cat_url = "7dd98d1001e939010b1f0c1e537c22e836d19614.jpeg"
	image = Image.open(url) # 要预测图片的路径
            
    # 定义归一化方法 计算均值和方差
    normalize = transforms.Normalize([0.162,0.151,0.138],[0.058,0.052,0.048])
    
    # 定义数据集处理方法变量
    test_transform = transforms.Compose( # 对数据集进行操作
    	[
            transforms.Resize((224,224)),# 修改数据集的大小为 224*224
            transforms.ToTensor(), # 将数据修改为tensor格式
            normalize # 数据归一化
        
        ]  
    )
    
    # 对下载的图片进行格式处理 转成torch类型数据
    image = test_transform(image)
    # print(image.shape) # torch.Size([3,224,224]) 打印处理后的图片的维度
    
    # 添加批次维度 1
    image = unsqueeze(0)
    # print(image.shape) # torch.Size([1,3,224,224]) 打印处理后的图片的维度,其中1表示批次,是由unsqueeze(0)得来的
    
    # 去除梯度下降
    with torch.no_grad():
        model.eval() # 开启验证模式
        image = image.to(device) # 将图片放入到设备当中
        output = model(image) # 将图片放入到模型当中
        pre_lab = torch.argmax(out_put,dim=1) # 标签
        result = pre_lab.item()
    # print(pre_lab) # tensor([0],device='cude:0')
    # print(result) # 0 表示猫
    print("预测值: ",classes[result])

device = torch.device(“cuda” if torch.cuda.is_available() else “cpu”)

# 将模型放入到设备当中
model = model.to(device)

# 数据的类别
classes = ['猫','狗']

# =================新增代码部分开始,到底直接结束=================
# 读取刚刚下载到本地猫的图片
cat_url = "7dd98d1001e939010b1f0c1e537c22e836d19614.jpeg"
image = Image.open(url) # 要预测图片的路径
        
# 定义归一化方法 计算均值和方差
normalize = transforms.Normalize([0.162,0.151,0.138],[0.058,0.052,0.048])

# 定义数据集处理方法变量
test_transform = transforms.Compose( # 对数据集进行操作
	[
        transforms.Resize((224,224)),# 修改数据集的大小为 224*224
        transforms.ToTensor(), # 将数据修改为tensor格式
        normalize # 数据归一化
    
    ]  
)

# 对下载的图片进行格式处理 转成torch类型数据
image = test_transform(image)
# print(image.shape) # torch.Size([3,224,224]) 打印处理后的图片的维度

# 添加批次维度 1
image = unsqueeze(0)
# print(image.shape) # torch.Size([1,3,224,224]) 打印处理后的图片的维度,其中1表示批次,是由unsqueeze(0)得来的

# 去除梯度下降
with torch.no_grad():
    model.eval() # 开启验证模式
    image = image.to(device) # 将图片放入到设备当中
    output = model(image) # 将图片放入到模型当中
    pre_lab = torch.argmax(out_put,dim=1) # 标签
    result = pre_lab.item()
# print(pre_lab) # tensor([0],device='cude:0')
# print(result) # 0 表示猫
print("预测值: ",classes[result])

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值