我們在構(gòu)建完成深度學(xué)習(xí)的模型之后逗堵,往往需要將模型部署到我們需要的設(shè)備上力喷,例如:電腦刽漂、手機(jī)、邊緣設(shè)備等
一弟孟、通常部署的設(shè)備
- PC/服務(wù)端:pytorch/C++
- 手機(jī)端(Android/IOS):NCNN框架(CPU推理)贝咙、tflite
- IOT設(shè)備:NVIDIA JETSON 系列(Linux,tensorRT)拂募、瑞芯微(Android)庭猩、海思(鴻蒙)
- HTTP部署:Flask + pytorch/C++
模型推理框架之間的轉(zhuǎn)換(pytorch / tensorflow):onnx
二乌奇、模型部署方案
一個模型格式轉(zhuǎn)換的網(wǎng)站:一鍵轉(zhuǎn)換 Caffe, ONNX, TensorFlow 到 NCNN, MNN, Tengine (convertmodel.com)
(1) torchscript ——讓其他語言調(diào)用pytorch模型。
那么python語言編寫的代碼怎么被其它語言調(diào)用呢眯娱?我們需要將模型轉(zhuǎn)換成torchScript的格式礁苗。這里以C++為例,官方教程:Loading a TorchScript Model in C++ — PyTorch Tutorials 2.0.1+cu117 documentation
網(wǎng)站中有詳細(xì)的教程(這里僅將代碼搬運過來):
- 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設(shè)備上
官網(wǎng)給出的解決方案:Android | PyTorch
徙缴、iOS | PyTorch
- step1: 模型準(zhǔn)備
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
詳細(xì)教程見官網(wǎng)
(3)ncnn 框架
ncnn是騰訊開發(fā)的一個為手機(jī)端極致優(yōu)化的高性能神經(jīng)網(wǎng)絡(luò)前向計算框架试伙,無第三方依賴,跨平臺于样。ncnn目前已在騰訊多款應(yīng)用中使用疏叨,如 QQ,Qzone穿剖,微信蚤蔓,天天P圖等。ncnn主要基于C++和caffe糊余,ncnn項目地址見:https://github.com/Tencent/ncnn秀又。ncnn是使用CPU進(jìn)行推理,其支持超多操作平臺贬芥、并支持大部分的CNN網(wǎng)絡(luò)吐辙,非常值得試試。
(4)onnx
ONNX 是一種基于numpy的用于表示機(jī)器學(xué)習(xí)的開放格式 模型蘸劈。ONNX 定義了一組通用運算符(機(jī)器學(xué)習(xí)和深度學(xué)習(xí)模型的構(gòu)建基塊)和通用文件格式昏苏,使 AI 開發(fā)人員能夠使用具有各種框架、工具威沫、運行時和編譯器的模型贤惯。其支持大部分神經(jīng)網(wǎng)絡(luò)框架。
- 我們使用onnx進(jìn)行不同模型之間的轉(zhuǎn)換時棒掠,模型的精度會有一定的下降孵构。
如何將pytorch打包成onnx格式?
首先需要安裝onnx和onnxruntime
onnx安裝命令:pip install onnx
onnx-cpu安裝命令:pip install onnxruntime
onnx-gpu安裝命令:pip install onnxruntime-gpu
這里需要注意版本問題句柠,官方參考:NVIDIA - CUDA | onnxruntime
轉(zhuǎn)換僅需一行代碼:
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)
- 常用參數(shù):
- model:torch.nn.model 要導(dǎo)出的模型
- args:tuple or tensor 模型的輸入?yún)?shù)浦译。注意tuple的最后參數(shù)為dict要小心,詳見pytorch文檔溯职。輸入?yún)?shù)只需滿足shape正確,為什么要輸入?yún)?shù)呢帽哑?因為后面torch.jit.trace要用到谜酒,先按下不表。
- f:file object or string 轉(zhuǎn)換輸出的模型的位置妻枕,如'yolov4.onnx'
- export_params:bool,default=True僻族,true表示導(dǎo)出trained model粘驰,false則untrained model。默認(rèn)即可
- verbose:bool,default=False述么,true表示打印調(diào)試信息
- input_names:list of string蝌数,default=None,指定輸入節(jié)點名稱
- output_names:list of string度秘,default=None顶伞,指定輸出節(jié)點名稱
- do_constant_folding:bool,default=True,是否使用常量折疊剑梳,默認(rèn)即可
- dynamic_axes:dict<string, dict<int, string>> or dict<string, list(int)>,default=None唆貌,有時模型的輸入輸出是可變的,如RNN垢乙,或者輸入輸出圖片的batch是可變的锨咙,這時我們通過dynamic_axes來指定輸入tensor的哪些參數(shù)可變。
- 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)
轉(zhuǎn)換完之后蓖宦,檢查并運行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]))
需要進(jìn)行精度驗證,如果精度不達(dá)標(biāo)油猫,則可能需要進(jìn)行精度對齊:
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
# 構(gòu)建模型稠茂,并載入權(quán)重向量
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])