diff --git a/math/calculus-for-ml/symbolic_numerical_automatic_differentiation_in_python.ipynb b/math/calculus-for-ml/symbolic_numerical_automatic_differentiation_in_python.ipynb new file mode 100644 index 0000000..58bc6e0 --- /dev/null +++ b/math/calculus-for-ml/symbolic_numerical_automatic_differentiation_in_python.ipynb @@ -0,0 +1,1673 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "reverse-interview", + "metadata": {}, + "source": [ + "# Differentiation in Python: Symbolic, Numerical and Automatic" + ] + }, + { + "cell_type": "markdown", + "id": "parental-conclusion", + "metadata": {}, + "source": [ + "In this lab you explore which tools and libraries are available in Python to compute derivatives. You will perform symbolic differentiation with `SymPy` library, numerical with `NumPy` and automatic with `JAX` (based on `Autograd`). Comparing the speed of calculations, you will investigate the computational efficiency of those three methods." + ] + }, + { + "cell_type": "markdown", + "id": "looking-barcelona", + "metadata": {}, + "source": [ + "# Table of Contents\n", + "- [ 1 - Functions in Python](#1)\n", + "- [ 2 - Symbolic Differentiation](#2)\n", + " - [ 2.1 - Introduction to Symbolic Computation with `SymPy`](#2.1)\n", + " - [ 2.2 - Symbolic Differentiation with `SymPy`](#2.2)\n", + " - [ 2.3 - Limitations of Symbolic Differentiation](#2.3)\n", + "- [ 3 - Numerical Differentiation](#3)\n", + " - [ 3.1 - Numerical Differentiation with `NumPy`](#3.1)\n", + " - [ 3.2 - Limitations of Numerical Differentiation](#3.2)\n", + "- [ 4 - Automatic Differentiation](#4)\n", + " - [ 4.1 - Introduction to `JAX`](#4.1)\n", + " - [ 4.2 - Automatic Differentiation with `JAX` ](#4.2)\n", + "- [ 5 - Computational Efficiency of Symbolic, Numerical and Automatic Differentiation](#5)" + ] + }, + { + "cell_type": "markdown", + "id": "101116ab", + "metadata": {}, + "source": [ + "\n", + "## 1 - Functions in Python" + ] + }, + { + "cell_type": "markdown", + "id": "bc6140d8", + "metadata": {}, + "source": [ + "This is just a reminder how to define functions in Python. A simple function $f\\left(x\\right) = x^2$, it can be set up as:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "d07a15ef", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "9\n" + ] + } + ], + "source": [ + "def f(x):\n", + " return x**2\n", + "\n", + "print(f(3))" + ] + }, + { + "cell_type": "markdown", + "id": "06330bd1", + "metadata": {}, + "source": [ + "You can easily find the derivative of this function analytically. You can set it up as a separate function:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1ff4ffb5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6\n" + ] + } + ], + "source": [ + "def dfdx(x):\n", + " return 2*x\n", + "\n", + "print(dfdx(3))" + ] + }, + { + "cell_type": "markdown", + "id": "8301af3f", + "metadata": {}, + "source": [ + "Since you have been working with the `NumPy` arrays, you can apply the function to each element of an array:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "9f5831d8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "x: \n", + " [1 2 3]\n", + "f(x) = x**2: \n", + " [1 4 9]\n", + "f'(x) = 2x: \n", + " [2 4 6]\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "\n", + "x_array = np.array([1, 2, 3])\n", + "\n", + "print(\"x: \\n\", x_array)\n", + "print(\"f(x) = x**2: \\n\", f(x_array))\n", + "print(\"f'(x) = 2x: \\n\", dfdx(x_array))" + ] + }, + { + "cell_type": "markdown", + "id": "8428f910", + "metadata": {}, + "source": [ + "Now you can apply those functions `f` and `dfdx` to an array of a larger size. The following code will plot function and its derivative (you don't have to understand the details of the `plot_f1_and_f2` function at this stage):" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5c255f4e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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7BQ0YSAhAd5910LA7797z//rX9YVcPvtEBLiXn0iIiJuCfgwsGkTjB4NkyZBfLydy58fOnSwroCqVd2tT0RExG0BGQZSU+Hrr22vgC+/9J6vXNkCQHQ0aJyUiIiICagwEBdnCwSNHm0tAmluv926Apo1gwxO9RQREQkaAREGfv/dxgK8+653c6sCBWwwYEwMVKrkbn0iIiK+zG//Tk5JgU8/tQGAV11lUwQTEqBaNTvevRtef11BQEREsp7jOHTp0oXChQvj8XhYu3btGc/ZuHEjUVFRHD16NEOveeDAASIjI9m1a1cmV3smvwsDhw7BsGF2k7/zTpsmmCOH93j9euje3QYJioiIZId58+YxefJkPvvsM/bs2cOwYcMYMGBAuuf069ePnj17Eh4enqHXLFq0KA8++CD9+/fPgorT85tugl9/tRUCp06FEyfsXKFCtkTwI49AuXKuliciIkFsy5YtlChRghtuuAGAnP9YtnbHjh189tlnjBw58qJet2PHjtStW5ehQ4dSuHDhTKv3n3w6DJw6BXPmWAhYutR7vlYtGxB4//0QFuZefSIikrUcx5aMz25hYeDxZOy50dHRTJkyBQCPx0PZsmVp1KhRuud88MEH1K5dmyuuuOL0uYceeogffviB1atXExoaSlJSEvXr16dmzZq8++67AFSvXp2SJUsya9YsOnXqlCn/trPxyW6CAwdg0CCoUAHatLEgEBLiPV671loEFAQkkL3yyivccMMNhIWFUbBgwbM+Z8eOHbRs2ZKwsDAiIyN54oknOHXqVPYWKpKFjh+3bt/sflxMAHnzzTd56aWXKFWqFHv27GH16tVnPOebb77h2muvTXduxIgRHDt2jKeffhqAZ599liNHjjBq1Kh0z6tXrx7ffPPNxb95F8GnWgZ+/NFaAd5/HxIT7VzRotClC3TrZrsHigSLpKQk7rnnHho0aMCECRPO+HxKSgotW7YkKiqK5cuXs2fPHh588EFy5crFq6++6kLFIsEpIiKC8PBwQkJCiIqKAmDy5MnpnrN9+/YzwkD+/PmZOnUqt9xyC+Hh4bzxxhssXrz4jA3DSpYsyU8//ZSl/wbXw0ByMnz8sYWA5cu95+vWta6Ae+8FbV4lwejFF18EzryopPn666/57bffWLBgAcWLF6dOnToMHDiQp556igEDBpA7d+5srFYka4SFeaeMZ/f3zUwnTpw4646CDRo04PHHHz/9u3vjjTee8Zy8efNyPIv7SlwNA2PGwMCBsGfP/xeTE+65x0LA9ddnvL9GJBitWLGCmjVrUrx48dPnmjdvTvfu3Vm/fj1XX331Wb8uMTGRxLSmN2wLYxFf5fFAvnxuV3H5ihYtyuG07XH/JjU1le+++46QkBA2b9581q89dOgQxYoVy9L6XB0zcPSoBYGoKOjfH3bsgOnToUEDBQGRC4mNjU0XBIDTH8fGxp7z6wYNGkRERMTpR2n1v4lkuauvvprffvvtjPNDhw5lw4YNLF26lHnz5jFp0qQznrNu3bpzhvvM4moY6NQJpk2D7dthwAAoUcLNakSy3tNPP43H4znvY8OGDVlaQ79+/YiLizv92LlzZ5Z+PxGxVrsVK1aQkpJy+txPP/3ECy+8wDvvvEPDhg0ZPnw4jz32GH/++efp5xw/fpw1a9bQrFmzLK3P1W6CIkVseqBIsOjbty/R0dHnfU6FChUy9FpRUVF8//336c7t3bv39OfOJTQ0lNDQ0Ax9DxHJHLfddhs5c+ZkwYIFNG/enJMnT9K+fXuio6O54447AOjSpQuff/45DzzwAMuWLSMkJIQ5c+ZQpkwZbrrppiytz/UBhCLBpFixYpnW99egQQNeeeUV9u3bR2RkJADz58+nQIECXHXVVZnyPUQkY3r16kWvXr3O+fmcOXPyzDPPMHz4cJo3b06ePHlYv379Gc+bM2dOuo/ffPNNXnjhhcwu98z6svw7iMgl2bFjB4cOHWLHjh2kpKScXuu8UqVK5M+fn2bNmnHVVVfxwAMPMGTIEGJjY3nuueeIiYnRX/4iPqhr164cOXKEo0ePZmhJ4gMHDnDXXXfRtm3bLK/N4ziOk+XfRUQu2t9XNfu7xYsXn17dbPv27XTv3p0lS5aQL18+OnTowGuvvXbGUqjnEx8fT0REBHFxcWfMbxbJTidPnmTr1q2UL1/+rNPw5Owy431TGBAJcgoD4isUBi5NZrxvPrkcsYiIBC/9jXpxMuP9UhgQERGfkCtXLoAsX20v0KS9X2nv36XQAEIREfEJISEhFCxYkH379gEQFhaGRyvQnZPjOBw/fpx9+/ZRsGBBQkJCLvm1FAZERMRnpK2RkRYI5MIKFix43rVFMkJhQEREfIbH46FEiRJERkaSnJzsdjk+L1euXJfVIpBGYUBERHxOSEhIptzkJGM0gFBERCTIKQyIiIgEOYUBERGRIKcwICIiEuQUBkRERIKc9iYQCXKO45zeRU0LvIgEJ4UBERGRIKduAhERkSCnMCAiIhLkFAZERESCnMKAiIhIkFMYEBERCXIKAyIiIkFOYUBERCTI/R8NnCOO7lHTiwAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "# Output of plotting commands is displayed inline within the Jupyter notebook.\n", + "%matplotlib inline\n", + "\n", + "def plot_f1_and_f2(f1, f2=None, x_min=-5, x_max=5, label1=\"f(x)\", label2=\"f'(x)\"):\n", + " x = np.linspace(x_min, x_max,100)\n", + "\n", + " # Setting the axes at the centre.\n", + " fig = plt.figure()\n", + " ax = fig.add_subplot(1, 1, 1)\n", + " ax.spines['left'].set_position('center')\n", + " ax.spines['bottom'].set_position('zero')\n", + " ax.spines['right'].set_color('none')\n", + " ax.spines['top'].set_color('none')\n", + " ax.xaxis.set_ticks_position('bottom')\n", + " ax.yaxis.set_ticks_position('left')\n", + "\n", + " plt.plot(x, f1(x), 'r', label=label1)\n", + " if not f2 is None:\n", + " # If f2 is an array, it is passed as it is to be plotted as unlinked points.\n", + " # If f2 is a function, f2(x) needs to be passed to plot it. \n", + " if isinstance(f2, np.ndarray):\n", + " plt.plot(x, f2, 'bo', markersize=3, label=label2,)\n", + " else:\n", + " plt.plot(x, f2(x), 'b', label=label2)\n", + " plt.legend()\n", + "\n", + " plt.show()\n", + " \n", + "plot_f1_and_f2(f, dfdx)" + ] + }, + { + "cell_type": "markdown", + "id": "ffb711b8", + "metadata": {}, + "source": [ + "In real life the functions are more complicated and it is not possible to calculate the derivatives analytically every time. Let's explore which tools and libraries are available in Python for the computation of derivatives without manual derivation." + ] + }, + { + "cell_type": "markdown", + "id": "8d17f76f", + "metadata": {}, + "source": [ + "\n", + "## 2 - Symbolic Differentiation" + ] + }, + { + "cell_type": "markdown", + "id": "severe-studio", + "metadata": {}, + "source": [ + "**Symbolic computation** deals with the computation of mathematical objects that are represented exactly, not approximately (e.g. $\\sqrt{2}$ will be written as it is, not as $1.41421356237$). For differentiation it would mean that the output will be somehow similar to if you were computing derivatives by hand using rules (analytically). Thus, symbolic differentiation can produce exact derivatives." + ] + }, + { + "cell_type": "markdown", + "id": "5b51ca07", + "metadata": {}, + "source": [ + "\n", + "### 2.1 - Introduction to Symbolic Computation with `SymPy`\n", + "\n", + "Let's explore symbolic differentiation in Python with commonly used `SymPy` library.\n", + "\n", + "If you want to compute the approximate decimal value of $\\sqrt{18}$, you could normally do it in the following way:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "52d0b0a6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4.242640687119285" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import math\n", + "\n", + "math.sqrt(18)" + ] + }, + { + "cell_type": "markdown", + "id": "a5227c24", + "metadata": {}, + "source": [ + "The output $4.242640687119285$ is an approximate result. You may recall that $\\sqrt{18} = \\sqrt{9 \\cdot 2} = 3\\sqrt{2}$ and see that it is pretty much impossible to deduct it from the approximate result. But with the symbolic computation systems the roots are not approximated with a decimal number but rather only simplified, so the output is exact:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d5f1747d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle 3 \\sqrt{2}$" + ], + "text/plain": [ + "3*sqrt(2)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# This format of module import allows to use the sympy functions without sympy. prefix.\n", + "from sympy import *\n", + "\n", + "# This is actually sympy.sqrt function, but sympy. prefix is omitted.\n", + "sqrt(18)" + ] + }, + { + "cell_type": "markdown", + "id": "f5495348", + "metadata": {}, + "source": [ + "Numerical evaluation of the result is available, and you can set number of the digits to show in the approximated output:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "925375b9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle 4.2426407$" + ], + "text/plain": [ + "4.2426407" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "N(sqrt(18),8)" + ] + }, + { + "cell_type": "markdown", + "id": "fb625f44", + "metadata": {}, + "source": [ + "In `SymPy` variables are defined using **symbols**. In this particular library they need to be predefined (a list of them should be provided). Have a look in the cell below, how the symbolic expression, correspoinding to the mathematical expression $2x^2 - xy$, is defined:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "b52721c0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle 2 x^{2} - x y$" + ], + "text/plain": [ + "2*x**2 - x*y" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# List of symbols.\n", + "x, y = symbols('x y')\n", + "# Definition of the expression.\n", + "expr = 2 * x**2 - x * y\n", + "expr" + ] + }, + { + "cell_type": "markdown", + "id": "edffde06", + "metadata": {}, + "source": [ + "Now you can perform various manipulations with this expression: add or subtract some terms, multiply by other expressions etc., just like if you were doing it by hands:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "ac575c24", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle x \\left(x^{3} + 2 x^{2}\\right)$" + ], + "text/plain": [ + "x*(x**3 + 2*x**2)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "expr_manip = x * (expr + x * y + x**3)\n", + "expr_manip" + ] + }, + { + "cell_type": "markdown", + "id": "db151265", + "metadata": {}, + "source": [ + "You can also expand the expression:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "afb04770", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle x^{4} + 2 x^{3}$" + ], + "text/plain": [ + "x**4 + 2*x**3" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "expand(expr_manip)" + ] + }, + { + "cell_type": "markdown", + "id": "77b3750c", + "metadata": {}, + "source": [ + "Or factorise it:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "0fc456cb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle x^{3} \\left(x + 2\\right)$" + ], + "text/plain": [ + "x**3*(x + 2)" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "factor(expr_manip)" + ] + }, + { + "cell_type": "markdown", + "id": "c885c385", + "metadata": {}, + "source": [ + "To substitute particular values for the variables in the expression, you can use the following code:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "3c7d239e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle 4.0$" + ], + "text/plain": [ + "4.00000000000000" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "expr.evalf(subs={x:-1, y:2})" + ] + }, + { + "cell_type": "markdown", + "id": "4acf4536", + "metadata": {}, + "source": [ + "This can be used to evaluate a function $f\\left(x\\right) = x^2$:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "622e4335", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle 9.0$" + ], + "text/plain": [ + "9.00000000000000" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f_symb = x ** 2\n", + "f_symb.evalf(subs={x:3})" + ] + }, + { + "cell_type": "markdown", + "id": "1346fb9f", + "metadata": {}, + "source": [ + "You might be wondering now, is it possible to evaluate the symbolic functions for each element of the array? At the beginning of the lab you have defined a `NumPy` array `x_array`:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "af6562d3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1 2 3]\n" + ] + } + ], + "source": [ + "print(x_array)" + ] + }, + { + "cell_type": "markdown", + "id": "f13ae4e0", + "metadata": {}, + "source": [ + "Now try to evaluate function `f_symb` for each element of the array. You will get an error:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "11df38ae", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "'Pow' object is not callable\n" + ] + } + ], + "source": [ + "try:\n", + " f_symb(x_array)\n", + "except TypeError as err:\n", + " print(err)" + ] + }, + { + "cell_type": "markdown", + "id": "4f7800fe", + "metadata": {}, + "source": [ + "It is possible to evaluate the symbolic functions for each element of the array, but you need to make a function `NumPy`-friendly first:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "3d7d8a58", + "metadata": {}, + "outputs": [], + "source": [ + "from sympy.utilities.lambdify import lambdify\n", + "\n", + "f_symb_numpy = lambdify(x, f_symb, 'numpy')" + ] + }, + { + "cell_type": "markdown", + "id": "631e3e71", + "metadata": {}, + "source": [ + "The following code should work now:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "1ab8f0da", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "x: \n", + " [1 2 3]\n", + "f(x) = x**2: \n", + " [1 4 9]\n" + ] + } + ], + "source": [ + "print(\"x: \\n\", x_array)\n", + "print(\"f(x) = x**2: \\n\", f_symb_numpy(x_array))" + ] + }, + { + "cell_type": "markdown", + "id": "681062e1", + "metadata": {}, + "source": [ + "`SymPy` has lots of great functions to manipulate expressions and perform various operations from calculus. More information about them can be found in the official documentation [here](https://docs.sympy.org/)." + ] + }, + { + "cell_type": "markdown", + "id": "6c6c9d29", + "metadata": {}, + "source": [ + "\n", + "### 2.2 - Symbolic Differentiation with `SymPy`\n", + "\n", + "Let's try to find a derivative of a simple power function using `SymPy`:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "abb93aeb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle 3 x^{2}$" + ], + "text/plain": [ + "3*x**2" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "diff(x**3,x)" + ] + }, + { + "cell_type": "markdown", + "id": "bd8a81b1", + "metadata": {}, + "source": [ + "Some standard functions can be used in the expression, and `SymPy` will apply required rules (sum, product, chain) to calculate the derivative:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "63ce2cd2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle 9 \\cos{\\left(3 x \\right)} - 2 e^{- 2 x}$" + ], + "text/plain": [ + "9*cos(3*x) - 2*exp(-2*x)" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dfdx_composed = diff(exp(-2*x) + 3*sin(3*x), x)\n", + "dfdx_composed" + ] + }, + { + "cell_type": "markdown", + "id": "13407c2b", + "metadata": {}, + "source": [ + "Now calculate the derivative of the function `f_symb` defined in [2.1](#2.1) and make it `NumPy`-friendly:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "597d5879", + "metadata": {}, + "outputs": [], + "source": [ + "dfdx_symb = diff(f_symb, x)\n", + "dfdx_symb_numpy = lambdify(x, dfdx_symb, 'numpy')" + ] + }, + { + "cell_type": "markdown", + "id": "0f9bee28", + "metadata": {}, + "source": [ + "Evaluate function `dfdx_symb_numpy` for each element of the `x_array`:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "b74b4d04", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "x: \n", + " [1 2 3]\n", + "f'(x) = 2x: \n", + " [2 4 6]\n" + ] + } + ], + "source": [ + "print(\"x: \\n\", x_array)\n", + "print(\"f'(x) = 2x: \\n\", dfdx_symb_numpy(x_array))" + ] + }, + { + "cell_type": "markdown", + "id": "ada41a99", + "metadata": {}, + "source": [ + "You can apply symbolically defined functions to the arrays of larger size. The following code will plot function and its derivative, you can see that it works:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "031a757c", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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7BQ0YSAhAd5910LA7797z//rX9YVcPvtEBLiXn0iIiJuCfgwsGkTjB4NkyZBfLydy58fOnSwroCqVd2tT0RExG0BGQZSU+Hrr22vgC+/9J6vXNkCQHQ0aJyUiIiICagwEBdnCwSNHm0tAmluv926Apo1gwxO9RQREQkaAREGfv/dxgK8+653c6sCBWwwYEwMVKrkbn0iIiK+zG//Tk5JgU8/tQGAV11lUwQTEqBaNTvevRtef11BQEREsp7jOHTp0oXChQvj8XhYu3btGc/ZuHEjUVFRHD16NEOveeDAASIjI9m1a1cmV3smvwsDhw7BsGF2k7/zTpsmmCOH93j9euje3QYJioiIZId58+YxefJkPvvsM/bs2cOwYcMYMGBAuuf069ePnj17Eh4enqHXLFq0KA8++CD9+/fPgorT85tugl9/tRUCp06FEyfsXKFCtkTwI49AuXKuliciIkFsy5YtlChRghtuuAGAnP9YtnbHjh189tlnjBw58qJet2PHjtStW5ehQ4dSuHDhTKv3n3w6DJw6BXPmWAhYutR7vlYtGxB4//0QFuZefSIikrUcx5aMz25hYeDxZOy50dHRTJkyBQCPx0PZsmVp1KhRuud88MEH1K5dmyuuuOL0uYceeogffviB1atXExoaSlJSEvXr16dmzZq8++67AFSvXp2SJUsya9YsOnXqlCn/trPxyW6CAwdg0CCoUAHatLEgEBLiPV671loEFAQkkL3yyivccMMNhIWFUbBgwbM+Z8eOHbRs2ZKwsDAiIyN54oknOHXqVPYWKpKFjh+3bt/sflxMAHnzzTd56aWXKFWqFHv27GH16tVnPOebb77h2muvTXduxIgRHDt2jKeffhqAZ599liNHjjBq1Kh0z6tXrx7ffPPNxb95F8GnWgZ+/NFaAd5/HxIT7VzRotClC3TrZrsHigSLpKQk7rnnHho0aMCECRPO+HxKSgotW7YkKiqK5cuXs2fPHh588EFy5crFq6++6kLFIsEpIiKC8PBwQkJCiIqKAmDy5MnpnrN9+/YzwkD+/PmZOnUqt9xyC+Hh4bzxxhssXrz4jA3DSpYsyU8//ZSl/wbXw0ByMnz8sYWA5cu95+vWta6Ae+8FbV4lwejFF18EzryopPn666/57bffWLBgAcWLF6dOnToMHDiQp556igEDBpA7d+5srFYka4SFeaeMZ/f3zUwnTpw4646CDRo04PHHHz/9u3vjjTee8Zy8efNyPIv7SlwNA2PGwMCBsGfP/xeTE+65x0LA9ddnvL9GJBitWLGCmjVrUrx48dPnmjdvTvfu3Vm/fj1XX331Wb8uMTGRxLSmN2wLYxFf5fFAvnxuV3H5ihYtyuG07XH/JjU1le+++46QkBA2b9581q89dOgQxYoVy9L6XB0zcPSoBYGoKOjfH3bsgOnToUEDBQGRC4mNjU0XBIDTH8fGxp7z6wYNGkRERMTpR2n1v4lkuauvvprffvvtjPNDhw5lw4YNLF26lHnz5jFp0qQznrNu3bpzhvvM4moY6NQJpk2D7dthwAAoUcLNakSy3tNPP43H4znvY8OGDVlaQ79+/YiLizv92LlzZ5Z+PxGxVrsVK1aQkpJy+txPP/3ECy+8wDvvvEPDhg0ZPnw4jz32GH/++efp5xw/fpw1a9bQrFmzLK3P1W6CIkVseqBIsOjbty/R0dHnfU6FChUy9FpRUVF8//336c7t3bv39OfOJTQ0lNDQ0Ax9DxHJHLfddhs5c+ZkwYIFNG/enJMnT9K+fXuio6O54447AOjSpQuff/45DzzwAMuWLSMkJIQ5c+ZQpkwZbrrppiytz/UBhCLBpFixYpnW99egQQNeeeUV9u3bR2RkJADz58+nQIECXHXVVZnyPUQkY3r16kWvXr3O+fmcOXPyzDPPMHz4cJo3b06ePHlYv379Gc+bM2dOuo/ffPNNXnjhhcwu98z6svw7iMgl2bFjB4cOHWLHjh2kpKScXuu8UqVK5M+fn2bNmnHVVVfxwAMPMGTIEGJjY3nuueeIiYnRX/4iPqhr164cOXKEo0ePZmhJ4gMHDnDXXXfRtm3bLK/N4ziOk+XfRUQu2t9XNfu7xYsXn17dbPv27XTv3p0lS5aQL18+OnTowGuvvXbGUqjnEx8fT0REBHFxcWfMbxbJTidPnmTr1q2UL1/+rNPw5Owy431TGBAJcgoD4isUBi5NZrxvPrkcsYiIBC/9jXpxMuP9UhgQERGfkCtXLoAsX20v0KS9X2nv36XQAEIREfEJISEhFCxYkH379gEQFhaGRyvQnZPjOBw/fpx9+/ZRsGBBQkJCLvm1FAZERMRnpK2RkRYI5MIKFix43rVFMkJhQEREfIbH46FEiRJERkaSnJzsdjk+L1euXJfVIpBGYUBERHxOSEhIptzkJGM0gFBERCTIKQyIiIgEOYUBERGRIKcwICIiEuQUBkRERIKc9iYQCXKO45zeRU0LvIgEJ4UBERGRIKduAhERkSCnMCAiIhLkFAZERESCnMKAiIhIkFMYEBERCXIKAyIiIkFOYUBERCTI/R8NnCOO7lHTiwAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_f1_and_f2(f_symb_numpy, dfdx_symb_numpy)" + ] + }, + { + "cell_type": "markdown", + "id": "de01feee", + "metadata": {}, + "source": [ + "\n", + "### 2.3 - Limitations of Symbolic Differentiation\n", + "\n", + "Symbolic Differentiation seems to be a great tool. But it also has some limitations. Sometimes the output expressions are too complicated and even not possible to evaluate. For example, find the derivative of the function $$\\left|x\\right| = \\begin{cases} x, \\ \\text{if}\\ x > 0\\\\ -x, \\ \\text{if}\\ x < 0 \\\\ 0, \\ \\text{if}\\ x = 0\\end{cases}$$ Analytically, its derivative is:\n", + "$$\\frac{d}{dx}\\left(\\left|x\\right|\\right) = \\begin{cases} 1, \\ \\text{if}\\ x > 0\\\\ -1, \\ \\text{if}\\ x < 0\\\\\\ \\text{does not exist}, \\ \\text{if}\\ x = 0\\end{cases}$$\n", + "\n", + "Have a look the output from the symbolic differentiation:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "collect-needle", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle \\frac{\\left(\\operatorname{re}{\\left(x\\right)} \\frac{d}{d x} \\operatorname{re}{\\left(x\\right)} + \\operatorname{im}{\\left(x\\right)} \\frac{d}{d x} \\operatorname{im}{\\left(x\\right)}\\right) \\operatorname{sign}{\\left(x \\right)}}{x}$" + ], + "text/plain": [ + "(re(x)*Derivative(re(x), x) + im(x)*Derivative(im(x), x))*sign(x)/x" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dfdx_abs = diff(abs(x),x)\n", + "dfdx_abs" + ] + }, + { + "cell_type": "markdown", + "id": "f9c3d8e5", + "metadata": {}, + "source": [ + "Looks complicated, but it would not be a problem if it was possible to evaluate. But check, that for $x=-2$ instead of the derivative value $-1$ it outputs some unevaluated expression:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "d53e7c64", + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle \\frac{\\left(\\operatorname{re}{\\left(x\\right)} \\frac{d}{d x} \\operatorname{re}{\\left(x\\right)} + \\operatorname{im}{\\left(x\\right)} \\frac{d}{d x} \\operatorname{im}{\\left(x\\right)}\\right) \\operatorname{sign}{\\left(x \\right)}}{x}$" + ], + "text/plain": [ + "(re(x)*Derivative(re(x), x) + im(x)*Derivative(im(x), x))*sign(x)/x" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dfdx_abs.evalf(subs={x:-2})" + ] + }, + { + "cell_type": "markdown", + "id": "f4a9140c", + "metadata": {}, + "source": [ + "And in the `NumPy` friendly version it also will give an error:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "644f00ce", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "name 'Derivative' is not defined\n" + ] + } + ], + "source": [ + "dfdx_abs_numpy = lambdify(x, dfdx_abs,'numpy')\n", + "\n", + "try:\n", + " dfdx_abs_numpy(np.array([1, -2, 0]))\n", + "except NameError as err:\n", + " print(err)" + ] + }, + { + "cell_type": "markdown", + "id": "3e1f3c94", + "metadata": {}, + "source": [ + "In fact, there are problems with the evaluation of the symbolic expressions wherever there is a \"jump\" in the derivative (e.g. function expressions are different for different intervals of $x$), like it happens with $\\frac{d}{dx}\\left(\\left|x\\right|\\right)$. \n", + "\n", + "Also, you can see in this example, that you can get a very complicated function as an output of symbolic computation. This is called **expression swell**, which results in unefficiently slow computations. You will see the example of that below after learning other differentiation libraries in Python." + ] + }, + { + "cell_type": "markdown", + "id": "1cb85963", + "metadata": {}, + "source": [ + "\n", + "## 3 - Numerical Differentiation\n", + "\n", + "This method does not take into account the function expression. The only important thing is that the function can be evaluated in the nearby points $x$ and $x+\\Delta x$, where $\\Delta x$ is sufficiently small. Then $\\frac{df}{dx}\\approx\\frac{f\\left(x + \\Delta x\\right) - f\\left(x\\right)}{\\Delta x}$, which can be called a **numerical approximation** of the derivative. \n", + "\n", + "Based on that idea there are different approaches for the numerical approximations, which somehow vary in the computation speed and accuracy. However, for all of the methods the results are not accurate - there is a round off error. At this stage there is no need to go into details of various methods, it is enough to investigate one of the numerial differentiation functions, available in `NumPy` package." + ] + }, + { + "cell_type": "markdown", + "id": "1cc2a87e", + "metadata": {}, + "source": [ + "\n", + "### 3.1 - Numerical Differentiation with `NumPy`" + ] + }, + { + "cell_type": "markdown", + "id": "c469b76c", + "metadata": {}, + "source": [ + "You can call function `np.gradient` to find the derivative of function $f\\left(x\\right) = x^2$ defined above. The first argument is an array of function values, the second defines the spacing $\\Delta x$ for the evaluation. Here pass it as an array of $x$ values, the differences will be calculated automatically. You can find the documentation [here](https://numpy.org/doc/stable/reference/generated/numpy.gradient.html)." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "b275519f", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "x_array_2 = np.linspace(-5, 5, 100)\n", + "dfdx_numerical = np.gradient(f(x_array_2), x_array_2)\n", + "\n", + "plot_f1_and_f2(dfdx_symb_numpy, dfdx_numerical, label1=\"f'(x) exact\", label2=\"f'(x) approximate\")" + ] + }, + { + "cell_type": "markdown", + "id": "6a1d5843", + "metadata": {}, + "source": [ + "Try to do numerical differentiation for more complicated function:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "9fa0d7cf", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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iRMn5IsvvhB3d3dZunSpZGVlyeuvvy46nU62bdtWq/d5/PhxqyskpqSkCAC555575Pvvv5cff/xRIiIipH///jJ48GD58ccfZceOHdKkSRNZsGCB0ttnn30mn3/+uWRnZ0t6err84Q9/kK5du0pZWZmIWK7sCED+97//yenTp+XcuXMiIvLhhx9KixYt5PPPP5eff/5ZPv/8cwkMDJRVq1ZV+Xnb4mdV3TDw/feW/zvCw1Vtg8iVMQxUzVZh4NSp34JA+U2ns2yvL7W5hHH37t3l5ZdfrrR9165d4u7uLi+88IK4ubnJd999V6nm2WeflQEDBtSpp6FDh8qMGTOUx/3795eOHTuKucKl62fNmiUdO3ZUHoeEhMiQIUOs9vPEE0/IQw89pDwGoPxRL9e3b18ZO3as1bYRI0bIww8/XKv3WV0Y+N///qfUzJ8/XwAol0wWERk/frzExMRU+xn8+uuvAkAOHTpU5fcp17ZtW/noo4+sts2bN0+io6Or3K8tflbVPUzAyYNE5OSyswGz2XpbWRmQk6NOP+WuXLlS5eVuo6Oj8dxzz2HevHmYMWMG+vXrV6nG29vbajj8ZmVlZZg3bx66du2KwMBA+Pn5YcuWLTh58qRV3T333AONRmP1vbOzs1FWVma17eb+jh49arWtd+/eVo+PHj2Ke++912rbvffea/W62rzPm3Xr1k35OigoCD4+PggPD7faVlBQoDzOzs5GbGwswsPD4e/vrxyyuflzqOjSpUs4duwY4uPj4efnp9z++c9/Wh1isDV1Z0cwDBCRk4uMBLRa60Cg0wEREer1BFjODLhQcd7WDWazGTt37oROp0NONYnl/PnzaNasWbX7XrRoEd5++2289dZb6Nq1K3x9fTFt2jRcu3bNZv1X5OvrW+fX1OZ93szd3V35WqPRWD0u32au8B/6D3/4A0JCQvDee++hZcuWMJvN6NKlS42fw8WLFwEA7733HqKioqye0+l0terzdqg7MlA+G5VhgIiclMEALF9uCQCA5T4x0bJdTT179kRGRkal7YsWLUJmZia+/fZbbN68GStXrqxUc/jwYfTs2bPafe/cuROPPPII/vSnP6F79+4IDw/HTz/9VKkuLS3N6vHu3bsRGRlp9Udv9+7dlWo6duxY43vr2LEjdu7cWamnTp061el93olz584hKysL//jHP/Dggw+iY8eOlcKXh4cHAFiNhAQFBaFly5b4+eefERERYXUrn3BYHzgyQERUz+LjgZgYy6GBiAj1gwAAxMTE4C9/+QvKysqUP77p6el48cUX8dlnn+Hee+/FG2+8galTp6J///7KcPjly5exb98+/Otf/6p235GRkfjss8+wa9cuNG7cGG+88QbOnDlj9ccYsAyXT58+HePHj8ePP/6IJUuW4PXXX7eq2blzJxYuXIjhw4cjOTkZn376Kb7++usa39vzzz+Pxx9/HD179sSgQYOwYcMGfPHFF/jf//5X6/d5pxo3bowmTZpg+fLlaNGiBU6ePInZs2db1TRv3hze3t7YvHkzDAYDvLy8oNfr8dJLL2HKlCnQ6/UYMmQISkpK8MMPP+DChQuYPn26Tfqr5LZnG9hCXp7Ijh2WswqISBWcQFg1W55N0NBqM4Hw+vXr0rJlS9m8ebOIWN5vp06dZNy4cVZ1w4YNk759+0ppaamIiHz00UfSvn37Gvd97tw5eeSRR8TPz0+aN28u//jHP2TUqFHyyCOPKDX9+/eXZ555RiZMmCD+/v7SuHFj+dvf/mY1oTAkJEReeuklGTFihPj4+EhwcLC8/fbbVt8LgHz55ZeVenjnnXckPDxc3N3dpV27dvKf//yn1u+zugmEFy5cUOpXrlwp+pvOhEtISJDu3bsrj5OTk6Vjx47i6ekp3bp1k+3bt1fq97333pPWrVuLVquV/v37K9tXr14tPXr0EA8PD2ncuLHcf//98sUXX1T5edviZ1UjUuGkTiJyOUVFRdDr9TCZTPD391e7Hbtx9epVHD9+HGFhYVVOtHMGS5cuxfr167Fly5Zav+aee+7BlClT8NRTT93R9x4wYAB69OiBt956q9qa0NBQTJs2DdOmTbuj7+XsbPGz6jrLKxERkZXx48ejsLAQxcXFtVqS+OzZs3j00UcRGxvbAN1RQ2IYICJyUW5ubvj73/9e6/qmTZti5syZ9dgRqYVhgIiIGtz27dtvWZObm1vvfZCF+lctJCIiIlUxDBAR1YBzrMne2eJnlGGAiKgK5avL1bTsLpE9KP8ZvXlFxLrgnAEioirodDoEBAQoa837+PhYraNPpDYRweXLl1FQUICAgIA7Wq6YYYCIqBrBwcEAYHXxGSJ7ExAQoPys3i6GASKiamg0GrRo0QLNmzfH9evX1W6HqBJ3d3ebXMCIYYCI6BZ0Ol29XjGOSG2cQEhEROTiGAaIiIhcHMMAERGRi2MYICIicnEMA0RERC5OI1xrk8iliYhyCVsuqkPkmhgGiIiIXBwPExAREbk4hgEiIiIXxzBARETk4hgGiIiIXBzDABERkYtjGCAiInJxDANEREQu7v8DjmbRm/8TpEkAAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def f_composed(x):\n", + " return np.exp(-2*x) + 3*np.sin(3*x)\n", + "\n", + "plot_f1_and_f2(lambdify(x, dfdx_composed, 'numpy'), np.gradient(f_composed(x_array_2), x_array_2),\n", + " label1=\"f'(x) exact\", label2=\"f'(x) approximate\")" + ] + }, + { + "cell_type": "markdown", + "id": "826da796", + "metadata": {}, + "source": [ + "The results are pretty impressive, keeping in mind that it does not matter at all how the function was calculated - only the final values of it!" + ] + }, + { + "cell_type": "markdown", + "id": "bc60825b", + "metadata": {}, + "source": [ + "\n", + "### 3.2 - Limitations of Numerical Differentiation" + ] + }, + { + "cell_type": "markdown", + "id": "8dbf76a0", + "metadata": {}, + "source": [ + "Obviously, the first downside of the numerical differentiation is that it is not exact. However, the accuracy of it is normally enough for machine learning applications. At this stage there is no need to evaluate errors of the numerical differentiation.\n", + "\n", + "Another problem is similar to the one which appeared in the symbolic differentiation: it is inaccurate at the points where there are \"jumps\" of the derivative. Let's compare the exact derivative of the absolute value function and with numerical approximation:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "28bb6a5f", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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MBudAQAiDABmozMAQIQBwGx0BgCIMACYjc4AABEGALPRGQAgwgBgNjoDAEQYAMxGZwCACAOA2egMABBhADAbnQEAIgwAZqMzAECEAcAncnJyNHz4cDmdToWGhmrUqFEqKCiocfz48eN1ww03qEmTJmrXrp3+8Ic/KC8vz2uczWarclm1alXtCyMMAJDUyN8FACYYPny4MjMzlZ6erpKSEiUmJiopKUkrV66sdvyxY8d07NgxzZkzR127dtXBgwf12GOP6dixY3r77be9xr722muKj4/3XA8NDa19YRwmACDJZlmW5e8igIZs165d6tq1q7Zs2aK+fftKktLS0nT33XfryJEjioyMrNU8b731lkaMGKHCwkI1alSW4202m9asWaMhQ4bUrbgxY+RKTVWIpLy8PDmdzrrNA6Be4zABcIVt3LhRoaGhniAgSbGxsbLb7dq0aVOt56n4z7oiCFQYO3asWrRooejoaC1dulQXyvdFRUVyuVxyuVwqPn364h4MgAaJwwTAFZaVlaWwsDCvbY0aNVLz5s2VlZVVqzlOnjypmTNnKikpyWv7jBkzdOeddyo4OFiffPKJHn/8cRUUFOgPf/jDeedKSUnRf/3Xf0mSlkj63cU9HAANEJ0BoI4mT55c7QK+cy+7d+++5PtxuVwaPHiwunbtqmeffdbrtqlTp+rWW29V79699cwzz2jSpEmaPXt2jfNNmTJFeXl5ysvL04hhwy65PgD1H50BoI4mTpyohISEGsd07NhREREROn78uNf2s2fPKicnRxERETXun5+fr/j4eDVt2lRr1qxR48aNaxwfExOjmTNnqqioSIGBgdWOCQwMrLzNbteZGmcEYALCAFBHLVu2VMuWLS84rn///srNzdXWrVvVp08fSdKnn34qt9utmJiY8+7ncrkUFxenwMBAvf/++woKCrrgfe3YsUPNmjU7bxCogk8TABBhALjibrzxRsXHx2v06NFKTU1VSUmJxo0bp2HDhnk+SXD06FENHDhQr7/+uqKjo+VyuTRo0CCdPn1ab7zxhmfBn1QWQhwOhz744ANlZ2erX79+CgoKUnp6up5//nk99dRTtS+O8wwAEGEA8IkVK1Zo3LhxGjhwoOx2u+677z7Nnz/fc3tJSYn27Nmj0+Wr+7dt2+b5pEFUVJTXXPv371f79u3VuHFjLVy4UE8++aQsy1JUVJTmzp2r0aNH174wOgMAxHkGALP95jdyrV3LeQYAw/FpAsBkdAYAiDAAmI01AwBEGADMRmcAgAgDgNnoDAAQYQAwG2EAgAgDgNk4TABAhAHAbHQGAIgwAJiNzgAAEQYAs9EZACDCAGA2OgMARBgAzEZnAIAIA4DZ6AwAEGEAMBudAQAiDABmozMAQIQBwGx0BgCIMACYjc4AABEGALPRGQAgwgBgNjoDAEQYAMxGZwCACAOA2egMABBhADAbnQEAIgwA5rIswgAASYQBwFxut78rAHCVIAwApqIrAKAcYQAwFYsHAZQjDACmojMAoBxhADAVnQEA5QgDgKnoDAAoRxgATEVnAEA5wgBgqorOgMPh3zoA+B1hADBVRWeAMAAYjzAA+EBOTo6GDx8up9Op0NBQjRo1SgUFBTXuM2DAANlsNq/LY4895jXm0KFDGjx4sIKDgxUWFqann35aZ2vb/q/oDDRqVJeHBKAB4VUA8IHhw4crMzNT6enpKikpUWJiopKSkrRy5coa9xs9erRmzJjhuR4cHOz5vbS0VIMHD1ZERIS+/PJLZWZmauTIkWrcuLGef/75C9Z05Ii0VwMUYTsqaW+dHxuA+s9mWZbl7yKAhmzXrl3q2rWrtmzZor59+0qS0tLSdPfdd+vIkSOKjIysdr8BAwaoV69emjdvXrW3/8///I9+/etf69ixYwoPD5ckpaam6plnntGJEycUEBBw3pqWLJGSkiy53TbZ9KMsNVdeXp6cTuelPVgA9RKHCYArbOPGjQoNDfUEAUmKjY2V3W7Xpk2batx3xYoVatGihbp166YpU6bo9OnTXvN2797dEwQkKS4uTi6XS99+++155/z++yJPEJAkS2VrBo4erdPDA9AAcJgAuMKysrIUFhbmta1Ro0Zq3ry5srKyzrvfgw8+qOuvv16RkZH65ptv9Mwzz2jPnj165513PPOeGwQkea7XNO+MGf+Q251QZfsPP0g33ljbRwWgISEMAHU0efJkvfDCCzWO2bVrV53nT0pK8vzevXt3tWrVSgMHDtT333+vTp061XneadMe0BtvVHYGKnTsWOcpAdRzhAGgjiZOnKiEhIQax3Ts2FERERE6fvy41/azZ88qJydHERERtb6/mJgYSdK+ffvUqVMnRUREaPPmzV5jsrOzJanGeTt1CtTixdKjSZZK3TbZdVZuSa1b17oUAA0MYQCoo5YtW6ply5YXHNe/f3/l5uZq69at6tOnjyTp008/ldvt9vwHXxs7duyQJLVq1coz71/+8hcdP37ccxgiPT1dTqdTXbt2rXGuUaOkuOu2at+9Tym87Rl1PVzrMgA0QCwgBK6wG2+8UfHx8Ro9erQ2b96sL774QuPGjdOwYcM8nyQ4evSounTp4nmn//3332vmzJnaunWrDhw4oPfff18jR47Ur371K/Xo0UOSNGjQIHXt2lUPPfSQvv76a3388cf685//rLFjxyowMPCCdbVpfloDtEGtA09duQcPoF4gDAA+sGLFCnXp0kUDBw7U3Xffrdtuu02LFy/23F5SUqI9e/Z4Pi0QEBCgf/7znxo0aJC6dOmiiRMn6r777tMHH3zg2cfhcGjt2rVyOBzq37+/RowYoZEjR3qdl6BGnI4YQDnOMwCYKj1dGjRIrptuUsi333KeAcBgdAYAU9EZAFCOMACYquI7DPhuAsB4hAHAVBWdATsvA4DpeBUATEVnAEA5wgBgKtYMAChHGABMVdEZIAwAxiMMAKaq6AxwmAAwHmEAMFVFZ4AFhIDxeBUATEVnAEA5wgBgKtYMAChHGABMRWcAQDnCAGAqPloIoBxhADAVJx0CUI4wAJiK0xEDKMerAGAqOgMAyhEGAFOxZgBAOcIAYCo6AwDKEQYAU9EZAFCOMACYipMOAShHGABMRWcAQDnCAGAqOgMAyhEGAFNxOmIA5QgDgKnoDAAoRxgATMWaAQDlCAOAqegMAChHGABMxZoBAOUIA4CpOEwAoBxhADAVpyMGUI4wAJiKrzAGUI5XAcBUdAYAlCMMAKZizQCAcoQBwAdycnI0fPhwOZ1OhYaGatSoUSooKDjv+AMHDshms1V7eeuttzzjqrt91apVtSuKzgCAcrwKAD4wfPhwZWZmKj09XSUlJUpMTFRSUpJWrlxZ7fi2bdsqMzPTa9vixYs1e/Zs3XXXXV7bX3vtNcXHx3uuh4aG1q4oOgMAyhEGgCts165dSktL05YtW9S3b19J0oIFC3T33Xdrzpw5ioyMrLKPw+FQRESE17Y1a9bo97//va699lqv7aGhoVXG1gonHQJQjsMEwBW2ceNGhYaGeoKAJMXGxsput2vTpk21mmPr1q3asWOHRo0aVeW2sWPHqkWLFoqOjtbSpUtlWVaNcxUVFcnlculsUZEk6XRx8UU8GgANEWEAuMKysrIUFhbmta1Ro0Zq3ry5srKyajXHkiVLdOONN+qWW27x2j5jxgy9+eabSk9P13333afHH39cCxYsqHGulJQUhYSE6MvPP5ckPTpu3EU8GgANEWEAqKPJkyefd5FfxWX37t2XfD8//fSTVq5cWW1XYOrUqbr11lvVu3dvPfPMM5o0aZJmz55d43xTpkxRXl6ebomJkSQtWrz4kmsEUL+xZgCoo4kTJyohIaHGMR07dlRERISOHz/utf3s2bPKycmp1bH+t99+W6dPn9bIkSMvODYmJkYzZ85UUVGRAgMDqx0TGBhYdlv54YTgpk0vOC+Aho0wANRRy5Yt1bJlywuO69+/v3Jzc7V161b16dNHkvTpp5/K7XYrpvzdeU2WLFmi3/72t7W6rx07dqhZs2bnDQJe+KIiAOV4FQCusBtvvFHx8fEaPXq0UlNTVVJSonHjxmnYsGGeTxIcPXpUAwcO1Ouvv67o6GjPvvv27dNnn32mjz76qMq8H3zwgbKzs9WvXz8FBQUpPT1dzz//vJ566qnaFVbxaQJORwwYjzAA+MCKFSs0btw4DRw4UHa7Xffdd5/mz5/vub2kpER79uzR6dOnvfZbunSp2rRpo0GDBlWZs3Hjxlq4cKGefPJJWZalqKgozZ07V6NHj65dUXQGAJSzWRf6HBKAhunGG6Xdu+Vau1Yhv/618vLy5HQ6/V0VAD+gPwiYis4AgHKEAcBUnI4YQDnCAGAqTkcMoBxhADAVnQEA5QgDgKn4CmMA5QgDgKnoDAAoRxgATEVnAEA5wgBgqorOAGcgBIzHqwBgKjoDAMoRBgBTsWYAQDnCAGAqOgMAyhEGABNZluR2l/1OZwAwHmEAMFHFIQKJMACAMAAYiTAA4ByEAcBEFesFJMIAAMIAYKRzOwMsIASMRxgATERnAMA5CAOAiVgzAOAchAHARBVhwGbjdMQACAOAkTjhEIBzEAYAE3EqYgDnIAwAJqIzAOAchAHARHQGAJyDMACYiM4AgHMQBgAT0RkAcA7CAGAiOgMAzkEYAExEZwDAOQgDgIkqOgOEAQAiDABmqugMcJgAgAgDgJnoDAA4B2EAMBGdAQDnIAwAPvCXv/xFt9xyi4KDgxUaGlqrfSzL0rRp09SqVSs1adJEsbGx2rt3r9eYnJwcDR8+XE6nU6GhoRo1apQKCgouPDmdAQDn8HsYOHJEWreu7Gd11/09xt/3T40No8aTJ4PUq9eTGjFicq3nefHFFzVv3tsaO/Ztvf/+Nl1zzTWKi4vTvn1nPOOGDx+u7dtPaNasTVq69BN99tlnSkpKunCNdAYAnMvyo1dftSy73bKksp8PP+x9/dVX/TvG3/dPjQ2vRput1GrSZNwF53nlFbfldD5p2Wylnm3z5xdajRo9atls7vJtbktaWv6zbMyECd9Y0iivbdXVaH3wQdmGm2+28vLyLElWXl6eL//8AVxFbJZlWf4IIUeOSNe3c8ttnducsCTZPNfsKnv34pbDL2P8ff/U2DBrtOmsbLL5rUaHSvVDeIzaZW/V2ehoZf2//6e2bdsqLy9PTqdTAMzjtx7h3r36WRCQzn3Bkn7+gub7Mf6+f2psmDVaaqSqCdx3NZbKoR+yr1U7SSs3b9bDbdtWsw8Ak/htzUDnzpLd/vOXRO/rdpslu81/Y/x9/9RY/2qUzkrl785rGnOheWw2d5V57DZLNrkvWOOF7t9htxT5+nQVfvKJhpw8qcOHDwuA2fwWBtq0kRYvtnkWMzsc0sMPe19f/IpNi1/x3xh/3z81Xu01WhoyJM8Tau12SzNnntDMmcc92xwOy2seu92tJk2evOB9Pf/8KUlJ58xTdv9RnWeXB4WyuRo3Xum1399T3bLZHpPd7j7v3IsW2/R/HrpD1/zf/yvndddxaACA/LZmoMKRI9K+fVJUVFlA+Pl1f4/x9/1TY8Oqcdu2NzVjRpJyc3NrnKd1a0uRkZF65JFnNXDgo4qKkpxOl8LCwvTSS6t10033yLL26s47/48+/PBrBQf3UFSU9N13nyg+Pl6bNx9TQUFEjTVWcLlcCgkJYc0AYDC/hwHABIcOHVJOTo7ef/99zZ49W59//rkkKSoqStdee60kqUuXLkpJSdG9994rSXrhhRc0a9YsLV++XB06dNDUqVP1zTff6LvvvlNQUJAk6a677lJ2drZSU1NVUlKixMRE9e3bVytXrqx1bYQBAHzIGPCBadOmafny5Z7rvXv3liStW7dOAwYMkCTt2bNHeXl5njGTJk1SYWGhkpLKOgm33Xab0tLSPEFAklasWKFx48Zp4MCBstvtuu+++zR//nzfPCgADQadAcBwdAYA+P0MhAAAwL8IAwAAGI4wAACA4QgDAAAYjjAAAIDhCAMAABiOMAAAgOEIAwAAGI4wAACA4QgDAAAYjjAAAIDh+G4CwHCWZSk/P19NmzaVzWbzdzkA/IAwAACA4ThMAACA4QgDAAAYjjAAAIDhCAMAABiOMAAAgOEIAwAAGI4wAACA4f4/Hd66ui/NY9oAAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def dfdx_abs(x):\n", + " if x > 0:\n", + " return 1\n", + " else:\n", + " if x < 0:\n", + " return -1\n", + " else:\n", + " return None\n", + "\n", + "plot_f1_and_f2(np.vectorize(dfdx_abs), np.gradient(abs(x_array_2), x_array_2))" + ] + }, + { + "cell_type": "markdown", + "id": "229fdab5", + "metadata": {}, + "source": [ + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "00ccb653", + "metadata": {}, + "source": [ + "You can see that the results near the \"jump\" are $0.5$ and $-0.5$, while they should be $1$ and $-1$. These cases can give significant errors in the computations.\n", + "\n", + "But the biggest problem with the numerical differentiation is slow speed. It requires function evalutation every time. In machine learning models there are hundreds of parameters and there are hundreds of derivatives to be calculated, performing full function evaluation every time slows down the computation process. You will see the example of it below." + ] + }, + { + "cell_type": "markdown", + "id": "2caeb33f", + "metadata": {}, + "source": [ + "\n", + "## 4 - Automatic Differentiation" + ] + }, + { + "cell_type": "markdown", + "id": "eba8f444", + "metadata": {}, + "source": [ + "**Automatic differentiation** (autodiff) method breaks down the function into common functions ($sin$, $cos$, $log$, power functions, etc.), and constructs the computational graph consisting of the basic functions. Then the chain rule is used to compute the derivative at any node of the graph. It is the most commonly used approach in machine learning applications and neural networks, as the computational graph for the function and its derivatives can be built during the construction of the neural network, saving in future computations.\n", + "\n", + "The main disadvantage of it is implementational difficulty. However, nowadays there are libraries that are convenient to use, such as [MyGrad](https://mygrad.readthedocs.io/en/latest/index.html), [Autograd](https://autograd.readthedocs.io/en/latest/) and [JAX](https://jax.readthedocs.io/en/latest/). `Autograd` and `JAX` are the most commonly used in the frameworks to build neural networks. `JAX` brings together `Autograd` functionality for optimization problems, and `XLA` (Accelerated Linear Algebra) compiler for parallel computing.\n", + "\n", + "The syntax of `Autograd` and `JAX` are slightly different. It would be overwhelming to cover both at this stage. In this notebook you will be performing automatic differentiation using one of them: `JAX`." + ] + }, + { + "cell_type": "markdown", + "id": "20071067", + "metadata": {}, + "source": [ + "\n", + "### 4.1 - Introduction to `JAX`" + ] + }, + { + "cell_type": "markdown", + "id": "f444d827", + "metadata": {}, + "source": [ + "To begin with, load the required libraries. From `jax` package you need to load just a couple of functions for now (`grad` and `vmap`). Package `jax.numpy` is a wrapped `NumPy`, which pretty much replaces `NumPy` when `JAX` is used. It can be loaded as `np` as if it was an original `NumPy` in most of the cases. However, in this notebook you'll upload it as `jnp` to distinguish them for now." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "85d818ea", + "metadata": {}, + "outputs": [], + "source": [ + "from jax import grad, vmap\n", + "import jax.numpy as jnp" + ] + }, + { + "cell_type": "markdown", + "id": "d42ede8e", + "metadata": {}, + "source": [ + "Create a new `jnp` array and check its type." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "8856647c", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:jax._src.lib.xla_bridge:No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Type of NumPy array: \n", + "Type of JAX NumPy array: \n" + ] + } + ], + "source": [ + "x_array_jnp = jnp.array([1., 2., 3.])\n", + "\n", + "print(\"Type of NumPy array:\", type(x_array))\n", + "print(\"Type of JAX NumPy array:\", type(x_array_jnp))\n", + "# Please ignore the warning message if it appears." + ] + }, + { + "cell_type": "markdown", + "id": "730a2dd3", + "metadata": {}, + "source": [ + "The same array can be created just converting previously defined `x_array = np.array([1, 2, 3])`, although in some cases `JAX` does not operate with integers, thus the values need to be converted to floats. You will see an example of it below." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "3008671b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "JAX NumPy array: [1. 2. 3.]\n", + "Type of JAX NumPy array: \n" + ] + } + ], + "source": [ + "x_array_jnp = jnp.array(x_array.astype('float32'))\n", + "print(\"JAX NumPy array:\", x_array_jnp)\n", + "print(\"Type of JAX NumPy array:\", type(x_array_jnp))" + ] + }, + { + "cell_type": "markdown", + "id": "f81ce077", + "metadata": {}, + "source": [ + "Note, that `jnp` array has a specific type `jaxlib.xla_extension.DeviceArray`. In most of the cases the same operators and functions are applicable to them as in the original `NumPy`, for example:" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "742003ec", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[2. 4. 6.]\n", + "3.0\n" + ] + } + ], + "source": [ + "print(x_array_jnp * 2)\n", + "print(x_array_jnp[2])" + ] + }, + { + "cell_type": "markdown", + "id": "3c7ef8a4", + "metadata": {}, + "source": [ + "But sometimes working with `jnp` arrays the approach needs to be changed. In the following code, trying to assign a new value to one of the elements, you will get an error:" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "3fc00cab", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "'' object does not support item assignment. JAX arrays are immutable. Instead of ``x[idx] = y``, use ``x = x.at[idx].set(y)`` or another .at[] method: https://jax.readthedocs.io/en/latest/_autosummary/jax.numpy.ndarray.at.html\n" + ] + } + ], + "source": [ + "try:\n", + " x_array_jnp[2] = 4.0\n", + "except TypeError as err:\n", + " print(err)" + ] + }, + { + "cell_type": "markdown", + "id": "cf9e29fe", + "metadata": {}, + "source": [ + "To assign a new value to an element in the `jnp` array you need to apply functions `.at[i]`, stating which element to update, and `.set(value)` to set a new value. These functions also operate **out-of-place**, the updated array is returned as a new array and the original array is not modified by the update." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "ffc53ad2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1. 2. 4.]\n" + ] + } + ], + "source": [ + "y_array_jnp = x_array_jnp.at[2].set(4.0)\n", + "print(y_array_jnp)" + ] + }, + { + "cell_type": "markdown", + "id": "05a07ce0", + "metadata": {}, + "source": [ + "Although, some of the `JAX` functions will work with arrays defined with `np` and `jnp`. In the following code you will get the same result in both lines:" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "5b80429d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0. 0.6931472 1.0986123]\n", + "[0. 0.6931472 1.0986123]\n" + ] + } + ], + "source": [ + "print(jnp.log(x_array))\n", + "print(jnp.log(x_array_jnp))" + ] + }, + { + "cell_type": "markdown", + "id": "89397092", + "metadata": {}, + "source": [ + "This is probably confusing - which `NumPy` to use then? Usually when `JAX` is used, only `jax.numpy` gets imported as `np`, and used instead of the original one." + ] + }, + { + "cell_type": "markdown", + "id": "20f12b94", + "metadata": {}, + "source": [ + " \n", + "### 4.2 - Automatic Differentiation with `JAX` " + ] + }, + { + "cell_type": "markdown", + "id": "9cd26792", + "metadata": {}, + "source": [ + "Time to do automatic differentiation with `JAX`. The following code will calculate the derivative of the previously defined function $f\\left(x\\right) = x^2$ at the point $x = 3$:" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "070e417a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Function value at x = 3: 9.0\n", + "Derivative value at x = 3: 6.0\n" + ] + } + ], + "source": [ + "print(\"Function value at x = 3:\", f(3.0))\n", + "print(\"Derivative value at x = 3:\",grad(f)(3.0))" + ] + }, + { + "cell_type": "markdown", + "id": "3514bda9", + "metadata": {}, + "source": [ + "Very easy, right? Keep in mind, please, that this cannot be done using integers. The following code will output an error:" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "a50295a3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "grad requires real- or complex-valued inputs (input dtype that is a sub-dtype of np.inexact), but got int32. If you want to use Boolean- or integer-valued inputs, use vjp or set allow_int to True.\n" + ] + } + ], + "source": [ + "try:\n", + " grad(f)(3)\n", + "except TypeError as err:\n", + " print(err)" + ] + }, + { + "cell_type": "markdown", + "id": "872bbbc6", + "metadata": {}, + "source": [ + "Try to apply the `grad` function to an array, calculating the derivative for each of its elements: " + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "caf0e431", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Gradient only defined for scalar-output functions. Output had shape: (3,).\n" + ] + } + ], + "source": [ + "try:\n", + " grad(f)(x_array_jnp)\n", + "except TypeError as err:\n", + " print(err)" + ] + }, + { + "cell_type": "markdown", + "id": "9452ebc2", + "metadata": {}, + "source": [ + "There is some broadcasting issue there. You don't need to get into more details of this at this stage, function `vmap` can be used here to solve the problem.\n", + "\n", + "*Note*: Broadcasting is covered in the Course 1 of this Specialization \"Linear Algebra\". You can also review it in the documentation [here](https://numpy.org/doc/stable/user/basics.broadcasting.html#:~:text=The%20term%20broadcasting%20describes%20how,that%20they%20have%20compatible%20shapes.)." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "f9b28641", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[2. 4. 6.]\n" + ] + } + ], + "source": [ + "dfdx_jax_vmap = vmap(grad(f))(x_array_jnp)\n", + "print(dfdx_jax_vmap)" + ] + }, + { + "cell_type": "markdown", + "id": "933e382f", + "metadata": {}, + "source": [ + "Great, now `vmap(grad(f))` can be used to calculate the derivative of function `f` for arrays of larger size and you can plot the output:" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "da0a1262", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_f1_and_f2(f, vmap(grad(f)))" + ] + }, + { + "cell_type": "markdown", + "id": "4162d5e5", + "metadata": {}, + "source": [ + "In the following code you can comment/uncomment lines to visualize the common derivatives. All of them are found using `JAX` automatic differentiation. The results look pretty good!" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "f68b4c0e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def g(x):\n", + "# return x**3\n", + "# return 2*x**3 - 3*x**2 + 5\n", + " return 1/x\n", + "# return jnp.exp(x)\n", + "# return jnp.log(x)\n", + "# return jnp.sin(x)\n", + "# return jnp.cos(x)\n", + "# return jnp.abs(x)\n", + "# return jnp.abs(x)+jnp.sin(x)*jnp.cos(x)\n", + "\n", + "plot_f1_and_f2(g, vmap(grad(g)))" + ] + }, + { + "cell_type": "markdown", + "id": "a58ee858", + "metadata": {}, + "source": [ + "\n", + "## 5 - Computational Efficiency of Symbolic, Numerical and Automatic Differentiation" + ] + }, + { + "cell_type": "markdown", + "id": "2211158e", + "metadata": {}, + "source": [ + "In sections [2.3](#2.3) and [3.2](#3.2) low computational efficiency of symbolic and numerical differentiation was discussed. Now it is time to compare speed of calculations for each of three approaches. Try to find the derivative of the same simple function $f\\left(x\\right) = x^2$ multiple times, evaluating it for an array of a larger size, compare the results and time used:" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "36c42dac", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Results\n", + "Symbolic Differentiation:\n", + "[-10. -9.99998 -9.99996 ... 9.99996 9.99998 10. ]\n", + "Numerical Differentiation:\n", + "[-9.99999 -9.99998 -9.99996 ... 9.99996 9.99998 9.99999]\n", + "Automatic Differentiation:\n", + "[-10. -9.99998 -9.99996 ... 9.99996 9.99998 10. ]\n", + "\n", + "\n", + "Time\n", + "Symbolic Differentiation:\n", + "3.7751197814941406 ms\n", + "Numerical Differentiation:\n", + "41.34178161621094 ms\n", + "Automatic Differentiation:\n", + "6.722927093505859 ms\n" + ] + } + ], + "source": [ + "import timeit, time\n", + "\n", + "x_array_large = np.linspace(-5, 5, 1000000)\n", + "\n", + "tic_symb = time.time()\n", + "res_symb = lambdify(x, diff(f(x),x),'numpy')(x_array_large)\n", + "toc_symb = time.time()\n", + "time_symb = 1000 * (toc_symb - tic_symb) # Time in ms.\n", + "\n", + "tic_numerical = time.time()\n", + "res_numerical = np.gradient(f(x_array_large),x_array_large)\n", + "toc_numerical = time.time()\n", + "time_numerical = 1000 * (toc_numerical - tic_numerical)\n", + "\n", + "tic_jax = time.time()\n", + "res_jax = vmap(grad(f))(jnp.array(x_array_large.astype('float32')))\n", + "toc_jax = time.time()\n", + "time_jax = 1000 * (toc_jax - tic_jax)\n", + "\n", + "print(f\"Results\\nSymbolic Differentiation:\\n{res_symb}\\n\" + \n", + " f\"Numerical Differentiation:\\n{res_numerical}\\n\" + \n", + " f\"Automatic Differentiation:\\n{res_jax}\")\n", + "\n", + "print(f\"\\n\\nTime\\nSymbolic Differentiation:\\n{time_symb} ms\\n\" + \n", + " f\"Numerical Differentiation:\\n{time_numerical} ms\\n\" + \n", + " f\"Automatic Differentiation:\\n{time_jax} ms\")" + ] + }, + { + "cell_type": "markdown", + "id": "493e5457", + "metadata": {}, + "source": [ + "The results are pretty much the same, but the time used is different. Numerical approach is obviously inefficient when differentiation needs to be performed many times, which happens a lot training machine learning models. Symbolic and automatic approach seem to be performing similarly for this simple example. But if the function becomes a little bit more complicated, symbolic computation will experiance significant expression swell and the calculations will slow down.\n", + "\n", + "*Note*: Sometimes the execution time results may vary slightly, especially for automatic differentiation. You can run the code above a few time to see different outputs. That does not influence the conclusion that numerical differentiation is slower. `timeit` module can be used more efficiently to evaluate execution time of the codes, but that would unnecessary overcomplicate the codes here.\n", + "\n", + "Try to define some polynomial function, which should not be that hard to differentiate, and compare the computation time for its differentiation symbolically and automatically:" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "13047a93", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Results\n", + "Symbolic Differentiation:\n", + "[2.88570423e+24 2.88556400e+24 2.88542377e+24 ... 1.86202587e+22\n", + " 1.86213384e+22 1.86224181e+22]\n", + "Automatic Differentiation:\n", + "[2.8857043e+24 2.8855642e+24 2.8854241e+24 ... 1.8620253e+22 1.8621349e+22\n", + " 1.8622416e+22]\n", + "\n", + "\n", + "Time\n", + "Symbolic Differentiation:\n", + "355.32283782958984 ms\n", + "Automatic Differentiation:\n", + "204.75244522094727 ms\n" + ] + } + ], + "source": [ + "def f_polynomial_simple(x):\n", + " return 2*x**3 - 3*x**2 + 5\n", + "\n", + "def f_polynomial(x):\n", + " for i in range(3):\n", + " x = f_polynomial_simple(x)\n", + " return x\n", + "\n", + "tic_polynomial_symb = time.time()\n", + "res_polynomial_symb = lambdify(x, diff(f_polynomial(x),x),'numpy')(x_array_large)\n", + "toc_polynomial_symb = time.time()\n", + "time_polynomial_symb = 1000 * (toc_polynomial_symb - tic_polynomial_symb)\n", + "\n", + "tic_polynomial_jax = time.time()\n", + "res_polynomial_jax = vmap(grad(f_polynomial))(jnp.array(x_array_large.astype('float32')))\n", + "toc_polynomial_jax = time.time()\n", + "time_polynomial_jax = 1000 * (toc_polynomial_jax - tic_polynomial_jax)\n", + "\n", + "print(f\"Results\\nSymbolic Differentiation:\\n{res_polynomial_symb}\\n\" + \n", + " f\"Automatic Differentiation:\\n{res_polynomial_jax}\")\n", + "\n", + "print(f\"\\n\\nTime\\nSymbolic Differentiation:\\n{time_polynomial_symb} ms\\n\" + \n", + " f\"Automatic Differentiation:\\n{time_polynomial_jax} ms\")" + ] + }, + { + "cell_type": "markdown", + "id": "231c9da0", + "metadata": {}, + "source": [ + "Again, the results are similar, but automatic differentiation is times faster. \n", + "\n", + "With the increase of function computation graph, the efficiency of automatic differentiation compared to other methods raises, because autodiff method uses chain rule!\n", + "\n", + "Congratulations! Now you are equiped with Python tools to perform differentiation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "93dfd229", + "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.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}