{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "%matplotlib inline"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n# Train, convert and predict with ONNX Runtime\n\nThis example demonstrates an end to end scenario\nstarting with the training of a scikit-learn pipeline\nwhich takes as inputs not a regular vector but a\ndictionary ``{ int: float }`` as its first step is a\n[DictVectorizer](http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.DictVectorizer.html).\n\n## Train a pipeline\n\nThe first step consists in retrieving the boston datset.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import pandas\nfrom sklearn.datasets import load_boston\n\nboston = load_boston()\nX, y = boston.data, boston.target\n\nfrom sklearn.model_selection import train_test_split\n\nX_train, X_test, y_train, y_test = train_test_split(X, y)\nX_train_dict = pandas.DataFrame(X_train[:, 1:]).T.to_dict().values()\nX_test_dict = pandas.DataFrame(X_test[:, 1:]).T.to_dict().values()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We create a pipeline.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "from sklearn.ensemble import GradientBoostingRegressor\nfrom sklearn.feature_extraction import DictVectorizer\nfrom sklearn.pipeline import make_pipeline\n\npipe = make_pipeline(DictVectorizer(sparse=False), GradientBoostingRegressor())\n\npipe.fit(X_train_dict, y_train)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We compute the prediction on the test set\nand we show the confusion matrix.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "from sklearn.metrics import r2_score\n\npred = pipe.predict(X_test_dict)\nprint(r2_score(y_test, pred))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Conversion to ONNX format\n\nWe use module\n[sklearn-onnx](https://github.com/onnx/sklearn-onnx)\nto convert the model into ONNX format.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "from skl2onnx import convert_sklearn\nfrom skl2onnx.common.data_types import DictionaryType, FloatTensorType, Int64TensorType, SequenceType\n\n# initial_type = [('float_input', DictionaryType(Int64TensorType([1]), FloatTensorType([])))]\ninitial_type = [(\"float_input\", DictionaryType(Int64TensorType([1]), FloatTensorType([])))]\nonx = convert_sklearn(pipe, initial_types=initial_type)\nwith open(\"pipeline_vectorize.onnx\", \"wb\") as f:\n    f.write(onx.SerializeToString())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We load the model with ONNX Runtime and look at\nits input and output.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import onnxruntime as rt\nfrom onnxruntime.capi.onnxruntime_pybind11_state import InvalidArgument\n\nsess = rt.InferenceSession(\"pipeline_vectorize.onnx\", providers=rt.get_available_providers())\n\nimport numpy\n\ninp, out = sess.get_inputs()[0], sess.get_outputs()[0]\nprint(\"input name='{}' and shape={} and type={}\".format(inp.name, inp.shape, inp.type))\nprint(\"output name='{}' and shape={} and type={}\".format(out.name, out.shape, out.type))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We compute the predictions.\nWe could do that in one call:\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "try:\n    pred_onx = sess.run([out.name], {inp.name: X_test_dict})[0]\nexcept (RuntimeError, InvalidArgument) as e:\n    print(e)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "But it fails because, in case of a DictVectorizer,\nONNX Runtime expects one observation at a time.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "pred_onx = [sess.run([out.name], {inp.name: row})[0][0, 0] for row in X_test_dict]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We compare them to the model's ones.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "print(r2_score(pred, pred_onx))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Very similar. *ONNX Runtime* uses floats instead of doubles,\nthat explains the small discrepencies.\n\n"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.10.6"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}