Verwendung des Python-Operators (PyOp)
Hinweis zur Veralterung: Diese Funktion ist veraltet und wird nicht mehr unterstützt.
Der Python-Operator bietet die Möglichkeit, beliebigen benutzerdefinierten Python-Code innerhalb eines einzelnen Knotens eines ONNX-Graphen mit ONNX Runtime einfach aufzurufen. Dies kann für schnellere Experimente nützlich sein, wenn ein Modell Operatoren benötigt, die in ONNX und ONNX Runtime nicht offiziell unterstützt werden, insbesondere wenn bereits eine Python-Implementierung für die benötigte Funktionalität vorhanden ist. Dies sollte in Produktionsszenarien mit Bedacht eingesetzt werden, und alle Sicherheits- oder sonstigen Risiken sollten im Voraus berücksichtigt werden.
Designübersicht
Die Funktion befindet sich unter language_interop_ops.
Hier ist ein Diagramm der Aufrufsequenz
onnxruntime python capi script
| | |
| ------------------------------> | |
| call with tensor(s) | ------------------------------> |
| | call with numpy(s) |
| | | compute
| | <------------------------------ |
| <------------------------------ | return numpys(s) |
| return tensor(s) | |
Verwendung
Schritt 1
Erstellen Sie onnxruntime mit --config Release --enable_language_interop_ops --build_wheel und installieren Sie die neueste Wheel-Datei mit pip.
Schritt 2
Erstellen Sie ein ONNX-Modell, das Python-Operator-Knoten enthält.
ad1_node = helper.make_node('Add', ['A','B'], ['S'])
mul_node = helper.make_node('Mul', ['C','D'], ['P'])
py1_node = helper.make_node(op_type = 'PyOp', #required, must be 'PyOp'
inputs = ['S','P'], #required
outputs = ['L','M','N'], #required
domain = 'pyopmulti_1', #required, must be unique
input_types = [TensorProto.FLOAT, TensorProto.FLOAT], #required
output_types = [TensorProto.FLOAT, TensorProto.FLOAT, TensorProto.FLOAT], #required
module = 'mymodule', #required
class_name = 'Multi_1', #required
compute = 'compute', #optional, 'compute' by default
W1 = '5', W2 = '7', W3 = '9') #optional, must all be strings
ad2_node = helper.make_node('Add', ['L','M'], ['H'])
py2_node = helper.make_node('PyOp',['H','N','E'],['O','W'], domain = 'pyopmulti_2',
input_types = [TensorProto.FLOAT, TensorProto.FLOAT, TensorProto.FLOAT],
output_types = [TensorProto.FLOAT, TensorProto.FLOAT],
module = 'mymodule', class_name = 'Multi_2')
sub_node = helper.make_node('Sub', ['O','W'], ['F'])
graph = helper.make_graph([ad1_node,mul_node,py1_node,ad2_node,py2_node,sub_node], 'multi_pyop_graph', [A,B,C,D,E], [F])
model = helper.make_model(graph, producer_name = 'pyop_model')
onnx.save(model, './model.onnx')
Schritt 3
Implementieren Sie mymodule.py
class Multi_1:
def __init__(self, W1, W2, W3):
self.W1 = int(W1)
self.W2 = int(W2)
self.W3 = int(W3)
def compute(self, S, P):
ret = S + P
return ret + self.W1, ret + self.W2, ret + self.W3
class Multi_2:
def compute(self, *kwargs):
return sum(kwargs[0:-1]), sum(kwargs[1:])
Schritt 4
Kopieren Sie mymodule.py in den Python sys.path, und führen Sie dann das Modell mit der ONNX Runtime Python API aus. Unter Windows setzen Sie bitte zuvor PYTHONHOME. Es sollte auf das Verzeichnis zeigen, in dem Python installiert ist, z. B. C:\Python37 oder C:\ProgramData\Anaconda3\envs\myconda1, wenn es sich um conda handelt.
Unterstützte Datentypen
- TensorProto.BOOL
- TensorProto.UINT8
- TensorProto.UINT16
- TensorProto.UINT32
- TensorProto.INT16
- TensorProto.INT32
- TensorProto.FLOAT
- TensorProto.DOUBLE
Einschränkungen
- Inferencing- und Compilierungsumgebungen müssen mit derselben Python-Version installiert sein.
- Unter Windows hat
--config Debugbekannte Probleme. Bitte kompilieren Sie mit--config RelWithDebInfo, wenn Debugging-Symbole benötigt werden. - Aufgrund von Einschränkungen der Python C API ist Multi-Threading deaktiviert, sodass Python-Operatoren sequenziell ausgeführt werden.
Testabdeckung
Der Operator wurde auf mehreren Plattformen mit oder ohne Conda getestet.
| Plattform | Python 3.5 | Python 3.6 | Python 3.7 |
|---|---|---|---|
| Windows | (conda) bestanden | (conda) bestanden | bestanden |
| Linux | (conda) bestanden | (conda) bestanden | bestanden |
| Mac | (conda) bestanden | (conda) bestanden | (conda) bestanden |
Beispiel
Entwickler können sich bei der Modellkonvertierung auf PyOp verlassen, um fehlende Operatoren zu ersetzen.
import os
import numpy as np
from onnx import *
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType
from skl2onnx.common.utils import check_input_and_output_numbers
X = np.array([[1, 1], [2, 1], [3, 1.2], [4, 1], [5, 0.8], [6, 1]],dtype=np.single)
nmf = NMF(n_components=2, init='random', random_state=0)
W = np.array(nmf.fit_transform(X), dtype=np.single)
def calculate_sklearn_nmf_output_shapes(operator):
check_input_and_output_numbers(operator, output_count_range=1, input_count_range=1)
operator.outputs[0].type.shape = operator.inputs[0].type.shape
def convert_nmf(scope, operator, container):
ws = [str(w) for w in W.flatten()]
attrs = {'W':'|'.join(ws)}
container.add_node(op_type='PyOp', name='nmf', inputs=['X'], outputs=['variable'],
op_version=10, op_domain='MyDomain', module='mymodule', class_name='MyNmf',
input_types=[TensorProto.FLOAT], output_types=[TensorProto.FLOAT], **attrs)
custom_shape_calculators = {type(nmf): calculate_sklearn_nmf_output_shapes}
custom_conversion_functions = {type(nmf): convert_nmf}
initial_types = [('X', FloatTensorType([6,2]))]
onx = convert_sklearn(nmf, '', initial_types, '', None, custom_conversion_functions, custom_shape_calculators)
with th open("model.onnx", "wb") as f:
f.write(onx.SerializeToString())
mymodule.py
import numpy as np
class MyNmf:
def __init__(self,W):
A = []
for w in W.split('|'):
A.append(float(w))
self.__W = np.array(A,dtype=np.single).reshape(6,2)
def compute(self,X):
return self.__W