Note
Click here to download the full example code
This example demonstrates an end to end scenario starting with the training of a machine learned model to its use in its converted from.
The first step consists in retrieving the iris datset.
from sklearn.datasets import load_iris
iris = load_iris()
X, y = iris.data, iris.target
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y)
Then we fit a model.
from sklearn.linear_model import LogisticRegression
clr = LogisticRegression()
clr.fit(X_train, y_train)
Out:
c:\users\hasesh\appdata\local\programs\python\python36\lib\site-packages\sklearn\linear_model\logistic.py:432: FutureWarning: Default solver will be changed to 'lbfgs' in 0.22. Specify a solver to silence this warning.
FutureWarning)
c:\users\hasesh\appdata\local\programs\python\python36\lib\site-packages\sklearn\linear_model\logistic.py:469: FutureWarning: Default multi_class will be changed to 'auto' in 0.22. Specify the multi_class option to silence this warning.
"this warning.", FutureWarning)
We compute the prediction on the test set and we show the confusion matrix.
from sklearn.metrics import confusion_matrix
pred = clr.predict(X_test)
print(confusion_matrix(y_test, pred))
Out:
[[14 0 0]
[ 0 9 4]
[ 0 1 10]]
We use module sklearn-onnx to convert the model into ONNX format.
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())
Out:
The maximum opset needed by this model is only 9.
The maximum opset needed by this model is only 1.
We load the model with ONNX Runtime and look at its input and output.
import onnxruntime as rt
sess = rt.InferenceSession("logreg_iris.onnx")
print("input name='{}' and shape={}".format(sess.get_inputs()[0].name, sess.get_inputs()[0].shape))
print("output name='{}' and shape={}".format(sess.get_outputs()[0].name, sess.get_outputs()[0].shape))
Out:
input name='float_input' and shape=[1, 4]
output name='output_label' and shape=[1]
We compute the predictions.
input_name = sess.get_inputs()[0].name
label_name = sess.get_outputs()[0].name
import numpy
pred_onx = sess.run([label_name], {input_name: X_test.astype(numpy.float32)})[0]
print(confusion_matrix(pred, pred_onx))
Out:
[[14 0 0]
[ 0 10 0]
[ 0 0 14]]
The prediction are perfectly identical.
Probabilities are needed to compute other relevant metrics such as the ROC Curve. Let’s see how to get them first with scikit-learn.
prob_sklearn = clr.predict_proba(X_test)
print(prob_sklearn[:3])
Out:
[[0.02503056 0.43689584 0.53807361]
[0.00202039 0.19895737 0.79902224]
[0.01142149 0.64908707 0.33949145]]
And then with ONNX Runtime. The probabilies appear to be
prob_name = sess.get_outputs()[1].name
prob_rt = sess.run([prob_name], {input_name: X_test.astype(numpy.float32)})[0]
import pprint
pprint.pprint(prob_rt[0:3])
Out:
[{0: 0.025030435994267464, 1: 0.4368962347507477, 2: 0.5380733609199524},
{0: 0.002020390471443534, 1: 0.1989573985338211, 2: 0.7990221977233887},
{0: 0.011421487666666508, 1: 0.6490871906280518, 2: 0.3394913375377655}]
Let’s benchmark.
from timeit import Timer
def speed(inst, number=10, repeat=20):
timer = Timer(inst, globals=globals())
raw = numpy.array(timer.repeat(repeat, number=number))
ave = raw.sum() / len(raw) / number
mi, ma = raw.min() / number, raw.max() / number
print("Average %1.3g min=%1.3g max=%1.3g" % (ave, mi, ma))
return ave
print("Execution time for clr.predict")
speed("clr.predict(X_test)")
print("Execution time for ONNX Runtime")
speed("sess.run([label_name], {input_name: X_test.astype(numpy.float32)})[0]")
Out:
Execution time for clr.predict
Average 4.86e-05 min=4.49e-05 max=8.59e-05
Execution time for ONNX Runtime
Average 0.00163 min=0.00118 max=0.0024
Let’s benchmark a scenario similar to what a webservice experiences: the model has to do one prediction at a time as opposed to a batch of prediction.
def loop(X_test, fct, n=None):
nrow = X_test.shape[0]
if n is None:
n = nrow
for i in range(0, n):
im = i % nrow
fct(X_test[im: im+1])
print("Execution time for clr.predict")
speed("loop(X_test, clr.predict, 100)")
def sess_predict(x):
return sess.run([label_name], {input_name: x.astype(numpy.float32)})[0]
print("Execution time for sess_predict")
speed("loop(X_test, sess_predict, 100)")
Out:
Execution time for clr.predict
Average 0.00537 min=0.00411 max=0.017
Execution time for sess_predict
Average 0.00241 min=0.0015 max=0.00453
Let’s do the same for the probabilities.
print("Execution time for predict_proba")
speed("loop(X_test, clr.predict_proba, 100)")
def sess_predict_proba(x):
return sess.run([prob_name], {input_name: x.astype(numpy.float32)})[0]
print("Execution time for sess_predict_proba")
speed("loop(X_test, sess_predict_proba, 100)")
Out:
Execution time for predict_proba
Average 0.00673 min=0.00536 max=0.0101
Execution time for sess_predict_proba
Average 0.00159 min=0.00148 max=0.00184
This second comparison is better as ONNX Runtime, in this experience, computes the label and the probabilities in every case.
We first train and save a model in ONNX format.
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier()
rf.fit(X_train, y_train)
initial_type = [('float_input', FloatTensorType([1, 4]))]
onx = convert_sklearn(rf, initial_types=initial_type)
with open("rf_iris.onnx", "wb") as f:
f.write(onx.SerializeToString())
Out:
c:\users\hasesh\appdata\local\programs\python\python36\lib\site-packages\sklearn\ensemble\forest.py:245: FutureWarning: The default value of n_estimators will change from 10 in version 0.20 to 100 in 0.22.
"10 in version 0.20 to 100 in 0.22.", FutureWarning)
The maximum opset needed by this model is only 9.
The maximum opset needed by this model is only 1.
We compare.
sess = rt.InferenceSession("rf_iris.onnx")
def sess_predict_proba_rf(x):
return sess.run([prob_name], {input_name: x.astype(numpy.float32)})[0]
print("Execution time for predict_proba")
speed("loop(X_test, rf.predict_proba, 100)")
print("Execution time for sess_predict_proba")
speed("loop(X_test, sess_predict_proba_rf, 100)")
Out:
Execution time for predict_proba
Average 0.0881 min=0.0836 max=0.111
Execution time for sess_predict_proba
Average 0.00222 min=0.0016 max=0.00379
Let’s see with different number of trees.
measures = []
for n_trees in range(5, 51, 5):
print(n_trees)
rf = RandomForestClassifier(n_estimators=n_trees)
rf.fit(X_train, y_train)
initial_type = [('float_input', FloatTensorType([1, 4]))]
onx = convert_sklearn(rf, initial_types=initial_type)
with open("rf_iris_%d.onnx" % n_trees, "wb") as f:
f.write(onx.SerializeToString())
sess = rt.InferenceSession("rf_iris_%d.onnx" % n_trees)
def sess_predict_proba_loop(x):
return sess.run([prob_name], {input_name: x.astype(numpy.float32)})[0]
tsk = speed("loop(X_test, rf.predict_proba, 100)", number=5, repeat=5)
trt = speed("loop(X_test, sess_predict_proba_loop, 100)", number=5, repeat=5)
measures.append({'n_trees': n_trees, 'sklearn': tsk, 'rt': trt})
from pandas import DataFrame
df = DataFrame(measures)
ax = df.plot(x="n_trees", y="sklearn", label="scikit-learn", c="blue", logy=True)
df.plot(x="n_trees", y="rt", label="onnxruntime",
ax=ax, c="green", logy=True)
ax.set_xlabel("Number of trees")
ax.set_ylabel("Prediction time (s)")
ax.set_title("Speed comparison between scikit-learn and ONNX Runtime\nFor a random forest on Iris dataset")
ax.legend()
Out:
5
The maximum opset needed by this model is only 9.
The maximum opset needed by this model is only 1.
Average 0.0557 min=0.054 max=0.06
Average 0.00168 min=0.00152 max=0.00199
10
The maximum opset needed by this model is only 9.
The maximum opset needed by this model is only 1.
Average 0.0966 min=0.0881 max=0.106
Average 0.00167 min=0.00158 max=0.00185
15
The maximum opset needed by this model is only 9.
The maximum opset needed by this model is only 1.
Average 0.121 min=0.119 max=0.126
Average 0.00199 min=0.00172 max=0.00227
20
The maximum opset needed by this model is only 9.
The maximum opset needed by this model is only 1.
Average 0.156 min=0.15 max=0.164
Average 0.00227 min=0.00172 max=0.00346
25
The maximum opset needed by this model is only 9.
The maximum opset needed by this model is only 1.
Average 0.189 min=0.184 max=0.192
Average 0.00191 min=0.0018 max=0.00217
30
The maximum opset needed by this model is only 9.
The maximum opset needed by this model is only 1.
Average 0.212 min=0.202 max=0.238
Average 0.00191 min=0.00181 max=0.00196
35
The maximum opset needed by this model is only 9.
The maximum opset needed by this model is only 1.
Average 0.245 min=0.232 max=0.26
Average 0.002 min=0.00194 max=0.00212
40
The maximum opset needed by this model is only 9.
The maximum opset needed by this model is only 1.
Average 0.318 min=0.28 max=0.427
Average 0.00287 min=0.00252 max=0.00322
45
The maximum opset needed by this model is only 9.
The maximum opset needed by this model is only 1.
Average 0.36 min=0.308 max=0.484
Average 0.00481 min=0.00458 max=0.00519
50
The maximum opset needed by this model is only 9.
The maximum opset needed by this model is only 1.
Average 0.491 min=0.428 max=0.539
Average 0.00233 min=0.00206 max=0.00259
Total running time of the script: ( 1 minutes 20.485 seconds)