{"cells": [{"cell_type": "markdown", "metadata": {}, "source": ["# Visualize a scikit-learn pipeline\n", "\n", "Pipeline can be big with *scikit-learn*, let's dig into a visual way to look a them."]}, {"cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [{"data": {"text/html": ["
run previous cell, wait for 2 seconds
\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, "metadata": {}, "outputs": [], "source": ["%matplotlib inline"]}, {"cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": ["import warnings\n", "warnings.simplefilter(\"ignore\")"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Simple model\n", "\n", "Let's vizualize a simple pipeline, a single model not even trained."]}, {"cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [{"data": {"text/plain": ["LogisticRegression()"]}, "execution_count": 5, "metadata": {}, "output_type": "execute_result"}], "source": ["import pandas\n", "from sklearn import datasets\n", "from sklearn.linear_model import LogisticRegression\n", "\n", "iris = datasets.load_iris()\n", "X = iris.data[:, :4]\n", "df = pandas.DataFrame(X)\n", "df.columns = [\"X1\", \"X2\", \"X3\", \"X4\"]\n", "clf = LogisticRegression()\n", "clf"]}, {"cell_type": "markdown", "metadata": {}, "source": ["The trick consists in converting the pipeline in a graph through the [DOT](https://en.wikipedia.org/wiki/DOT_(graph_description_language)) language."]}, {"cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["digraph{\n", " orientation=portrait;\n", " nodesep=0.05;\n", " ranksep=0.25;\n", " sch0[label=\" X1| X2| X3| X4\",shape=record,fontsize=8];\n", "\n", " node1[label=\"union\",shape=box,style=\"filled,rounded\",color=cyan,fontsize=12];\n", " sch0:f0 -> node1;\n", " sch0:f1 -> node1;\n", " sch0:f2 -> node1;\n", " sch0:f3 -> node1;\n", " sch1[label=\" -v-0\",shape=record,fontsize=8];\n", " node1 -> sch1:f0;\n", "\n", " node2[label=\"LogisticRegression\",shape=box,style=\"filled,rounded\",color=yellow,fontsize=12];\n", " sch1:f0 -> node2;\n", " sch2[label=\" PredictedLabel| Probabilities\",shape=record,fontsize=8];\n", " node2 -> sch2:f0;\n", " node2 -> sch2:f1;\n", "}\n"]}], "source": ["from mlinsights.plotting import pipeline2dot\n", "dot = pipeline2dot(clf, df)\n", "print(dot)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["It is lot better with an image."]}, {"cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": ["dot_file = \"graph.dot\"\n", "with open(dot_file, \"w\", encoding=\"utf-8\") as f:\n", " f.write(dot)"]}, {"cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": ["# might be needed on windows\n", "import sys\n", "import os\n", "if sys.platform.startswith(\"win\") and \"Graphviz\" not in os.environ[\"PATH\"]:\n", " os.environ['PATH'] = os.environ['PATH'] + r';C:\\Program Files (x86)\\Graphviz2.38\\bin'"]}, {"cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[run_cmd] execute dot -G=300 -Tpng graph.dot -ograph.dot.png\n", "end of execution dot -G=300 -Tpng graph.dot -ograph.dot.png\n"]}], "source": ["from pyquickhelper.loghelper import run_cmd\n", "cmd = \"dot -G=300 -Tpng {0} -o{0}.png\".format(dot_file)\n", "run_cmd(cmd, wait=True, fLOG=print);"]}, {"cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [{"data": {"image/png": "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\n", "text/plain": [""]}, "execution_count": 10, "metadata": {}, "output_type": "execute_result"}], "source": ["from PIL import Image\n", "img = Image.open(\"graph.dot.png\")\n", "img"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Complex pipeline\n", "\n", "*scikit-learn* instroduced a couple of transform to play with features in a single pipeline. The following example is taken from [Column Transformer with Mixed Types](https://scikit-learn.org/stable/auto_examples/compose/plot_column_transformer_mixed_types.html#sphx-glr-auto-examples-compose-plot-column-transformer-mixed-types-py)."]}, {"cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": ["from sklearn import datasets\n", "from sklearn.linear_model import LogisticRegression, LinearRegression\n", "from sklearn.preprocessing import StandardScaler\n", "from sklearn.compose import ColumnTransformer\n", "from sklearn.pipeline import Pipeline\n", "from sklearn.impute import SimpleImputer\n", "from sklearn.preprocessing import StandardScaler, OneHotEncoder\n", "from sklearn.linear_model import LogisticRegression"]}, {"cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [{"data": {"text/plain": ["Pipeline(steps=[('preprocessor',\n", " ColumnTransformer(transformers=[('num',\n", " Pipeline(steps=[('imputer',\n", " SimpleImputer(strategy='median')),\n", " ('scaler',\n", " StandardScaler())]),\n", " ['age', 'fare']),\n", " ('cat',\n", " Pipeline(steps=[('imputer',\n", " SimpleImputer(fill_value='missing',\n", " strategy='constant')),\n", " ('onehot',\n", " OneHotEncoder(handle_unknown='ignore'))]),\n", " ['embarked', 'sex',\n", " 'pclass'])])),\n", " ('classifier', LogisticRegression())])"]}, "execution_count": 12, "metadata": {}, "output_type": "execute_result"}], "source": ["columns = ['pclass', 'name', 'sex', 'age', 'sibsp', 'parch', 'ticket', 'fare',\n", " 'cabin', 'embarked', 'boat', 'body', 'home.dest']\n", "\n", "numeric_features = ['age', 'fare']\n", "numeric_transformer = Pipeline(steps=[\n", " ('imputer', SimpleImputer(strategy='median')),\n", " ('scaler', StandardScaler())])\n", "\n", "categorical_features = ['embarked', 'sex', 'pclass']\n", "categorical_transformer = Pipeline(steps=[\n", " ('imputer', SimpleImputer(strategy='constant', fill_value='missing')),\n", " ('onehot', OneHotEncoder(handle_unknown='ignore'))])\n", "\n", "preprocessor = ColumnTransformer(\n", " transformers=[\n", " ('num', numeric_transformer, numeric_features),\n", " ('cat', categorical_transformer, categorical_features),\n", " ])\n", "\n", "clf = Pipeline(steps=[('preprocessor', preprocessor),\n", " ('classifier', LogisticRegression(solver='lbfgs'))])\n", "clf"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Let's see it first as a simplified text."]}, {"cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Pipeline\n", " ColumnTransformer\n", " Pipeline(age,fare)\n", " SimpleImputer\n", " StandardScaler\n", " Pipeline(embarked,sex,pclass)\n", " SimpleImputer\n", " OneHotEncoder\n", " LogisticRegression\n"]}], "source": ["from mlinsights.plotting import pipeline2str\n", "print(pipeline2str(clf))"]}, {"cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": ["dot = pipeline2dot(clf, columns)"]}, {"cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": ["dot_file = \"graph2.dot\"\n", "with open(dot_file, \"w\", encoding=\"utf-8\") as f:\n", " f.write(dot)"]}, {"cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[run_cmd] execute dot -G=300 -Tpng graph2.dot -ograph2.dot.png\n", "end of execution dot -G=300 -Tpng graph2.dot -ograph2.dot.png\n"]}], "source": ["cmd = \"dot -G=300 -Tpng {0} -o{0}.png\".format(dot_file)\n", "run_cmd(cmd, wait=True, fLOG=print);"]}, {"cell_type": "code", "execution_count": 16, "metadata": {"scrolled": false}, "outputs": [{"data": {"image/png": "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FWTSpEly7tw53epZsGCBVKtWTRITE+WRRx6R48eP61ZLsE6ePCmTJk2SpKQkqVSpknz66aearn/Xrl3SrVs3URRFbr/9dtmwYYOm6y8Kp9MpqampUq9ePbFarfLII4/I+fPni7tYho3CUmv72O12SU1Nla5du4rZbJakpCS566675Pvvvw+LA5HROBwO+e677+Suu+6SpKQkMZvN0rVrV0lLSxO73a5bXUuWLJGqVatKqVKl5N133w3L13b16tXSrFkzsdls8uyzz2q6PQ8fPiwjR44Ui8Ui11xzjcyYMUPX4SYtHDt2TB577DFJSEiQ8uXLy5QpUzR9zocPH/YeMEePHi0nTpzQbN2hkpmZKXfeeacoiiLdu3eXw4cPq7o+u90uEydOFJvNJtddd52sXbtW1fWpweFwyDvvvCOlSpWSatWqydKlS4uzOIaNwgr19snIyJAXX3xRqlSpImazWXr16iVz5swxTLdpJLh8+bKkpaVJz549xWQySdWqVeWll17SdKd59epVeeyxx0RRFBkyZIhkZGRotm41OBwOmTp1qsTFxUmbNm3kwIEDqq7vypUr8n//93+SkJAgtWvXlv/9739hGdSK48SJE/L0009LXFyc1KtXT5YtW6b6OufPny9ly5aVevXqyapVq1Rfn9p+/vlnqVevnpQtW1a14cD9+/dL69atJT4+Xt58882wb6fHjx+XwYMHi6Io8sQTTxQ16DJsFFaots+xY8fkoYcekpiYGClTpow88cQTsn///uIXSAHt27dPHn/8cSldurTExsbKuHHjVO8OPnHihLRu3VoSExPlk08+UXVdWtu6das0atRISpcuLT/++KMq61i/fr3UrVtXEhIS5P/+7//kypUrqqwnXOzfv1/69OkjAGTo0KGqzPNxuVzy9NNPi6Iocvfdd4flxGt/Ll68KHfddZcoiiLPPPNMSCcPL1++XEqVKiWNGzfWdZhRDTNmzJCEhARp06ZNUSYCM2wUVnG3z6lTp+TJJ5+U+Ph4qVy5srz33nuSlZUVwgopGFlZWfLOO+9IpUqVJCEhQZ5++mk5ffp0yNezb98+qVu3rtSqVUt27NgR8uUbwaVLl2TgwIESExMjqampIVuu0+mUl19+WaxWq3Tt2lX1ru9ws3jxYqlYsaLUqFEjpL0O2dnZMnLkSLFarTJjxoyQLddoPvroI7FarXLnnXeGZChw5syZEhMTI4MHD47Ynunt27dLjRo1pF69eoX9cMywUVjF2T6pqalSrlw5KVu2rEyePFkuXboU4uqosK5evSrTpk2T5ORkKV26tEybNi1kn3T2798vlStXluuvv16OHTsWkmUaldPplHHjxonJZJIvv/yy2MvLysqSXr16ic1mk1dffdWwp67qLTMzU26//XaxWCzywQcfFHt5DodD+vXrJyVKlNBkmEZvixcvlsTERBkwYECxzg75/PPPxWQyyYQJEwx3lkmoHT16VK677jqpUqWKHDx4MNg/Y9gorKJsn71790qXLl3EZDKFamYvhdi5c+fkoYceEpPJJN26dZN9+/YVa3mZmZlSt25dadasmeFOZ1XT448/LjabrViTyTIyMqRFixZSrlw5Wb16dQiri0wul0v+/e9/i6Io8s9//rNYwcxz9svPP/8cwgqNbeXKlRIbGytjx44t0t8vWrRIrFarPPnkkyGuzLjOnDkjTZo0kXr16gU7941ho7AKu30WLlwoSUlJ0qhRI1mzZo2KlVEorF69Who2bCiJiYny1VdfFWkZDodD2rdvL7Vr1474Ho28XC6XjBo1SkqUKFGkc/RPnTolDRo0kNq1a6txYaGI9vHHH4vVapXx48cX6e9ff/11sVgshrqOilbmzZsnZrNZ3nrrrUL93Z9//ikJCQkyevToqOt9O3r0qNSsWVNuuummYCbBMmwUVrDbx+VyyaRJk0RRFBk7dqxcvXpVg+ooFK5evSr33XefKIoi//73vwu9E3nuueckJiZGfv/9d5UqNLbs7Gxp2bKl3HDDDYVq95cuXZJ27dpJlSpV5NChQypWGLlmzZolJpNJXnnllUL93ZYtWyQ2NlZeeukllSozvueff15iYmJk06ZNQT0+OztbWrRoIc2bN4/a/fuWLVskLi5OXnzxxYIeyrBRWMFsH6fTKSNHjhSbzSbTpk3TqDIKtffff19sNpvceeedQQeOTZs2icVikbffflvl6oxt165dkpiYKJMmTQr6b4YPHy5lypTR9fLNkWDq1KmiKIosXLgwqMfb7XZp1KiRdOrUKeLnGwTicDikY8eO0qRJk6BOV504caKUKFHC8FcqVtubb74pFotFNm/eHOhhDBuFFcz2eeCBByQmJiYqJlhFuqVLl0pMTIw8/PDDQT2+a9eu0rJly6jrUvXl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bdokFotF3nzzTZWrM7adO3dKYmJiob46fsSIEVKmTBnZtm2bipVFvjfeeEMURZFvvvkmqMc7HA5p1KiRdOzYURwOh8rVGZfD4ZD27dtLkyZNgtoOEydOlBIlSsju3bs1qM643njjDbFYLLJ58+ZAD2PYKKxgt4/L5ZLnnntOFEWR++67L2CXHBnLlStX5J577hGTySTPP/98oXsp/v3vf0tMTIzqXxRlVNnZ2dKiRQu54YYbCtXuL126JO3atZNq1aqpejpmJPvyyy/FZDIV+gyXLVu2SGxsbMi/FC+cPPfccxITEyObNm0K6vFFbeeR5I8//gi23TBsFFZht88333wjSUlJcs0116jyHRAUWr/88os0aNBASpQoIXPnzi3SMhwOh3To0EFq1aoV1BBCJHG5XDJ8+PAif+I7ffq0NGrUSGrVqiV//vmnChVGro8++kgsFku+q14Ga+rUqWI2m2X+/Pkhrsz4vvrqKzGbzfLOO+8U6u88PXh33nln1A2dHjlyRGrUqCGdOnUK5gxMho3CKsr22bdvn3Tv3l1MJpM89NBDcu7cOZWqo6I6e/asPPDAA2IymeTWW2+VAwcOFGt5J06ckHr16knTpk01+bItoxg/frzYbDb59ttvi7yMEydOSKtWraRs2bKyatWqEFYXmVwul0yaNEkURZFnn322WAe9MWPGSFxcXFRNcv7xxx8lNjbW38WoCrRkyRKxWq3yxBNPhLgy4zp9+rQ0btxY6tevH+w1chg2Cqs42yc1NVXKly8vZcqUkcmTJ6t6ASgKTlZWlkydOlUqVKggpUuXlmnTpoVs2QcOHJAqVarIddddF/E9HE6nUx566CExmUwh+cr4rKws6d27t9hsNvnPf/7Da9f4kZGRIbfddptYLBb58MMPi708h8Mh/fv3l8TERFmyZEkIKjS2b775RhISEmTgwIHFamNffPGFmEwmefTRRyO+rR45ckSaNm0qVatWlUOHDgX7ZwwbhVXc7XP69Gn55z//KYmJiVKxYkV5++23fV6ljtR18eJFefPNNyUlJUUSExPlmWeekTNnzoR8Pfv375drr71WatSoEbETH7OysqRfv34SGxsraWlpIVuu0+mU//znP2Kz2eTmm29W/aqR4WbhwoWSnJwsNWvWlF9++SVky83OzpY777xTrFar34uxRYLp06eLxWKR0aNH+7xQ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\n", "text/plain": [""]}, "execution_count": 17, "metadata": {}, "output_type": "execute_result"}], "source": ["img = Image.open(\"graph2.dot.png\")\n", "img"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## With javascript"]}, {"cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [{"data": {"text/html": ["
\n", ""], "text/plain": [""]}, "execution_count": 18, "metadata": {}, "output_type": "execute_result"}], "source": ["from jyquickhelper import RenderJsDot\n", "RenderJsDot(dot)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Example with FeatureUnion"]}, {"cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [{"data": {"text/html": ["
\n", ""], "text/plain": [""]}, "execution_count": 19, "metadata": {}, "output_type": "execute_result"}], "source": ["from sklearn.pipeline import FeatureUnion\n", "from sklearn.preprocessing import MinMaxScaler, PolynomialFeatures\n", "\n", "model = Pipeline([('poly', PolynomialFeatures()),\n", " ('union', FeatureUnion([\n", " ('scaler2', MinMaxScaler()),\n", " ('scaler3', StandardScaler())]))])\n", "dot = pipeline2dot(model, columns)\n", "RenderJsDot(dot)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["## Compute intermediate outputs\n", "\n", "It is difficult to access intermediate outputs with *scikit-learn* but it may be interesting to do so. The method [alter_pipeline_for_debugging](find://alter_pipeline_for_debugging) modifies the pipeline to intercept intermediate outputs."]}, {"cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [{"data": {"text/plain": ["Pipeline(steps=[('scaler1', StandardScaler()),\n", " ('union',\n", " FeatureUnion(transformer_list=[('scaler2', StandardScaler()),\n", " ('scaler3', MinMaxScaler())])),\n", " ('lr', LinearRegression())])"]}, "execution_count": 20, "metadata": {}, "output_type": "execute_result"}], "source": ["from numpy.random import randn\n", "\n", "model = Pipeline([('scaler1', StandardScaler()),\n", " ('union', FeatureUnion([\n", " ('scaler2', StandardScaler()),\n", " ('scaler3', MinMaxScaler())])),\n", " ('lr', LinearRegression())])\n", "\n", "X = randn(4, 5)\n", "y = randn(4)\n", "model.fit(X, y)"]}, {"cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Pipeline\n", " StandardScaler\n", " FeatureUnion\n", " StandardScaler\n", " MinMaxScaler\n", " LinearRegression\n"]}], "source": ["print(pipeline2str(model))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Let's now modify the pipeline to get the intermediate outputs."]}, {"cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": ["from mlinsights.helpers.pipeline import alter_pipeline_for_debugging\n", "alter_pipeline_for_debugging(model)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["The function adds a member ``_debug`` which stores inputs and outputs in every piece of the pipeline."]}, {"cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [{"data": {"text/plain": ["BaseEstimatorDebugInformation(StandardScaler)"]}, "execution_count": 23, "metadata": {}, "output_type": "execute_result"}], "source": ["model.steps[0][1]._debug"]}, {"cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([ 0.73619378, 0.87936142, -0.56528874, -0.2675163 ])"]}, "execution_count": 24, "metadata": {}, "output_type": "execute_result"}], "source": ["model.predict(X)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["The member was populated with inputs and outputs."]}, {"cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [{"data": {"text/plain": ["BaseEstimatorDebugInformation(StandardScaler)\n", " transform(\n", " shape=(4, 5) type=\n", " [[ 1.22836841 2.35164607 -0.37367786 0.61490475 -0.45377634]\n", " [-0.77187962 0.43540786 0.20465106 0.8910651 -0.23104796]\n", " [-0.36750208 0.35154324 1.78609517 -1.59325463 1.51595267]\n", " [ 1.37547609 1.59470748 -0.5932628 0.57822003 0.56034736]]\n", " ) -> (\n", " shape=(4, 5) type=\n", " [[ 0.90946066 1.4000516 -0.67682808 0.49311806 -1.03765861]\n", " [-1.20030006 -0.89626498 -0.05514595 0.76980985 -0.7493565 ]\n", " [-0.77378303 -0.99676381 1.64484777 -1.71929067 1.51198065]\n", " [ 1.06462242 0.49297719 -0.91287374 0.45636275 0.27503446]]\n", " )"]}, "execution_count": 25, "metadata": {}, "output_type": "execute_result"}], "source": ["model.steps[0][1]._debug"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Every piece behaves the same way."]}, {"cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["(0,)\n", "BaseEstimatorDebugInformation(Pipeline)\n", " predict(\n", " shape=(4, 5) type=\n", " [[ 1.22836841 2.35164607 -0.37367786 0.61490475 -0.45377634]\n", " [-0.77187962 0.43540786 0.20465106 0.8910651 -0.23104796]\n", " [-0.36750208 0.35154324 1.78609517 -1.59325463 1.51595267]\n", " [ 1.37547609 1.59470748 -0.5932628 0.57822003 0.56034736]]\n", " ) -> (\n", " shape=(4,) type=\n", " [ 0.73619378 0.87936142 -0.56528874 -0.2675163 ]\n", " )\n", "(0, 0)\n", "BaseEstimatorDebugInformation(StandardScaler)\n", " transform(\n", " shape=(4, 5) type=\n", " [[ 1.22836841 2.35164607 -0.37367786 0.61490475 -0.45377634]\n", " [-0.77187962 0.43540786 0.20465106 0.8910651 -0.23104796]\n", " [-0.36750208 0.35154324 1.78609517 -1.59325463 1.51595267]\n", " [ 1.37547609 1.59470748 -0.5932628 0.57822003 0.56034736]]\n", " ) -> (\n", " shape=(4, 5) type=\n", " [[ 0.90946066 1.4000516 -0.67682808 0.49311806 -1.03765861]\n", " [-1.20030006 -0.89626498 -0.05514595 0.76980985 -0.7493565 ]\n", " [-0.77378303 -0.99676381 1.64484777 -1.71929067 1.51198065]\n", " [ 1.06462242 0.49297719 -0.91287374 0.45636275 0.27503446]]\n", " )\n", "(0, 1)\n", "BaseEstimatorDebugInformation(FeatureUnion)\n", " transform(\n", " shape=(4, 5) type=\n", " [[ 0.90946066 1.4000516 -0.67682808 0.49311806 -1.03765861]\n", " [-1.20030006 -0.89626498 -0.05514595 0.76980985 -0.7493565 ]\n", " [-0.77378303 -0.99676381 1.64484777 -1.71929067 1.51198065]\n", " [ 1.06462242 0.49297719 -0.91287374 0.45636275 0.27503446]]\n", " ) -> (\n", " shape=(4, 10) type=\n", " [[ 0.90946066 1.4000516 -0.67682808 0.49311806 -1.03765861 0.93149357\n", " 1. 0.09228748 0.88883864 0. ]\n", " [-1.20030006 -0.89626498 -0.05514595 0.76980985 -0.7493565 0.\n", " 0.04193015 0.33534839 1. 0.11307564]\n", " [-0.77378303 -0.99676381 1.64484777 -1.71929067 1.51198065 0.18831419\n", " ...\n", " )\n", "(0, 1, 0)\n", "BaseEstimatorDebugInformation(StandardScaler)\n", " transform(\n", " shape=(4, 5) type=\n", " [[ 0.90946066 1.4000516 -0.67682808 0.49311806 -1.03765861]\n", " [-1.20030006 -0.89626498 -0.05514595 0.76980985 -0.7493565 ]\n", " [-0.77378303 -0.99676381 1.64484777 -1.71929067 1.51198065]\n", " [ 1.06462242 0.49297719 -0.91287374 0.45636275 0.27503446]]\n", " ) -> (\n", " shape=(4, 5) type=\n", " [[ 0.90946066 1.4000516 -0.67682808 0.49311806 -1.03765861]\n", " [-1.20030006 -0.89626498 -0.05514595 0.76980985 -0.7493565 ]\n", " [-0.77378303 -0.99676381 1.64484777 -1.71929067 1.51198065]\n", " [ 1.06462242 0.49297719 -0.91287374 0.45636275 0.27503446]]\n", " )\n", "(0, 1, 1)\n", "BaseEstimatorDebugInformation(MinMaxScaler)\n", " transform(\n", " shape=(4, 5) type=\n", " [[ 0.90946066 1.4000516 -0.67682808 0.49311806 -1.03765861]\n", " [-1.20030006 -0.89626498 -0.05514595 0.76980985 -0.7493565 ]\n", " [-0.77378303 -0.99676381 1.64484777 -1.71929067 1.51198065]\n", " [ 1.06462242 0.49297719 -0.91287374 0.45636275 0.27503446]]\n", " ) -> (\n", " shape=(4, 5) type=\n", " [[0.93149357 1. 0.09228748 0.88883864 0. ]\n", " [0. 0.04193015 0.33534839 1. 0.11307564]\n", " [0.18831419 0. 1. 0. 1. ]\n", " [1. 0.62155016 0. 0.87407214 0.51485443]]\n", " )\n", "(0, 2)\n", "BaseEstimatorDebugInformation(LinearRegression)\n", " predict(\n", " shape=(4, 10) type=\n", " [[ 0.90946066 1.4000516 -0.67682808 0.49311806 -1.03765861 0.93149357\n", " 1. 0.09228748 0.88883864 0. ]\n", " [-1.20030006 -0.89626498 -0.05514595 0.76980985 -0.7493565 0.\n", " 0.04193015 0.33534839 1. 0.11307564]\n", " [-0.77378303 -0.99676381 1.64484777 -1.71929067 1.51198065 0.18831419\n", " ...\n", " ) -> (\n", " shape=(4,) type=\n", " [ 0.73619378 0.87936142 -0.56528874 -0.2675163 ]\n", " )\n"]}], "source": ["from mlinsights.helpers.pipeline import enumerate_pipeline_models\n", "for coor, model, vars in enumerate_pipeline_models(model):\n", " print(coor)\n", " print(model._debug)"]}, {"cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": []}], "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.9.5"}}, "nbformat": 4, "nbformat_minor": 2}