{ "cells": [ { "attachments": {}, "cell_type": "markdown", "id": "6900b762", "metadata": {}, "source": [ "# Basic ML example (Ray Workflow)\n", "\n", "Here, we use different backend (RayWorkflow) for Basic ML example. Again, we reimplement sklearn [example](https://scikit-learn.org/stable/auto_examples/linear_model/plot_sparse_logistic_regression_mnist.html#sphx-glr-auto-examples-linear-model-plot-sparse-logistic-regression-mnist-py) as Function Fuse workflow . \n", "\n", "## The workflow" ] }, { "cell_type": "code", "execution_count": 1, "id": "c843a80a", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "2023-05-15 08:03:01,762\tINFO worker.py:1538 -- Started a local Ray instance.\n" ] } ], "source": [ "from functionfuse import workflow\n", "from functionfuse.backends.builtin.rayback import RayWorkflow\n", "from functionfuse.storage import storage_factory\n", "\n", "import os\n", "\n", "\n", "# Frontend\n", "\n", "@workflow\n", "def openml_dataset():\n", " from sklearn.datasets import fetch_openml\n", " from sklearn.utils import check_random_state\n", "\n", " X, y = fetch_openml(\"mnist_784\", version=1, return_X_y=True, as_frame=False, parser=\"pandas\")\n", " random_state = check_random_state(0)\n", " permutation = random_state.permutation(X.shape[0])\n", " X = X[permutation]\n", " y = y[permutation]\n", " X = X.reshape((X.shape[0], -1))\n", " return X, y\n", "\n", "\n", "@workflow\n", "def train_test_split(X, y, train_samples, test_size):\n", " from sklearn.model_selection import train_test_split\n", "\n", " X_train, X_test, y_train, y_test = train_test_split(\n", " X, y, train_size=train_samples, test_size=test_size\n", " )\n", " return {\"X_train\": X_train, \"X_test\": X_test, \"y_train\": y_train, \"y_test\": y_test}\n", "\n", "@workflow\n", "def train_model(X, y):\n", " from sklearn.preprocessing import StandardScaler\n", " from sklearn.pipeline import make_pipeline\n", " from sklearn.linear_model import LogisticRegression\n", "\n", " train_samples = len(X)\n", " clf = make_pipeline(StandardScaler(), LogisticRegression(C=50.0 / train_samples, penalty=\"l1\", solver=\"saga\", tol=0.1))\n", " clf.fit(X, y)\n", " return clf\n", "\n", "\n", "dataset = openml_dataset().set_name(\"dataset\")\n", "X, y = dataset[0], dataset[1]\n", "dataset_split = train_test_split(X, y, train_samples = 5000, test_size = 10000).set_name(\"dataset_split\")\n", "model = train_model(dataset_split[\"X_train\"], dataset_split[\"y_train\"]).set_name(\"model\")\n", "\n", "\n", "# Ray Backend\n", "\n", "ray_init_args = {\n", " \"resources\": {\"_disk\": 1.0, \"_model\": 1}\n", "}\n", "\n", "ray_storage_remote_args = {\n", " \"resources\": {\"_disk\": 0.001}\n", "}\n", "\n", "ray_workflow = RayWorkflow(dataset, workflow_name=\"classifier\", ray_init_args=ray_init_args)\n", "\n", "# Ray init is called in the RayWorkflow constructor!!! Storage should be created AFTER RayWorkflow is created. \n", "storage_path = os.path.join(os.getcwd(), \"storage\")\n", "opt = {\n", " \"kind\": \"ray\",\n", " \"options\": {\n", " \"remoteArgs\": ray_storage_remote_args,\n", " \"path\": storage_path,\n", " }\n", "}\n", "\n", "storage = storage_factory(opt)\n", "ray_workflow.set_storage(storage)\n", "ray_workflow.query(pattern=\"^model$\").set_remote_args({\"num_cpus\": 1, \"resources\": {\"_model\": 1}})\n", "\n", "_ = ray_workflow.run()" ] }, { "attachments": {}, "cell_type": "markdown", "id": "f2d7826a", "metadata": {}, "source": [ "## Model Prediction\n", "\n", "The code above is designed to train models and save the workflow data in the storage. Typically this code should be placed in a separate Python module. We placed the workflow code in the Jupyter Notebook for demonstration purposes only. Final model analysis and data visualization are performed from the stored data in the Jupyter Notebook as shown below." ] }, { "cell_type": "code", "execution_count": 2, "id": "225a0031", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "2023-05-15 08:03:16,941\tINFO worker.py:1538 -- Started a local Ray instance.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "All graph node names: \n", "['dataset', 'dataset_split', 'model']\n" ] } ], "source": [ "import pprint, os\n", "from functionfuse.storage import storage_factory\n", "\n", "the_workflow_name = \"classifier\"\n", "\n", "\n", "ray_init_args = {\n", " \"resources\": {\"_disk\": 1}\n", "}\n", "\n", "remote_args = {\n", " \"resources\": {\"_disk\": 0.001}\n", "}\n", "\n", "storage_path = os.path.join(os.getcwd(), \"storage\")\n", "opt = {\n", " \"kind\": \"ray\",\n", " \"options\": {\n", " \"rayInitArgs\": ray_init_args,\n", " \"remoteArgs\": remote_args,\n", " \"path\": storage_path,\n", " }\n", "}\n", "\n", "storage = storage_factory(opt)\n", "all_tasks = storage.list_tasks(workflow_name=the_workflow_name, pattern=\"*\")\n", "\n", "pp = pprint.PrettyPrinter(width=141, compact=True)\n", "print(\"All graph node names: \")\n", "pp.pprint(all_tasks)" ] }, { "cell_type": "code", "execution_count": 3, "id": "5e8dcb15", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Sparsity with L1 penalty: 76.62%\n", "Test score with L1 penalty: 0.8333\n" ] }, { "data": { "text/plain": [ "Text(0.5, 0.98, 'Classification vector for...')" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAx8AAAHKCAYAAABiyPYQAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAA9hAAAPYQGoP6dpAABRVklEQVR4nO3de3hU5dXw/zWZzEwyyeRASAjhkHAWEBAPCKgFqiCIeHi0lWoVFGgfBfva12rt0yo+1tZqrVZtsU/9qWjtq/3ZerZqbZHaekAs4InzISBnAknIeZKZ+/3Dl9Q96waGkOyZSb6f6/K63Cv3zOzJrOyZxey1l8cYYwQAAAAAOlhaoncAAAAAQNdA8QEAAADAFRQfAAAAAFxB8QEAAADAFRQfAAAAAFxB8QEAAADAFRQfAAAAAFxB8QEAAADAFRQfAAAAAFxB8QEgIT7++GO5+uqrpV+/fpKRkSHZ2dly8sknyz333CMHDhxoXTdx4kSZOHFiwvZz6dKl4vF4ZOnSpY74Qw89JAMHDhS/3y8ej0eqqqpk9uzZUlZW1mH78uc//1luv/1268/Kyspk9uzZHfbYyaS+vl5uv/129Zp0tJUrV8qECRMkNzdXPB6P/PKXv3T18QGgM/AYY0yidwJA1/LII4/IddddJ0OGDJHrrrtOhg0bJs3NzfLhhx/KI488IqNGjZLnn39eRKS18HD7g+YhBw8elNWrV8uwYcMkJydHRERWrVolo0ePlrlz58qsWbMkPT1dTjvtNCkvL5eDBw/K6NGjO2RfFixYIL/+9a/FdtheuXKl5OTkyIABAzrksZNJRUWFFBYWysKFCw9bjHWE0aNHS11dnTzwwAOSn58vZWVlUlxc7NrjA0BnkJ7oHQDQtbz33nty7bXXyuTJk+WFF16QQCDQ+rPJkyfLjTfeKK+//noC99ApJydHxo4d64h99tlnIiIyb948GTNmTGs8kR/8O6rg6UoaGhokIyNDPB6P9eeffvqpzJs3T6ZNm9Yuj9fc3Cwej0fS03krBtB1cNoVAFf99Kc/FY/HI7/97W8dhcchfr9fLrjggiPex3//93/L6aefLt26dZOcnBw5+eST5dFHH1XfCCxZskQmTpwoBQUFkpmZKX379pVLLrlE6uvrW9c8/PDDMmrUKMnOzpZQKCQnnHCC/Nd//Vfrz2NPu5o4caJ885vfFBGR008/XTweT+vpTrbTrqLRqDz00ENy0kknSWZmpuTl5cnYsWPlpZdeal3zhz/8QaZMmSI9e/aUzMxMGTp0qNxyyy1SV1fXumb27Nny61//WkREPB5P63/l5eUiYj/tatu2bfLNb35TioqKJBAIyNChQ+UXv/iFRKPR1jXl5eXi8Xjk3nvvlfvuu0/69esn2dnZMm7cOHn//feP+Dp89NFH4vF45NFHH1U/e+2118Tj8Tie54YNG+Tyyy937M+h5/RlVVVVcuONN0r//v0lEAhIUVGRnHfeebJ27VopLy+XwsJCEfkiDw79Hr783P/5z3/K2WefLaFQSILBoIwfP15effVVx2MsXrxYPB6P/OUvf5FrrrlGCgsLJRgMSlNTk9qfQ2tbWlrk4Ycfbn3MQz799FO58MILJT8/XzIyMuSkk06SJ554wnEfh/Lod7/7ndx4443Sq1cvCQQCsnHjxiP+jgGgs+GfWwC4JhKJyJIlS+SUU06RPn36tPl+ysvL5dvf/rb07dtXRETef/99uf7662XHjh1y2223ta6ZPn26nHXWWfLYY49JXl6e7NixQ15//XUJh8MSDAblmWeekeuuu06uv/56uffeeyUtLU02btwoq1evPuxjL1q0SJ5++mm588475fHHH5cTTjih9cOwzezZs+Wpp56SOXPmyB133CF+v19WrFjRWjSIfPGh/LzzzpMbbrhBsrKyZO3atXL33XfLBx98IEuWLBERkVtvvVXq6urkj3/8o7z33nutt+3Zs6f1cfft2yfjx4+XcDgsP/7xj6WsrExeeeUV+d73viebNm2SRYsWOdb/+te/lhNOOKG1j+HWW2+V8847T7Zs2SK5ubnWxxg1apSMHj1aHn/8cZkzZ47jZ4sXL24tGkREVq9eLePHj5e+ffvKL37xCykuLpY33nhDvvOd70hFRYUsXLhQRERqamrkzDPPlPLycvn+978vp59+utTW1srbb78tu3btkvHjx8vrr78uU6dOlTlz5sjcuXNFRFpfg7///e8yefJkGTlypDz66KMSCARk0aJFMmPGDHn66aflsssuc+znNddcI9OnT5ff/e53UldXJz6fTz3P6dOny3vvvSfjxo2TSy+9VG688cbWn61bt07Gjx8vRUVF8uCDD0pBQYE89dRTMnv2bNmzZ4/cfPPNjvv6wQ9+IOPGjZPf/OY3kpaWJkVFRdbfLQB0WgYAXLJ7924jImbmzJlx32bChAlmwoQJh/15JBIxzc3N5o477jAFBQUmGo0aY4z54x//aETErFq16rC3XbBggcnLyzvi47/11ltGRMxbb73VGnv88ceNiJjly5c71s6aNcuUlpa2br/99ttGRMwPf/jDIz7Gl0WjUdPc3Gz+/ve/GxExH330UevP5s+fbw532C4tLTWzZs1q3b7llluMiJhly5Y51l177bXG4/GYdevWGWOM2bJlixERM2LECNPS0tK67oMPPjAiYp5++ukj7u+DDz5oRKT1/owx5sCBAyYQCJgbb7yxNXbuueea3r17m+rqasftFyxYYDIyMsyBAweMMcbccccdRkTMm2++edjH3LdvnxERs3DhQvWzsWPHmqKiIlNTU9Maa2lpMSeeeKLp3bt3a34ceg2vuuqqIz6/LxMRM3/+fEds5syZJhAImG3btjni06ZNM8Fg0FRVVRlj/p1HX/nKV+J+PADojDjtCkDKWbJkiZxzzjmSm5srXq9XfD6f3HbbbbJ//37Zu3eviIicdNJJ4vf75Vvf+pY88cQTsnnzZnU/Y8aMkaqqKvnGN74hL774olRUVLTrfr722msiIjJ//vwjrtu8ebNcfvnlUlxc3Pp8JkyYICIia9asadNjL1myRIYNG+boSRH54psYY0zrNyqHTJ8+Xbxeb+v2yJEjRURk69atR3ycK664QgKBgCxevLg19vTTT0tTU5NcffXVIiLS2Ngof/vb3+Tiiy+WYDAoLS0trf+dd9550tjY2HqK12uvvSaDBw+Wc84555ifc11dnSxbtkwuvfRSyc7Obo17vV658sorZfv27bJu3TrHbS655JJjfpwvW7JkiZx99tnqm7zZs2dLfX2941uq9ng8AEh1FB8AXNO9e3cJBoOyZcuWNt/HBx98IFOmTBGRL66a9c4778jy5cvlhz/8oYh80TQs8kXz91//+lcpKiqS+fPny4ABA2TAgAHywAMPtN7XlVdeKY899phs3bpVLrnkEikqKpLTTz9d3nzzzeN4lv+2b98+8Xq9R7wiUm1trZx11lmybNkyufPOO2Xp0qWyfPlyee655xzP51jt37/fekpWSUlJ68+/rKCgwLF9qB/naI/frVs3ueCCC+TJJ5+USCQiIl+ccjVmzBgZPnx462O1tLTIQw89JD6fz/HfodOyDhV++/btk969ex/r0xURkcrKSjHGHNPzPtxpa/E61t/z8T4eAKQ6ej4AuMbr9crZZ58tr732mmzfvr1NHzKfeeYZ8fl88sorr0hGRkZr/IUXXlBrzzrrLDnrrLMkEonIhx9+KA899JDccMMN0qNHD5k5c6aIiFx99dVy9dVXS11dnbz99tuycOFCOf/882X9+vVSWlra5ucq8kUfQiQSkd27dx/2Q+eSJUtk586dsnTp0tZvO0S+aLo+HgUFBbJr1y4V37lzp4h8UQi2l6uvvlqeffZZefPNN6Vv376yfPlyefjhh1t/np+f3/rtw+G+BerXr5+IfPE72759e5v2Iz8/X9LS0o7peR/uylbxOtbf8/E+HgCkOr75AOCqH/zgB2KMkXnz5kk4HFY/b25ulpdffvmwtz90adIvnyLU0NAgv/vd7w57G6/XK6effnrrlZVWrFih1mRlZcm0adPkhz/8oYTD4dbL6R6PQ5dk/fIH8ViHPozGXvnrf/7nf9TaeL+NEBE5++yzZfXq1eq5Pvnkk+LxeGTSpElHvY94TZkyRXr16iWPP/64PP7445KRkSHf+MY3Wn8eDAZl0qRJsnLlShk5cqSceuqp6r9D37xMmzZN1q9fr04L+7LD/R6ysrLk9NNPl+eee87xs2g0Kk899ZT07t1bBg8e3G7PW+SL3/OhAvLLnnzySQkGg+oyzQDQ1fHNBwBXjRs3Th5++GG57rrr5JRTTpFrr71Whg8fLs3NzbJy5Ur57W9/KyeeeKLMmDHDevvp06fLfffdJ5dffrl861vfkv3798u9996rPrz/5je/kSVLlsj06dOlb9++0tjYKI899piISGs/wbx58yQzM1POOOMM6dmzp+zevVvuuusuyc3NldNOO+24n+tZZ50lV155pdx5552yZ88eOf/88yUQCMjKlSslGAzK9ddfL+PHj5f8/Hz5z//8T1m4cKH4fD75/e9/Lx999JG6vxEjRoiIyN133y3Tpk0Tr9crI0eOFL/fr9Z+97vflSeffFKmT58ud9xxh5SWlsqrr74qixYtkmuvvbZdP4R7vV656qqr5L777pOcnBz5j//4D3WFrAceeEDOPPNMOeuss+Taa6+VsrIyqampkY0bN8rLL7/cWmzccMMN8oc//EEuvPBCueWWW2TMmDHS0NAgf//73+X888+XSZMmSSgUktLS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Xz/zox/9yITD4dafr1q1ykycONFkZ2ebUChkTj75ZLN8+XJjjDHl5eXm/PPPN3l5eSYYDJphw4aZV1991bp/0WjUFBcXm5/97GetscbGRpObm2t+85vfHOezR6Ile/592ZYtW4yImJUrV7b5+SK5pFL+HfLBBx8YETFbt2499ieMpJOKOVhdXW1ExPz1r3899ieMpJIq+bd9+3bTq1cv8+mnn5rS0lJz//33H9fzTlZd4puPAwcOyOuvvy4/+clPJCsrS/08Ly/vsLcNhUKyePFiKSkpkU8++UTmzZsnoVBIbr75ZhERueKKK2T06NHy8MMPi9frlVWrVonP5xMRkfnz50s4HJa3335bsrKyZPXq1ZKdnW19nC1btsju3btlypQprbFAICATJkyQd999V7797W8fx28AiZQK+YfOK1Xzr7q6WjwezxH3D6khFXMwHA7Lb3/7W8nNzZVRo0Yd+5NG0kiV/ItGo3LllVfKTTfdJMOHDz++J53sEl39uGHZsmVGRMxzzz131LUSU/XGuueee8wpp5zSuh0KhczixYuta0eMGGFuv/32uPbxnXfeMSJiduzY4YjPmzfPTJkyJa77QHJKhfz7Mr756FxSLf+MMaahocGccsop5oorrmjT7ZFcUikHX375ZZOVlWU8Ho8pKSkxH3zwwTHdHsknVfLvpz/9qZk8ebKJRqPGGNOpv/noEg3nxhgR+eIqBsfqj3/8o5x55plSXFws2dnZcuutt8q2bdtaf/6///f/lrlz58o555wjP/vZz2TTpk2tP/vOd74jd955p5xxxhmycOFC+fjjj4/6eLH7aIxp034jeaRS/qHzSbX8a25ulpkzZ0o0GpVFixYd8z4j+aRSDk6aNElWrVol7777rkydOlW+/vWvy969e495v5E8UiH//vWvf8kDDzwgixcv7hKf+bpE8TFo0CDxeDyyZs2aY7rd+++/LzNnzpRp06bJK6+8IitXrpQf/vCHEg6HW9fcfvvt8tlnn8n06dNlyZIlMmzYMHn++edFRGTu3LmyefNmufLKK+WTTz6RU089VR566CHrYxUXF4vIF1dj+LK9e/dKjx49jmm/kVxSIf/QeaVS/jU3N8vXv/512bJli7z55puSk5Nz7E8YSSeVcjArK0sGDhwoY8eOlUcffVTS09Pl0UcfPfYnjaSRCvn3j3/8Q/bu3St9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Xl6e3HPPPTJy5Ehpbm6WN954Q+bPny9r165N9C6KiMjUqVPl8ccfb932+/0J3Bu0l1TIv02bNsmZZ54pc+bMkf/+7/+W3NxcWbNmjWRkZCR613CcUiH/du3a5dh+7bXXZM6cOXLJJZckaI/QnlIhB7/73e/KW2+9JU899ZSUlZXJX/7yF7nuuuukpKRELrzwwkTvHo5DsuefMUYuuugi8fl88uKLL0pOTo7cd999cs4558jq1aslKysrofvX7kwXMW3aNNOrVy9TW1urflZZWdn6/yJinn/++dbtm2++2QwaNMhkZmaafv36mR/96EcmHA63/nzVqlVm4sSJJjs724RCIXPyySeb5cuXG2OMKS8vN+eff77Jy8szwWDQDBs2zLz66quH3cdZs2aZCy+88LifK5JPKuTfZZddZr75zW8e/5NF0kmF/It14YUXmq9+9avH/mSRlFIhB4cPH27uuOMOR+zkk082P/rRj9r4rJEskj3/1q1bZ0TEfPrpp62xlpYW061bN/PII48c57NPPl3im48DBw7I66+/Lj/5yU+s1WNeXt5hbxsKhWTx4sVSUlIin3zyicybN09CoZDcfPPNIiJyxRVXyOjRo+Xhhx8Wr9crq1atEp/PJyIi8+fPl3A4LG+//bZkZWXJ6tWrJTs7+4j7unTpUikqKpK8vDyZMGGC/OQnP5GioqK2P3kkXCrkXzQalVdffVVuvvlmOffcc2XlypXSr18/+cEPfiAXXXTRcf8OkDipkH+x9uzZI6+++qo88cQTx/6EkXRSJQfPPPNMeemll+Saa66RkpISWbp0qaxfv14eeOCB4/sFIKFSIf+amppERBxnGni9XvH7/fLPf/5T5s6d29ann5wSXf24YdmyZUZEzHPPPXfUtRJT9ca65557zCmnnNK6HQqFzOLFi61rR4wYYW6//fa49/OZZ54xr7zyivnkk0/MSy+9ZEaNGmWGDx9uGhsb474PJJ9UyL9du3YZETHBYNDcd999ZuXKleauu+4yHo/HLF26NK77QHJKhfyLdffdd5v8/HzT0NDQptsjuaRKDjY1NZmrrrrKiIhJT083fr/fPPnkk3HfHskpFfIvHA6b0tJS87Wvfc0cOHDANDU1mbvuusuIiJkyZUpc95FKukTx8f777x81oQ6JXffss8+aM844w/To0cNkZWWZQCBgCgsLW3++cOFCk56ebs4++2xz1113mY0bN7b+7JFHHjHp6elm/Pjx5rbbbjMfffTRMe33zp07jc/nM3/605+O6XZILqmQfzt27DAiYr7xjW844jNmzDAzZ86M/8ki6aRC/sUaMmSIWbBgQdzrkdxSJQd//vOfm8GDB5uXXnrJfPTRR+ahhx4y2dnZ5s033zzm54zkkSr59+GHH5pRo0YZETFer9ece+65Ztq0aWbatGnH/JyTXZcoPvbv3288Ho/56U9/etS1X0689957z3i9XnPnnXea5cuXm/Xr15s77rjD5ObmOm6zbt06c99995nJkycbv9/vqK63bdtmHn74YXPxxRcbn89nHnzwwWPa94EDB5qf/exnx3QbJJdUyL+mpiaTnp5ufvzjHzviN998sxk/fvyxPWEklVTIvy97++23jYiYVatWHdPzRPJKhRysr683Pp/PvPLKK474nDlzzLnnnntsTxhJJRXy78uqqqrM3r17jTHGjBkzxlx33XXxP9kU0SWKD2OMmTp16jE3G917772mf//+jrVz5sxRifdlM2fONDNmzLD+7JZbbjEjRoyIe58rKipMIBAwTzzxRNy3QXJKhfwbN26caji/6KKL1LchSD2pkH+HzJo1y3FaAzqHZM/B6upqIyLmz3/+syP+rW99y0yePPmwj4fUkOz5Z7N+/XqTlpZm3njjjbhvkyq6zJyPRYsWSSQSkTFjxsif/vQn2bBhg6xZs0YefPBBGTdunPU2AwcOlG3btskzzzwjmzZtkgcffFCef/751p83NDTIggULZOnSpbJ161Z55513ZPny5TJ06FAREbnhhhvkjTfekC1btsiKFStkyZIlrT+LVVtbK9/73vfkvffek/Lyclm6dKnMmDFDunfvLhdffHH7/0LgqmTPPxGRm266Sf7whz/II488Ihs3bpRf/epX8vLLL8t1113Xvr8MuC4V8k9E5ODBg/Lss892vuZKJH0O5uTkyIQJE+Smm26SpUuXypYtW2Tx4sXy5JNP8h7cCSR7/omIPPvss7J06VLZvHmzvPjiizJ58mS56KKLZMqUKe37y0gGia5+3LRz504zf/58U1paavx+v+nVq5e54IILzFtvvdW6RmLO97vppptMQUGByc7ONpdddpm5//77W6vepqYmM3PmTNOnTx/j9/tNSUmJWbBgQWuT5IIFC8yAAQNazxG88sorTUVFhXXf6uvrzZQpU0xhYaHx+Xymb9++ZtasWWbbtm0d9euAy5I5/w559NFHzcCBA01GRoYZNWqUeeGFF9r714AESYX8+5//+R+TmZlpqqqq2vvpIwkkew7u2rXLzJ4925SUlJiMjAwzZMgQ84tf/MJEo9GO+HXAZcmefw888IDp3bt362fAH/3oR6apqakjfhUJ5zHGmIRWPwAAAAC6hC5z2hUAAACAxKL4AAAAAOAKig8AAAAArqD4AAAAAOAKig8AAAAArqD4AAAAAOAKig8AAAAArqD4AAAAAOAKig8AAAAArqD4AAAAAOAKig8AAAAArvi/Ox63pMjRtZkAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "clf = storage.read_task(workflow_name=the_workflow_name, task_name=\"model\")\n", "dataset_split = storage.read_task(workflow_name=the_workflow_name, task_name=\"dataset_split\")\n", "X_test, y_test = dataset_split[\"X_test\"], dataset_split[\"y_test\"]\n", "\n", "lr = clf.named_steps[\"logisticregression\"]\n", "\n", "sparsity = np.mean(lr.coef_ == 0) * 100\n", "score = clf.score(X_test, y_test)\n", "# print('Best C % .4f' % clf.C_)\n", "print(\"Sparsity with L1 penalty: %.2f%%\" % sparsity)\n", "print(\"Test score with L1 penalty: %.4f\" % score)\n", " \n", "\n", "\n", "coef = lr.coef_.copy()\n", "plt.figure(figsize=(10, 5))\n", "scale = np.abs(coef).max()\n", "for i in range(10):\n", " l1_plot = plt.subplot(2, 5, i + 1)\n", " l1_plot.imshow(\n", " coef[i].reshape(28, 28),\n", " interpolation=\"nearest\",\n", " cmap=plt.cm.RdBu,\n", " vmin=-scale,\n", " vmax=scale,\n", " )\n", " l1_plot.set_xticks(())\n", " l1_plot.set_yticks(())\n", " l1_plot.set_xlabel(\"Class %i\" % i)\n", "plt.suptitle(\"Classification vector for...\")" ] } ], "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.9.15" } }, "nbformat": 4, "nbformat_minor": 5 }