目录
1.MNN简介
简单介绍一下:MNN(Mobile Neural Network)是一个轻量级的深度神经网络推理引擎,在端侧加载深度神经网络模型进行推理预测。想详细了解MNN的定义以及构成,请参考官方文档:欢迎使用MNN文档 — MNN-Doc 2.1.1 documentation
2.MNN编译
2.1准备工作
从github下载MNN,参考如下:
git clone https://github.com/alibaba/MNN.git
2.2编译
2.2.1基础环境依赖
(1)cmake(3.10 以上)
(2)protobuf (3.0 以上)
- 指protobuf库以及protobuf编译器。版本号使用
protoc --version
打印出来。 - Ubuntu需要分别安装
libprotobuf-dev
以及protobuf-compiler
两个包。sudo apt update sudo apt install protobuf-compiler libprotobuf-dev
2.2.2主库编译
cd /MNN安装目录/MNN
./schema/generate.sh
./tools/script/get_model.sh # 不建议通过这种方法下载,模型仅demo工程需要,后续有需要时另外下载
mkdir build && cd build && cmake .. && make -j8
# 编译OpenCL库
# mkdir build && cd build && cmake .. -DMNN_OPENCL=ON && make -j8
2.2.3主要工具模块编译
(1)编译模型转化工具
# 在主库编译中,已经创建了build目录,后续其它编译操作均在build目录下进行
cmake .. -DMNN_BUILD_CONVERTER=ON
make -j8
(2)编译测试工具
cmake .. -DMNN_BUILD_TOOL=ON
make -j8
(3)编译BnechMark工具
cmake .. -DMNN_BUILD_BENCHMARK=ON
make -j8
(4)编译模型量化工具
cmake .. -DMNN_BUILD_QUANTOOLS=ON
make -j8
(5)编译评估工具
cmake .. -DMNN_EVALUATION=ON
make -j8
2.3 pymnn构建
cd /MNN安装目录/MNN/pymnn/pip_package
python build_deps.py
python setup.py install --version {MNN版本}
MNN版本可通过如下方式查看:
cd /MNN安装目录/MNN/build
./MNNConvert --version
编译pymnn时如果遇到问题,可以参考我的另一篇博文:
3.模型转化
cd /Your Dictionary/build
./MNNConvert -f ONNX --modelFile /PATH_to_Transfer_test.onnx --MNNModel /PATH_to_Transfer_test.mnn
# 常用参数说明
# -- f:进行转化的模型类型,如:TF, CAFFE, ONNX, TFLITE, MNN, TORCH, JSON
# -- modelFile: 进行转化的模型文件名,如:transfer_test.onnx
# -- MNNModel: 转化后得到的模型文件名,如:transfer_test.mnn
# -- fp16: 将conv/matmul/LSTM的float32参数保存为float16,模型将减小一半,精度基本无损
# -- prototxt: caffe模型结构描述文件,仅当模型是caffe时选择,如:transfer_text.prototxt
4.使用MNN进行推理
官方给出的推理API有三种方法,分别是Session API, Module API,Expr API。下面我们将着重介绍Expr API(推荐)和Session API 。
4.1 Module API(推荐)
使用步骤以及流程图,仅展示如何使用该API以及一些注意事项,完整的demo请参考我的另一篇博客:
(1)加载MNN模型:
API:MNN.nn.load_module_from_file('mnn_model', ["input_name"], ["output_name"])
参数介绍:input_name和output_name分别代表模型的输入输出名。输入输出的名字可通过netron来查看,如图:
(2)构建占位符,保存输入numpy:
API:MNN.expr.placeholder(shape, format, dtype)
参数介绍:输入图片的shape、输入图片的数据排列格式、数据的类型。并通过Var.write(data)将数据写入Var中。
(3)对输入数据进行格式转换
原因:opencv读取图片的格式是NHWC,但是MNN Module需要的数据排列格式是NC4HW4。
API:MNN.expr.convert(data, format)
参数介绍:目标数据、目标数据排列格式
(4)执行推理
API: module.forward(data)
参数介绍:module是通过MNN.nn.load_module_from_file()实例化的对象。data是输入数据
(5)对输出数据进行格式转换
原因:推理后的数据排列格式是NC4HW4,为了方便后续处理,需要转成NCHW
API:同(3)
(6)对输出数据进行类型转换
原因:通过expr API推理得到的结果的数据类型是Var数据类型,不方便后续的处理,因此需要将其转化为其它数据类型,如ndarray。
API:var_data.read(),var_data.read_as_tuple()
参数介绍:var_data:需要进行转化的Var数据,前者转换后得到是ndarray数据类型,后者转换后得到是tuple数据类型。
# (1) 加载MNN模型
net = MNN.nn.load_module_from_file(mnn_model_path, ["input_name"], ["output_name"])
# (2) 构建一个Var类型的占位符来保存numpy
input_var = MNN.expr.placeholder(im.shape, MNN.expr.NCHW)
input_var.write(im)
# (3) 数据排列方式转换
input_var = MNN.expr.convert(input_var, MNN.expr.NC4HW4)
# (4) 推理
output_var = net.forward(input_var)
# (5) 数据排列方式转换
output_var = MNN.expr.convert(output_var, MNN.expr.NCHW)
# (6) 数据类型转换
output_var = output_var.read()
4.2 Session API
使用步骤及流程图,仅展示如何使用该API以及一些注意事项,完整的demo请参考我的另一篇博客:
(1)加载MNN模型
API:MNN.Interpreter(mnn_path)
(2)定义配置,并根据配置创建session,常用配置如下:
API: interpreter.createSession(config)
参数介绍:interpreter,(1)中加载的模型名
KEY | 说明 |
---|---|
backend | 可选值:"CPU"或0 (默认), "OPENCL"或3 ,"OPENGL"或6 , "VULKAN"或7 , "METAL"或1 , "TRT"或9 , "CUDA"或2 , "HIAI"或8 |
precision | 可选值:"normal" (默认), "low" ,"high","lowBF" |
numThread | value 为推理线程数,只在 CPU 后端下起作用 |
(3)获取session的输入,session是推理数据的持有者。此时,该tensor为空,类似占位符的作用。
API: interpreter.getSessionInput(session)
参数介绍:session,(2)中创建的session名
(4) 创建临时Tensor,用来存储实际输入数据
API:Tensor(shape, dtype, input_data, dimension)
参数介绍: 创建一个指定形状,指定数据类型,数据,数据排列方式(tensorflow是nhwc,caffe是nchw)的tensor。
注:如果输入数据是numpy或者其它格式类型(除MNN定义的类型外),都需要使用这种方式(带四个参数的方式)创建,否此创建出来的tensor中没有数据或者读不出来。
(可选) 创建完Tensor后,可以手动将输入Tensor的形状校正成正常输入形状,在校正完成后记得重新分配内存。
API:interpreter.resizeTensor(data, shape)
API: interpreter.resizeSession(session)
(5)将临时Tensor的数据复制给输入Tensor,即(3)中的Tensor。需要注意的是:MNN Tensor的数据排列方式是NC4HW4,并非NCHW格式,因此对中间结果的可视化并不友善。
API: target_tensor.copyFrom(ori_tensor)
参数介绍: 从ori_tensor拷贝数据到target_tensor中
(6)执行会话推理
API: interpreter.runSession(session)
(7)创建输出Tensor,此时,该tensor为空,类似占位符的作用。
API: interpreter.getSessionOutput(session)
(8)创建临时Tensor,用来存储算法结果。
API:Tensor(shape, dtype, input_data, dimension)
(9)拷贝临时数据到输出Tensor
API:ori_tensor.copyToHostTensor(target_tensor)
参数介绍: 从ori_tensor拷贝数据到target_tensor中
(10)将输出tensor转换为numpy类型方便后续处理
API:tensor.getNumpyData()
(11)补充
mnn中的输入输出的shape并没有在netron中显著标明,要通过MNNTensor.getShape()查看。
# (1)加载模型
interpreter = MNN.Interpreter(mnnModulePath)
# (2)配置
config = {}
config['precision'] = 'low'
config['backend'] = 'CPU'
config['thread'] = 4
# 根据配置创建session
session = interpreter.createSession(config)
# (3)获取输入Tensor名
input_tensor = interpreter.getSessionInput(session)
# (可选)改变Tensor shape
interpreter.resizeTensor(input_tensor, (1, 3, 640, 640))
interpreter.resizeSession(session)
# (4)创建临时Tensor
mnn_input = MNN.Tensor((1, 3, 640, 640), MNN.Halide_Type_Float, im, MNN.Tensor_DimensionType_Caffe)
# (5)拷贝数据到输入Tensor
input_tensor.copyFrom(mnn_input)
# (6)执行会话推理
interpreter.runSession(session)
# (7)获取输出Tensor名
output_tensor = interpreter.getSessionOutput(session)
# (8)创建临时Tensor
tmp_output = MNN.Tensor((1, 25000, 85), MNN.Halide_Type_Float, np.ones([1, 25000, 85]).astype(np.float32),
MNN.Tensor_DimensionType_Caffe)
# (9)拷贝数据到输出Tensor
output_tensor.copyToHostTensor(tmp_output)
# (10)转换类型
pred = tmp_output.getNumpyData()