{"cells": [{"cell_type": "markdown", "id": "59a065e8", "metadata": {}, "source": ["# NeuralTreeNet et co\u00fbt\n", "\n", "La classe [NeuralTreeNet](:ref:`cl-NeuralTreeNet`) convertit un arbre de d\u00e9cision en r\u00e9seau de neurones. Si la conversion n'est pas exacte mais elle permet d'obtenir un mod\u00e8le diff\u00e9rentiable et apprenable avec un algorithme d'optimisation \u00e0 base de gradient. Ce notebook compare le temps d'\u00e9x\u00e9cution entre un arbre et le r\u00e9seau de neurones."]}, {"cell_type": "code", "execution_count": 1, "id": "dd51a9c7", "metadata": {}, "outputs": [{"data": {"text/html": ["
\n", ""], "text/plain": [""]}, "execution_count": 2, "metadata": {}, "output_type": "execute_result"}], "source": ["from jyquickhelper import add_notebook_menu\n", "add_notebook_menu()"]}, {"cell_type": "code", "execution_count": 2, "id": "e6ad71f6", "metadata": {}, "outputs": [], "source": ["%matplotlib inline"]}, {"cell_type": "code", "execution_count": 3, "id": "04cccbdc", "metadata": {}, "outputs": [], "source": ["%load_ext mlprodict"]}, {"cell_type": "markdown", "id": "52d5e7f8", "metadata": {}, "source": ["## Jeux de donn\u00e9es\n", "\n", "On construit un jeu de donn\u00e9es al\u00e9atoire."]}, {"cell_type": "code", "execution_count": 4, "id": "0abef0bf", "metadata": {}, "outputs": [], "source": ["import numpy\n", "\n", "X = numpy.random.randn(10000, 10)\n", "y = X.sum(axis=1) / X.shape[1]\n", "X = X.astype(numpy.float64)\n", "y = y.astype(numpy.float64)"]}, {"cell_type": "code", "execution_count": 5, "id": "8850b4e7", "metadata": {}, "outputs": [], "source": ["middle = X.shape[0] // 2\n", "X_train, X_test = X[:middle], X[middle:]\n", "y_train, y_test = y[:middle], y[middle:]"]}, {"cell_type": "markdown", "id": "12c4a84c", "metadata": {}, "source": ["## Caler un arbre de d\u00e9cision"]}, {"cell_type": "code", "execution_count": 6, "id": "1c0b0169", "metadata": {}, "outputs": [{"data": {"text/plain": ["(0.6091161526814477, 0.3884519134946681)"]}, "execution_count": 7, "metadata": {}, "output_type": "execute_result"}], "source": ["from sklearn.tree import DecisionTreeRegressor\n", "\n", "tree = DecisionTreeRegressor(max_depth=7)\n", "tree.fit(X_train, y_train)\n", "tree.score(X_train, y_train), tree.score(X_test, y_test)"]}, {"cell_type": "code", "execution_count": 7, "id": "6b158b44", "metadata": {}, "outputs": [{"data": {"text/plain": ["0.3884519134946681"]}, "execution_count": 8, "metadata": {}, "output_type": "execute_result"}], "source": ["from sklearn.metrics import r2_score\n", "r2_score(y_test, tree.predict(X_test))"]}, {"cell_type": "markdown", "id": "36db83ef", "metadata": {}, "source": ["Covnersion de l'arbre en r\u00e9seau de neurones"]}, {"cell_type": "code", "execution_count": 8, "id": "60e3e6ac", "metadata": {}, "outputs": [{"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " \n", " 0 \n", " \n", " \n", " \n", " \n", " average absolute error \n", " 0.210314 \n", " \n", " \n", " max absolute error \n", " 1.679553 \n", " \n", " \n", "
\n", "
"], "text/plain": [" 0\n", "average absolute error 0.210314\n", "max absolute error 1.679553"]}, "execution_count": 9, "metadata": {}, "output_type": "execute_result"}], "source": ["from pandas import DataFrame\n", "from mlstatpy.ml.neural_tree import NeuralTreeNet, NeuralTreeNetRegressor\n", "\n", "xe = X_test.astype(numpy.float32)\n", "expected = tree.predict(xe)\n", "\n", "nn = NeuralTreeNetRegressor(NeuralTreeNet.create_from_tree(tree, arch='compact'))\n", "got = nn.predict(xe)\n", "me = numpy.abs(got - expected).mean()\n", "mx = numpy.abs(got - expected).max()\n", "DataFrame([{\"average absolute error\": me, \"max absolute error\": mx}]).T"]}, {"cell_type": "markdown", "id": "559f0a25", "metadata": {}, "source": ["La conversion est loin d'\u00eatre parfaite. La raison vient du fait que les fonctions de seuil sont approch\u00e9es par des fonctions sigmo\u00efdes. Il suffit d'une erreur minime pour que la d\u00e9cision prenne un chemin diff\u00e9rent dans l'arbre et soit compl\u00e8tement diff\u00e9rente."]}, {"cell_type": "markdown", "id": "c1ad28cf", "metadata": {}, "source": ["## Conversion au format ONNX"]}, {"cell_type": "code", "execution_count": 9, "id": "b01518ec", "metadata": {}, "outputs": [], "source": ["from mlprodict.onnx_conv import to_onnx\n", "\n", "onx_tree = to_onnx(tree, X[:1].astype(numpy.float32))\n", "onx_nn = to_onnx(nn, X[:1].astype(numpy.float32))"]}, {"cell_type": "markdown", "id": "f59994a5", "metadata": {}, "source": ["Le r\u00e9seau de neurones peut \u00eatre repr\u00e9sent\u00e9 comme suit."]}, {"cell_type": "code", "execution_count": 10, "id": "2329bb80", "metadata": {}, "outputs": [{"data": {"text/html": ["\n", ""], "text/plain": [""]}, "execution_count": 11, "metadata": {}, "output_type": "execute_result"}], "source": ["%onnxview onx_nn"]}, {"cell_type": "code", "execution_count": 11, "id": "a33fedcb", "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["opset: domain='' version=15\n", "input: name='X' type=dtype('float32') shape=[None, 10]\n", "init: name='Ma_MatMulcst' type=dtype('float32') shape=(1270,)\n", "init: name='Ad_Addcst' type=dtype('float32') shape=(127,)\n", "init: name='Mu_Mulcst' type=dtype('float32') shape=(1,) -- array([4.], dtype=float32)\n", "init: name='Ma_MatMulcst1' type=dtype('float32') shape=(16256,)\n", "init: name='Ad_Addcst1' type=dtype('float32') shape=(128,)\n", "init: name='Ma_MatMulcst2' type=dtype('float32') shape=(128,)\n", "init: name='Ad_Addcst2' type=dtype('float32') shape=(1,) -- array([0.], dtype=float32)\n", "MatMul(X, Ma_MatMulcst) -> Ma_Y02\n", " Add(Ma_Y02, Ad_Addcst) -> Ad_C02\n", " Mul(Ad_C02, Mu_Mulcst) -> Mu_C01\n", " Sigmoid(Mu_C01) -> Si_Y01\n", " MatMul(Si_Y01, Ma_MatMulcst1) -> Ma_Y01\n", " Add(Ma_Y01, Ad_Addcst1) -> Ad_C01\n", " Mul(Ad_C01, Mu_Mulcst) -> Mu_C0\n", " Sigmoid(Mu_C0) -> Si_Y0\n", " MatMul(Si_Y0, Ma_MatMulcst2) -> Ma_Y0\n", " Add(Ma_Y0, Ad_Addcst2) -> Ad_C0\n", " Identity(Ad_C0) -> variable\n", "output: name='variable' type=dtype('float32') shape=[None, 1]\n"]}], "source": ["from mlprodict.plotting.text_plot import onnx_simple_text_plot\n", "print(onnx_simple_text_plot(onx_nn))"]}, {"cell_type": "markdown", "id": "857f2e42", "metadata": {}, "source": ["## Temps de calcul des pr\u00e9dictions"]}, {"cell_type": "code", "execution_count": 12, "id": "7c810819", "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["No CUDA runtime is found, using CUDA_HOME='C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v11.5'\n", "518 \u00b5s \u00b1 41.4 \u00b5s per loop (mean \u00b1 std. dev. of 7 runs, 1,000 loops each)\n"]}], "source": ["from mlprodict.onnxrt import OnnxInference\n", "\n", "oinf_tree = OnnxInference(onx_tree, runtime='onnxruntime1')\n", "oinf_nn = OnnxInference(onx_nn, runtime='onnxruntime1')\n", "\n", "%timeit tree.predict(xe)"]}, {"cell_type": "code", "execution_count": 13, "id": "51f6c958", "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["124 \u00b5s \u00b1 5.21 \u00b5s per loop (mean \u00b1 std. dev. of 7 runs, 10,000 loops each)\n"]}], "source": ["%timeit oinf_tree.run({'X': xe})"]}, {"cell_type": "code", "execution_count": 14, "id": "ab1ff3a8", "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["3.18 ms \u00b1 569 \u00b5s per loop (mean \u00b1 std. dev. of 7 runs, 100 loops each)\n"]}], "source": ["%timeit oinf_nn.run({'X': xe})"]}, {"cell_type": "markdown", "id": "5d8ecaa5", "metadata": {}, "source": ["Le temps de calcul est nettement plus long pour le r\u00e9seau de neurones. Si l'arbre de d\u00e9cision a une profondeur de *d*, l'arbre de d\u00e9cision va faire exactement *d* comparaisons. Le r\u00e9seau de neurones quant \u00e0 lui \u00e9value tous les seuils pour chaque pr\u00e9diction, soit $2^d$. V\u00e9rifions cela en faisant variable la profondeur."]}, {"cell_type": "markdown", "id": "b27675e4", "metadata": {}, "source": ["## Temps de calcul en fonction de la profondeur"]}, {"cell_type": "code", "execution_count": 15, "id": "ecef383a", "metadata": {}, "outputs": [{"name": "stderr", "output_type": "stream", "text": ["100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 8/8 [00:07<00:00, 1.04it/s]\n"]}, {"data": {"text/html": ["\n", "\n", "
\n", " \n", " \n", " \n", " average \n", " deviation \n", " min_exec \n", " max_exec \n", " repeat \n", " number \n", " ttime \n", " context_size \n", " d \n", " exp \n", " \n", " \n", " \n", " \n", " 0 \n", " 0.005656 \n", " 0.001292 \n", " 0.004990 \n", " 0.009533 \n", " 20 \n", " 20 \n", " 0.113116 \n", " 64 \n", " 2 \n", " skl \n", " \n", " \n", " 1 \n", " 0.000609 \n", " 0.000084 \n", " 0.000538 \n", " 0.000830 \n", " 10 \n", " 10 \n", " 0.006088 \n", " 64 \n", " 2 \n", " onx_tree \n", " \n", " \n", " 2 \n", " 0.002337 \n", " 0.000563 \n", " 0.001737 \n", " 0.003299 \n", " 10 \n", " 10 \n", " 0.023374 \n", " 64 \n", " 2 \n", " onx_nn \n", " \n", " \n", " 3 \n", " 0.005607 \n", " 0.000962 \n", " 0.004669 \n", " 0.007374 \n", " 20 \n", " 20 \n", " 0.112131 \n", " 64 \n", " 3 \n", " skl \n", " \n", " \n", " 4 \n", " 0.000709 \n", " 0.000240 \n", " 0.000534 \n", " 0.001363 \n", " 10 \n", " 10 \n", " 0.007087 \n", " 64 \n", " 3 \n", " onx_tree \n", " \n", " \n", " 5 \n", " 0.002506 \n", " 0.000448 \n", " 0.002073 \n", " 0.003736 \n", " 10 \n", " 10 \n", " 0.025064 \n", " 64 \n", " 3 \n", " onx_nn \n", " \n", " \n", " 6 \n", " 0.006398 \n", " 0.000942 \n", " 0.005576 \n", " 0.009556 \n", " 20 \n", " 20 \n", " 0.127961 \n", " 64 \n", " 4 \n", " skl \n", " \n", " \n", " 7 \n", " 0.000852 \n", " 0.000335 \n", " 0.000633 \n", " 0.001715 \n", " 10 \n", " 10 \n", " 0.008519 \n", " 64 \n", " 4 \n", " onx_tree \n", " \n", " \n", " 8 \n", " 0.004151 \n", " 0.000763 \n", " 0.003408 \n", " 0.005826 \n", " 10 \n", " 10 \n", " 0.041507 \n", " 64 \n", " 4 \n", " onx_nn \n", " \n", " \n", " 9 \n", " 0.007266 \n", " 0.000731 \n", " 0.006400 \n", " 0.009247 \n", " 20 \n", " 20 \n", " 0.145314 \n", " 64 \n", " 5 \n", " skl \n", " \n", " \n", " 10 \n", " 0.000963 \n", " 0.000169 \n", " 0.000826 \n", " 0.001327 \n", " 10 \n", " 10 \n", " 0.009634 \n", " 64 \n", " 5 \n", " onx_tree \n", " \n", " \n", " 11 \n", " 0.006113 \n", " 0.000560 \n", " 0.005360 \n", " 0.007255 \n", " 10 \n", " 10 \n", " 0.061134 \n", " 64 \n", " 5 \n", " onx_nn \n", " \n", " \n", " 12 \n", " 0.008881 \n", " 0.001234 \n", " 0.007601 \n", " 0.012340 \n", " 20 \n", " 20 \n", " 0.177622 \n", " 64 \n", " 6 \n", " skl \n", " \n", " \n", " 13 \n", " 0.001175 \n", " 0.000613 \n", " 0.000815 \n", " 0.002984 \n", " 10 \n", " 10 \n", " 0.011754 \n", " 64 \n", " 6 \n", " onx_tree \n", " \n", " \n", " 14 \n", " 0.011537 \n", " 0.001079 \n", " 0.010146 \n", " 0.013582 \n", " 10 \n", " 10 \n", " 0.115367 \n", " 64 \n", " 6 \n", " onx_nn \n", " \n", " \n", " 15 \n", " 0.011134 \n", " 0.000887 \n", " 0.009569 \n", " 0.013273 \n", " 20 \n", " 20 \n", " 0.222676 \n", " 64 \n", " 7 \n", " skl \n", " \n", " \n", " 16 \n", " 0.001399 \n", " 0.000131 \n", " 0.001301 \n", " 0.001742 \n", " 10 \n", " 10 \n", " 0.013988 \n", " 64 \n", " 7 \n", " onx_tree \n", " \n", " \n", " 17 \n", " 0.034831 \n", " 0.004986 \n", " 0.027853 \n", " 0.046760 \n", " 10 \n", " 10 \n", " 0.348306 \n", " 64 \n", " 7 \n", " onx_nn \n", " \n", " \n", " 18 \n", " 0.011263 \n", " 0.001060 \n", " 0.009911 \n", " 0.013893 \n", " 20 \n", " 20 \n", " 0.225251 \n", " 64 \n", " 8 \n", " skl \n", " \n", " \n", " 19 \n", " 0.001226 \n", " 0.000148 \n", " 0.001033 \n", " 0.001609 \n", " 10 \n", " 10 \n", " 0.012259 \n", " 64 \n", " 8 \n", " onx_tree \n", " \n", " \n", " 20 \n", " 0.141722 \n", " 0.051041 \n", " 0.089979 \n", " 0.270129 \n", " 10 \n", " 10 \n", " 1.417218 \n", " 64 \n", " 8 \n", " onx_nn \n", " \n", " \n", " 21 \n", " 0.016436 \n", " 0.004827 \n", " 0.011379 \n", " 0.028261 \n", " 20 \n", " 20 \n", " 0.328716 \n", " 64 \n", " 9 \n", " skl \n", " \n", " \n", " 22 \n", " 0.003879 \n", " 0.001963 \n", " 0.002402 \n", " 0.008579 \n", " 10 \n", " 10 \n", " 0.038786 \n", " 64 \n", " 9 \n", " onx_tree \n", " \n", " \n", " 23 \n", " 0.323764 \n", " 0.063045 \n", " 0.261038 \n", " 0.456420 \n", " 10 \n", " 10 \n", " 3.237635 \n", " 64 \n", " 9 \n", " onx_nn \n", " \n", " \n", "
\n", "
"], "text/plain": [" average deviation min_exec max_exec repeat number ttime \\\n", "0 0.005656 0.001292 0.004990 0.009533 20 20 0.113116 \n", "1 0.000609 0.000084 0.000538 0.000830 10 10 0.006088 \n", "2 0.002337 0.000563 0.001737 0.003299 10 10 0.023374 \n", "3 0.005607 0.000962 0.004669 0.007374 20 20 0.112131 \n", "4 0.000709 0.000240 0.000534 0.001363 10 10 0.007087 \n", "5 0.002506 0.000448 0.002073 0.003736 10 10 0.025064 \n", "6 0.006398 0.000942 0.005576 0.009556 20 20 0.127961 \n", "7 0.000852 0.000335 0.000633 0.001715 10 10 0.008519 \n", "8 0.004151 0.000763 0.003408 0.005826 10 10 0.041507 \n", "9 0.007266 0.000731 0.006400 0.009247 20 20 0.145314 \n", "10 0.000963 0.000169 0.000826 0.001327 10 10 0.009634 \n", "11 0.006113 0.000560 0.005360 0.007255 10 10 0.061134 \n", "12 0.008881 0.001234 0.007601 0.012340 20 20 0.177622 \n", "13 0.001175 0.000613 0.000815 0.002984 10 10 0.011754 \n", "14 0.011537 0.001079 0.010146 0.013582 10 10 0.115367 \n", "15 0.011134 0.000887 0.009569 0.013273 20 20 0.222676 \n", "16 0.001399 0.000131 0.001301 0.001742 10 10 0.013988 \n", "17 0.034831 0.004986 0.027853 0.046760 10 10 0.348306 \n", "18 0.011263 0.001060 0.009911 0.013893 20 20 0.225251 \n", "19 0.001226 0.000148 0.001033 0.001609 10 10 0.012259 \n", "20 0.141722 0.051041 0.089979 0.270129 10 10 1.417218 \n", "21 0.016436 0.004827 0.011379 0.028261 20 20 0.328716 \n", "22 0.003879 0.001963 0.002402 0.008579 10 10 0.038786 \n", "23 0.323764 0.063045 0.261038 0.456420 10 10 3.237635 \n", "\n", " context_size d exp \n", "0 64 2 skl \n", "1 64 2 onx_tree \n", "2 64 2 onx_nn \n", "3 64 3 skl \n", "4 64 3 onx_tree \n", "5 64 3 onx_nn \n", "6 64 4 skl \n", "7 64 4 onx_tree \n", "8 64 4 onx_nn \n", "9 64 5 skl \n", "10 64 5 onx_tree \n", "11 64 5 onx_nn \n", "12 64 6 skl \n", "13 64 6 onx_tree \n", "14 64 6 onx_nn \n", "15 64 7 skl \n", "16 64 7 onx_tree \n", "17 64 7 onx_nn \n", "18 64 8 skl \n", "19 64 8 onx_tree \n", "20 64 8 onx_nn \n", "21 64 9 skl \n", "22 64 9 onx_tree \n", "23 64 9 onx_nn "]}, "execution_count": 16, "metadata": {}, "output_type": "execute_result"}], "source": ["from tqdm import tqdm\n", "from cpyquickhelper.numbers import measure_time\n", "\n", "data = []\n", "for d in tqdm(range(2, 10)):\n", " tree = DecisionTreeRegressor(max_depth=d)\n", " tree.fit(X_train, y_train)\n", " obs = measure_time(lambda: tree.predict(xe), number=20, repeat=20)\n", " obs.update(dict(d=d, exp='skl'))\n", " data.append(obs)\n", " \n", " nn = NeuralTreeNetRegressor(NeuralTreeNet.create_from_tree(tree, arch='compact'))\n", " \n", " onx_tree = to_onnx(tree, X[:1].astype(numpy.float32))\n", " onx_nn = to_onnx(nn, X[:1].astype(numpy.float32))\n", " oinf_tree = OnnxInference(onx_tree, runtime='onnxruntime1')\n", " oinf_nn = OnnxInference(onx_nn, runtime='onnxruntime1')\n", " \n", " obs = measure_time(lambda: oinf_tree.run({'X': xe}), number=10, repeat=10)\n", " obs.update(dict(d=d, exp='onx_tree'))\n", " data.append(obs)\n", "\n", " obs = measure_time(lambda: oinf_nn.run({'X': xe}), number=10, repeat=10)\n", " obs.update(dict(d=d, exp='onx_nn'))\n", " data.append(obs)\n", "\n", "df = DataFrame(data)\n", "df"]}, {"cell_type": "code", "execution_count": 16, "id": "871130aa", "metadata": {}, "outputs": [{"data": {"image/png": "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\n", "text/plain": [""]}, "metadata": {"needs_background": "light"}, "output_type": "display_data"}], "source": ["piv = df.pivot('d', 'exp', 'average')\n", "piv.plot(logy=True, title=\"Temps de calcul en fonction de la profondeur\");"]}, {"cell_type": "markdown", "id": "b30bbefe", "metadata": {}, "source": ["L'hypoth\u00e8se est v\u00e9rifi\u00e9e."]}, {"cell_type": "code", "execution_count": 17, "id": "8b163d83", "metadata": {}, "outputs": [], "source": []}], "metadata": {"kernelspec": {"display_name": "Python 3 (ipykernel)", "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.5"}}, "nbformat": 4, "nbformat_minor": 5}