转载地址:https://bbs.huaweicloud.com/forum/thread-92959-1-1.html
作者:小呆瓶
看了大佬的文章分享,就用昇腾环境复现了端侧的应用开发实战,收获满满呀!本案例是基于MindSpore框架预置的图像分类预训练模型,用垃圾分类数据集在昇腾芯片上进行迁移学习训练;然后用训练好的模型文件基于MindSpore Lite部署到手机上实现垃圾分类应用,将分类图片进一步分类成可回收物、干垃圾、有害垃圾和湿垃圾四大类。通过这个例子,能快速了解如何利用MindSpore框架进行端侧个性化应用开发的端对端过程。 1. PC侧迁移学习步骤 在深度学习中,大部分任务的数据和网络模型规模较大,训练网络模型时,如果不使用预训练模型,从头开始训练,需要消耗大量的时间。因此,大部分任务都会选择预训练模型,在其上做微调。本端侧垃圾分类应用是基于MobileNetV2预训练模型,在其上进行迁移学习。 环境安装
在Gitee中克隆MindSpore开源项目仓库,进入./model_zoo/official/cv/mobilenetv2/。 在这里可以找到完整可运行的样例代码:https://gitee.com/mindspore/mindspore/tree/master/model_zoo/official/cv/mobilenetv2 git clone https://gitee.com/mindspore/mindspore.git cd ./mindspore/model_zoo/official/cv/mobilenetv2 预训练模型准备 用户可以下载提前训练好的预训练模型, 当然也可以参照model zoo自行训练预训练模型。 为快速进行迁移学习,本用例直接选用提前训练好的预训练模型。 数据集准备 MobileNetV2的代码默认使用ImageFolder格式管理数据集,每一类图片整理成单独的一个文件夹, 数据集结构如下: └─ImageFolder ├─train │ class1Folder │ ...... └─eval class1Folder 也可以参考MindSpore支持图像领域常用的数据集, 修改src/dataset.py换成适合的数据集接口。src/dataset.py中默认使用随机裁剪,随机水平翻转,随机色彩增强,正则化等基本的数据增强方法, 也可以参考数据增强添加新的数据处理算子。 模型训练和推理 一般一个分类网络包含两部分:backbone和head,其中backbone部分通常是一系列卷积层,负责提取图片特征;head部分通常是一组全连接层,用于分类,一般最后一个全连接层的输出对应数据集的分类数。这里我们选择冻结backbone部分的参数,只训练head的参数进行微调。 定义网络模型及加载预训练参数 首先按照代码第1行,构建MobileNetV2的backbone网络,head网络,并且构建包含这两个子网络的MobileNetV2网络。 代码第3-10行展示了如何定义backbone_net与head_net,以及将两个子网络置入mobilenet_v2中。 代码第12-23行,展示了在微调训练模式下,需要将预训练模型加载入backbone_net子网络,并且冻结backbone_net中的参数,不参与训练。 代码第21-23行展示了如何冻结网络参数。 1: backbone_net, head_net, net = define_net(args_opt, config) 2: ... 3: def define_net(config, is_training): 4: backbone_net = MobileNetV2Backbone() 5: activation = config.activation if not is_training else "None" 6: head_net = MobileNetV2Head(input_channel=backbone_net.out_channels, 7: num_classes=config.num_classes, 8: activation=activation) 9: net = mobilenet_v2(backbone_net, head_net) 10: return backbone_net, head_net, net 11: ... 12: if args_opt.pretrain_ckpt and args_opt.freeze_layer == "backbone": 13: load_ckpt(backbone_net, args_opt.pretrain_ckpt, trainable=False) 14: ... 15: def load_ckpt(network, pretrain_ckpt_path, trainable=True): 16: """ 17: train the param weight or not 18: """ 19: param_dict = load_checkpoint(pretrain_ckpt_path) 20: load_param_into_net(network, param_dict) 21: if not trainable: 22: for param in network.get_parameters(): 23: param.requires_grad = False 执行训练 训练时,运行train.py时需要传入dataset_path、platform、pretrain_ckpt与freeze_layer四个参数。 验证时,运行eval.py并且传入dataset_path、platform、pretrain_ckpt三个参数。 # train with Python file python train.py --platform=Ascend --dataset_path [DATASET_PATH] --pretrain_ckpt [PRETRAIN_CHECKPOINT_PATH] --freeze_layer=backbone] # eval with Python file python eval.py --platform=Ascend --dataset_path [DATASET_PATH] --pretrain_ckpt [PRETRAIN_CHECKPOINT_PATH]
结果展示
epoch[1/15], iter[15] cost: 4300.820, per step time: 286.721, avg loss: 2.354 epoch[2/15], iter[15] cost: 4010.630, per step time: 267.375, avg loss: 1.601 epoch[3/15], iter[15] cost: 4009.706, per step time: 267.314, avg loss: 1.353 epoch[4/15], iter[15] cost: 3991.812, per step time: 266.121, avg loss: 1.208 epoch[5/15], iter[15] cost: 4007.363, per step time: 267.158, avg loss: 1.125 epoch[6/15], iter[15] cost: 3995.016, per step time: 266.334, avg loss: 1.068 epoch[7/15], iter[15] cost: 4017.721, per step time: 267.848, avg loss: 1.038 epoch[8/15], iter[15] cost: 4016.534, per step time: 267.769, avg loss: 0.994 epoch[9/15], iter[15] cost: 4006.452, per step time: 267.097, avg loss: 0.986 epoch[10/15], iter[15] cost: 3992.108, per step time: 266.141, avg loss: 0.993 epoch[11/15], iter[15] cost: 4006.859, per step time: 267.124, avg loss: 0.959 epoch[12/15], iter[15] cost: 4042.366, per step time: 269.491, avg loss: 0.938 epoch[13/15], iter[15] cost: 4010.737, per step time: 267.382, avg loss: 0.921 epoch[14/15], iter[15] cost: 4010.041, per step time: 267.336, avg loss: 0.919 epoch[15/15], iter[15] cost: 4003.595, per step time: 266.906, avg loss: 0.941 total cost 173.1637 s
result:{'acc': 0.86} pretrain_ckpt=ckpt_0\mobilenetv2_15.ckpt 模型导出 当有了训练好的CheckPoint文件后,想接着在MindSpore Lite端侧做推理,需要通过网络和CheckPoint生成对应的MINDIR格式模型文件。代码如下: import argparse import numpy as np from mindspore import Tensor from mindspore.train.serialization import export from src.config import set_config from src.models import define_net, load_ckpt parser = argparse.ArgumentParser(description='MobilenetV2 export') parser.add_argument('--platform', type=str, default="Ascend", choices=("Ascend", "GPU", "CPU"), \ help='run platform, only support GPU, CPU and Ascend') parser.add_argument('--pretrain_ckpt', type=str, required=True, help='Pretrained checkpoint path \ for fine tune or incremental learning') args_opt = parser.parse_args() args_opt.is_training = False args_opt.run_distribute = False config = set_config(args_opt) backbone_net, head_net, net = define_net(config, args_opt.is_training) load_ckpt(net, args_opt.pretrain_ckpt) input = np.random.uniform(0.0, 1.0, size=[1, 3, 224, 224]).astype(np.float32) export(net, Tensor(input), file_name='mobilenetv2.mindir', file_format='MINDIR') 执行命令: python export.py --platform=Ascend --pretrain_ckpt [PRETRAIN_CHECKPOINT_PATH] 最后会在执行命令同目录下生成 mobilenetv2.mindir 文件。 2. 端侧推理步骤 在端侧利用MindSpore Lite C++ API和通过迁移学习训练好的模型完成端侧推理,实现对摄像头设备捕获的内容进行分类,并在APP图像预览界面中,显示出分类结果。 转换模型 端侧推理框架使用的模型是.ms格式,所以这里需要对模型做进一步转换。 这里基于上述导出的.mindir格式的模型文件,使用MindSpore Lite模型转换工具将其转换成.ms格式文件。 以MobileNetV2模型为例,如下脚本将其转换为MindSpore Lite模型用于端侧推理。 ./converter_lite --fmk=MINDIR --modelFile=mobilenetv2.mindir --outputFile=garbage_mobilenetv2.ms 部署应用 将模型转换为端侧的.ms格式文件后,想进一步将其部署到手机进行推理应用,需安装对应的端侧开发环境Android Studio。 运行依赖
构建与运行
核心代码 配置MindSpore Lite依赖项 Android JNI层调用MindSpore C++ API时,需要相关库文件支持。可通过MindSpore Lite源码编译生成mindspore-lite-{version}-minddata-{os}-{device}.tar.gz库文件包并解压缩(包含libmindspore-lite.so库文件和相关头文件)。 本示例中,build过程由app/download.gradle文件自动下载MindSpore Lite版本文件,并放置在相关目录下,可直接官网下载获取。 在app/CMakeLists.txt文件中建立.so库文件链接,可查看源码。 部署模型文件 本示例中使用的端侧垃圾分类模型文件为garbage_mobilenetv2.ms,放置在app/src/main/assets/model目录下。 编写端侧推理代码 在JNI层调用MindSpore Lite C++ API实现端测推理。 推理代码流程如下,完整代码请参见src/cpp/GarbageMindSporeNetnative.cpp。 重训推理流程 如果想用其他数据集,以实现自己的图像分类任务,步骤如下: 1.将重新转换生成的.ms模型放置在app/src/main/assets/model目录下,替换原有模型。 2.并根据重训后的标签分类,更改src/cpp/GarbageMindSporeNetnative.cpp代码中的分类数组,标签数组,或修改其他文件的相应代码。比如:垃圾分类新模型分为4大类别,共对应26种小类别垃圾,代码如下: static const int RET_GARBAGE_SORT_SUM = 4; static const char *labels_name_grbage_sort_map[RET_GARBAGE_SORT_SUM] = {{"可回收物"}, {"干垃圾"}, {"有害垃圾"}, {"湿垃圾"}}; static const int RET_GARBAGE_DETAILED_SUM = 26; static const char *labels_name_grbage_detailed_map[RET_GARBAGE_DETAILED_SUM] = { {"塑料瓶"},{"帽子"},{"报纸"},{"易拉罐"},{"玻璃制品"},{"玻璃瓶"}, {"硬纸板"}, {"篮球"}, {"纸张"},{"金属制品"},{"一次性筷子"},{"打火机"},{"扫把"},{"旧镜子"},{"牙刷"},{"脏污衣服"},{"贝壳"},{"陶瓷碗"}, {"油漆桶"},{"电池"},{"荧光灯"},{"药片胶囊"},{"橙皮"},{"菜叶"},{"蛋壳"},{"香蕉皮"}}; 3.根据输出结果的最大值,获取对应标签数组的垃圾类别。 std::string ProcessRunnetResult(const int RET_CATEGORY_SUM, const char *const labels_name_map[], std::unordered_map<std::string, mindspore::tensor::MSTensor *> msOutputs) { // Get the branch of the model output. std::unordered_map<std::string, mindspore::tensor::MSTensor *>::iterator iter; iter = msOutputs.begin(); // The mobilenetv2.ms model output just one branch. auto outputTensor = iter->second; // Get a pointer to the first score. float *temp_scores = static_cast<float *>(outputTensor->MutableData()); float max = 0.0; int maxIndex = 0; for (int i = 0; i < RET_CATEGORY_SUM; ++i) { if (temp_scores > max) { max = temp_scores; maxIndex = i; } } if(maxIndex >= 0 && maxIndex <= 9){ categoryScore += labels_name_grbage_sort_map[0]; categoryScore += ":"; }else if(maxIndex > 9 && maxIndex <= 17){ categoryScore += labels_name_grbage_sort_map[1]; categoryScore += ":"; }else if(maxIndex > 17 && maxIndex <= 21){ categoryScore += labels_name_grbage_sort_map[2]; categoryScore += ":"; }else if(maxIndex > 21 && maxIndex <= 25){ categoryScore += labels_name_grbage_sort_map[3]; categoryScore += ":"; } categoryScore += labels_name_map[maxIndex]; return categoryScore; } | |