可以从本人以前的文章中可以看出作者以前从事的是嵌入式控制方面相关的工作,是一个机器视觉小白,之所以开始入门机器视觉的学习只要是一个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