推理
本节是初级教程的最后一节,为了更好地适配不同推理设备,因此推理分为 1)昇腾AI处理器推理和 2)移动设备推理。
昇腾AI处理器推理
昇腾(Ascend)AI处理器是面向边缘场景的高能效高集成度AI处理器。可以实现图像、视频等多种数据分析与推理计算,可广泛用于智能监控、机器人、无人机、视频服务器等场景。本节我们来学习如何在昇腾AI处理器上使用MindSpore执行推理。
推理代码介绍
首先创建目录放置推理代码工程,例如/home/HwHiAiUser/mindspore_sample/ascend910_resnet50_preprocess_sample
,可以从官网示例下载样例代码,model
目录用于存放上述导出的MindIR
模型文件,test_data
目录用于存放待分类的图片,推理代码工程目录结构如下:
└─ascend910_resnet50_preprocess_sample
├── CMakeLists.txt // 构建脚本
├── README.md // 使用说明
├── main.cc // 主函数
├── model
│ └── resnet50_imagenet.mindir // MindIR模型文件
└── test_data
├── ILSVRC2012_val_00002138.JPEG // 输入样本图片1
├── ILSVRC2012_val_00003014.JPEG // 输入样本图片2
├── ... // 输入样本图片n
引用mindspore
和mindspore::dataset
的名字空间。
namespace ms = mindspore;
namespace ds = mindspore::dataset;
初始化环境,指定推理使用的硬件平台,设置DeviceID。
这里设置硬件为Ascend 910,DeviceID为0,示例代码如下:
auto context = std::make_shared<ms::Context>();
auto ascend910_info = std::make_shared<ms::Ascend910DeviceInfo>();
ascend910_info->SetDeviceID(0);
context->MutableDeviceInfo().push_back(ascend910_info);
加载模型文件:
// 加载MindIR模型
ms::Graph graph;
ms::Status ret = ms::Serialization::Load(resnet_file, ms::ModelType::kMindIR, &graph);
// 用图构建模型
ms::Model resnet50;
ret = resnet50.Build(ms::GraphCell(graph), context);
获取模型所需的输入信息:
std::vector<ms::MSTensor> model_inputs = resnet50.GetInputs();
加载图片文件:
// ReadFile是读取图像的函数
ms::MSTensor ReadFile(const std::string &file);
auto image = ReadFile(image_file);
图片预处理:
// 使用MindData提供的CPU算子进行图片预处理
// 创建算子,该算子将输入编码成RGB格式
std::shared_ptr<ds::TensorTransform> decode(new ds::vision::Decode());
// 创建算子,该算子把图片缩放到指定大小
std::shared_ptr<ds::TensorTransform> resize(new ds::vision::Resize({256}));
// 创建算子,该算子归一化输入
std::shared_ptr<ds::TensorTransform> normalize(new ds::vision::Normalize(
{0.485 * 255, 0.456 * 255, 0.406 * 255}, {0.229 * 255, 0.224 * 255, 0.225 * 255}));
// 创建算子,该算子执行中心抠图
std::shared_ptr<ds::TensorTransform> center_crop(new ds::vision::CenterCrop({224, 224}));
// 创建算子,该算子将shape (H, W, C)变换成shape (C, H, W)
std::shared_ptr<ds::TensorTransform> hwc2chw(new ds::vision::HWC2CHW());
// 定义一个MindData数据预处理函数,按顺序包含上述算子
ds::Execute preprocessor({decode, resize, normalize, center_crop, hwc2chw});
// 调用数据预处理函数获取处理后的图像
ret = preprocessor(image, &image);
执行推理:
// 创建输出vector
std::vector<ms::MSTensor> outputs;
// 创建输入vector
std::vector<ms::MSTensor> inputs;
inputs.emplace_back(model_inputs[0].Name(), model_inputs[0].DataType(), model_inputs[0].Shape(),
image.Data().get(), image.DataSize());
// 调用Model的Predict函数进行推理
ret = resnet50.Predict(inputs, &outputs);
获取推理结果:
// 输出概率最大值
std::cout << "Image: " << image_file << " infer result: " << GetMax(outputs[0]) << std::endl;
构建脚本
为编译器添加头文件搜索路径:
option(MINDSPORE_PATH "mindspore install path" "")
include_directories(${MINDSPORE_PATH})
include_directories(${MINDSPORE_PATH}/include)
在MindSpore中查找所需动态库:
find_library(MS_LIB libmindspore.so ${MINDSPORE_PATH}/lib)
file(GLOB_RECURSE MD_LIB ${MINDSPORE_PATH}/_c_dataengine*)
使用指定的源文件生成目标可执行文件,并为目标文件链接MindSpore库:
add_executable(resnet50_sample main.cc)
target_link_libraries(resnet50_sample ${MS_LIB} ${MD_LIB})
编译推理代码
接下来编译推理的代码,首先要进入工程目录ascend910_resnet50_preprocess_sample
,设置如下环境变量:
如果是Ascend 310设备,则进入工程目录
ascend310_resnet50_preprocess_sample
,以下代码均用Ascend 910为例。
# 控制log的打印级别. 0-DEBUG, 1-INFO, 2-WARNING, 3-ERROR, 默认是WARNING级别.
export GLOG_v=2
# 选择Conda环境
LOCAL_ASCEND=/usr/local/Ascend # 运行包的根目录
# 运行包依赖的lib库
export LD_LIBRARY_PATH=${LOCAL_ASCEND}/ascend-toolkit/latest/fwkacllib/lib64:${LOCAL_ASCEND}/driver/lib64/common:${LOCAL_ASCEND}/driver/lib64/driver:${LOCAL_ASCEND}/opp/op_impl/built-in/ai_core/tbe/op_tiling:${LD_LIBRARY_PATH}
# MindSpore依赖的lib库
export LD_LIBRARY_PATH=`pip3 show mindspore-ascend | grep Location | awk '{print $2"/mindspore/lib"}' | xargs realpath`:${LD_LIBRARY_PATH}
# 配置必要的环境变量
export TBE_IMPL_PATH=${LOCAL_ASCEND}/ascend-toolkit/latest/opp/op_impl/built-in/ai_core/tbe # TBE算子的路径
export ASCEND_OPP_PATH=${LOCAL_ASCEND}/ascend-toolkit/latest/opp # OPP路径
export PATH=${LOCAL_ASCEND}/ascend-toolkit/latest/fwkacllib/ccec_compiler/bin/:${PATH} # TBE算子编译工具的路径
export PYTHONPATH=${TBE_IMPL_PATH}:${PYTHONPATH} # TBE依赖的Python库
执行cmake
命令,其中pip3
需要按照实际情况修改:
cmake . -DMINDSPORE_PATH=`pip3 show mindspore-ascend | grep Location | awk '{print $2"/mindspore"}' | xargs realpath`
再执行make
命令编译即可。
make
编译完成后,在ascend910_resnet50_preprocess_sample
下会生成可执行main
文件。
执行推理并查看结果
以上操作完成之后,我们可以开始学习如何执行推理。
首先,登录Ascend 910环境,创建model
目录放置MindIR文件resnet50_imagenet.mindir
,例如/home/HwHiAiUser/mindspore_sample/ascend910_resnet50_preprocess_sample/model
。
创建test_data
目录放置图片,例如/home/HwHiAiUser/mindspore_sample/ascend910_resnet50_preprocess_sample/test_data
。
就可以开始执行推理了:
./resnet50_sample
执行后,会对test_data
目录下放置的所有图片进行推理,比如放置了2张ImageNet2012验证集中label为0的图片,可以看到推理结果如下。
Image: ./test_data/ILSVRC2012_val_00002138.JPEG infer result: 0
Image: ./test_data/ILSVRC2012_val_00003014.JPEG infer result: 0
移动设备推理
MindSpore Lite是端边云全场景AI框架MindSpore的端侧部分,可以在手机等移动设备上实现智能应用。MindSpore Lite提供高性能推理引擎和超轻量的解决方案,支持iOS、Android等手机操作系统以及LiteOS嵌入式操作系统,支持手机、大屏、平板、IoT等各种智能设备,支持MindSpore/TensorFlow Lite/Caffe/Onnx模型的应用。
在本环节中,提供了运行在Windows和Linux操作系统下,基于C++ API编写的Demo,帮助用户熟悉端侧推理流程。Demo通过随机生成的数据作为输入数据,执行MobileNetV2模型的推理,直接在电脑中打印获得输出数据。
运行在手机中的完整实例可以参考官网示例:基于JNI接口的Android应用开发。
模型转换
模型在用于端侧推理之前需要先进行格式的转换。当前,MindSpore Lite支持MindSpore、TensorFlow Lite、Caffe和ONNX 4类AI框架。
下面以MindSpore训练得到的mobilenetv2.mindir模型为例,说明Demo中所使用的mobilenetv2.ms
模型是如何生成的。
本小节展开说明了转换的操作过程,仅实现Demo运行可跳过本小节。
本小节仅针对Demo所用模型,详细的转换工具使用说明请参考官网推理模型转换章节。
转换工具下载
根据所使用的操作系统,下载转换工具的压缩包并解压至本地目录,获得
converter
工具,并配置环境变量。转换工具使用
Linux使用说明
进入
converter_lite
可执行文件所在的目录,将下载的mobilenetv2.mindir
模型放入同一路径下,在电脑终端中输入命令完成转换:./converter_lite --fmk=MINDIR --modelFile=mobilenetv2.mindir --outputFile=mobilenetv2
Windows使用说明
进入
converter_lite
可执行文件所在的目录,将下载的mobilenetv2.mindir
模型放入同一路径下,在电脑终端中输入命令完成转换:call converter_lite --fmk=MINDIR --modelFile=mobilenetv2.mindir --outputFile=mobilenetv2
参数说明
在执行命令的过程中设置了三个参数,
--fmk
代表输入模型的原始格式,这里设置为MINDIR
,即MindSpore框架训练模型的导出格式;--modelFile
指输入模型的路径;--outputFile
设定了模型的输出路径,这里自动将转换后的模型添加了.ms
后缀。
构建环境与运行
Linux系统构建与运行
编译构建
在
mindspore/lite/examples/quick_start_cpp
目录下执行build脚本,将能够自动下载相关文件并编译Demo。bash build.sh
执行推理
编译构建后,进入
mindspore/lite/examples/quick_start_cpp/build
目录,并执行以下命令,体验MindSpore Lite推理MobileNetV2模型。./mindspore_quick_start_cpp ../model/mobilenetv2.ms
执行完成后将能得到如下结果,打印输出Tensor的名称、输出Tensor的大小,输出Tensor的数量以及前50个数据:
tensor name is:Default/head-MobileNetV2Head/Softmax-op204 tensor size is:4000 tensor elements num is:1000 output data is:5.26823e-05 0.00049752 0.000296722 0.000377607 0.000177048 .......
Windows系统构建与运行
编译构建
库下载:请手动下载硬件平台为CPU、操作系统为Windows-x64的MindSpore Lite模型推理框架mindspore-lite-{version}-win-x64.zip,将解压后
inference/lib
目录下的libmindspore-lite.a
拷贝到mindspore/lite/examples/quick_start_cpp/lib
目录、inference/include
目录拷贝到mindspore/lite/examples/quick_start_cpp/include
目录。模型下载:请手动下载相关模型文件mobilenetv2.ms,并将其拷贝到
mindspore/lite/examples/quick_start_cpp/model
目录。可选择使用模型转换小节所获得的mobilenetv2.ms模型文件。
编译:在
mindspore/lite/examples/quick_start_cpp
目录下执行build脚本,将能够自动下载相关文件并编译Demo。call build.bat
执行推理
编译构建后,进入
mindspore/lite/examples/quick_start_cpp/build
目录,并执行以下命令,体验MindSpore Lite推理MobileNetV2模型。call ./mindspore_quick_start_cpp.exe ../model/mobilenetv2.ms
执行完成后将能得到如下结果,打印输出Tensor的名称、输出Tensor的大小,输出Tensor的数量以及前50个数据:
tensor name is:Default/head-MobileNetV2Head/Softmax-op204 tensor size is:4000 tensor elements num is:1000 output data is:5.26823e-05 0.00049752 0.000296722 0.000377607 0.000177048 .......
推理代码解析
下面分析Demo源代码中的推理流程,显示C++ API的具体使用方法。
模型加载
首先从文件系统中读取MindSpore Lite模型,并通过mindspore::lite::Model::Import
函数导入模型进行解析。
// 读模型文件
size_t size = 0;
char *model_buf = ReadFile(model_path, &size);
if (model_buf == nullptr) {
std::cerr << "Read model file failed." << std::endl;
return RET_ERROR;
}
// 加载模型
auto model = mindspore::lite::Model::Import(model_buf, size);
delete[](model_buf);
if (model == nullptr) {
std::cerr << "Import model file failed." << std::endl;
return RET_ERROR;
}
模型编译
模型编译主要包括创建配置上下文、创建会话、图编译等步骤。
mindspore::session::LiteSession *Compile(mindspore::lite::Model *model) {
// 初始化上下文
auto context = std::make_shared<mindspore::lite::Context>();
if (context == nullptr) {
std::cerr << "New context failed while." << std::endl;
return nullptr;
}
// 创建session
mindspore::session::LiteSession *session = mindspore::session::LiteSession::CreateSession(context.get());
if (session == nullptr) {
std::cerr << "CreateSession failed while running." << std::endl;
return nullptr;
}
// 图编译
auto ret = session->CompileGraph(model);
if (ret != mindspore::lite::RET_OK) {
delete session;
std::cerr << "Compile failed while running." << std::endl;
return nullptr;
}
// 注意:如果使用 model->Free(),模型将不能再次被编译
if (model != nullptr) {
model->Free();
}
return session;
}
模型推理
模型推理主要包括输入数据、执行推理、获得输出等步骤,其中本示例中的输入数据是通过随机数据构造生成,最后将执行推理后的输出结果打印出来。
int Run(mindspore::session::LiteSession *session) {
// 获取输入数据
auto inputs = session->GetInputs();
auto ret = GenerateInputDataWithRandom(inputs);
if (ret != mindspore::lite::RET_OK) {
std::cerr << "Generate Random Input Data failed." << std::endl;
return ret;
}
// 运行
ret = session->RunGraph();
if (ret != mindspore::lite::RET_OK) {
std::cerr << "Inference error " << ret << std::endl;
return ret;
}
// 获取输出数据
auto out_tensors = session->GetOutputs();
for (auto tensor : out_tensors) {
std::cout << "tensor name is:" << tensor.first << " tensor size is:" << tensor.second->Size()
<< " tensor elements num is:" << tensor.second->ElementsNum() << std::endl;
auto out_data = reinterpret_cast<float *>(tensor.second->MutableData());
std::cout << "output data is:";
for (int i = 0; i < tensor.second->ElementsNum() && i <= 50; i++) {
std::cout << out_data[i] << " ";
}
std::cout << std::endl;
}
return mindspore::lite::RET_OK;
}
内存释放
无需使用MindSpore Lite推理框架时,需要释放已经创建的LiteSession
和Model
。
// 删除模型缓存
delete model;
// 删除session缓存
delete session;