ResNet,是2015年何恺明大佬发表在CVPR上的一篇文章,运用了残差连接这个概念。该论文一出,直接引爆了整个cv界。并且在2016年ImageNet上ResNet获得第一名。而ResNet至今被用在AI各个领域内的前沿技术当中。

要是我以后的论文引用量有ResNet的十分之一我就满足了(笑)

ResNet介绍

ResNet解决的是深度网络的退化问题。按常理讲,网络越深模型就能拟合更复杂的结果。但是在实际训练中,模型一旦加深效果不一定会好很有可能会产生拟合效果差,梯度消失等缺点。比如论文中展示的在CIFAR-10上20层CNN和56层CNN测试精度。由图可知,56层CNN的精度还比20层CNN的精度差。

在训练过程中,网络回传时是得到每一层网络的梯度再相乘。而越训练到后期或者较深的网络,它的梯度都非常的小,这样相乘后最后得到的总梯度也就很少甚至接近于0。为了解决这一问题,何博士在论文中提出了残差学习这一概念。

残差学习

当我们需要在一个网络的基础上再加几层网络时,常规的做法是直接在后面加网络原先网络的输出做加上网络的输入。但现在我们不这样做,根据残差学习当新网络输入为x时其学习到的特征记为 H(x) ,现在我们希望新网络可以学习到残差值 F(x)=H(x)-x ,这样其实原始的学习特征是 F(x)+x 。也就是说再最后的输出时,我们还是需要在F(x)的基础上加上x。

在原网络的基础加上新增网络,容易使网络退化梯度变得非常小。而将其输出改为残差值和网络值相加时,在求梯度时就不会有产生小梯度的值。因为在求导时式子中有一个x,众所周知我们对变量进行求导时,x的导数就是1。也可以浅显的说,现在对该层的网络求导得到的梯度是一个小梯度再加上一个1。这样就增加了梯度的值而弥补了梯度会消失的缺点。当然残差梯度不会那么巧全为1,而且就算其比较小,有1的存在也不会导致梯度消失。所以残差学习会更容易。

网络结构

采用了类似与VGG的网络,并在其基础上进行了改进,并通过短路机制加入了残差单元。基本的单元结构还是卷积,BN,激活函数这个套路。但在每个单元的输出位置加上了残差连接,单元输出再加上单元输入最后通过一个激活函数做为最后的输出。

而对于不同层的ResNet来说,残差单元的结构也不相同

在小于50层时一般残差单元内只有两层卷积,并且一个卷积是3*3卷积核大小然后填充是1不改变feature map的大小,而另一个卷积则将大小缩小一半这个操作为了不使信息丢失的太多将feature map的通道数增大一倍,而且也降低网络的复杂性。大于50层时,先用了一个1*1的卷积层将feature map的通道数映射回我需要的通道数,再通过与上述一样的3*3改变大小的卷积层。最后再通过一个将通道数乘四倍的卷积层。从图中可以看到,ResNet相比普通网络每两层间增加了短路机制,这就形成了残差学习,其中虚线表示feature map数量发生了改变。

Pytorch实现ResNet

import torch
import time
from torch import nn# 初始的卷积层,对输入的图片进行处理成feature map
class Conv1(nn.Module):def __init__(self,inp_channels,out_channels,stride = 2):super(Conv1,self).__init__()self.net = nn.Sequential(nn.Conv2d(inp_channels,out_channels,kernel_size=7,stride=stride,padding=3,bias=False),# 卷积的结果(i - k + 2*p)/s + 1,此时图像大小缩小一半nn.BatchNorm2d(out_channels),nn.ReLU(inplace=True),nn.MaxPool2d(kernel_size=3,stride=2,padding=1)# 根据卷积的公式,该feature map尺寸变为原来的一半)def forward(self,x):y = self.net(x)return yclass Simple_Res_Block(nn.Module):def __init__(self,inp_channels,out_channels,stride=1,downsample = False,expansion_=False):super(Simple_Res_Block,self).__init__()self.downsample = downsampleif expansion_:self.expansion = 4# 将维度扩展成expansion倍else:self.expansion = 1self.block = nn.Sequential(nn.Conv2d(inp_channels,out_channels,kernel_size=3,stride=stride,padding=1),nn.BatchNorm2d(out_channels),nn.ReLU(inplace=True),nn.Conv2d(out_channels,out_channels*self.expansion,kernel_size=3,padding=1),nn.BatchNorm2d(out_channels*self.expansion))if self.downsample:self.down = nn.Sequential(nn.Conv2d(inp_channels,out_channels*self.expansion,kernel_size=1,stride=stride,bias=False),nn.BatchNorm2d(out_channels*self.expansion))self.relu = nn.ReLU(inplace=True)def forward(self,input):residual = inputx = self.block(input)if self.downsample:residual = self.down(residual)# 使x和h的维度相同out = residual + xout = self.relu(out)return outclass Residual_Block(nn.Module):def __init__(self,inp_channels,out_channels,stride=1,downsample = False,expansion_=False):super(Residual_Block,self).__init__()self.downsample = downsample# 判断是否对x进行下采样使x和该模块输出值维度通道数相同if expansion_:self.expansion = 4# 将维度扩展成expansion倍else:self.expansion = 1# 模块self.conv1 = nn.Conv2d(inp_channels,out_channels,kernel_size=1,stride=1,bias=False)# 不对特征图尺寸发生改变,起映射作用self.drop = nn.Dropout(0.5)self.BN1 = nn.BatchNorm2d(out_channels)self.conv2 = nn.Conv2d(out_channels,out_channels,kernel_size=3,stride=stride,padding=1,bias=False)# 此时卷积核大小和填充大小不会影响特征图尺寸大小,由步长决定self.BN2 = nn.BatchNorm2d(out_channels)self.conv3 = nn.Conv2d(out_channels,out_channels*self.expansion,kernel_size=1,stride=1,bias=False)# 改变通道数self.BN3 = nn.BatchNorm2d(out_channels*self.expansion)self.relu = nn.ReLU(inplace=True)if self.downsample:self.down = nn.Sequential(nn.Conv2d(inp_channels,out_channels*self.expansion,kernel_size=1,stride=stride,bias=False),nn.BatchNorm2d(out_channels*self.expansion))def forward(self,input):residual = inputx = self.relu(self.BN1(self.conv1(input)))x = self.relu(self.BN2(self.conv2(x)))h = self.BN3(self.conv3(x))if self.downsample:residual = self.down(residual)# 使x和h的维度相同out = h + residual# 残差部分out = self.relu(out)return outclass Resnet(nn.Module):def __init__(self,net_block,block,num_class = 1000,expansion_=False):super(Resnet,self).__init__()self.expansion_ = expansion_if expansion_:self.expansion = 4# 将维度扩展成expansion倍else:self.expansion = 1# 输入的初始图像经过的卷积# (3*64*64) --> (64*56*56)self.conv = Conv1(3,64)# 构建模块# (64*56*56) --> (256*56*56)self.block1 = self.make_layer(net_block,block[0],64,64,expansion_=self.expansion_,stride=1)# stride为1,不改变尺寸大小# (256*56*56) --> (512*28*28)self.block2 = self.make_layer(net_block,block[1],64*self.expansion,128,expansion_=self.expansion_,stride=2)# (512*28*28) --> (1024*14*14)self.block3 = self.make_layer(net_block,block[2],128*self.expansion,256,expansion_=self.expansion_,stride=2)# (1024*14*14) --> (2048*7*7)self.block4 = self.make_layer(net_block,block[3],256*self.expansion,512,expansion_=self.expansion_,stride=2)self.avgPool = nn.AvgPool2d(7,stride=1)# (2048*7*7) --> (2048*1*1)经过平均池化层将所有像素融合并取平均if expansion_:length = 2048else:length = 512self.linear = nn.Linear(length,num_class)for m in self.modules():if isinstance(m, nn.Conv2d):nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')elif isinstance(m, nn.BatchNorm2d):nn.init.constant_(m.weight, 1)nn.init.constant_(m.bias, 0)def make_layer(self,net_block,layers,inp_channels,out_channels,expansion_=False,stride = 1):block = []block.append(net_block(inp_channels,out_channels,stride=stride,downsample=True,expansion_=expansion_))# 先将上一个模块的通道数缩小为该模块需要的通道数if expansion_:self.expansion = 4else:self.expansion = 1for i in range(1,layers):block.append(net_block(out_channels*self.expansion,out_channels,expansion_=expansion_))return nn.Sequential(*block)def forward(self,x):x = self.conv(x)x = self.block1(x)x = self.block2(x)x = self.block3(x)x = self.block4(x)# x = self.avgPool(x)x = x.view(x.shape[0],-1)x = self.linear(x)return xdef Resnet18():return Resnet(Simple_Res_Block,[2,2,2,2],num_class=10,expansion_=False)# 此时每个模块里面只有两层卷积def Resnet34():return Resnet(Simple_Res_Block,[3,4,6,3],num_class=10,expansion_=False)def Resnet50():return Resnet(Residual_Block,[3,4,6,3],expansion_=True)# 也叫50层resnet,这个网络有16个模块,每个模块有三层卷积,最后还剩下初始的卷积和最后的全连接层,总共50层def Resnet101():return Resnet(Residual_Block,[3,4,23,3],expansion_=True)def Resnet152():return Resnet(Residual_Block,[3,8,36,3],expansion_=True)

其中包括了ResNet18,34,50,101,152。

对CIFAR-10进行分类

# 基于cifar10或cifar100的训练
import torch
import os
import time
import torchvision
import tqdm
import numpy as np
from torch.utils.data import Dataset,DataLoader
from ResNet import Resnet18,Resnet34,Resnet50,Resnet101,Resnet152
from visualizer import Visclass opt():model_name = 'Resnet18'save_path = 'checkpoints'save_name = 'lastest_param.pth'device = 'cuda'batch_size = 128learning_rate = 0.001epoch = 60state_file = 'checkpoints/result/lastest_param.pth'load_f = Trueclasses = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')train_transform = torchvision.transforms.Compose([torchvision.transforms.RandomCrop(32,padding=4),torchvision.transforms.RandomHorizontalFlip(p=0.5),torchvision.transforms.ToTensor(),torchvision.transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))])test_transform = torchvision.transforms.Compose([torchvision.transforms.ToTensor(),torchvision.transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))])def load_save(model,load_f = False):if load_f:state = torch.load(opt.state_file)model.load_state_dict(state)return modelelse:return model# model
if opt.model_name == "Resnet18":model = Resnet18()model.to(opt.device)
elif opt.model_name == "Resnet34":model = Resnet34()model.to(opt.device)
elif opt.model_name == "Resnet50":model = Resnet50()model.to(opt.device)
load_save(model,opt.load_f)# dataset
train_dataset = torchvision.datasets.CIFAR10(root = 'data',train = True,transform = opt.train_transform,download=True
)test_dataset = torchvision.datasets.CIFAR10(root = 'data',train = False,transform = opt.test_transform,download=True
)# dataloader
train_loader = DataLoader(train_dataset,batch_size=opt.batch_size,shuffle=True,num_workers=6
)test_loader = DataLoader(test_dataset,batch_size=100,shuffle=False,num_workers=6
)# loss
loss_fn = torch.nn.CrossEntropyLoss()# 交叉熵
# 优化器
optim = torch.optim.SGD(model.parameters(),lr=opt.learning_rate,momentum=0.9,weight_decay=5e-4)# 对权重做衰减,也就是给损失函数加一个l2正则项,若模型没有较好收敛,则降低参数
flag = 0def reverse_norm(img,mean=None,std=None):imgs = []for i in range(img.size(0)):image = img[i].data.cpu().numpy().transpose(1, 2, 0)if (mean is not None) and (std is not None):image = (image * std + mean) * 255else:  # 如果只是经过了ToTensor()image = image * 255imgs.append(image.transpose(2,0,1))return np.stack(imgs)for epoch in range(opt.epoch):now = time.time()print('---epoch{}---'.format(epoch))model.train()loss_epoch = 0true_pre_epoch = 0correct = 0for i,(img,label) in enumerate(tqdm.tqdm(train_loader)):img,label = img.to(opt.device),label.to(opt.device)output = model(img)loss = loss_fn(output,label)loss.backward()optim.step()optim.zero_grad()flag += 1loss_epoch += loss.datapre = torch.argmax(output, dim=1)num_true = (pre == label).sum()true_pre_epoch += num_truecorrect += label.shape[0]if (i+1)%100 == 0:print('epoch {} iter {} loss : {}'.format(epoch,i+1,loss_epoch/(i+1)))if (i+1)%200 == 0:acc = true_pre_epoch/correctprint('epoch {} iter {} train_acc : {}'.format(epoch,i+1,acc))imgs = reverse_norm(img,mean=(0.4914, 0.4822, 0.4465),std=(0.2023, 0.1994, 0.2010))# 可视化vis = Vis()vis.linee(Y=loss_epoch/(i+1),X=flag,win='loss')vis.linee(Y=acc,X=flag,win='acc')vis.Image(imgs,pre,opt.classes)# savemodel_path = os.path.join(opt.save_path,opt.save_name)torch.save(model.state_dict(),model_path)# testmodel.eval()num = 0labels = 0for img ,label in test_loader:img, label = img.to(opt.device), label.to(opt.device)output = model(img)num += (torch.argmax(output,dim=1).data == label.data).sum()labels += label.shape[0]fin = time.time()print('epoch {} test_acc : {}   运行一个epoch花费时间:{}s'.format(epoch,num/labels,fin-now))

结果

因为CIFAR-10的数据集较小也只是一个简单的10分类,图片才32*32的大小。所以我选择的是ResNet18去进行训练。在手动调整学习率之后,模型的测试精度能达到87%。我采用了三个学习率去训练,先用0.1训练了150个epoch,后面又分别用了0.01和0.001训练了60个epoch。训练时的loss大小和训练精度如下图,图像中每次值的突变代表我手动调整了学习率。

测试精度

---epoch57---25%|██▍       | 97/391 [00:03<00:08, 35.42it/s]epoch 57 iter 100 loss : 0.0178847014904022250%|█████     | 197/391 [00:05<00:05, 35.00it/s]Setting up a new session...
epoch 57 iter 200 loss : 0.019015971571207047
epoch 57 iter 200 train_acc : 0.993749976158142177%|███████▋  | 301/391 [00:09<00:02, 32.77it/s]epoch 57 iter 300 loss : 0.01771947182714939
100%|██████████| 391/391 [00:11<00:00, 32.87it/s]
epoch 57 test_acc : 0.8694999814033508   运行一个epoch花费时间:12.92395305633545s
---epoch58---25%|██▍       | 97/391 [00:03<00:08, 33.84it/s]epoch 58 iter 100 loss : 0.0174857471138238950%|█████     | 197/391 [00:06<00:06, 32.05it/s]Setting up a new session...
epoch 58 iter 200 loss : 0.016185222193598747
epoch 58 iter 200 train_acc : 0.995234370231628477%|███████▋  | 301/391 [00:09<00:02, 35.15it/s]epoch 58 iter 300 loss : 0.015332281589508057
100%|██████████| 391/391 [00:11<00:00, 33.29it/s]
epoch 58 test_acc : 0.8686999678611755   运行一个epoch花费时间:12.811056137084961s
---epoch59---26%|██▌       | 101/391 [00:03<00:08, 35.97it/s]epoch 59 iter 100 loss : 0.0167238991707563450%|█████     | 197/391 [00:05<00:05, 32.87it/s]Setting up a new session...
epoch 59 iter 200 loss : 0.0159761980175972
epoch 59 iter 200 train_acc : 0.995624959468841676%|███████▌  | 297/391 [00:08<00:02, 35.49it/s]epoch 59 iter 300 loss : 0.016513127833604813
100%|██████████| 391/391 [00:11<00:00, 33.80it/s]
epoch 59 test_acc : 0.8678999543190002   运行一个epoch花费时间:12.58652377128601s进程已结束,退出代码为 0

调参总结

1.给SGD加一个权重衰减,要不然会过拟合导致训练精度很高,测试精度很低。
2.再加一个momentum,并将值设为0.9
3.将权重衰减的参数调为5e-4
4.将batch_size调为128,一开始设置的64不足以使模型收敛的很好
5.训练时无法收敛的很好时,可以多加一些数据增强
6.为了提高训练精度,采用手动调学习率的方法。100个epoch之后,将学习率改为1e-3在训练60个epoch

这一部分的调参具体参考了Pytorch实战2:ResNet-18实现Cifar-10图像分类(测试集分类准确率95.170%)_sunqiande88的博客-CSDN博客

我相信针对该经典模型还有更好的trick或者调参来提高测试精度,如果你有更好的精度还请不吝惜你的方法在评论区留言告诉我,谢谢!

简介ResNet18并用其对CIFAR-10数据集进行分类相关推荐

  1. 深度学习入门——利用卷积神经网络训练CIFAR—10数据集

    CIFAR-10数据集简介 CIFAR-10是由Hinton的学生Alex Krizhevsky和Ilya Sutskever整理的一个用于普适物体的小型数据集.它一共包含10个类别的RGB彩色图片: ...

  2. 机器学习之sklearn使用下载MNIST数据集进行分类识别

    机器学习之sklearn使用下载MNIST数据集进行分类识别 一.MNIST数据集 1.MNIST数据集简介 2.获取MNIST数据集 二.训练一个二分类器 1.随机梯度下降(SGD)分类器 2.分类 ...

  3. 使用SVM对分泌效应蛋白数据集进行分类预测

    1.SVM简介 支持向量机(Support Vector Machine, SVM)是一类按监督学习(supervised learning)方式对数据进行二元分类的广义线性分类器(generaliz ...

  4. ML之SVM:利用SVM算法(超参数组合进行多线程网格搜索+3fCrVa)对20类新闻文本数据集进行分类预测、评估

    ML之SVM:利用SVM算法(超参数组合进行多线程网格搜索+3fCrVa)对20类新闻文本数据集进行分类预测.评估 目录 输出结果 设计思路 核心代码 输出结果 Fitting 3 folds for ...

  5. ML之SVM:利用SVM算法(超参数组合进行单线程网格搜索+3fCrVa)对20类新闻文本数据集进行分类预测、评估

    ML之SVM:利用SVM算法(超参数组合进行单线程网格搜索+3fCrVa)对20类新闻文本数据集进行分类预测.评估 目录 输出结果 设计思路 核心代码 输出结果 Fitting 3 folds for ...

  6. (pytorch-深度学习系列)pytorch实现多层感知机(自动定义模型)对Fashion-MNIST数据集进行分类-学习笔记

    pytorch实现多层感知机(自动定义模型)对Fashion-MNIST数据集进行分类 导入模块: import torch from torch import nn from torch.nn im ...

  7. (pytorch-深度学习系列)pytorch实现多层感知机(手动定义模型)对Fashion-MNIST数据集进行分类-学习笔记

    pytorch实现多层感知机对Fashion-MNIST数据集进行分类(手动定义模型) 多层感知机: 多层感知机在单层神经网络的基础上引入了一到多个隐藏层(hidden layer).隐藏层位于输入层 ...

  8. 用matlab实现用Bp神经网络对iris数据集进行分类(以及影响分类性能的参数条件)

    数据集已上传,结尾链接下载即可!!! 一.实验内容 Iris鸢尾花卉数据集,是一类多重变量分析的数据集.数据集包含150个数据样本,分为3类,每类50个数据,每个数据包含4个属性,分别对应花萼长度,花 ...

  9. python训练数据集_python – 如何训练大型数据集进行分类

    我有一个1600000推文的训练数据集.我该如何训练这类巨大的数据. 我尝试过使用nltk.NaiveBayesClassifier.如果我跑步,训练需要5天以上. def extract_featu ...

最新文章

  1. Java云托管服务的开支削减策略
  2. 影响SDN和NFV部署速度的两个因素
  3. SpringBoot 路径处理
  4. python读压缩文件内容_使用Python读写及压缩和解压缩文件的示例
  5. hbm配置文件 hibernate中
  6. python打包不能在其他电脑打开_pyinstaller打包python+opencv 无法在别人电脑上正常运行 问题所在:opencv_ffmpeg341_64.dll...
  7. 面试官:如何实现单行/多行文本溢出的省略样式?
  8. Linux 根分区扩容
  9. 基于stm32f401的双按键可视化多模式选择模块
  10. zabbix通过sendmail进行邮箱警报
  11. 路由器映射,端口映射?
  12. PoE交换机可以当普通交换机吗?
  13. 基于cesium和mars3d海洋三维管线信息系统开发完工总结
  14. rabbit 消息丢失
  15. IDEA提示Multi-catches are not supported at this language level的解决办法
  16. lua报错:1: unfinished string near ‘<eof>‘
  17. java余弦距离_使用TensorFlow实现余弦距离/欧氏距离(Euclideandistance)以及Attention矩阵的计算...
  18. Mybatis错误Illegal overloaded gette
  19. CLOB、BLOB , CLOB与BLOB的区别
  20. 【Window 入侵排查】

热门文章

  1. 文字保护纱-Material Design
  2. Word字数统计怎么用?2003/2007/2010统计字数全攻略!
  3. CHROME源码剖析 上《转》
  4. CSS一篇文章搞懂系列6:超全的字体篇与背景设置内容,花里胡哨起来
  5. 韩国区块链步入快车道:SM、Kakao、三星、LG等巨头ALL IN
  6. 推荐机制 协同过滤和基于内容推荐的区别
  7. java双冒号_jdk8新特性之双冒号 :: 用法及详解
  8. Android调试工具adb的高逼格使用方式
  9. 云宏广东省中医院虚拟化管理平台
  10. [Pyhon大数据分析] 五.人民网新闻话题抓取及Gephi构建主题知识图谱