以GENET为例,pytorch迁移到mindspore学习经验分享

野次郎的冬天 2022-12-02 14:26:50

GENET

简介

GENet_Res50是一个基于GEBlock构建于ResNet50之上的卷积神经网络,可以将ImageNet图像分成1000个目标类,准确率达78.47%。

在Resnet50的基础上,作者改进了原本的残差结构,设计了两个操作:gather和excite。gather从局部空间位置上提取特征,excite将gather提取到的特征还原回原来的尺度。这个过程类似于encoder-decoder模型,称为编码-解码模型。这个GEnet跟SEnet一样可以嵌入到任何卷积网络中,以很小的参数和计算复杂度为代价能够提升网络的性能,主要应用在目标检测中。

论文

https://arxiv.org/abs/1810.12348

环境要求

模型基础

ResNet50

从经验来看,当增加网络层数后,网络可以进行更加复杂的特征模式的提取,所以当模型更深时理论上可以取得更好的结果。但是更深的网络其性能一定会更好吗?实验发现深度网络出现了退化问题:网络深度增加时,网络准确度出现饱和,甚至出现下降。

何凯明博士提出了残差学习来解决退化问题。对于一个堆积层结构,当输入为x时其学习到的特征记为 H(x) ,现在我们希望可以学习到残差F(x)=H(x)-x,这样其实原始的学习特征是F(x)+x。之所以这样是因为残差学习相比原始特征直接学习更容易。当残差为0时,此时堆积层仅仅做了恒等映射,至少网络性能不会下降,实际上残差不会为0,这也会使得堆积层在输入特征基础上学习到新的特征,从而拥有更好的性能。

SENet

对于CNN网络来说,其核心计算是卷积算子,其通过卷积核从输入特征图学习到新特征图。从本质上讲,卷积是对一个局部区域进行特征融合,这包括空间上(H和W维度)以及通道间(C维度)的特征融合。对于卷积操作,很大一部分工作是提高感受野,即空间上融合更多特征融合,或者是提取多尺度空间信息。SENet网络的创新点在于关注channel之间的关系,希望模型可以自动学习到不同channel特征的重要程度。为此,SENet提出了Squeeze-and-Excitation (SE)模块。

Squeeze-and-Excitation:又称为特征重标定卷积,或者注意力机制。具体来说,就是通过学习的方式来自动获取到每个特征通道的重要程度,然后依照这个重要程度去提升有用的特征并抑制对当前用处不大的特征

  • Squeeze操作,先进行全局池化,具有全局的感受野,并且输出的维度和输入的特征通道数相匹配,它表征着在特征通道上响应的全局分布。
  • Excitation操作通过全连接层为每个特征通道生成权重,建立通道间的相关性输出的权重看做是进过特征选择后的每个特征通道的重要性,然后通过乘法逐通道加权到先前的特征上,完成在通道维度上的对原始特征的重标定。

SE模块很容易嵌入到其它网络中,在其它网络如ResNet和VGG中引入SE模块,都能提升网络的效果。SE模块主要为了提升模型对channel特征的敏感性,这个模块是轻量级的,而且可以应用在现有的网络结构中,只需要增加较少的计算量就可以带来性能的提升。

GENet模型结构

GENet其实是SENet的改进版,以便于更好地利用特征图空间上下文信息。

Global-Avg-Pooling的方式已被SENet证明是有效的方式,且一系列Bag-of-Visual-words模型也表明:用汇集局部区域所得的局部描述子,来组建成新的表示,这种方法是有效的。 故基于SENet,GENet针对如何从特征图中提取出好的feature context,再用于特征图间重要程度的调控进行了研究。

GENet定义了Gather算子和Excite算子,如图所示:

  • gather operator: 从每个feature map中的每个空间邻域提取出context,即聚合了给定空间范围内的神经元响应。
  • excite operator: 通过gather的值和输入值,来调整特征图它们的重要程度操作,产生一个与原始输入相同维度的新张量。

代码算子映射

代码里涉及的pytorch与mindspore算子的对应关系如下:

pytorchmindspore功能
torch.nnmindspore.nn构建神经网络的预定义构建块或计算单元
torch.nn.Conv2d; torch.nn.BatchNorm2d ;torch.nn.ReLUmindspore.nn.Conv2dBnAct二维卷积层+归一化函数+ReLU激活函数
torch.nn.Conv2dmindspore.nn.Conv2d二维卷积层
torch.nn.BatchNorm2dmindspore.nn.BatchNorm2d归一化函数
torch.nn.ModuleListmindspore.nn.CellList将Cell保存在List中
torch.nn.Sequentialmindspore.nn.SequentialCellCell的List将按照它们在构造函数中传递的顺序添加到里面
torch.nn.AdaptiveAvgPool2d(1,1)mindspore.ops.ReduceMean平均池化
torch.nn.ReLUmindspore.nn.ReLUReLU激活函数
torch.nn.Sigmoidnn.Sigmoidsigmoid激活函数
torch.addmindspore.ops.Add将两个输入张量相加
torch.mulmindspore.ops.Mul将两个输入张量相乘
torch.nn.Flattenmindspore.nn.Flatten展平张量
torch.nn.functional.one_hotmindspore.ops.OneHot把tensor转为one-hot向量形式
nn.CrossEntropyLossnn.SoftmaxCrossEntropyWithLogits交叉熵损失
torch.cuda.set_devicemindspore.context.set_context设置运行环境的context
torch.nn.Module.load_state_dictmindspore.load_checkpoint mindspore.load_param_into_net加载模型权重
torch.onnx.export;torch.jit.exportmindspore.export导出对应的模型文件:pytorch主要是jit和onnx文件;mindspore主要是导出AIR, MINDIR,ONNX文件
torch.optim.SGDmindspore.nn.Momentum梯度下降优化器

数据集

  • 使用的数据集:imagenet 2012

    • 数据集大小:144G,共1000个类、125万张彩色图像
    • 训练集:138G,共120万张图像
    • 测试集:6G,共5万张图像
    • 数据格式:RGB
  • 数据集处理主要分为四个步骤:

    • 定义函数create_dataset来创建数据集。
    • 定义需要进行的数据增强和处理操作,为之后进行map映射做准备。
    • 使用map映射函数,将数据操作应用到数据集。
    • 进行数据shuffle、batch操作。

训练过程:

  • 初始化context

    # init context
    context.set_context(mode=context.GRAPH_MODE, device_target=target, save_graphs=False)
    复制
  • 是否分布式训练,若是,设置分布式训练的一些初始化的参数。

    if run_distribute:
        context.set_context(device_id=device_id,
        enable_auto_mixed_precision=True)
        context.set_auto_parallel_context(device_num=device_num,                                  parallel_mode=ParallelMode.DATA_PARALLEL,
                                          gradients_mean=True)
        set_algo_parameters(elementwise_op_strategy_follow=True)
        context.set_auto_parallel_context(all_reduce_fusion_config=[85, 160])
        init()
    复制
  • 创建数据集

    dataset = create_dataset(dataset_path=local_train_data_url, do_train=True, repeat_num=1,
                             batch_size=config.batch_size, target=target, distribute=run_distribute)
    step_size = dataset.get_dataset_size()
    复制
  • 定义网络

    mlp = trans_char_to_bool(args_opt.mlp)
    extra = trans_char_to_bool(args_opt.extra)
    net = net(class_num=config.class_num, extra=extra, mlp=mlp)
    复制
  • 初始化权重

    如果有预训练权重,直接加载checkpoint;若没有,且是卷积操作,则用HeKaiMing均匀算法初始化数组,并从均匀分布中采集样本;若没有,且是全连接操作,则初始化用截断正态分布,是有界正态分布的。

        if args_opt.pre_trained:
            param_dict = load_checkpoint(args_opt.pre_trained)
    
            load_param_into_net(net, param_dict)
        else:
            for _, cell in net.cells_and_names():
                if isinstance(cell, nn.Conv2d):           cell.weight.set_data(weight_init.initializer(weight_init.HeUniform(),                                                        cell.weight.shape,                                                         cell.weight.dtype))
                if isinstance(cell, nn.Dense):
    cell.weight.set_data(weight_init.initializer(weight_init.TruncatedNormal(),                                                                                                                         cell.weight.shape,                                                       cell.weight.dtype))
    
        lr = get_lr(config.lr_init, config.lr_end, config.epoch_size, step_size, config.decay_mode)
    
        lr = Tensor(lr)
    复制
  • 定义优化器

    采用的是Momentum优化器

    decayed_params = []
    no_decayed_params = []
    for param in net.trainable_params():
        if 'beta' not in param.name and 'gamma' not in param.name and 'bias' not in param.name:
            decayed_params.append(param)
        else:
            no_decayed_params.append(param)
    
    group_params = [{'params': decayed_params, 'weight_decay': config.weight_decay},
                    {'params': no_decayed_params},
                    {'order_params': net.trainable_params()}]
    
    opt = Momentum(group_params, lr, config.momentum, loss_scale=config.loss_scale)
    复制
  • 定义loss和模型

    网络采用的是交叉熵损失函数。

    if target == "Ascend":
        if not config.use_label_smooth:
            config.label_smooth_factor = 0.0
    
        loss = CrossEntropySmooth(sparse=True, reduction="mean",
                                  smooth_factor=config.label_smooth_factor,
                                  num_classes=config.class_num)
    
        loss_scale = FixedLossScaleManager(config.loss_scale, drop_overflow_update=False)
        model = Model(net, loss_fn=loss, optimizer=opt, loss_scale_manager=loss_scale,
                      metrics={'acc'}, amp_level="O2", keep_batchnorm_fp32=False)
    else:
        raise ValueError("Unsupported device target.")
    复制
  • 定义callbacks

    time_cb = TimeMonitor(data_size=step_size)
    loss_cb = LossMonitor()
    rank_id = int(os.getenv("RANK_ID"))
    
    cb = [time_cb, loss_cb]
    
    if rank_id == 0:
        config_ck = CheckpointConfig(save_checkpoint_steps=config.save_checkpoint_epochs*step_size,
                                     keep_checkpoint_max=config.keep_checkpoint_max)
        ckpt_cb = ModelCheckpoint(prefix="GENet", directory=ckpt_save_dir, config=config_ck)
        cb += [ckpt_cb]
    复制
  • 进行训练

    dataset_sink_mode = target != "CPU"
    model.train(config.epoch_size, dataset, callbacks=cb,
                sink_size=dataset.get_dataset_size(), dataset_sink_mode=dataset_sink_mode)
    
    if device_id == 0 and args_opt.is_modelarts == "True":
        mox.file.copy_parallel(ckpt_save_dir, args_opt.train_url)
    复制

遇到的问题

从pytorch迁移到mindspore中遇到了很多问题,感谢论坛中的各位老师的帮助。 印象较深的主要有一下两点:

问题1

AdaptivePooling与AvgPooling

  • 昇腾上用mindspore.ops.AvgPool替换mindspore.ops.AdaptiveAvgPool2D

  • mindspore.ops.AdaptiveAvgPool2D只支持GPU,所以要用mindspore.ops.AvgPool替换

  • AdaptivePooling与AvgPooling相互转换的时候卷积核和stride应该怎么设置

  • mindspore.ops.AdaptiveAvgPool2D(output_size)

  • mindspore.ops.AvgPool(kernel_size=1strides=1pad_mode="valid"data_format="NCHW")

    • 需要用output_size换算kernel_size和stride

    • stride = floor(input_size/output_size) kernel_size = input_size − (output_size−1) * stride

问题2

在pynative模式下跑模型,会遇到The pointer[cnode] is null.的问题(跑到一半就报错)

该问题主要是因为在自定义数据集里面使用了Tensor计算

Tensor计算会调用底层的算子进行计算,但是数据处理是多线程并行处理

因此会起多个线程进行计算,但是计算当前不支持多线程执行,因此报错;

自定义数据集中的getitem中尽量不使用MindSpore的Tensor及相关操作,使用numpy

...全文
487 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

164

社区成员

发帖
与我相关
我的任务
社区描述
AI技术分享以及交流
人工智能 其他
社区管理员
  • 跳楼梯企鹅
  • 平凡的人1
  • 微枫Micromaple
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧