diff --git a/tutorials/source_zh_cn/advanced_use/resnet50_tf2ms.md b/tutorials/source_zh_cn/advanced_use/resnet50_tf2ms.md new file mode 100644 index 0000000000000000000000000000000000000000..96e3b5b15a10b534dcd965344cb058537001fb71 --- /dev/null +++ b/tutorials/source_zh_cn/advanced_use/resnet50_tf2ms.md @@ -0,0 +1,189 @@ +# 迁移TensorFlow-ResNet-50到MindSpore + +## 1. 熟悉你的TensorFlow脚本 +TensorFlow与MindpSpore在网络结构组织方式上,存在较大差别,要做迁移,首先需要对原版脚本有较为清晰的了解。最好能够明确的知道每一层的shape。 + +## 2. 重建网络 +### 2.1 Conv和BatchNorm +[Conv](https://www.mindspore.cn/api/zh-CN/master/api/python/mindspore/mindspore.nn.html?highlight=conv#mindspore.nn.Conv2d)和[BatchNorm](https://www.mindspore.cn/api/zh-CN/master/api/python/mindspore/mindspore.nn.html?highlight=batchnorm#mindspore.nn.BatchNorm2d)是resnet50中最主要的两个算子,知道了如何使用这两个算子,就明确了如何构造resnet50网络结构。 + +相比与TensorFlow, MindSpore对于卷积的最大差异在于数据格式。MindSpore整网默认使用`NCHW`的格式,与常见的TensorFlow所使用的`NHWC`不同。 + +以batch_size=32的ResNet-50网络中第一层卷积为例: +- 在TensorFlow中,输入feature的格式为[32, 224, 224, 3], 卷积核大小为[7, 7, 3, 64]。 +- 在MindSpore中,输入feature的格式为[32, 3, 224, 224], 卷积核大小为[64, 3, 7, 7]。 + +```python +def _conv7x7(in_channel, out_channel, stride=1): + weight_shape = (out_channel, in_channel, 7, 7) + weight = _weight_variable(weight_shape) + return nn.Conv2d(in_channel, out_channel, + kernel_size=7, stride=stride, padding=0, pad_mode='same', weight_init=weight) + + +def _bn(channel): + return nn.BatchNorm2d(channel, eps=1e-4, momentum=0.9, + gamma_init=1, beta_init=0, moving_mean_init=0, moving_var_init=1) +``` + +### 2.2 构造BottleNeck +在MindSpore中使用`nn.Cell`构造一个子网结构。子网内遵循先定义后使用的逻辑来搭建网络结构。每一个需要使用的算子需要先在Cell的`__init__`函数内进行定义,然后在`construct`函数内将定义好的算子连接起来,将子网的输出通过`return`进行返回。 + +```python +class ResidualBlock(nn.Cell): + """ + ResNet V1 residual block definition. + + Args: + in_channel (int): Input channel. + out_channel (int): Output channel. + stride (int): Stride size for the first convolutional layer. Default: 1. + + Returns: + Tensor, output tensor. + + Examples: + >>> ResidualBlock(3, 256, stride=2) + """ + expansion = 4 + + def __init__(self, + in_channel, + out_channel, + stride=1): + super(ResidualBlock, self).__init__() + + channel = out_channel // self.expansion + self.conv1 = _conv1x1(in_channel, channel, stride=1) + self.bn1 = _bn(channel) + + self.conv2 = _conv3x3(channel, channel, stride=stride) + self.bn2 = _bn(channel) + + self.conv3 = _conv1x1(channel, out_channel, stride=1) + self.bn3 = _bn_last(out_channel) + + 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)]) + self.add = P.TensorAdd() + + def construct(self, x): + identity = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.down_sample: + identity = self.down_sample_layer(identity) + + out = self.add(out, identity) + out = self.relu(out) + + return out +``` + +### 2.3定义串联结构 +ResNet-50网络中有大量的重复结构,在TensorFlow实践中可以使用for循环调用函数的方式来减少重复代码。在MindSpore中,注意我们定义的每一个Cell对象都是独立的,尤其对于内部存在权重参数的子网,定义的Cell时不能重复使用的,如果出现大量重复串联结构,可以使用循环构造多个Cell实例并通过`SequentialCell`来串联。 +```python +def _make_layer(self, block, layer_num, in_channel, out_channel, stride): + """ + Make stage network of ResNet. + + Args: + block (Cell): Resnet block. + layer_num (int): Layer number. + in_channel (int): Input channel. + out_channel (int): Output channel. + stride (int): Stride size for the first convolutional layer. + + Returns: + SequentialCell, the output layer. + + Examples: + >>> _make_layer(ResidualBlock, 3, 128, 256, 2) + """ + layers = [] + + resnet_block = block(in_channel, out_channel, stride=stride) + layers.append(resnet_block) + + for _ in range(1, layer_num): + resnet_block = block(out_channel, out_channel, stride=1) + layers.append(resnet_block) + + return nn.SequentialCell(layers) +``` + +### 2.4构造整网 +将定义好的多个子网连接起来就是整个[ResNet-50](https://gitee.com/mindspore/mindspore/blob/master/mindspore/model_zoo/resnet.py)网络的结构了。同样遵循先定义后使用的逻辑,在`__init__`中定义所有用到的子网,在`construct`中连接子网。 + +## 3. 训练 + +### 3.1 准备数据 +首先使用[MindData](https://www.mindspore.cn/tutorial/zh-CN/master/use/data_preparation/data_preparation.html)构造你要使用的数据集,根据数据集格式的不同,`MindData`提供了对接`RawData原始数据`、`TFRecord`, [MindRecord](https://www.mindspore.cn/tutorial/zh-CN/master/use/data_preparation/converting_datasets.html)等多种不同数据格式的数据集接口。 + +### 3.2 定义loss和优化器 +刚才定义的网络直接输出最后的logits,如果想要进行训练,。 +```python +loss = SoftmaxCrossEntropyWithLogits(sparse=True) +opt = Momentum(filter(lambda x: x.requires_grad, net.get_parameters()), lr, config.momentum, config.weight_decay, config.loss_scale) +``` + +### 3.3 构造model +类似于TensorFlow的`Estimator`接口,将定义的网络原型、loss、优化器传到`Model`接口内,model内部会自动将其组合为一个可用于训练的网络。 + +如果需要在训练中使用Loss_scale,则可以在model外定义一个loss_scale_manager,一同传入model接口。 + +如果希望使用model内置的评估方法,则可以使用[metrics](https://www.mindspore.cn/tutorial/zh-CN/master/advanced_use/customized_debugging_information.html?highlight=metrics#mindspore-metrics)属性设置希望使用的评估方法。当前可用的评估方法有 +```python +model = Model(net, loss_fn=loss, optimizer=opt, loss_scale_manager=loss_scale, metrics={'acc'}) +``` + +类似于调用`estimator.train()`,通过调用`model.train`接口来进行训练。checkpoint和中间结果打印等功能,则通过Callback的方式挂载到train接口上。 +```python +time_cb = TimeMonitor(data_size=step_size) +loss_cb = LossMonitor() +cb = [time_cb, loss_cb] +if config.save_checkpoint: + config_ck = CheckpointConfig(save_checkpoint_steps=config.save_checkpoint_steps, + keep_checkpoint_max=config.keep_checkpoint_max) + ckpt_cb = ModelCheckpoint(prefix="resnet", directory=config.save_checkpoint_path, config=config_ck) + cb += [ckpt_cb] +model.train(epoch_size, dataset, callbacks=cb) +``` + +## 4. 验证与推理 +类似于`estimator.evaluate()`接口,model提供了`model.eval()`接口来进行模型验证。 +```python +output = model.eval(dataset_eval) +print("eval result: ", output) +``` + +如果想要用于推理,可自行处理每条输入数据和每条logits输出,我们定义的ResNet-50网络,本身就是一个可执行的对象,可以直接用于对给定数据进行推理。 +```python +net = resnet50() +logits = net(Tensor(img_input)) +print(logits.asnumpy()) +``` + +如果希望运行[端侧推理](https://www.mindspore.cn/tutorial/zh-CN/master/advanced_use/on_device_inference.html?highlight=export),可以调用`export`接口将已训练的模型进行导出。当前提供三种导出模型以供选择: +- GEIR:用于在Ascend310芯片上进行推理 +- ONNX:通用模型格式,用于转换到其他框架和硬件平台上进行推理 +- LITE:移动端模型 \ No newline at end of file