MindSpore学习之网络迁移调试与调优
MindSpore学习之网络迁移调试与调优
- ResNet50为例
迁移流程
- 迁移目标: 网络实现、数据集、收敛精度、训练性能
- 复现指标:不仅要复现训练阶段,推理阶段也同样重要。细微差别,属于正常的波动范围。
- 复现步骤:单步复现+整合网络。复现单 Step 的运行结果,即获取只执行第一个 Step 后网络的状态,然后多次迭代出整个网络的运行结果(数据预处理、权重初始化、正向计算、loss 计算、反向梯度计算和优化器更新之后的结果)
准备工作
-
安装MindSpore,Python等环境
-
下载源代码: https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py
-
ResNet50 是 CV 中经典的深度神经网络,主流 ResNet 系列网络的实现(ResNet18、ResNet34、ResNet50、ResNet101、ResNet152)。ResNet50 所使用的数据集为 ImageNet2012
网络分析
-
MindSpore 既支持动态图(PyNative)模式,又支持静态图(Graph)模式,动态图模式灵活、易于调试,因此动态图模式主要用于网络调试,静态图模式性能好,主要用于整网训练,在分析缺失算子和功能时,要分别分析这两种模式。
-
如果发现有缺失的算子和功能,首先可考虑基于当前算子或功能来组合出缺失的算子和功能
-
ResNet 系列网络结构
-
算子分析:可参考算子映射
配套算子:(nn.Conv2D-nn.Conv2d、nn.BatchNorm2D-nn.BatchNom2d、nn.ReLU-nn.ReLU、nn.MaxPool2D-nn.MaxPool2d、nn.Linear-nn.Dense、torch.flatten-nn.Flatten)
缺失算子:nn.AdaptiveAvgPool2D -
缺少算子替代方案:在 ResNet50 网络中,输入的图片 shape 是固定的,统一为
N,3,224,224
,其中 N 为 batch size,3 为通道的数量,224 和 224 分别为图片的宽和高,网络中改变图片大小的算子有Conv2d
和Maxpool2d
,这两个算子对shape 的影响是固定的,因此,nn.AdaptiveAvgPool2D
的输入和输出 shape 是可以提前确定的,只要我们计算出nn.AdaptiveAvgPool2D
的输入和输出 shape,就可以通过nn.AvgPool
或nn.ReduceMean
来实现,所以该算子的缺失是可替代的,并不影响网络的训练。 -
其他功能对照
Pytorch 使用功能 | MindSpore 对应功能 |
---|---|
nn.init.kaiming_normal_ |
initializer(init='HeNormal') |
nn.init.constant_ |
initializer(init='Constant') |
nn.Sequential |
nn.SequentialCell |
nn.Module |
nn.Cell |
nn.distibuted |
context.set_auto_parallel_context |
torch.optim.SGD |
nn.optim.SGD or nn.optim.Momentum |
网络脚本开发
-
CIFAR-10、CIFAR-100 数据集下载:http://www.cs.toronto.edu/~kriz/cifar.html
-
CIFAR-10:共10个类、60,000个32*32彩色图像。二进制文件,数据在dataset.py中处理。
- 训练集:50,000个图像
- 测试集:10,000个图像
-
ImageNet2012:https://image-net.org/
-
ImageNet2012:共1000个类、224*224彩色图像。数据格式:JPEG,数据在dataset.py中处理。
-
训练集:共1,281,167张图像
- 测试集:共50,000张图像
数据集处理
- 使用 MindData 进行数据预处理主要包括以下几个步骤:
- 传入数据路径,读取数据文件。
- 解析数据。
- 数据处理(如常见数据切分、shuffle、数据增强等操作)。
- 数据分发(以 batch_size 为单位分发数据,分布式训练涉及多机分发)。
- ResNet50 网络使用的是 ImageNet2012 数据集(PyTorch版)
# sample execution (requires torchvision)
from PIL import Image
from torchvision import transforms
input_image = Image.open(filename)
preprocess = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
input_tensor = preprocess(input_image)
input_batch = input_tensor.unsqueeze(0) # create a mini-batch as expected by the model
- 主要做了 Resize、CenterCrop、Normalize 操作
基于 MindData 开发的数据处理
"""
create train or eval dataset.
"""
from mindspore import dtype as mstype
import mindspore.dataset as ds
import mindspore.dataset.vision.c_transforms as C
import mindspore.dataset.transforms.c_transforms as C2
# 创建数据集 (路径,batch_size,rank_size:device数,rank_id:device在所有机器中的序号,训练模式)
def create_dataset(dataset_path, batch_size=32, rank_size=1, rank_id=0, do_train=True):
# num_paralel_workers: parallel degree of data process 并行度 并行训练时用到
# num_shards: total number devices for distribute training, which equals number shard of data # devices数量
# shard_id: the sequence of current device in all distribute training devices, # device在所有机器中的序号
# which equals the data shard sequence for current device
data_set = ds.ImageFolderDataset(dataset_path, num_parallel_workers=8, shuffle=do_train,
num_shards=rank_size, shard_id=rank_id)
mean = [0.485 * 255, 0.456 * 255, 0.406 * 255]
std = [0.229 * 255, 0.224 * 255, 0.225 * 255]
# define map operations
trans = [
C.Decode(),
C.Resize(256),
C.CenterCrop(224),
C.Normalize(mean=mean, std=std),
C.HWC2CHW()
]
type_cast_op = C2.TypeCast(mstype.int32) # 精度转换
# call data operations by map
data_set = data_set.map(operations=trans, input_columns="image", num_parallel_workers=8)
data_set = data_set.map(operations=type_cast_op, input_columns="label", num_parallel_workers=8)
# apply batch operations batch_size
data_set = data_set.batch(batch_size, drop_remainder=do_train)
return data_set
- 分布式训练需要额外指定
num_shard
和shard_id
两个参数
子网开发:训练子网和 loss 子网
- 将网络中不同模块或子模块作为一个个子网抽离出来单独开发,这样可以保证各个子网并行开发,互相不受干扰。
分析 ResNet50 网络代码,主要可以分成以下几个子网:
- conv1x1、conv3x3:定义了不同 kernel_size 的卷积。
- BasicBlock:ResNet 系列网络中 ResNet18 和 ResNet34 的最小子网,由 Conv、BN、ReLU 和 残差组成。
- BottleNeck:ResNet 系列网络中 ResNet50、ResNet101 和 ResNet152 的最小子网,相比 BasicBlock 多了一层 Conv、BN 和 ReLU的结构,下采样的卷积位置也做了改变。
- ResNet:封装了 BasiclBlock、BottleNeck 和 Layer 结构的网络,传入不同的参数即可构造不同的ResNet系列网络。在该结构中,也使用了一些 PyTorch 自定义的初始化功能。
重新开发 conv3x3 和 conv1x1
import mindspore.nn as nn
# 3x3的卷积
def _conv3x3(in_channel, out_channel, stride=1):
return nn.Conv2d(in_channel, out_channel, kernel_size=3, stride=stride, padding=0, pad_mode='same')
# 1x1的卷积
def _conv1x1(in_channel, out_channel, stride=1):
return nn.Conv2d(in_channel, out_channel, kernel_size=1, stride=stride, padding=0, pad_mode='same')
重新开发 BasicBlock 和 BottleNeck:
# ResNet50 ResNet101 ResNet152 残差子网(输入通道,输出通道,步长:卷积步长) : ResidualBlock(3, 256, stride=2)
class ResidualBlock(nn.Cell):
expansion = 4 #
def __init__(self, in_channel, out_channel, stride=1):
super(ResidualBlock, self).__init__()
self.stride = stride
channel = out_channel // self.expansion
self.conv1 = _conv1x1(in_channel, channel, stride=1) # 1x1卷积
self.bn1 = _bn(channel) # BatchNorm
if self.stride != 1: # 步长不为1
self.e2 = nn.SequentialCell([_conv3x3(channel, channel, stride=1), _bn(channel),
nn.ReLU(), nn.MaxPool2d(kernel_size=2, stride=2, pad_mode='same')])
else: # 步长为1
self.conv2 = _conv3x3(channel, channel, stride=stride)
self.bn2 = _bn(channel)
self.conv3 = _conv1x1(channel, out_channel, stride=1) # 1x1卷积
self.bn3 = _bn_last(out_channel) # 最后一层 BatchNorm
self.relu = nn.ReLU() # 激活函数
self.down_sample = False # 下采样
if stride != 1 or in_channel != out_channel: # 下采样
self.down_sample = True
self.down_sample_layer = None
if self.down_sample: # # 下采样
self.down_sample_layer = nn.SequentialCell([_conv1x1(in_channel, out_channel, stride), _bn(out_channel)])
def construct(self, x):
identity = x
out = self.conv1(x) # 1x1卷积
out = self.bn1(out) # BatchNorm
out = self.relu(out) # 激活
if self.stride != 1: # 步长不为1
out = self.e2(out)
else: # 步长为1
out = self.conv2(out)
out = self.bn2(out)
out = self.relu(out)
out = self.conv3(out) # 1x1卷积
out = self.bn3(out) # BatchNorm
if self.down_sample: # 下采样 需要转换维度
identity = self.down_sample_layer(identity)
out = out + identity # 残差
out = self.relu(out) # 激活
return out
# ResNet18 和 ResNet34 残差子网(输入通道,输出通道,步长:卷积步长) : ResidualBlock(