ONNX

ONNX Runtime | onnxruntime

sklearn-onnx

sklearn-onnx只支持scikit-learn的模型转换.而 onnxmltools可用于 libsvm, lightgbm, _xgboost_的模型转换.

其它的ONNX模型转换工具:
github/onnx, torch.onnx, ONNX-MXNet API, Microsoft.ML.Onnx

支持的模型

Supported scikit-learn Models - sklearn-onnx 1.18.0 documentation

安装

1
mamba install skl2onnx onnxruntime

入门

1
2
3
4
5
6
7
8
9
10
11
12
# 训练模型
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier

iris = load_iris()
X, y = iris.data, iris.target
X = X.astype(np.float32)
X_train, X_test, y_train, y_test = train_test_split(X, y)
clr = RandomForestClassifier()
clr.fit(X_train, y_train)

将模型转为ONNX类型

1
2
3
4
5
6
7
8
from skl2onnx import to_onnx

# 将clr这个模型转为onnx模型,并用X的第一行进行类型推断
onx = to_onnx(clr, X[:1])

# 打开并保存文件
with open("rf_iris.onnx", "wb") as f:
f.write(onx.SerializeToString())

加载模型,推理

1
2
3
4
5
6
7
8
9
import onnxruntime as rt

# 加载rf_iris.onnx这个路径的模型,推理后端设置为CPU
sess = rt.InferenceSession("rf_iris.onnx", providers=["CPUExecutionProvider"])
# 获取输入和输出节点名称
input_name = sess.get_inputs()[0].name
label_name = sess.get_outputs()[0].name
# 推理X_test,设置列名
pred_onx = sess.run([label_name], {input_name: X_test.astype(np.float32)})[0]

convert_sklearn和to_onnx

convert_sklearn是原来的函数,需要手动指定输入类型和大小;而to_onnx自动从dataframe或ndarray推断
大小和类型.
convert_sklearn的用法:

1
2
3
4
5
6
7
8
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType

# 输入叫'float_input',类型为FloatTensorType,行数为None,即未知需要推断多少行,列数为4(特征维度)
initial_type = [('float_input', FloatTensorType([None, 4]))]
onx = convert_sklearn(clr, initial_types=initial_type)
with open("logreg_iris.onnx", "wb") as f:
f.write(onx.SerializeToString())

查看支持的硬件

硬件在此指示:sess = rt.InferenceSession(“rf_iris.onnx”, providers=[“CPUExecutionProvider”]),查看providers:

1
2
print(f'所有的硬件:{rt.get_all_providers()}')
print(f'可用的硬件:{rt.get_available_providers()}')

如果你在providers列表中指定了多个提供者,ONNX Runtime会尝试按照列表中的顺序使用这些提供者来执行模型。

具体来说,当你有多个提供者时,ONNX Runtime会逐个尝试使用它们:

首先,ONNX Runtime会尝试使用列表中指定的第一个提供者来加载和执行模型。如果这个提供者可用(即相应的硬件或软件环境已就绪),并且模型可以成功在该提供者上执行,那么ONNX Runtime就会使用这个提供者来运行模型的推理。

如果第一个提供者不可用(比如因为硬件不支持、驱动未安装等原因),ONNX Runtime会尝试列表中的下一个提供者。这个过程会一直继续,直到找到一个可用的提供者或者列表中所有的提供者都尝试过了。

训练部署a scikit-learn pipeline

使用sklearn.pipeline创建流程,从三个分类器投票获得结果

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
import numpy
from onnxruntime import InferenceSession
from sklearn.datasets import load_diabetes
from sklearn.ensemble import (
GradientBoostingRegressor,
RandomForestRegressor,
VotingRegressor,
)
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from skl2onnx import to_onnx
from onnx.reference import ReferenceEvaluator


X, y = load_diabetes(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y)

# Train classifiers
reg1 = GradientBoostingRegressor(random_state=1, n_estimators=5)
reg2 = RandomForestRegressor(random_state=1, n_estimators=5)
reg3 = LinearRegression()

ereg = Pipeline(
steps=[
("voting", VotingRegressor([("gb", reg1), ("rf", reg2), ("lr", reg3)])),
]
)
ereg.fit(X_train, y_train)
1
2
3
4
5
6
7
8
9
10
# 保存ONNX模型
onx = to_onnx(ereg, X_train[:1].astype(numpy.float32), target_opset=12)
sess = InferenceSession(onx.SerializeToString(), providers=["CPUExecutionProvider"])
# 使用ONNX预测
pred_ort = sess.run(None, {"X": X_test.astype(numpy.float32)})[0]
# 使用sklearn pipeline预测
pred_skl = ereg.predict(X_test.astype(numpy.float32))

print("Onnx Runtime prediction:\n", pred_ort[:5])
print("Sklearn rediction:\n", pred_skl[:5])

撰写diff函数对比结果

1
2
3
4
5
6
7
def diff(p1, p2):# 返回最大绝对值误差,和使用原始序列的绝对值归一化后的最大误差
p1 = p1.ravel()
p2 = p2.ravel()
d = numpy.abs(p2 - p1)
return d.max(), (d / numpy.abs(p1)).max()

print(diff(pred_skl, pred_ort))

可以直接使用ReferenceEvaluator从一个onnx对象创建参考评估器,从而对比推理精度

1
2
3
oinf = ReferenceEvaluator(onx)
pred_pyrt = oinf.run(None, {"X": X_test.astype(numpy.float32)})[0]
print(diff(pred_skl, pred_pyrt))

当输入是Dataframe类型时

管道通常接收矩阵格式的数据(ndarray),如果所有数据共享相同的类型,则可以在矩阵中使用数据。但df中保存的数据通常有多种类型,float、integer或字符串表示类别。ONNX也支持df。