我们在构建完成深度学习的模型之后,往往需要将模型部署到我们需要的设备上,例如:电脑、手机、边缘设备等
一、通常部署的设备
- PC/服务端:pytorch/C++
- 手机端(Android/IOS):NCNN框架(CPU推理)、tflite
- IOT设备:NVIDIA JETSON 系列(Linux,tensorRT)、瑞芯微(Android)、海思(鸿蒙)
- HTTP部署:Flask + pytorch/C++
模型推理框架之间的转换(pytorch / tensorflow):onnx
二、模型部署方案
一个模型格式转换的网站:一键转换 Caffe, ONNX, TensorFlow 到 NCNN, MNN, Tengine (convertmodel.com)
(1) torchscript ——让其他语言调用pytorch模型。
那么python语言编写的代码怎么被其它语言调用呢?我们需要将模型转换成torchScript的格式。这里以C++为例,官方教程:Loading a TorchScript Model in C++ — PyTorch Tutorials 2.0.1+cu117 documentation
网站中有详细的教程(这里仅将代码搬运过来):
- step1: 打包模型
import torch
import torchvision
# An instance of your model.
model = torchvision.models.resnet18()
# An example input you would normally provide to your model's forward() method.
example = torch.rand(1, 3, 224, 224)
# Use torch.jit.trace to generate a torch.jit.ScriptModule via tracing.
traced_script_module = torch.jit.trace(model, example)
打包自定义模型
class MyModule(torch.nn.Module):
def __init__(self, N, M):
super(MyModule, self).__init__()
self.weight = torch.nn.Parameter(torch.rand(N, M))
def forward(self, input):
if input.sum() > 0:
output = self.weight.mv(input)
else:
output = self.weight + input
return output
my_module = MyModule(10,20)
sm = torch.jit.script(my_module)
- step2:保存模型文件
traced_script_module.save("traced_resnet_model.pt")
- step3:在C++程序中使用模型
#include <torch/script.h> // One-stop header.
#include <iostream>
#include <memory>
int main(int argc, const char* argv[]) {
if (argc != 2) {
std::cerr << "usage: example-app <path-to-exported-script-module>\n";
return -1;
}
torch::jit::script::Module module;
try {
// Deserialize the ScriptModule from a file using torch::jit::load().
module = torch::jit::load(argv[1]);
}
catch (const c10::Error& e) {
std::cerr << "error loading the model\n";
return -1;
}
std::cout << "ok\n";
}
(2) 将模型部署到安卓或IOS设备上
官网给出的解决方案:Android | PyTorch
、iOS | PyTorch
- step1: 模型准备
import torch
import torchvision
from torch.utils.mobile_optimizer import optimize_for_mobile
model = torchvision.models.mobilenet_v2(pretrained=True)
model.eval()
example = torch.rand(1, 3, 224, 224)
traced_script_module = torch.jit.trace(model, example)
traced_script_module_optimized = optimize_for_mobile(traced_script_module)
traced_script_module_optimized._save_for_lite_interpreter("app/src/main/assets/model.ptl")
- step2: 从Git上下载安卓示例APP
git clone https://github.com/pytorch/android-demo-app.git
cd HelloWorldApp
详细教程见官网
(3)ncnn 框架
ncnn是腾讯开发的一个为手机端极致优化的高性能神经网络前向计算框架,无第三方依赖,跨平台。ncnn目前已在腾讯多款应用中使用,如 QQ,Qzone,微信,天天P图等。ncnn主要基于C++和caffe,ncnn项目地址见:https://github.com/Tencent/ncnn。ncnn是使用CPU进行推理,其支持超多操作平台、并支持大部分的CNN网络,非常值得试试。
(4)onnx
ONNX 是一种基于numpy的用于表示机器学习的开放格式 模型。ONNX 定义了一组通用运算符(机器学习和深度学习模型的构建基块)和通用文件格式,使 AI 开发人员能够使用具有各种框架、工具、运行时和编译器的模型。其支持大部分神经网络框架。
- 我们使用onnx进行不同模型之间的转换时,模型的精度会有一定的下降。
如何将pytorch打包成onnx格式?
首先需要安装onnx和onnxruntime
onnx安装命令:pip install onnx
onnx-cpu安装命令:pip install onnxruntime
onnx-gpu安装命令:pip install onnxruntime-gpu
这里需要注意版本问题,官方参考:NVIDIA - CUDA | onnxruntime
转换仅需一行代码:
torch.onnx.export(model, args, f, export_params=True, verbose=False, input_names=None,
output_names=None,do_constant_folding=True,dynamic_axes=None,opset_version=9)
- 常用参数:
- model:torch.nn.model 要导出的模型
- args:tuple or tensor 模型的输入参数。注意tuple的最后参数为dict要小心,详见pytorch文档。输入参数只需满足shape正确,为什么要输入参数呢?因为后面torch.jit.trace要用到,先按下不表。
- f:file object or string 转换输出的模型的位置,如'yolov4.onnx'
- export_params:bool,default=True,true表示导出trained model,false则untrained model。默认即可
- verbose:bool,default=False,true表示打印调试信息
- input_names:list of string,default=None,指定输入节点名称
- output_names:list of string,default=None,指定输出节点名称
- do_constant_folding:bool,default=True,是否使用常量折叠,默认即可
- dynamic_axes:dict<string, dict<int, string>> or dict<string, list(int)>,default=None,有时模型的输入输出是可变的,如RNN,或者输入输出图片的batch是可变的,这时我们通过dynamic_axes来指定输入tensor的哪些参数可变。
- opset_version:int,default=9,指定onnx的opset版本,版本过低的话,不支持upsample等操作。
举个例子:
import torch
from torch import nn
from torchvision.models import resnet18
model = resnet18()
model.eval()
x = torch.randn((1,3,224,224), requires_grad=True)
input_name = ["input"]
output_name = ["output"]
torch.onnx.export(model, x, "./resnet18.onnx", input_names=input_name, output_names=output_name, verbose=False)
转换完之后,检查并运行onnx模型。
import onnxruntime as ort
import torch
import onnx
import numpy as np
# 加载模型
model = onnx.load("./resnet18.onnx")
# 检查模型
onnx.checker.check_model(model)
# 1. 开启会话
session = ort.InferenceSession("./resnet18.onnx")
x = np.random.randn(1,3,224,224).astype(np.float32) # 输入的类型必须是numpy float32
outputs = session.run(None, input_feed={"input" : x})
print(len(outputs[0][0]))
需要进行精度验证,如果精度不达标,则可能需要进行精度对齐:
import torch
from torch import nn
from torchvision.models import resnet18
from torchvision.models import ResNet18_Weights
import onnxruntime as ort
import onnx
import numpy as np
# 构建模型,并载入权重向量
model1 = resnet18(weights = ResNet18_Weights.IMAGENET1K_V1)
model1.eval()
x1 = torch.randn((1,3,224,224), requires_grad=True)
x2 = x1.detach().numpy().astype(np.float32)
input_name = ["input"]
output_name = ["output"]
# 保存模型
torch.onnx.export(model1, x1, "./resnet18.onnx", input_names=input_name, output_names=output_name, verbose=False)
# 加载模型
model2 = onnx.load("./resnet18.onnx")
# 检查模型
onnx.checker.check_model(model2)
# 开启会话
session = ort.InferenceSession("./resnet18.onnx")
output1 = model1(x1)
output2 = session.run(None, input_feed={"input" : x2})
print("torch: ", output1[0][0].item())
print("onnx: ", output2[0][0][0])