diff --git a/Deployment/Linear_regression/requirements.txt b/Deployment/Linear_regression/requirements.txt index 042b208..8cfc955 100644 --- a/Deployment/Linear_regression/requirements.txt +++ b/Deployment/Linear_regression/requirements.txt @@ -1,4 +1,4 @@ -numpy==1.16.3 +numpy==1.22.0 pandas==0.24.2 requests==2.22.0 scikit-learn==0.21.2 diff --git a/Deployment/rnn_app/requirements.txt b/Deployment/rnn_app/requirements.txt index 4fb6913..cee79e4 100644 --- a/Deployment/rnn_app/requirements.txt +++ b/Deployment/rnn_app/requirements.txt @@ -1,4 +1,4 @@ -numpy==1.16.3 +numpy==1.22.0 pandas==0.24.2 requests==2.22.0 diff --git a/README.md b/README.md index bc4c537..077c9d7 100644 --- a/README.md +++ b/README.md @@ -148,4 +148,11 @@ See my articles on Medium on this topic. ### Unit testing ML code with Pytest Check the files and detailed instructions in the [Pytest](https://github.com/tirthajyoti/Machine-Learning-with-Python/tree/master/Pytest) directory to understand how one should write unit testing code/module for machine learning models +--- + +### Memory and timing profiling + +Profiling data science code and ML models for memory footprint and computing time is a critical but often overlooed area. Here are a couple of Notebooks showing the ideas, +* [Memory profling using Scalene](https://github.com/tirthajyoti/Machine-Learning-with-Python/tree/master/Memory-profiling/Scalene) +* [Time-profiling data science code](https://github.com/tirthajyoti/Machine-Learning-with-Python/blob/master/Time-profiling/cProfile.ipynb) diff --git a/Time-profiling/8.14 - profile-DS-workflow.png b/Time-profiling/8.14 - profile-DS-workflow.png new file mode 100644 index 0000000..e17e393 Binary files /dev/null and b/Time-profiling/8.14 - profile-DS-workflow.png differ diff --git a/Time-profiling/Readme.md b/Time-profiling/Readme.md new file mode 100644 index 0000000..6d6d34d --- /dev/null +++ b/Time-profiling/Readme.md @@ -0,0 +1 @@ +## Time-profiling ML code diff --git a/Time-profiling/cProfile.ipynb b/Time-profiling/cProfile.ipynb new file mode 100644 index 0000000..a65359a --- /dev/null +++ b/Time-profiling/cProfile.ipynb @@ -0,0 +1,513 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "fc118b68-6776-4504-ace0-0e076616fde6", + "metadata": {}, + "source": [ + "# Time-profiling Data Science code using `cProfile`\n", + "\n", + "## Dr. Tirthajyoti Sarkar\n", + "\n", + "---" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "98192c2c-f90d-4a7b-b3b2-ae23870d6f4e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 3 function calls in 0.064 seconds\n", + "\n", + " Ordered by: standard name\n", + "\n", + " ncalls tottime percall cumtime percall filename:lineno(function)\n", + " 1 0.064 0.064 0.064 0.064 :1()\n", + " 1 0.000 0.000 0.064 0.064 {built-in method builtins.exec}\n", + " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", + "\n", + "\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "import cProfile\n", + "\n", + "SIZE = 10_000_000\n", + "a = np.arange(SIZE)\n", + "b = np.random.normal(size=SIZE)\n", + "\n", + "cProfile.run('a+b')" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9500a64c-9d19-4e05-97cf-231ab5f1842e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SIZE = 10_000_000\n", + "a = np.arange(SIZE)\n", + "b = np.random.normal(size=SIZE)\n", + "a+b\n" + ] + } + ], + "source": [ + "code = \"\"\"SIZE = 10_000_000\n", + "a = np.arange(SIZE)\n", + "b = np.random.normal(size=SIZE)\n", + "a+b\"\"\"\n", + "\n", + "print(code)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "6d810ec6-245d-4082-8f64-d5c16b162936", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 5 function calls in 0.488 seconds\n", + "\n", + " Ordered by: standard name\n", + "\n", + " ncalls tottime percall cumtime percall filename:lineno(function)\n", + " 1 0.078 0.078 0.488 0.488 :1()\n", + " 1 0.000 0.000 0.488 0.488 {built-in method builtins.exec}\n", + " 1 0.028 0.028 0.028 0.028 {built-in method numpy.arange}\n", + " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", + " 1 0.381 0.381 0.381 0.381 {method 'normal' of 'numpy.random.mtrand.RandomState' objects}\n", + "\n", + "\n" + ] + } + ], + "source": [ + "cProfile.run(code)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "8a5bf74f-fa5b-4997-a864-82a6d9503216", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 6 function calls in 0.531 seconds\n", + "\n", + " Ordered by: standard name\n", + "\n", + " ncalls tottime percall cumtime percall filename:lineno(function)\n", + " 1 0.055 0.055 0.510 0.510 1735574101.py:1(add)\n", + " 1 0.021 0.021 0.531 0.531 :1()\n", + " 1 0.000 0.000 0.531 0.531 {built-in method builtins.exec}\n", + " 1 0.057 0.057 0.057 0.057 {built-in method numpy.arange}\n", + " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", + " 1 0.397 0.397 0.397 0.397 {method 'normal' of 'numpy.random.mtrand.RandomState' objects}\n", + "\n", + "\n" + ] + } + ], + "source": [ + "def add():\n", + " SIZE = 10_000_000\n", + " a = np.arange(SIZE)\n", + " b = np.random.normal(size=SIZE)\n", + " c=a+b\n", + "\n", + "cProfile.run('add()')" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "f899c334-2d1f-41ef-aceb-71348e5a6818", + "metadata": {}, + "outputs": [], + "source": [ + "def add(size):\n", + " a = np.arange(size)\n", + " b = np.random.normal(size=size)\n", + " c=a+b" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cdf8d43a-f240-4de6-9a92-6dee6c1cd98b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 6 function calls in 0.500 seconds\n", + "\n", + " Ordered by: standard name\n", + "\n", + " ncalls tottime percall cumtime percall filename:lineno(function)\n", + " 1 0.054 0.054 0.478 0.478 1565836920.py:1(add)\n", + " 1 0.021 0.021 0.500 0.500 :1()\n", + " 1 0.000 0.000 0.500 0.500 {built-in method builtins.exec}\n", + " 1 0.030 0.030 0.030 0.030 {built-in method numpy.arange}\n", + " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", + " 1 0.394 0.394 0.394 0.394 {method 'normal' of 'numpy.random.mtrand.RandomState' objects}\n", + "\n", + "\n" + ] + } + ], + "source": [ + "SIZE = 10_000_000\n", + "cProfile.run('add(SIZE)')" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "8d038a76-4394-472b-9949-be9ef936dca5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 6 function calls in 1.034 seconds\n", + "\n", + " Ordered by: standard name\n", + "\n", + " ncalls tottime percall cumtime percall filename:lineno(function)\n", + " 1 0.114 0.114 0.987 0.987 1565836920.py:1(add)\n", + " 1 0.047 0.047 1.034 1.034 :1()\n", + " 1 0.000 0.000 1.034 1.034 {built-in method builtins.exec}\n", + " 1 0.082 0.082 0.082 0.082 {built-in method numpy.arange}\n", + " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", + " 1 0.791 0.791 0.791 0.791 {method 'normal' of 'numpy.random.mtrand.RandomState' objects}\n", + "\n", + "\n" + ] + } + ], + "source": [ + "SIZE = 20_000_000\n", + "cProfile.run('add(SIZE)')" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "f14c476d-cc19-4ce5-b74e-abf211eabc7a", + "metadata": {}, + "outputs": [], + "source": [ + "def ops(a,b):\n", + " x1 = a+b\n", + " x2 = a-b\n", + " x3 = a*b\n", + " x4 = a/b" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "771a1452-2d4a-476e-8775-3479beac3700", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 4 function calls in 0.287 seconds\n", + "\n", + " Ordered by: standard name\n", + "\n", + " ncalls tottime percall cumtime percall filename:lineno(function)\n", + " 1 0.251 0.251 0.251 0.251 3200973052.py:1(ops)\n", + " 1 0.036 0.036 0.286 0.286 :1()\n", + " 1 0.000 0.000 0.287 0.287 {built-in method builtins.exec}\n", + " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", + "\n", + "\n" + ] + } + ], + "source": [ + "cProfile.run('ops(a,b)')" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "5af6602e-a66f-4bf0-9d22-bc5b71e8c4d0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Total function calls: 48\n", + "Total time (seconds): 1.1839559\n" + ] + } + ], + "source": [ + "import cProfile, pstats\n", + "\n", + "profiler = cProfile.Profile()\n", + "# Enable profiler\n", + "profiler.enable()\n", + "# Function execution\n", + "add(SIZE)\n", + "# Disable profiler\n", + "profiler.disable()\n", + "# pstats\n", + "stats = pstats.Stats(profiler)\n", + "# Print the total time and function calls\n", + "print(\"Total function calls:\", stats.total_calls)\n", + "print(\"Total time (seconds):\", stats.total_tt)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "91f2d625-37bf-41f6-9c92-ae52bf608d02", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 48 function calls in 1.184 seconds\n", + "\n", + " Random listing order was used\n", + "\n", + " ncalls tottime percall cumtime percall filename:lineno(function)\n", + " 2 0.000 0.000 0.000 0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\contextlib.py:86(__init__)\n", + " 2 0.000 0.000 0.000 0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\contextlib.py:112(__enter__)\n", + " 2 0.000 0.000 0.000 0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\contextlib.py:121(__exit__)\n", + " 2 0.000 0.000 0.000 0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\contextlib.py:242(helper)\n", + " 2 0.000 0.000 0.000 0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\site-packages\\traitlets\\traitlets.py:535(get)\n", + " 2 0.000 0.000 0.000 0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\site-packages\\traitlets\\traitlets.py:566(__get__)\n", + " 2 0.000 0.000 0.000 0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\site-packages\\IPython\\utils\\ipstruct.py:125(__getattr__)\n", + " 2 0.000 0.000 0.000 0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\codeop.py:142(__call__)\n", + " 4 0.000 0.000 0.000 0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\site-packages\\IPython\\core\\compilerop.py:166(extra_flags)\n", + " 2 0.000 0.000 0.000 0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\site-packages\\IPython\\core\\interactiveshell.py:1286(user_global_ns)\n", + " 2 0.000 0.000 0.000 0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\site-packages\\IPython\\core\\interactiveshell.py:3354(compare)\n", + " 2 0.000 0.000 1.184 0.592 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\site-packages\\IPython\\core\\interactiveshell.py:3416(run_code)\n", + " 2 0.000 0.000 0.000 0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\site-packages\\IPython\\core\\hooks.py:103(__call__)\n", + " 2 0.000 0.000 0.000 0.000 c:\\users\\tirtha\\appdata\\local\\programs\\python\\python39\\lib\\site-packages\\IPython\\core\\hooks.py:168(pre_run_code_hook)\n", + " 1 0.000 0.000 0.000 0.000 C:\\Users\\Tirtha\\AppData\\Local\\Temp/ipykernel_12356/3775033682.py:9()\n", + " 1 0.044 0.044 1.184 1.184 C:\\Users\\Tirtha\\AppData\\Local\\Temp/ipykernel_12356/3775033682.py:7()\n", + " 1 0.114 0.114 1.140 1.140 C:\\Users\\Tirtha\\AppData\\Local\\Temp/ipykernel_12356/1565836920.py:1(add)\n", + " 1 0.081 0.081 0.081 0.081 {built-in method numpy.arange}\n", + " 1 0.945 0.945 0.945 0.945 {method 'normal' of 'numpy.random.mtrand.RandomState' objects}\n", + " 2 0.000 0.000 0.000 0.000 {built-in method builtins.compile}\n", + " 2 0.000 0.000 1.184 0.592 {built-in method builtins.exec}\n", + " 4 0.000 0.000 0.000 0.000 {built-in method builtins.getattr}\n", + " 4 0.000 0.000 0.000 0.000 {built-in method builtins.next}\n", + " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", + "\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "stats = pstats.Stats(profiler)\n", + "stats.print_stats()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "06fa3a62-5fef-4d9e-8c6a-d9e309d80e16", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "pstats.Stats" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(stats)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "cefecaa0-9933-49ae-b0dc-3f869e83fa89", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.1839559" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "stats.total_tt" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "fe15e37e-d329-4168-a479-60c219126e16", + "metadata": {}, + "outputs": [], + "source": [ + "stats.fcn_list" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "15c966eb-e094-4e5f-8e54-1a2c3923f6c7", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "560a8a4d-84fc-43c2-a1c8-79113dcc73ac", + "metadata": {}, + "outputs": [], + "source": [ + "size = [int(i*1e6) for i in range(5,26,5)]\n", + "total_tt = []\n", + "for s in size:\n", + " profiler = cProfile.Profile()\n", + " profiler.enable()\n", + " add(s)\n", + " profiler.disable()\n", + " stats = pstats.Stats(profiler)\n", + " total_tt.append(round(stats.total_tt,3)) " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "264f4589-e40b-4c2d-bb08-9116dc4eb2e4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0.274, 0.464, 0.706, 0.94, 1.187]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "total_tt" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "695c9306-bcc6-45ea-8939-28e8e127eec3", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(6,3),dpi=120)\n", + "plt.bar(x=[str(i)+'-million' for i in range(5,26,5)],\n", + " height=total_tt, \n", + " edgecolor='k',\n", + " color=\"#2c75b0\")\n", + "plt.xlabel(\"Array size\", fontsize=16)\n", + "plt.ylabel(\"Time taken (seconds)\",fontsize=16)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "9b1e4af3-385a-420a-be85-449119210f93", + "metadata": {}, + "source": [ + "## Data science workflow profiling\n", + "\n", + "While measuring the execution time of these small standalone functions serve as basic demonstration of the usage of these profilers, the real utility is realized when they are used in a large-scale data science workflow. Such a workflow has a variety of modules and functions and we can set up profiling for all of them if necessary. The output may be logged into a database or even be fed into a monitoring system that will track the performance of the modules over time and take action if needed (e.g., a function has poorly performed and took too much time in a certain run or for a certain input data).\n", + "\n", + "![ds-workflow](https://raw.githubusercontent.com/tirthajyoti/Machine-Learning-with-Python/master/Time-profiling/8.14%20-%20profile-DS-workflow.png)" + ] + } + ], + "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.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}