ONNX(英语:Open Neural Network Exchange)是一种针对机器学习所设计的开放式的文件格式,用于存储训练好的模型。它使得不同的人工智能框架(如Pytorch、MXNet)可以采用相同格式存储模型数据并交互。 ONNX的规范及代码主要由微软,亚马逊,Facebook和IBM等公司共同开发,以开放源代码的方式托管在Github上。13 目前官方支持加载ONNX模型并进行推理的深度学习框架有: Caffe2, PyTorch, MXNet,ML.NET,TensorRT 和 Microsoft CNTK,并且 TensorFlow 也非官方的支持ONNX。
ONNX Runtime(ORT) 是机器学习模型的加速器,具有多平台支持和与硬件特定库集成的灵活接口。ONNX Runtime 可与来自 PyTorch、Tensorflow/Keras、TFLite、scikit-learn 和其他框架的模型一起使用。主要是通过图优化技术来提高模型的性能, 同时也支持CUDA还有一些线程级别的优化。
ONNX Go Live ONNX的性能调优和可视化工具,可以帮助开发快速找出最佳的参数配置组合。
调研目的: 提高当前在线推断模型的性能, 最大限度地降低工程的机器成本。
环境准备, 使用前请保证好了以下软件, 如果没有可以先自行安装:
pip install onnx
pip install onnxruntime
训练一个传统的Logistic Regression模型,使用sklearn训练,训练集直接选择sklearn自带的鸢尾花数据集
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from onnx import onnx_ml_pb2
# 数据集内包含 3 类共 150 条记录,每类各 50 个数据,每条记录都有 4 项特征:花萼长度、花萼宽度、花瓣长度、花瓣宽度,可以通过这4个特征预测鸢尾花卉属于(iris-setosa, iris-versicolour, iris-virginica)中的哪一品种
iris = load_iris()
X, y = iris.data, iris.target
# 测试集和训练集打散拆分,这里一定要打散,因为原始数据是相对有序的。
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666, test_size=0.3)
# 训练出一个分类器
clr = LogisticRegression(solver='liblinear', multi_class='auto')
clr.fit(X_train, y_train)
output:
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
intercept_scaling=1, l1_ratio=None, max_iter=100,
multi_class='auto', n_jobs=None, penalty='l2',
random_state=None, solver='liblinear', tol=0.0001, verbose=0,
warm_start=False)
使用 skl2onnx
把Scikit-learn模型序列化为ONNX格式,并检查模型文件是否生成正常
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType
initial_type = [('float_input', FloatTensorType([1, 4]))]
onx = convert_sklearn(clr, initial_types=initial_type)
with open("logreg_iris.onnx", "wb") as f:
f.write(onx.SerializeToString())
import onnx
model = onnx.load('logreg_iris.onnx')
print(model)
output:
ir_version: 8
producer_name: "skl2onnx"
producer_version: "1.10.0"
domain: "ai.onnx"
model_version: 0
doc_string: ""
graph {
node {
input: "float_input"
output: "label"
output: "probability_tensor"
name: "LinearClassifier"
op_type: "LinearClassifier"
attribute {
此处内容过多已省略...
}
使用ONNX Runtime Python API预测该ONNX模型,当前仅使用了测试数据集中的第一条数据。
import onnxruntime as rt
import numpy
initial_type = [('float_input', FloatTensorType([None, 4]))]
sess = rt.InferenceSession("logreg_iris.onnx")
input_name = sess.get_inputs()[0].name
label_name = sess.get_outputs()[0].name
probability_name = sess.get_outputs()[1].name
pred_onx = sess.run([label_name, probability_name], {input_name: X_test[0:1].astype(numpy.float32)})
# print info
print('input_name: ' + input_name)
print('label_name: ' + label_name)
print('probability_name: ' + probability_name)
print(X_test[0])
print(pred_onx)
output:
input_name: float_input
label_name: output_label
probability_name: output_probability
# 这里输入的是一个4维的向量,分别表示花萼长度、花萼宽度、花瓣长度、花瓣宽度
[5. 3.5 1.3 0.3]
# 品种为iris-setosa
[array([0], dtype=int64), [{0: 0.9112248420715332, 1: 0.08868706971406937, 2: 8.812700252747163e-05}]]
以HTTP&GRPC 的方式对外提供服务, 使用mcr.microsoft.com/onnxruntime/server
镜像部署刚才的logreg_iris.onnx
模型。
首先刚才生成好的模型文件logreg_iris.onnx
拷贝到/data/www/ai/models 目录下, 然后进行/data/www/ai/models执行:
cd /data/www/ai/models
docker run \
-it \
-v "$PWD:/models" \
-p 9001:8001 \
-p 50051:50051 \
--name ort mcr.microsoft.com/onnxruntime/server \
--model_path="/models/logreg_iris.onnx"
output:
Version: local_build
Commit ID: default
[2021-10-23 13:28:53.657] [ServerApp] [info] Model path: /models/logreg_iris.onnx
[2021-10-23 13:28:53.660] [ServerApp] [info] [ onnxruntime inference_session.cc:545 Initialize]: Initializing session.
此处内容过多已省略...
SaveInitializedTensors]: Done saving initialized tensors
[2021-10-23 13:28:53.663] [ServerApp] [info] [ onnxruntime inference_session.cc:620 Initialize]: Session successfully initialized.
[2021-10-23 13:28:53.664] [ServerApp] [info] GRPC Listening at: 0.0.0.0:50051
[2021-10-23 13:28:53.664] [ServerApp] [info] Listening at: http://0.0.0.0:8001
结果分析: 同时启动了一个GRPC和HTTP服务, 其中Http通过一下路由访问
http://<your_ip_address>:<port>/v1/models/<your-model-name>/versions/<your-version>:predict
也就是 http://127.0.0.1:9001/v1/models/default/versions/1:predict
访问。
注意这里的<your-model-name>
其实是Commit ID你可以在上面的docker run命令后看到了。
前期准备工作:一个已经训练好的算法推荐模型
# 进入模型目录
cd /Users/guirong/Desktop/ai/models/
# 环境准备
pip install tensorflow-cpu==2.3.0
pip install tf2onnx
# 转换
python -m tf2onnx.convert \
--saved-model output/1634309909/ \
--output output/recommend.onnx \
--opset 10 \
--tag serve
执行如下命令:
docker run \
-it \
--rm \
-v "$PWD:/models" \
-p 9001:8001 \
-p 50051:50051 \
--name ort mcr.microsoft.com/onnxruntime/server \
--model_path="/models/recommend.onnx"
需要注意⚠️的是当前目录下必须要有一个models目录并且放置了上一步生成的onnx模型文件。
import onnxruntime as rt
import warnings
import numpy
import json
from array import array
warnings.filterwarnings('ignore')
sess = rt.InferenceSession("recommend.onnx")
input_name0 = sess.get_inputs()[0].name
input_name1 = sess.get_inputs()[1].name
input_name2 = sess.get_inputs()[2].name
input_name3 = sess.get_inputs()[3].name
print(input_name0,input_name1,input_name2,input_name3)
req = {
'dense_input' : [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]],
'seq_input': [[
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]],
'sparse_ids_input': [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73,
74, 75, 76, 77, 78]],
'sparse_wgt_input': [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]],
}
import datetime
start_time = datetime.datetime.now()
# 输出有9个值, 我们取最后一个pctv_pcvr
pred_onx = sess.run([sess.get_outputs()[8].name,], req,)
end_time = datetime.datetime.now()
print("耗时: {}秒".format(end_time - start_time))
print(sess.get_outputs()[8].name)
print(pred_onx)
output:
tf_op_layer_pcvr_ctr
[array([[0.00265199, 0.04154155]], dtype=float32)]
由于ONNX本身没有直接提供免费的服务化的方案, 故没有再进行ORT性能测试.
如果想使用ONNX模型到线上, 建议使用Triton服务化, 个人本地测试并没有获得非常明显的性能提升(和个人模型有关), 所以没再深度研究和使用ONNX, 其实工具/框架带来的提升其实非常有限, 建议先把注意力放到模型结构优化上去, 如果结构没法优化了再使用工具/框架来进一步优化.
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。