CIFAR10图像分类ResNet模型实战(pytorch)

  • 1 ResNet18网络实现及略微改进
  • 2 学习过程中的部分问题总结:
    • 2.1 为什么nn.ReLU() 设置 inplace=True?
    • 2.2 nn.Sequential(*layers)加了一个\*
    • 2.3 net.train()/ net.eval()
    • 2.4 用到的argsparse模块
    • 2.5 创建记录数据的txt文件
    • 2.6 sum_loss 、 predicted 、total 、correct (重点理解)
    • 2.7 __init__中使用nn.Relu(),forward中使用F.relu(),两者的区别?
  • 3 源代码附详细解释

      Kaggle中【CIFAR-10 - Object Recognition in Images】竞赛实战,小白自学参考ResNet-18实现Cifar-10图像分类(测试集分类准确率95.170%)代码,熟悉了解每一步功能作用及思路。
参考2:pytorch入坑笔记1: 从ResNet出发引发的几点思考

1 ResNet18网络实现及略微改进

      首先根据下图利用pyTorch实现ResNet网络,这里需要注意原论文中采用的数据集为ImageNet数据集,输入图像数据大小224×224,第一个卷积核大小kernel_size=7,而CIFAR10数据集的输入图像大小为32×32,故这里将卷积核大小改为kernel_size=3。这里也没有加入最大池化层maxpool,可能是因为32×32的输入比较小,一开始使用maxpool可能会损失较多信息,该结构在224×224下表现应该比较正常,故这里做了些调整。

# 1 定义ResNet18网络
# 1.1 定义残差块
class ResidualBlock(nn.Module):def __init__(self, inchannel, outchannel, stride=1):    # 残差块可以指定输入通道数、输出通道数、步长super(ResidualBlock, self).__init__()self.left = nn.Sequential(nn.Conv2d(inchannel, outchannel, kernel_size=3, stride=stride, padding=1, bias=False),nn.BatchNorm2d(outchannel),nn.ReLU(inplace=True),nn.Conv2d(outchannel, outchannel, kernel_size=3, stride=1, padding=1, bias=False),nn.BatchNorm2d(outchannel))self.shortcut = nn.Sequential()# 条件stride != 1指在每个残差块连接时,如果发生了尺寸减半、通道数增倍的情况下,参数s均取2, 其余情况均为s=1# 条件inchannel != outchannel指上一个残差块的输入与这个残差块本身的的输出不同时,即需要1×1卷积来完成通道数增倍# 将inchannel直接连接1×1卷积层将输入改变大小(s2=)及通道数(#filter个数)if stride != 1 or inchannel != outchannel:self.shortcut = nn.Sequential(nn.Conv2d(inchannel, outchannel, kernel_size=1, stride=stride, bias=False),nn.BatchNorm2d(outchannel))def forward(self, x):out = self.left(x)out += self.shortcut(x)out = F.relu(out)return out# 1.2 定义ResNet整个网络模型
class ResNet(nn.Module):# 输入参数为残差块、def __init__(self, ResidualBlock, num_classes=10):super(ResNet, self).__init__()self.inchannel = 64self.conv1 = nn.Sequential(nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False),nn.BatchNorm2d(64),nn.ReLU(),)self.layer1 = self.make_layer(ResidualBlock, 64,  2, stride=1)self.layer2 = self.make_layer(ResidualBlock, 128, 2, stride=2)self.layer3 = self.make_layer(ResidualBlock, 256, 2, stride=2)self.layer4 = self.make_layer(ResidualBlock, 512, 2, stride=2)self.fc = nn.Linear(512, num_classes)def make_layer(self, block, channels, num_blocks, stride):strides = [stride] + [1] * (num_blocks - 1)   #strides=[1,1]# 第一个ResidualBlock的步幅由make_layer的函数参数stride指定# ,后续的num_blocks-1个ResidualBlock步幅是1layers = []for stride in strides:layers.append(block(self.inchannel, channels, stride))self.inchannel = channels  # 第一个残差块之后的每一个残差块的输入通道数都等于上一个残差块额输出通道数return nn.Sequential(*layers)def forward(self, x):out = self.conv1(x)out = self.layer1(out)out = self.layer2(out)out = self.layer3(out)out = self.layer4(out)out = F.avg_pool2d(out, 4)out = out.view(out.size(0), -1)out = self.fc(out)return outdef ResNet18():return ResNet(ResidualBlock)

2 学习过程中的部分问题总结:

2.1 为什么nn.ReLU() 设置 inplace=True?

      ReLU函数有个inplace参数,如果设为True,它会把输出直接覆盖到输入中,这样可以节省内存/显存。可以理解为就地操作。之所以可以覆盖是因为在计算ReLU的反向传播时,只需根据输出就能够推算出反向传播的梯度,但是只有少数的autograd操作支持inplace操作(如tensor.sigmoid_()),一般建议不要使用inplace操作。

在 pytorch 中, 有两种情况不能使用 inplace operation:

  1. 对于 requires_grad=True 的叶子张量(leaf tensor) 不能使用inplace operation
  2. 对于在求梯度阶段需要用到的张量 ,不能使用 inplace operation

2.2 nn.Sequential(*layers)加了一个*

layers = []
layers.append(nn.Linear())
··· # 可以继续append网络层
return nn.Sequential(*layers)

如果*号加在了是实参上,代表的是将输入迭代器拆成一个个元素

2.3 net.train()/ net.eval()

      使用PyTorch进行训练和测试时,要把实例化的模型设定为train() / eval()模式。当net.eval()时,框架会自动把BNDropOut固定住,不会取平均,而是使用训练好的值。
      对于BN层来说, 在训练过程中,对每一个batch取一个样本均值和方差,然后使用滑动指数平均所有的batch的均值和方差来近似整个样本的均值和方差。 对于测试阶段,固定样本和方差后,BN相当于一个线性的映射关系。所以说对于pytorch来说,在训练阶段设置 net.train() 相当于打开滑动指数平均按钮,不断的更新;测试阶段设置 net.eval()关闭更新,相当于一个线性映射关系。
      Dropout是概率权重衰减,在测试时为了得到准确结果而非一个概率解,故采用 net.train() / net.eval()来分别对应训练和测试阶段。

2.4 用到的argsparse模块

# 参数设置,使得我们能够手动输入命令行参数,就是让风格变得和Linux命令行差不多
parser = argparse.ArgumentParser(description='PyTorch CIFAR10 Training')
parser.add_argument('--outf', default='./model/', help='folder to output images and model checkpoints') #输出结果保存路径
args = parser.parse_args()

      代码中的这一部分的目的是使得该项目可以通过命令行来执行py文件并传入相关参数,即可以不必打开pycharm等其他解释器,在命令行终端传入所需参数并运行即可。
当在命令行中输入:python cifar10.py --help 时,命令行中出现:

usage: cifar10.py [-h] --outfPyTorch CIFAR10 Trainingpositional arguments:
--outf   older to output images and model checkpoints

      在命令行中输入python cifar10.py "./model/"即表示该文件夹路径"./model/"为输入参数,该文件夹指的是输出结果的保存路径;default='./model/'表示当没有输入参数时,参数--outf的默认值为'./model/'
args = parser.parse_args()表示将改参数保存到args当中,后续会用到该参数。

      在开始训练时的两行代码:

if not os.path.exists(args.outf): os.makedirs(args.outf)

      即用到了参数args,表示如果当前工作目录不存在args.outf表示的这个文件夹,则在当前工作目录下创建一个文件夹,该文件夹的名字默认为model

2.5 创建记录数据的txt文件

      在if __name__ == "__main__":语句下的with open("acc.txt", "w") as f:表示以写方式打开当前工作目录名为acc.txt的文件,若不存在则系统创建一个;
     语句with open("log.txt", "w")as f2:同理。

2.6 sum_loss 、 predicted 、total 、correct (重点理解)

sum_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += predicted.eq(labels.data).cpu().sum()

      对于训练过程中的每一个batch,loss.item()表示每一个batch计算出的loss,每一次迭代sum_loss不断加上loss.item(), 最终输出为sum_loss / (i + 1)
output.data为输入经过网络模型后的输出数据,对于此项目,batch_size=128,网络输出通道数为10,所以output.data.shape = torch.Size([128, 10]), 其中随意截取一个output.data表示为:

output.data tensor([[-0.3080,  0.7289, -0.4223,  ..., -0.0324,  1.1634, -1.3871],[-0.4914,  0.6484, -0.3070,  ..., -0.2455,  1.3141, -1.3008],[-0.0460, -0.0178, -0.3106,  ..., -0.1473,  0.6722, -0.5494],...,[ 0.1572,  0.2035, -0.3714,  ..., -0.1039,  0.6811, -0.5764],[ 0.0404,  0.8012, -0.4957,  ...,  0.0377,  1.2612, -1.0142],[-0.0138,  0.0319, -0.2495,  ..., -0.1314,  0.6073, -0.6382]],device='cuda:0') torch.Size([128, 10])

      每一行表示对于一张图片,output.data对应一个batch的全部输出。网络模型给出的该图片对应10种分类中的10个概率,最终认为概率最大的那个数所对应的类型即为模型判断出的该图片所属类型。
_, predicted = torch.max(outputs.data, 1)
      torch.max(input, dim)函数输入一个tensor, dim=1表示取每行的最大值,该函数会返回两个tensor:第一个tensor是每行的最大值;第二个tensor是每行最大值的索引。我们这里仅需要每行最大值的索引数而不需要每行的概率最大值,故返回值取_, predicted。其中随意截取一个predicted表示为:

tensor([8, 8, 8, 8, 8, 8, 8, 8, 5, 8, 8, 8, 8, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 8, 8, 8, 8, 8, 5, 8, 8, 8, 8,8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 8, 8, 1,8, 8, 8, 5, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 5, 8, 8, 8, 8, 8, 8, 8,8, 8, 8, 8, 8, 8, 8, 8, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 5, 8, 8, 8,8, 8, 8, 8, 8, 8, 8, 5], device='cuda:0')
torch.Size([128])

labels.size() = torch.Size([128]),所以total += labels.size(0)表示保存所遍历过的图片总数目。
      predicted.eq(labels.data)表示将预测的结果与labels.data的真实值进行比较(predicted、labels.data均为一个tensor,且tensor.size() = ([128]) ),ep()函数返回一个同predicted相同大小的tensor,其中的每一个值相同返回True,不同返回False。其中随意截取一个labels.data表示为:

label.data tensor([7, 9, 3, 1, 4, 8, 0, 8, 2, 5, 6, 1, 7, 5, 2, 9, 5, 3, 8, 4, 8, 1, 6, 9,7, 7, 8, 6, 1, 6, 9, 7, 5, 8, 9, 7, 0, 8, 3, 5, 0, 2, 6, 3, 6, 8, 4, 4,4, 6, 6, 0, 4, 8, 8, 4, 3, 7, 2, 1, 7, 5, 4, 1, 2, 7, 5, 0, 4, 7, 9, 3,0, 0, 3, 7, 8, 4, 4, 2, 0, 5, 0, 8, 4, 2, 7, 4, 3, 0, 3, 0, 7, 7, 9, 8,0, 5, 4, 4, 6, 4, 6, 8, 1, 4, 2, 7, 7, 3, 0, 5, 8, 4, 6, 3, 3, 9, 4, 0,8, 4, 5, 8, 0, 7, 1, 7], device='cuda:0')

      所对应的predicted.eq(labels.data)表示为:

tensor([False, False, False, False, False,  True, False,  True, False, False,False, False, False,  True, False, False, False, False,  True, False,True, False, False, False, False, False,  True, False, False, False,False, False, False,  True, False, False, False, False, False, False,False, False, False, False, False,  True, False, False, False, False,False, False, False,  True,  True, False, False, False, False, False,False, False, False, False, False, False, False, False, False, False,False, False, False, False, False, False, False, False, False, False,False, False, False,  True, False, False, False, False, False, False,False, False, False, False, False,  True, False, False, False, False,False, False, False,  True, False, False, False, False, False, False,False, False,  True, False, False, False, False, False, False, False,True, False, False,  True, False, False, False, False],device='cuda:0')

      correct += predicted.eq(labels.data).cpu().sum()sum()函数返回ep()函数返回tensor中结果为True的个数,可以看到predicted.eq(labels.data)返回的True的个数有16个,故该函数返回correct tensor(16.)

2.7 __init__中使用nn.Relu(),forward中使用F.relu(),两者的区别?

英文文档的解释:

How to choose between torch.nn.Functional and torch.nn module?In PyTorch you define your Models as subclasses of torch.nn.Module.
In the init function, you are supposed to initialize the layers you want to use. Unlike keras, Pytorch goes more low level and you have to specify the sizes of your network so that everything matches.
In the forward method, you specify the connections of your layers. This means that you will use the layers you already initialized, in order to re-use the same layer for each forward pass of data you make.
torch.nn.Functional contains some useful functions like activation functions a convolution operations you can use. However, these are not full layers so if you want to specify a layer of any kind you should use torch.nn.Module.
You would use the torch.nn.Functional conv operations to define a custom layer for example with a convolution operation, but not to define a standard convolution layer.

     也就是说__init__定义的是标准层,比如这里nn.Relu()是标准层,而在forward里面是用的F.relu()更像是一种操作,不改变网络的参数权值。

3 源代码附详细解释

# 2 参数设置,使得我们能够手动输入命令行参数,就是让风格变得和Linux命令行差不多
parser = argparse.ArgumentParser(description='PyTorch CIFAR10 Training')
parser.add_argument('--outf', default='./model/', help='folder to output images and model checkpoints') #输出结果保存路径
args = parser.parse_args()# 2.1 超参数设置
EPOCH = 135   #遍历数据集次数
pre_epoch = 0  # 定义已经遍历数据集的次数
BATCH_SIZE = 128      #批处理尺寸(batch_size)
LR = 0.1        #学习率# 3 数据获取及预处理
transform = transforms.Compose([transforms.Resize((32,32)),transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) #R,G,B每层的归一化用到的均值和方差])training_data = torchvision.datasets.CIFAR10(root='./data', train=True, download=False, transform=transform)  # 50000个训练集
test_date = torchvision.datasets.CIFAR10(root='./data', train=False, download=False, transform=transform)     # 10000个测试集train_iter = torch.utils.data.DataLoader(training_data, batch_size=BATCH_SIZE, shuffle=True)
test_iter = torch.utils.data.DataLoader(test_date, batch_size=BATCH_SIZE, shuffle=True)# Cifar-10的标签
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')# 4 模型定义-ResNet
net = ResNet18().to(device)# 5 定义损失函数和优化方式
criterion = nn.CrossEntropyLoss()  # 损失函数为交叉熵,多用于多分类问题
optimizer = optim.SGD(net.parameters(), lr=LR, momentum=0.9, weight_decay=5e-4) #优化方式为mini-batch momentum-SGD,并采用L2正则化(权重衰减)# 6 训练
if __name__ == "__main__":if not os.path.exists(args.outf):os.makedirs(args.outf)best_acc = 85  #2 假设一个best test accuracyprint("Start Training, Resnet-18!")with open("acc.txt", "w") as f:with open("log.txt", "w")as f2:for epoch in range(pre_epoch, EPOCH):print('\nEpoch: %d' % (epoch + 1))     # 每开始一次新的epoch时,显示一次;net.train()sum_loss = 0.0correct = 0.0total = 0.0for i, data in enumerate(train_iter, 0):# 用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,# 参数0表示下标起始位置为0,返回 enumerate(枚举) 对象。# 准备数据length = len(train_iter)inputs, labels = datainputs, labels = inputs.to(device), labels.to(device)optimizer.zero_grad()# forward + backwardoutputs = net(inputs)loss = criterion(outputs, labels)loss.backward()optimizer.step()# 每训练1个batch打印一次loss和准确率sum_loss += loss.item()# 取得分最高的那个类 (outputs.data的索引号)_, predicted = torch.max(outputs.data, 1)total += labels.size(0)print('output.data', outputs.data, outputs.data.shape)print("loss.item()", loss.item())print('predicted = torch.max(outputs.data, 1):', predicted)print("labels.size(0):", labels.size())print("total:", total)correct += predicted.eq(labels.data).cpu().sum()print('label.data', labels.data)print('correct += predicted.eq(labels.data).cpu().sum()', predicted.eq(labels.data))print("correct", correct)print('[epoch:%d, iter:%d] Loss: %.03f | Acc: %.3f%% '% (epoch + 1, (i + 1 + epoch * length), sum_loss / (i + 1), 100. * correct / total))f2.write('%03d  %05d |Loss: %.03f | Acc: %.3f%% '% (epoch + 1, (i + 1 + epoch * length), sum_loss / (i + 1), 100. * correct / total))f2.write('\n')f2.flush()# 每训练完一个epoch测试一下准确率print("Waiting Test!")with torch.no_grad():correct = 0total = 0for data in test_iter:net.eval()images, labels = dataimages, labels = images.to(device), labels.to(device)outputs = net(images)# 取得分最高的那个类 (outputs.data的索引号)_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum()print('测试分类准确率为:%.3f%%' % (100 * correct / total))acc = 100. * correct / total# 将每次测试结果实时写入acc.txt文件中print('Saving model......')torch.save(net.state_dict(), '%s/net_%03d.pth' % (args.outf, epoch + 1))f.write("EPOCH=%03d,Accuracy= %.3f%%" % (epoch + 1, acc))f.write('\n')f.flush()# 记录最佳测试分类准确率并写入best_acc.txt文件中if acc > best_acc:f3 = open("best_acc.txt", "w")f3.write("EPOCH=%d,best_acc= %.3f%%" % (epoch + 1, acc))f3.close()best_acc = accprint("Training Finished, TotalEPOCH=%d" % EPOCH)

欢迎关注【OAOA】

CIFAR10图像分类ResNet模型实战(pytorch)相关推荐

  1. Pytorch实战2:ResNet-18实现Cifar-10图像分类(测试集分类准确率95.170%)

    版权说明:此文章为本人原创内容,转载请注明出处,谢谢合作! Pytorch实战2:ResNet-18实现Cifar-10图像分类 实验环境: Pytorch 0.4.0 torchvision 0.2 ...

  2. 【深度学习】Pytorch实现CIFAR10图像分类任务测试集准确率达95%

    文章目录 前言 CIFAR10简介 Backbone选择 训练+测试 训练环境及超参设置 完整代码 部分测试结果 完整工程文件 Reference 前言 分享一下本人去年入门深度学习时,在CIFAR1 ...

  3. [Pytorch图像分类全流程实战]Task06:可解释性分析

    目录 前言 CAM热力图系列算法 [A]安装配置环境 [B] torchcam命令行 [C1]Pytorch预训练ImageNet图像分类-单张图像 [C2] Pytorch预训练lmageNet图像 ...

  4. keras实战项目:CIFAR-10 图像分类

    转自:https://yq.aliyun.com/articles/606966 我们可以简单的将深度神经网络的模块,分成以下的三个部分,即深度神经网络上游的基于生成器的 输入模块,深度神经网络本身, ...

  5. Pytorch CIFAR10图像分类 LeNet5篇

    Pytorch CIFAR10图像分类 LeNet5篇 文章目录 Pytorch CIFAR10图像分类 LeNet5篇 4.定义网络(LeNet5) 5. 定义损失函数和优化器 6. 训练 损失函数 ...

  6. 【Pytorch进阶一】基于LeNet的CIFAR10图像分类

    [Pytorch进阶一]基于LeNet的CIFAR10图像分类 一.LeNet网络介绍 二.CIFAR10数据集介绍 三.程序架构介绍 3.1 LeNet模型(model.py) 3.2 训练(tra ...

  7. Pytorch CIFAR10图像分类 数据加载与可视化篇

    Pytorch CIFAR10图像分类 数据加载与可视化篇 文章目录 Pytorch CIFAR10图像分类 数据加载与可视化篇 1.数据读取 2. 查看数据(格式,大小,形状) 3. 查看图片 np ...

  8. 手把手快速实现 Resnet 残差模型实战

    作者 | 李秋键 出品 | AI科技大本营(ID:rgznai100) 引言:随着深度学习的发展,网络模型的深度也随之越来越深,但随着网络模型深度的加深,往往会曾在这随着模型深度的加大,模型准确率反而 ...

  9. 【pytorch速成】Pytorch图像分类从模型自定义到测试

    文章首发于微信公众号<与有三学AI> [pytorch速成]Pytorch图像分类从模型自定义到测试 前面已跟大家介绍了Caffe和TensorFlow,链接如下. [caffe速成]ca ...

  10. 计算机视觉:基于眼疾分类数据集iChallenge-PM图像分类经典模型剖析(LeNet,AlexNet,VGG,GoogLeNet,ResNet)

    计算机视觉:图像分类经典模型 LeNet AlexNet VGG GoogLeNet ResNet 图像分类是根据图像的语义信息对不同类别图像进行区分,是计算机视觉的核心,是物体检测.图像分割.物体跟 ...

最新文章

  1. 客快物流大数据项目(五十三):实时ETL模块开发准备
  2. ModelSim几种不同的版本的区别
  3. Linux 的启动流程--转
  4. adb 获取当前activity_ADB 你想找的命令都在这里
  5. JavaWeb基础—dbutils的简单入门
  6. 被word格式折磨疯掉的我
  7. 【SpringCloud】Spring Cloud bus
  8. ASP.NET MVC分页的实现(上)
  9. mc2180 刷机方法_MC控制和时差方法
  10. error while loading shared libraries: libtinfo.so.5
  11. ASP.NET Web程序设计 第五章 页面状态管理
  12. 使用Depix进行马赛克的消除测试
  13. 服务器硬件配置应如何选择?
  14. 20221118-数学函数图像在线工具推荐
  15. linux显卡用amd还是NVIDIA,Linux NVIDIA显卡驱动年度横评,不同于AMD,NVI
  16. 安卓系统开机时间优化分析
  17. poi3.17excel加边框
  18. 面试经验:我是如何得到谷歌、脸书和亚马逊offer的?
  19. 新人拍摄2299元婚纱照套餐最终花费13000元
  20. 网络安全-Kali更新源(APT)

热门文章

  1. 在VB中如何使IE窗口最大化
  2. 密码1-分类,常用类型,密码分析
  3. ECMAScript 是什么?
  4. 网站入侵工具之wscan使用详解
  5. 使用keytool转换签名证书格式,keyStore、jks签名证书相互转换
  6. 一个屌丝程序猿的人生(四十)
  7. 02. Compose 可组合组件之 属性 modifier
  8. 基于@Valid注解自定义参数校验
  9. 编写程序,先将输入的一系列整数中的最小值与第一个数交换,然后将最大值与最后一个数交换,最后输出交换后的序列。
  10. 基于特征全埋点的精排ODL实践总结