ResNet 实现Cifar-10 识别以及一点思考

最近看了ResNet的论文,由于刚从CS231n过渡过来,所以还在使用Cifar-10数据集。刚好论文后面就有一节是专门研究(经过简化后的)ResNet在Cifar-10数据集上的表现的,所以决定着手复现这一结构。再加上百度了一下好像也几乎没有针对这一节论文的复现的,所以记录下来自己复现的过程。最终32层(0.46M参数)准确率达到了90%,44层(0.66M参数)准确率达到91%,距离作者所述的92.5%+的准确度还有差距
ResNet具体是什么结构也不用细说了,我喜欢先说两句再上代码,想直接看代码的翻到最后就好了。
在代码之前,我先描述一下作者对这个结构的设计和训练细节,以及我在复现过程中遇到的问题和思考

1. 作者针对Cifar-10分类提出的网络结构

作者在论文中提到自己总体网络结构(节选):
The first layer is 3x3 convolutions.
Then we use a stack of 6n layers with 3x3 convolutions
on the feature maps of sizes {32; 16; 8} respectively,
with 2n layers for each feature map size. The numbers of filters are {16; 32; 64} respectively. The subsampling is performed by convolutions with a stride of 2. The network ends with a global average pooling, a 10-way fully-connected layer, and softmax. There are totally 6n+2 stacked weighted layers. The following table summarizes the architecture:

output map size 32x32 16x16 8x8
# layers 1+2n 2n 2n
# filters 16 32 64

When shortcut connections are used, they are connected
to the pairs of 3x3 layers (totally 3n shortcuts). On this dataset we use identity shortcuts in all cases.
简单翻译一下:输入32x32的图片,第一层是3x3卷积层,接下来,对于每个相同分辨率的feature map做2n次卷积,每次卷积的通道数都相同。一共有3个这样的2n层,卷完8x8的特征图之后就做全局均值池化,最后用一个全连接层实现10类的分类。
高速通路只连接每对3x3卷积。因此最后有3n个高速通路。每个高速通路都是使用的恒等连接(不变换维度)

最后的结构就是:32x32输入->3x3卷积->2n个3x3卷积->步长为2的卷积->2n个3x3卷积->步长为2的卷积->2n个3x3卷积->步长为2的卷积->全局均值池化->全连接层->Softmax
每个2n卷积的通道数已经在上面的表格里给出。

2. 实现网络结构中遇到的问题

看起来还是蛮简单的,实现起来遇到了两个问题
第一个困扰我的问题就是,采用卷积做池化,这就不算有参数的层了吗?到底是把每个2n的第一层作为池化层,还是在每个2n的第一层之前加入了一个池化层?这里我抓住作者说“使用恒等链接作为高速通路”,所以倾向于后面的解释。
ResidualBlock在原论文示意图
图1 ResidualBlock在原论文示意图

第二个困扰我的问题就是,根据残差的结构图,最后一层的ReLU是放在残差和输入相加之后的,论文中作者只是轻描淡写地说自己采用了BN,但是BN是放在加操作之前,还是放在加操作之后?一开始我想当然地(不知为何这么想)放在了加之前。偶然地看起了Stochastic Depth(以下简称SD)的文章,文章里有一张很显眼的图画的就是带有BN的ResBlock结构。很明显BN放在加之前。SD的作者也没解释为什么,但是他指出,每一个残差模块的输出为:
Hl = ReLU( f(Hl-1) + Hl-1
当残差为0时,输出退化为:
Hl = ReLU( Hl-1 ) = Hl-1
懒得用公式打出来了,Hl 是某一层的输出,而Hl-1是上一层的输出,这是残差模块的基本原理。当残差为0,而本层的输入即上一层的输出也是通过ReLU激活的话,本来就全都非负,再经过一次ReLU自然就是原来的值了。
所以,如果我把BN放在加之后,就算残差为0,BN非常可能改变上一层的输出,e.g.使之非负。这样残差模块的意义就削弱了。当然,是不是可以考虑干脆把ReLU也放在加之前呢,有机会做做试验hhh
ps:单纯改了这个小结构就让我的准确率从87.5上升到89了。
ResidualBlock在SD论文中的示意图
图2 ResidualBlock在SD论文中的示意图

3. 作者训练网络的细节

对于训练的参数设定,作者文中如此描述(方括号内为原论文的引用序号,这里就懒得去掉了):
We use a weight decay of 0.0001 and momentum of 0.9,
and adopt the weight initialization in [13] and BN [16] but with no dropout. These models are trained with a minibatch size of 128 on two GPUs. We start with a learning rate of 0.1, divide it by 10 at 32k and 48k iterations, and terminate training at 64k iterations, which is determined on a 45k/5k train/val split. We follow the simple data augmentation in [24] for training: 4 pixels are padded on each side, and a 32x32 crop is randomly sampled from the padded image or its horizontal flip. For testing, we only evaluate the single view of the original 32x32 image.

提炼关键信息:SGD的m=0.9,weight_decay=1e-4,采用了He Kaiming权值初始化;采用了BN,minibatch=128,lr=0.1,32k和48k轮都会把学习率除以10,60k后停止训练;数据增强手段为4像素的pad并随机切割出32x32的图片、随机水平翻转。

4. 复现过程

由于是刚学完CS231n,所以学习率默认都是1e-3,1e-4级别的,优化器都是Adam,所以刚开始复现的时候,采用的n=5(根据前文的叙述,参数层共有6n+2因此共有32层),准确率只有85,61(分别表示在训练集和测试集上,下同),和作者描述的92.5%的准确率相差甚远。后来在网上找了一份ResNet-18的代码跑了一遍,发现准确率可以简单地上91%,所以进行了对比。
首先,单纯的把优化器换为SGD,并且SGD的参数也和作者相同,训练的效果就有了质的飞跃,达到了100%,87.5%,训练曲线如图3所示。
在这里插入图片描述
图3 ResNet损失和准确率曲线

最后训练准确率有几个点的差异,但是图形基本上就是这个图形,我也不想贴一堆长得差不多的图进行对比了。
后来又加入了原文的数据增强(水平翻转、4pad截取),好像准确率没有很大提升;然后把BN放在加之前,准确率提升到了89%+,接近90%的样子,最高到过90.4%。
再后来,把网络替换成n=7共44层,准确率可以比较轻松地达到90,最好能飙上91%

5. 总结分析

这应该是自己认真复现的第一个网络,比较简单,但是因为没啥经验所以一开始网络的效果很差。但是通过复现这个网络让我明白了Adam不是万能的,学习率是要调整的,怎么做数据增强等balabala的在训练网络中需要知道的细节知识。
这里使用的网络结构是比较高效的,32层的总参数不超过0.5M(个),还能在训练集上实现实现90%的准确率,网上找的ResNet18直接跑一通也就是91%,而且ResNet18的参数是它的20多倍了(直接比PyTorch保存下来的模型参数文件大小)。训练时间,按照作者60k次迭代,在2080Ti上大约是1小时20分钟。

6. 实现代码

初始化和数据集处理,还有两个常用的层

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

%matplotlib inline
plt.rcParams['figure.figsize'] = (10.0, 8.0) # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'

数据集的导入和预处理


transform_train = transforms.Compose([
    transforms.RandomCrop(32, padding=4),  #先四周填充0,再把图像随机裁剪成32*32
    transforms.RandomHorizontalFlip(),  #图像一半的概率翻转,一半的概率不翻转
    transforms.ToTensor(),
    transforms.Normalize((0, 0, 0), (1, 1, 1)), #R,G,B每层的归一化用到的均值和方差
])

transform_test = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0, 0, 0), (1, 1, 1)),
])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform_train)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=128,
                                          shuffle=True, num_workers=0)

testset = torchvision.datasets
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您的问题是关于MindSpore框架中ResNet50模型在CIFAR-10数据集上的应用。 ResNet50是一个深度卷积神经网络模型,用于图像分类任务。CIFAR-10是一个经典的图像分类数据集,包含10个类别的60000张32x32的彩色图像。 在MindSpore框架中,您可以使用ResNet50模型对CIFAR-10数据集进行训练和测试。首先,您需要准备好CIFAR-10数据集并将其转换为MindSpore支持的数据格式。然后,您可以使用MindSpore提供的ResNet50模型进行训练和测试。 以下是一个示例代码片段,演示如何在MindSpore中使用ResNet50模型对CIFAR-10数据集进行训练和测试: ``` import mindspore.nn as nn import mindspore.ops.operations as P from mindspore import Model from mindspore import Tensor from mindspore import context from mindspore import dataset as ds from mindspore.train.callback import ModelCheckpoint, CheckpointConfig, LossMonitor from mindspore.train.serialization import load_checkpoint, load_param_into_net from mindspore.nn.metrics import Accuracy # Define the ResNet50 model class ResNet50(nn.Cell): def __init__(self, num_classes=10): super(ResNet50, self).__init__() self.resnet50 = nn.ResNet50(num_classes=num_classes) def construct(self, x): x = self.resnet50(x) return x # Load the CIFAR-10 dataset data_home = "/path/to/cifar-10/" train_data = ds.Cifar10Dataset(data_home, num_parallel_workers=8, shuffle=True) test_data = ds.Cifar10Dataset(data_home, num_parallel_workers=8, shuffle=False) # Define the hyperparameters learning_rate = 0.1 momentum = 0.9 epoch_size = 200 batch_size = 32 # Define the optimizer optimizer = nn.Momentum(filter(lambda x: x.requires_grad, resnet50.get_parameters()), learning_rate, momentum) # Define the loss function loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean') # Define the model net = ResNet50() # Define the model checkpoint config_ck = CheckpointConfig(save_checkpoint_steps=1000, keep_checkpoint_max=10) ckpt_cb = ModelCheckpoint(prefix="resnet50", directory="./checkpoints/", config=config_ck) # Define the training dataset train_data = train_data.batch(batch_size, drop_remainder=True) # Define the testing dataset test_data = test_data.batch(batch_size, drop_remainder=True) # Define the model and train it model = Model(net, loss_fn=loss_fn, optimizer=optimizer, metrics={"Accuracy": Accuracy()}) model.train(epoch_size, train_data, callbacks=[ckpt_cb, LossMonitor()], dataset_sink_mode=True) # Load the trained model and test it param_dict = load_checkpoint("./checkpoints/resnet50-200_1000.ckpt") load_param_into_net(net, param_dict) model = Model(net, loss_fn=loss_fn, metrics={"Accuracy": Accuracy()}) result = model.eval(test_data) print("Accuracy: ", result["Accuracy"]) ``` 注意:在上述示例代码中,我们使用的是ResNet50网络的一个变体,该变体适用于CIFAR-10数据集的图像大小。如果您想使用标准的ResNet50网络,您需要调整输入图像的大小。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值