注意
转到结尾 下载完整的示例代码
一个模型,多种可能的转换选项¶
转换模型的方法不止一种。在新版本的 ONNX 中可能会添加新的操作符,这可以加快转换后的模型的速度。合理的选择是使用这个新的操作符,但这意味着关联的运行时对此有一个实现。如果两个不同的用户需要对同一个模型进行两种不同的转换怎么办?让我们看看如何做到这一点。
选项 zipmap¶
根据设计,每个分类器都会被转换为一个 ONNX 图,该图输出两个结果:预测标签和每个标签的预测概率。默认情况下,标签是整数,概率存储在字典中。这是在以下图形末尾添加的操作符 ZipMap 的目的。
graph ONNX(LogisticRegression) (
%X[FLOAT, ?x4]
) {
%label, %probability_tensor = LinearClassifier[classlabels_ints = [0, 1, 2], coefficients = [-0.374590873718262, 0.882017612457275, -2.25903177261353, -0.96484386920929, 0.463038802146912, -0.698963463306427, -0.0836651995778084, -0.888288736343384, -0.0884479135274887, -0.18305416405201, 2.34269690513611, 1.85313260555267], intercepts = [8.58371162414551, 2.95640826225281, -11.5401201248169], multi_class = 1, post_transform = 'SOFTMAX'](%X)
%output_label = Cast[to = 7](%label)
%probabilities = Normalizer[norm = 'L1'](%probability_tensor)
%output_probability = ZipMap[classlabels_int64s = [0, 1, 2]](%probabilities)
return %output_label, %output_probability
}
此操作符效率并不高,因为它会将每个概率和标签复制到不同的容器中。对于小型分类器来说,这段时间通常很长。因此,删除它是有意义的。
graph ONNX(LogisticRegression) (
%X[FLOAT, ?x4]
) {
%label, %probability_tensor = LinearClassifier[classlabels_ints = [0, 1, 2], coefficients = [-0.374590873718262, 0.882017612457275, -2.25903177261353, -0.96484386920929, 0.463038802146912, -0.698963463306427, -0.0836651995778084, -0.888288736343384, -0.0884479135274887, -0.18305416405201, 2.34269690513611, 1.85313260555267], intercepts = [8.58371162414551, 2.95640826225281, -11.5401201248169], multi_class = 1, post_transform = 'SOFTMAX'](%X)
%probabilities = Normalizer[norm = 'L1'](%probability_tensor)
return %label, %probabilities
}
图中可能有许多分类器,重要的是要有一种方法来指定哪个分类器应该保留其 ZipMap,哪个不应该。因此,可以通过 ID 指定选项。
from pprint import pformat
import numpy
from onnx.reference import ReferenceEvaluator
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import MinMaxScaler
from sklearn.pipeline import Pipeline
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from skl2onnx.common._registration import _converter_pool
from skl2onnx import to_onnx
from onnxruntime import InferenceSession
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, _ = train_test_split(X, y, random_state=11)
clr = LogisticRegression()
clr.fit(X_train, y_train)
model_def = to_onnx(
clr, X_train.astype(numpy.float32), options={id(clr): {"zipmap": False}}
)
oinf = ReferenceEvaluator(model_def)
print(oinf)
/home/xadupre/github/scikit-learn/sklearn/linear_model/_logistic.py:472: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.
Increase the number of iterations (max_iter) or scale the data as shown in:
https://scikit-learn.cn/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
https://scikit-learn.cn/stable/modules/linear_model.html#logistic-regression
n_iter_i = _check_optimize_result(
ReferenceEvaluator(X) -> label, probabilities
使用函数 id 存在一个缺陷:它不可选取。使用字符串会更好。
model_def = to_onnx(clr, X_train.astype(numpy.float32), options={"zipmap": False})
oinf = ReferenceEvaluator(model_def)
print(oinf)
ReferenceEvaluator(X) -> label, probabilities
管道中的选项¶
在管道中,sklearn-onnx 使用相同的命名约定。
pipe = Pipeline([("norm", MinMaxScaler()), ("clr", LogisticRegression())])
pipe.fit(X_train, y_train)
model_def = to_onnx(pipe, X_train.astype(numpy.float32), options={"clr__zipmap": False})
oinf = ReferenceEvaluator(model_def)
print(oinf)
ReferenceEvaluator(X) -> label, probabilities
选项 raw_scores¶
每个分类器都会被转换为一个图,该图默认返回概率。但是许多模型计算未缩放的 raw_scores。首先,使用概率
pipe = Pipeline([("norm", MinMaxScaler()), ("clr", LogisticRegression())])
pipe.fit(X_train, y_train)
model_def = to_onnx(
pipe, X_train.astype(numpy.float32), options={id(pipe): {"zipmap": False}}
)
oinf = ReferenceEvaluator(model_def)
print(oinf.run(None, {"X": X.astype(numpy.float32)[:5]}))
[array([0, 0, 0, 0, 0]), array([[0.88268626, 0.10948393, 0.00782984],
[0.7944385 , 0.19728662, 0.00827491],
[0.85557765, 0.13792053, 0.00650185],
[0.8262804 , 0.16634221, 0.00737737],
[0.90050155, 0.092388 , 0.00711049]], dtype=float32)]
然后使用原始分数
model_def = to_onnx(
pipe,
X_train.astype(numpy.float32),
options={id(pipe): {"raw_scores": True, "zipmap": False}},
)
oinf = ReferenceEvaluator(model_def)
print(oinf.run(None, {"X": X.astype(numpy.float32)[:5]}))
[array([0, 0, 0, 0, 0]), array([[0.88268626, 0.10948393, 0.00782984],
[0.7944385 , 0.19728662, 0.00827491],
[0.85557765, 0.13792053, 0.00650185],
[0.8262804 , 0.16634221, 0.00737737],
[0.90050155, 0.092388 , 0.00711049]], dtype=float32)]
这似乎不起作用……我们需要说明它适用于管道的特定部分,而不是整个管道。
model_def = to_onnx(
pipe,
X_train.astype(numpy.float32),
options={id(pipe.steps[1][1]): {"raw_scores": True, "zipmap": False}},
)
oinf = ReferenceEvaluator(model_def)
print(oinf.run(None, {"X": X.astype(numpy.float32)[:5]}))
[array([0, 0, 0, 0, 0]), array([[ 2.2707398 , 0.18354762, -2.4542873 ],
[ 1.9857951 , 0.5928172 , -2.5786123 ],
[ 2.2349296 , 0.4098304 , -2.6447601 ],
[ 2.1071343 , 0.5042473 , -2.6113818 ],
[ 2.3727787 , 0.095824 , -2.4686027 ]], dtype=float32)]
存在负值。这有效。字符串仍然更容易使用。
model_def = to_onnx(
pipe,
X_train.astype(numpy.float32),
options={"clr__raw_scores": True, "clr__zipmap": False},
)
oinf = ReferenceEvaluator(model_def)
print(oinf.run(None, {"X": X.astype(numpy.float32)[:5]}))
[array([0, 0, 0, 0, 0]), array([[ 2.2707398 , 0.18354762, -2.4542873 ],
[ 1.9857951 , 0.5928172 , -2.5786123 ],
[ 2.2349296 , 0.4098304 , -2.6447601 ],
[ 2.1071343 , 0.5042473 , -2.6113818 ],
[ 2.3727787 , 0.095824 , -2.4686027 ]], dtype=float32)]
负数。我们仍然有原始分数。
选项 decision_path¶
scikit-learn 实现了一个函数来检索决策路径。可以通过选项 decision_path 启用它。
clrrf = RandomForestClassifier(n_estimators=2, max_depth=2)
clrrf.fit(X_train, y_train)
clrrf.predict(X_test[:2])
paths, n_nodes_ptr = clrrf.decision_path(X_test[:2])
print(paths.todense())
model_def = to_onnx(
clrrf,
X_train.astype(numpy.float32),
options={id(clrrf): {"decision_path": True, "zipmap": False}},
)
sess = InferenceSession(
model_def.SerializeToString(), providers=["CPUExecutionProvider"]
)
[[1 0 0 0 1 0 1 1 1 0 1 0 0 0]
[1 0 0 0 1 0 1 1 1 0 1 0 0 0]]
模型产生 3 个输出。
print([o.name for o in sess.get_outputs()])
['label', 'probabilities', 'decision_path']
让我们显示最后一个。
res = sess.run(None, {"X": X_test[:2].astype(numpy.float32)})
print(res[-1])
[['1000101' '1101000']
['1000101' '1101000']]
可用选项列表¶
为每个转换注册选项,以便在运行转换时检测任何支持的选项。
all_opts = set()
for k, v in sorted(_converter_pool.items()):
opts = v.get_allowed_options()
if not isinstance(opts, dict):
continue
name = k.replace("Sklearn", "")
print("%s%s %r" % (name, " " * (30 - len(name)), opts))
for o in opts:
all_opts.add(o)
print("all options:", pformat(list(sorted(all_opts))))
AdaBoostClassifier {'zipmap': [True, False, 'columns'], 'nocl': [True, False], 'output_class_labels': [False, True], 'raw_scores': [True, False]}
BaggingClassifier {'zipmap': [True, False, 'columns'], 'nocl': [True, False], 'output_class_labels': [False, True], 'raw_scores': [True, False]}
BayesianGaussianMixture {'score_samples': [True, False]}
BayesianRidge {'return_std': [True, False]}
BernoulliNB {'zipmap': [True, False, 'columns'], 'output_class_labels': [False, True], 'nocl': [True, False]}
CalibratedClassifierCV {'zipmap': [True, False, 'columns'], 'output_class_labels': [False, True], 'nocl': [True, False]}
CategoricalNB {'zipmap': [True, False, 'columns'], 'output_class_labels': [False, True], 'nocl': [True, False]}
ComplementNB {'zipmap': [True, False, 'columns'], 'output_class_labels': [False, True], 'nocl': [True, False]}
CountVectorizer {'tokenexp': None, 'separators': None, 'nan': [True, False], 'keep_empty_string': [True, False], 'locale': None}
DecisionTreeClassifier {'zipmap': [True, False, 'columns'], 'nocl': [True, False], 'output_class_labels': [False, True], 'decision_path': [True, False], 'decision_leaf': [True, False]}
DecisionTreeRegressor {'decision_path': [True, False], 'decision_leaf': [True, False]}
ExtraTreeClassifier {'zipmap': [True, False, 'columns'], 'nocl': [True, False], 'output_class_labels': [False, True], 'decision_path': [True, False], 'decision_leaf': [True, False]}
ExtraTreeRegressor {'decision_path': [True, False], 'decision_leaf': [True, False]}
ExtraTreesClassifier {'zipmap': [True, False, 'columns'], 'raw_scores': [True, False], 'nocl': [True, False], 'output_class_labels': [False, True], 'decision_path': [True, False], 'decision_leaf': [True, False]}
ExtraTreesRegressor {'decision_path': [True, False], 'decision_leaf': [True, False]}
GaussianMixture {'score_samples': [True, False]}
GaussianNB {'zipmap': [True, False, 'columns'], 'output_class_labels': [False, True], 'nocl': [True, False]}
GaussianProcessClassifier {'optim': [None, 'cdist'], 'nocl': [False, True], 'output_class_labels': [False, True], 'zipmap': [False, True]}
GaussianProcessRegressor {'return_cov': [False, True], 'return_std': [False, True], 'optim': [None, 'cdist']}
GradientBoostingClassifier {'zipmap': [True, False, 'columns'], 'raw_scores': [True, False], 'output_class_labels': [False, True], 'nocl': [True, False]}
HistGradientBoostingClassifier {'zipmap': [True, False, 'columns'], 'raw_scores': [True, False], 'output_class_labels': [False, True], 'nocl': [True, False]}
HistGradientBoostingRegressor {'zipmap': [True, False, 'columns'], 'raw_scores': [True, False], 'output_class_labels': [False, True], 'nocl': [True, False]}
IsolationForest {'score_samples': [True, False]}
KMeans {'gemm': [True, False]}
KNNImputer {'optim': [None, 'cdist']}
KNeighborsClassifier {'zipmap': [True, False, 'columns'], 'nocl': [True, False], 'raw_scores': [True, False], 'output_class_labels': [False, True], 'optim': [None, 'cdist']}
KNeighborsRegressor {'optim': [None, 'cdist']}
KNeighborsTransformer {'optim': [None, 'cdist']}
KernelPCA {'optim': [None, 'cdist']}
LinearClassifier {'zipmap': [True, False, 'columns'], 'nocl': [True, False], 'output_class_labels': [False, True], 'raw_scores': [True, False]}
LinearSVC {'nocl': [True, False], 'output_class_labels': [False, True], 'raw_scores': [True, False]}
LocalOutlierFactor {'score_samples': [True, False], 'optim': [None, 'cdist']}
MLPClassifier {'zipmap': [True, False, 'columns'], 'output_class_labels': [False, True], 'nocl': [True, False]}
MaxAbsScaler {'div': ['std', 'div', 'div_cast']}
MiniBatchKMeans {'gemm': [True, False]}
MultiOutputClassifier {'nocl': [False, True], 'output_class_labels': [False, True], 'zipmap': [False, True]}
MultinomialNB {'zipmap': [True, False, 'columns'], 'output_class_labels': [False, True], 'nocl': [True, False]}
NearestNeighbors {'optim': [None, 'cdist']}
OneVsOneClassifier {'zipmap': [True, False, 'columns'], 'nocl': [True, False], 'output_class_labels': [False, True]}
OneVsRestClassifier {'zipmap': [True, False, 'columns'], 'nocl': [True, False], 'output_class_labels': [False, True], 'raw_scores': [True, False]}
QuadraticDiscriminantAnalysis {'zipmap': [True, False, 'columns'], 'nocl': [True, False], 'output_class_labels': [False, True]}
RadiusNeighborsClassifier {'zipmap': [True, False, 'columns'], 'nocl': [True, False], 'raw_scores': [True, False], 'output_class_labels': [False, True], 'optim': [None, 'cdist']}
RadiusNeighborsRegressor {'optim': [None, 'cdist']}
RandomForestClassifier {'zipmap': [True, False, 'columns'], 'raw_scores': [True, False], 'nocl': [True, False], 'output_class_labels': [False, True], 'decision_path': [True, False], 'decision_leaf': [True, False]}
RandomForestRegressor {'decision_path': [True, False], 'decision_leaf': [True, False]}
RobustScaler {'div': ['std', 'div', 'div_cast']}
SGDClassifier {'zipmap': [True, False, 'columns'], 'nocl': [True, False], 'output_class_labels': [False, True], 'raw_scores': [True, False]}
SVC {'zipmap': [True, False, 'columns'], 'nocl': [True, False], 'output_class_labels': [False, True], 'raw_scores': [True, False]}
Scaler {'div': ['std', 'div', 'div_cast']}
StackingClassifier {'zipmap': [True, False, 'columns'], 'nocl': [True, False], 'output_class_labels': [False, True], 'raw_scores': [True, False]}
TfidfTransformer {'nan': [True, False]}
TfidfVectorizer {'tokenexp': None, 'separators': None, 'nan': [True, False], 'keep_empty_string': [True, False], 'locale': None}
VotingClassifier {'zipmap': [True, False, 'columns'], 'output_class_labels': [False, True], 'nocl': [True, False]}
_ConstantPredictor {'zipmap': [True, False, 'columns'], 'nocl': [True, False]}
all options: ['decision_leaf',
'decision_path',
'div',
'gemm',
'keep_empty_string',
'locale',
'nan',
'nocl',
'optim',
'output_class_labels',
'raw_scores',
'return_cov',
'return_std',
'score_samples',
'separators',
'tokenexp',
'zipmap']
脚本的总运行时间:(0 分钟 0.116 秒)