├── .gitignore ├── Coding_practices.ipynb ├── LICENSE ├── Lab1 └── Lab1_addendum.ipynb ├── Numba example.ipynb ├── Pretty_plots.ipynb ├── README.md ├── Reference_arrays_args.ipynb └── fast_python.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | \.ipynb_checkpoints/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Pierre Ablin 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Lab1/Lab1_addendum.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "\n", 11 | "from numpy.linalg import norm" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "# A word on global variables and closures" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "Consider the following (simplified, and with wrong formulas) design, inspired by TP1:" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": 2, 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "def simu_linreg():\n", 35 | " np.random.seed(24) # seed to always get the same A and b\n", 36 | " A = np.random.randn(10, 10)\n", 37 | " b = A.dot(np.random.randn(10)) # values in ]- \\infty, \\infty[]\n", 38 | " return A, b\n", 39 | " \n", 40 | "def simu_logreg():\n", 41 | " np.random.seed(24) # seed to always get the same A and b\n", 42 | " A = np.random.randn(10, 10)\n", 43 | " b = np.sign(A.dot(np.random.randn(10))) # values exactly -1 or 1\n", 44 | " return A, b\n", 45 | "\n", 46 | "def loss_linreg(x):\n", 47 | " return norm(A.dot(x) - b)\n", 48 | "\n", 49 | "def loss_logreg(x):\n", 50 | " return norm(np.exp(A.dot(x) * b))" 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "metadata": {}, 56 | "source": [ 57 | "It is a bad design, because loss_linreg and loss_logreg use two global variables, `A` and `b`, which are not meant to be the same (in the TP case, for linreg b has values in $[-\\infty, + \\infty]$.\n", 58 | " whereas for logreg b has values in {-1, 1}.\n", 59 | " \n", 60 | " \n", 61 | "`A` and `b` must be defined as global variables before we call loss_linreg():" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": 3, 67 | "metadata": {}, 68 | "outputs": [ 69 | { 70 | "ename": "NameError", 71 | "evalue": "name 'A' is not defined", 72 | "output_type": "error", 73 | "traceback": [ 74 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 75 | "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", 76 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0marange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mloss_linreg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", 77 | "\u001b[0;32m\u001b[0m in \u001b[0;36mloss_linreg\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 13\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mloss_linreg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 14\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mnorm\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mA\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0mb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 15\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 16\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mloss_logreg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 78 | "\u001b[0;31mNameError\u001b[0m: name 'A' is not defined" 79 | ] 80 | } 81 | ], 82 | "source": [ 83 | "x = np.arange(10)\n", 84 | "loss_linreg(x)" 85 | ] 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "metadata": {}, 90 | "source": [ 91 | "So we fix that by instantiating A and b:" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": 4, 97 | "metadata": {}, 98 | "outputs": [ 99 | { 100 | "data": { 101 | "text/plain": [ 102 | "64.08451461074556" 103 | ] 104 | }, 105 | "execution_count": 4, 106 | "metadata": {}, 107 | "output_type": "execute_result" 108 | } 109 | ], 110 | "source": [ 111 | "A, b = simu_linreg()\n", 112 | "loss_linreg(x)" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": 5, 118 | "metadata": {}, 119 | "outputs": [ 120 | { 121 | "data": { 122 | "text/plain": [ 123 | "319044211109848.7" 124 | ] 125 | }, 126 | "execution_count": 5, 127 | "metadata": {}, 128 | "output_type": "execute_result" 129 | } 130 | ], 131 | "source": [ 132 | "# we do it for logreg too:\n", 133 | "A, b = simu_logreg()\n", 134 | "loss_logreg(x)" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "metadata": {}, 140 | "source": [ 141 | "So now everything works fine? No: if we call again `loss_linreg()`, A and b now refer to variables generated by `simu_logreg()` in the previous cell, so the value is not the old one:" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 7, 147 | "metadata": {}, 148 | "outputs": [ 149 | { 150 | "data": { 151 | "text/plain": [ 152 | "65.18082850239307" 153 | ] 154 | }, 155 | "execution_count": 7, 156 | "metadata": {}, 157 | "output_type": "execute_result" 158 | } 159 | ], 160 | "source": [ 161 | "loss_linreg(x) # slightly different from what we had earlier" 162 | ] 163 | }, 164 | { 165 | "cell_type": "markdown", 166 | "metadata": {}, 167 | "source": [ 168 | "The hack that you saw in class is to define different variables for linreg and logreg so that the name won't clash:" 169 | ] 170 | }, 171 | { 172 | "cell_type": "code", 173 | "execution_count": 9, 174 | "metadata": {}, 175 | "outputs": [ 176 | { 177 | "data": { 178 | "text/plain": [ 179 | "64.08451461074556" 180 | ] 181 | }, 182 | "execution_count": 9, 183 | "metadata": {}, 184 | "output_type": "execute_result" 185 | } 186 | ], 187 | "source": [ 188 | "def loss_linreg_v1(x):\n", 189 | " return norm(A_linreg.dot(x) - b_linreg)\n", 190 | "\n", 191 | "A_linreg, b_linreg = simu_linreg()\n", 192 | "A_logreg, b_logreg = simu_logreg()\n", 193 | "loss_linreg_v1(x) # the good one" 194 | ] 195 | }, 196 | { 197 | "cell_type": "markdown", 198 | "metadata": {}, 199 | "source": [ 200 | "But this is a bit ugly. The clean solution #1 is to use A and b as parameters:" 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": 10, 206 | "metadata": {}, 207 | "outputs": [], 208 | "source": [ 209 | "def loss_linreg_v2(x, A, b):\n", 210 | " return norm(A.dot(x) - b)" 211 | ] 212 | }, 213 | { 214 | "cell_type": "markdown", 215 | "metadata": {}, 216 | "source": [ 217 | "But then you can't just call loss_linreg_v2(x), and passing A and b to ISTA and FISTA is a bit heavy:" 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": 11, 223 | "metadata": {}, 224 | "outputs": [ 225 | { 226 | "ename": "TypeError", 227 | "evalue": "loss_linreg_v2() missing 2 required positional arguments: 'A' and 'b'", 228 | "output_type": "error", 229 | "traceback": [ 230 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 231 | "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", 232 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mloss_linreg_v2\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", 233 | "\u001b[0;31mTypeError\u001b[0m: loss_linreg_v2() missing 2 required positional arguments: 'A' and 'b'" 234 | ] 235 | } 236 | ], 237 | "source": [ 238 | "loss_linreg_v2(x)" 239 | ] 240 | }, 241 | { 242 | "cell_type": "markdown", 243 | "metadata": {}, 244 | "source": [ 245 | "You don't want to do this:" 246 | ] 247 | }, 248 | { 249 | "cell_type": "code", 250 | "execution_count": 12, 251 | "metadata": {}, 252 | "outputs": [], 253 | "source": [ 254 | "def loss_linreg_v2(x):\n", 255 | " A, b = simu_linreg()\n", 256 | " return norm(A.dot(x) - b)" 257 | ] 258 | }, 259 | { 260 | "cell_type": "markdown", 261 | "metadata": {}, 262 | "source": [ 263 | "because you would create a new dataset everytime you compute the loss" 264 | ] 265 | }, 266 | { 267 | "cell_type": "markdown", 268 | "metadata": {}, 269 | "source": [ 270 | "Solution #2 (cleanest one) is to use a closure: a function which defines a function inside, and returns this function:" 271 | ] 272 | }, 273 | { 274 | "cell_type": "code", 275 | "execution_count": 13, 276 | "metadata": {}, 277 | "outputs": [ 278 | { 279 | "name": "stdout", 280 | "output_type": "stream", 281 | "text": [ 282 | "64.08451461074556\n", 283 | "64.08451461074556\n" 284 | ] 285 | } 286 | ], 287 | "source": [ 288 | "def create_loss_linreg():\n", 289 | " A, b = simu_linreg() # now A and b are defined in this scope, not as global variables.\n", 290 | " # /!\\ A and b could also be arguments of create_loss_linreg().\n", 291 | " \n", 292 | " # I define a function inside the closure\n", 293 | " def loss_linreg(x):\n", 294 | " return norm(A.dot(x) - b)\n", 295 | " \n", 296 | " # I return this function:\n", 297 | " return loss_linreg\n", 298 | "\n", 299 | "\n", 300 | "loss_linreg_v3 = create_loss_linreg() # create_loss_linreg returns a function\n", 301 | "print(loss_linreg_v3(x))\n", 302 | "\n", 303 | "# now changing A and b as global variables doesn't affect the function:\n", 304 | "A, b = simu_logreg()\n", 305 | "print(loss_linreg_v3(x)) # the same value, it has not changed" 306 | ] 307 | }, 308 | { 309 | "cell_type": "markdown", 310 | "metadata": {}, 311 | "source": [ 312 | "As a conclusion:\n", 313 | "- either you go with the original design, and you *make sure to redefine A and b* when you change between algorithms\n", 314 | "- either you create a closure, and you recreate a new loss_function with it everytime you change algorithm or design" 315 | ] 316 | } 317 | ], 318 | "metadata": { 319 | "anaconda-cloud": {}, 320 | "kernelspec": { 321 | "display_name": "Python [conda env:anaconda3]", 322 | "language": "python", 323 | "name": "conda-env-anaconda3-py" 324 | }, 325 | "language_info": { 326 | "codemirror_mode": { 327 | "name": "ipython", 328 | "version": 3 329 | }, 330 | "file_extension": ".py", 331 | "mimetype": "text/x-python", 332 | "name": "python", 333 | "nbconvert_exporter": "python", 334 | "pygments_lexer": "ipython3", 335 | "version": "3.6.6" 336 | } 337 | }, 338 | "nbformat": 4, 339 | "nbformat_minor": 2 340 | } 341 | -------------------------------------------------------------------------------- /Numba example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Numba: fast 'for' loops in python\n", 8 | "\n", 9 | "Author: Pierre Ablin, Mathurin Massias\n", 10 | "\n", 11 | "\n", 12 | "\n", 13 | "\n", 14 | "Numba is a Python package that does Just In Time compilation. It can greatly accelerate Python `for` loops. It implements most Python/Numpy operations.\n", 15 | "\n", 16 | "To install it, simply do `conda install numba` or `pip install numba`. Be sure to have an up-to-date version:`pip install --upgrade numba`.\n", 17 | "\n", 18 | "## First example\n", 19 | "\n", 20 | "Say you want to compute $\\sum_{i=1}^n \\frac{1}{i^2}$. The following code does it in pure Python:" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 1, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "def sum_python(n):\n", 30 | " output = 0.\n", 31 | " for i in range(1, n + 1):\n", 32 | " output += 1. / i ** 2\n", 33 | " return output" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 2, 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "name": "stdout", 43 | "output_type": "stream", 44 | "text": [ 45 | "2.88 ms ± 63.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" 46 | ] 47 | } 48 | ], 49 | "source": [ 50 | "%timeit sum_python(10000)" 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "metadata": {}, 56 | "source": [ 57 | "### Numpy\n", 58 | "To accelerate this loop, you can vectorize it using Numpy:" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 3, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "import numpy as np\n", 68 | "\n", 69 | "\n", 70 | "def sum_numpy(n):\n", 71 | " return np.sum(1. / np.arange(1, n + 1) ** 2)" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": 4, 77 | "metadata": {}, 78 | "outputs": [ 79 | { 80 | "name": "stdout", 81 | "output_type": "stream", 82 | "text": [ 83 | "34.1 µs ± 2 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n" 84 | ] 85 | } 86 | ], 87 | "source": [ 88 | "%timeit sum_numpy(10000)" 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "metadata": {}, 94 | "source": [ 95 | "### Numba\n", 96 | "\n", 97 | "You can also use the `@njit` decorator from Numba. Simply put it on top of the python function:" 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": 5, 103 | "metadata": {}, 104 | "outputs": [], 105 | "source": [ 106 | "from numba import njit\n", 107 | "\n", 108 | "@njit\n", 109 | "def sum_numba(n):\n", 110 | " output = 0.\n", 111 | " for i in range(1, n + 1):\n", 112 | " output += 1. / i ** 2\n", 113 | " return output" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": 6, 119 | "metadata": {}, 120 | "outputs": [ 121 | { 122 | "name": "stdout", 123 | "output_type": "stream", 124 | "text": [ 125 | "12.7 µs ± 192 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)\n" 126 | ] 127 | } 128 | ], 129 | "source": [ 130 | "%timeit sum_numba(10000)" 131 | ] 132 | }, 133 | { 134 | "cell_type": "markdown", 135 | "metadata": {}, 136 | "source": [ 137 | "Orders of magnitude faster than pure Python code, and also (for this example) faster than Numpy !" 138 | ] 139 | }, 140 | { 141 | "cell_type": "markdown", 142 | "metadata": {}, 143 | "source": [ 144 | "## Second example: stochastic gradients\n", 145 | "\n", 146 | "Numba can be very handy when coding a stochastic algorithm. Indeed, computing a stochastic gradient can be a very fast operation, hence coding a `for` loop over it in pure Python can slow the code down. \n", 147 | "\n", 148 | "Take the ridge regression $ \\min f(x) = \\frac 1n \\sum_{i=1}^n f_i(x)$ where:\n", 149 | "\n", 150 | "$$f_i(x) = \\frac{1}{2}(a_i^\\top x- b_i)^2 + \\frac \\lambda 2 \\|x\\|_2^2$$\n", 151 | "\n", 152 | "We have the stochastic gradients: $\\nabla f_i(x) = (a_i^\\top x - b_i) a_i + \\lambda x$,\n", 153 | "and the full batch gradient: $\\nabla f(x) = \\frac1n A^{\\top}(A x - b) + \\lambda x$" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": 7, 159 | "metadata": {}, 160 | "outputs": [], 161 | "source": [ 162 | "n, p = 100, 100\n", 163 | "\n", 164 | "A = np.random.randn(n, p)\n", 165 | "b = np.random.randn(n)\n", 166 | "\n", 167 | "lam = 0.1\n", 168 | "x = np.zeros(p)" 169 | ] 170 | }, 171 | { 172 | "cell_type": "markdown", 173 | "metadata": {}, 174 | "source": [ 175 | "### Numpy" 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": 8, 181 | "metadata": {}, 182 | "outputs": [], 183 | "source": [ 184 | "def grad_i(x, i, A, b, lam):\n", 185 | " ai = A[i]\n", 186 | " return (np.dot(ai, x) - b[i]) * ai + lam * x\n", 187 | "\n", 188 | "\n", 189 | "def sgd(x, max_iter, step, A, b, lam):\n", 190 | " n, _ = A.shape\n", 191 | " for i in range(max_iter):\n", 192 | " x -= step * grad_i(x, i % n, A, b, lam)\n", 193 | " return x" 194 | ] 195 | }, 196 | { 197 | "cell_type": "code", 198 | "execution_count": 9, 199 | "metadata": { 200 | "scrolled": false 201 | }, 202 | "outputs": [ 203 | { 204 | "name": "stdout", 205 | "output_type": "stream", 206 | "text": [ 207 | "4.22 µs ± 225 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)\n" 208 | ] 209 | } 210 | ], 211 | "source": [ 212 | "%timeit grad_i(x, 0, A, b, lam)" 213 | ] 214 | }, 215 | { 216 | "cell_type": "code", 217 | "execution_count": 10, 218 | "metadata": { 219 | "scrolled": true 220 | }, 221 | "outputs": [ 222 | { 223 | "name": "stdout", 224 | "output_type": "stream", 225 | "text": [ 226 | "5.86 ms ± 36.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" 227 | ] 228 | } 229 | ], 230 | "source": [ 231 | "%timeit sgd(x, 1000, 0.0001, A, b, lam)" 232 | ] 233 | }, 234 | { 235 | "cell_type": "markdown", 236 | "metadata": {}, 237 | "source": [ 238 | "### Numba" 239 | ] 240 | }, 241 | { 242 | "cell_type": "code", 243 | "execution_count": 11, 244 | "metadata": {}, 245 | "outputs": [], 246 | "source": [ 247 | "@njit\n", 248 | "def grad_i(x, i, A, b, lam):\n", 249 | " ai = A[i]\n", 250 | " return (np.dot(ai, x) - b[i]) * ai + lam * x\n", 251 | "\n", 252 | "\n", 253 | "@njit\n", 254 | "def sgd(x, max_iter, step, A, b, lam):\n", 255 | " n, _ = A.shape\n", 256 | " for i in range(max_iter):\n", 257 | " x -= step * grad_i(x, i % n, A, b, lam)\n", 258 | " return x" 259 | ] 260 | }, 261 | { 262 | "cell_type": "code", 263 | "execution_count": 12, 264 | "metadata": { 265 | "scrolled": false 266 | }, 267 | "outputs": [ 268 | { 269 | "name": "stdout", 270 | "output_type": "stream", 271 | "text": [ 272 | "1.02 µs ± 8.88 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)\n" 273 | ] 274 | } 275 | ], 276 | "source": [ 277 | "%timeit grad_i(x, 0, A, b, lam)" 278 | ] 279 | }, 280 | { 281 | "cell_type": "code", 282 | "execution_count": 13, 283 | "metadata": { 284 | "scrolled": true 285 | }, 286 | "outputs": [ 287 | { 288 | "name": "stdout", 289 | "output_type": "stream", 290 | "text": [ 291 | "328 µs ± 11.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n" 292 | ] 293 | } 294 | ], 295 | "source": [ 296 | "%timeit sgd(x, 1000, 0.0001, A, b, lam)" 297 | ] 298 | }, 299 | { 300 | "cell_type": "code", 301 | "execution_count": null, 302 | "metadata": {}, 303 | "outputs": [], 304 | "source": [] 305 | } 306 | ], 307 | "metadata": { 308 | "anaconda-cloud": {}, 309 | "kernelspec": { 310 | "display_name": "Python [default]", 311 | "language": "python", 312 | "name": "python3" 313 | }, 314 | "language_info": { 315 | "codemirror_mode": { 316 | "name": "ipython", 317 | "version": 3 318 | }, 319 | "file_extension": ".py", 320 | "mimetype": "text/x-python", 321 | "name": "python", 322 | "nbconvert_exporter": "python", 323 | "pygments_lexer": "ipython3", 324 | "version": "3.6.6" 325 | } 326 | }, 327 | "nbformat": 4, 328 | "nbformat_minor": 2 329 | } 330 | -------------------------------------------------------------------------------- /Pretty_plots.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Prettier plots with matplotlib\n", 8 | "\n", 9 | "Author: Pierre Ablin, Mathurin Massias\n", 10 | "\n", 11 | "\n", 12 | "How to update matplotlib's default parameter for more readable plots and better math rendering." 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 1, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "import numpy as np\n", 22 | "import matplotlib.pyplot as plt\n", 23 | "%matplotlib inline" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 2, 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "n_points = 1000\n", 33 | "y = 0.1 + 1. / np.arange(1, n_points + 1)" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 3, 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "data": { 43 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZMAAAEYCAYAAACZaxt6AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvIxREBQAAIABJREFUeJzt3Xm8XHV9//HXe+au2S4J2SAEQtgEeYgsUkAtFHBtwY3+KtSftFKp7U9rXWqltr+2VqutPtRaWyulFbeCCGjBDazIz6psQSgGMRjWJASSELIvd/v8/jhnbiaTO3NmzUzmvp+PxzzumXPOnPM5c5L7vt/vmTlfRQRmZmaNyLW7ADMzO/A5TMzMrGEOEzMza5jDxMzMGuYwMTOzhjlMzMysYQ4Tsw4k6UFJ57Rhv49LOr9F2/4XSX9RYflfSfpyK/ZtrdfT7gLswCfpduAkYGFE7G5zOV0hIp7f7hqaLSLeVphOg/LLEXFY+yqyZnLLxBoiaQnwUiCAC9tazAFC0pT7I05Svt01WGs5TKxRbwbuBK4GLq20oqTbJf2NpB9L2irpVklzi5ZfmHbvbErXPb5o2eOS3ivpAUmbJX1V0kCFfb1V0kPpfn4u6ZR0/vHptjel+7qw6DVXS/onSd9KX3eXpKPSZf8i6eMl+/hPSe9Opw+VdIOk9ZIek/RHRev9laTrJX1Z0hbgdyQNSvqCpOfSOt8naXXJ8Z5f9PrrJH0xretBSacVrXuKpPvSZV9L35sPlXlfjpJ0m6RnJW2Q9BVJB5VZN6vGrPfys5K+LWk78GvpvA9Jmg58BzhU0rb0cWj60r4Kx/m4pD9J/w1sl/RvkhZI+k66/n9Jml3u34S1WET44UfdD2Al8IfAqcAIsKDCurcDjwDHAoPp84+my44FtgMvA3qB96Xb7kuXPw7cDRwKzAEeAt5WZj+/CawBXgQIOBo4It3uSuDPgD7gXGArcFz6uquBjcDpJF3AXwGuTZf9KrAKUPp8NrAzrScH3Av833S7S4FHgVek6/5V+t68Nl13EPgo8P/S7RwGPACsLjqGx4Hzi16/C3g1kAc+AtyZLusDngDemR7f64Fh4ENl3puj0/e4H5gH/BD4VJn9lq2xyvdyM/Di9JgH0nkfSpefU3y8WcdZVNudwAJgEbAO+Clwcno8twF/2e7/E1P14ZaJ1U3SS0h+SV8XEfeSBMUlGS/7fEQ8HBE7geuAF6bzfwv4VkR8LyJGgI+T/NI9q+i1n46IpyJiI3Bz0WtL/R7w9xFxTyRWRsQTwBnADJIAG46I24BvAhcXvfbGiLg7IkZJwqSwj/8m6cp7afr8IuCOiHiKJLTmRcQH0+0+Cvwr8Mai7d4REd+IiPH02P8X8LcR8VxErAY+nfG+/Sgivh0RY8CXSK5RkR5TT/rejETEjSShO6n0vfheROyOiPXAJ4Czy6xeqcZq3sv/jIgfp8e8K+P4so6z4B8j4pmIWENyTu6KiPsiuVb3dZJgsTZwmFgjLgVujYgN6fP/IKOrC3i6aHoHyS8kSP7Cf6KwICLGSVoCi6p4banFJMFW6lBgVbrtgieq2UdEBHAte35ZXkISNpAE6qFpd88mSZtI/mJfULStVZPVUmF5qdK6BtJrL4cCa9L6Mrclab6kayWtSbvcvgzMLbN6pRqreS+zjmky5Y6z4Jmi6Z2TPC/3b8JazGFidZE0SPKX69mSnpb0NPAu4CRJpX9NVuMpkl/Khe2LJBTW1LGtVcBRZfaxWFLxv/vDa9jHNcBFko4AfgW4oWh/j0XEQUWPmRHx6qLXlt6eey1J11HB4iprKLUWWJS+X9Vs6yNpLS+IiFnAm0i6Asttu1yN1byXlW5J7tuVdxmHidXrtcAYcAJJV9ALgeNJuh7eXMf2rgN+XdJ5knqB9wC7gZ/Usa2rgPdKOlWJo9MAuIvkusz7JPUq+XjqBSQtjkwRcR+wPt3+LRGxKV10N7BF0p+mF63zkk6U9KKM471C0mxJi4C313GcAHeQnIe3S+qR9BqSaz7lzAS2AZvS/f5JnTU29F6StCgOljRU5frW4RwmVq9LSa5/PBkRTxcewGeA31aNH3+NiBUkfyX/I7CB5BfTBRExXGthEfE14MMk3W5bgW8Ac9JtXQi8Kt3HPwNvjohf1LD5a4Dz020X9jeW1vtC4LF021cBlX5RfhBYna7/X8D1JOFZk/SYXg9cBmwieQ+/WWFbfw2cQnJx/FvAjfXU2Oh7ma53DfBo2jV4aNZrrLNp765WM2sHSX8AvDEiyl0Mr2VbdwH/EhGfb7yyvbbbtBqt+7hlYtYGkg6R9GJJOUnHkXTrfb3ObZ0taWHazXUp8ALgu51Uo3W/KfdNXLMO0Qd8DjiSpHvqWpKuonocR3J9YwbJp9guioi1HVajdTl3c5mZWcPczWVmZg2bMt1cc+fOjSVLlrS7DDOzA8q99967ISLmZa03ZcJkyZIlLFu2rN1lmJkdUCQ9kb2Wu7nMzKwJHCZmZtYwh4mZmTXMYWJmZg1zmJiZWcMcJmZm1jCHiZmZNcxhkuHeJzbyiVtXsGtkrN2lmJl1LIdJhvue3MSnb1vJ8Nh49spmZlOUwyRDYTTUcJaYmZXlMMmQS0fHDg9ZbWZWlsMkQ5oljDtLzMzKcphkyKVNE4/7YmZW3gF512BJ00lGfBsGbo+Ir7RwX4BbJmZmlXRMy0TSv0taJ2l5yfxXSlohaaWk96ezXw9cHxFvBS5saV3pT7dMzMzK65gwAa4GXlk8Q1Ie+CfgVcAJwMWSTgAOA1alq7X0CyC5wqe5WrkTM7MDXMeESUT8ENhYMvt0YGVEPBoRw8C1wGuA1SSBAhWOQdLlkpZJWrZ+/fq66kqzhHG3TMzMyuqYMCljEXtaIJCEyCLgRuANkj4L3FzuxRFxZUScFhGnzZuXOerkpCY+GuwsMTMrq9MvwGuSeRER24Hf3T8FFC7AO03MzMrp9JbJamBx0fPDgKf2ZwFyy8TMLFOnh8k9wDGSjpTUB7wRuGl/FjBxOxWHiZlZWR0TJpKuAe4AjpO0WtJlETEKvB24BXgIuC4iHtyfdfl2KmZm2TrmmklEXFxm/reBb+/ncibk/KVFM7NMHdMy6VT+aLCZWTaHSQZfMzEzy9b1YSLpAklXbt68ub7Xpz99OxUzs/K6Pkwi4uaIuHxoaKiu1/t2KmZm2bo+TBrlayZmZtkcJhl8OxUzs2wOk0y+nYqZWRaHSQa3TMzMsjlMMuT80WAzs0wOkwy+AG9mls1hksEfDTYzy+YwyeKWiZlZpq4Pk0a/Ae9rJmZm2bo+TBr9Brxvp2Jmlq3rw6RRvmZiZpbNYZJh4tNcHtDEzKwsh0mGiTHg21uGmVlHc5hkkG+nYmaWyWGSITdxBb6tZZiZdTSHSYZczmPAm5llcZhkKDRM3M1lZlaewySD/NFgM7NMDpMMvtGjmVm2rg+TZt1OxU0TM7Pyuj5MmnU7FbdMzMzK6/owaZRv9Ghmls1hksHXTMzMsjlMMviSiZlZNodJhj3dXI4TM7NyHCYZ9nRztbcOM7NO5jDJ4AvwZmbZHCYZ/NFgM7NsDpMMvp2KmVk2h0mGiU9zuWViZlaWwySDr5mYmWXr+jBp/N5cyc8xf5zLzKysrg+TRu/NVWiZjLlpYmZWVteHSaPyhZEW3TIxMyvLYZKhJw2TUYeJmVlZDpMMe8aAd5iYmZXjMMkw0TIZc5iYmZXjMMnglomZWTaHSQZfMzEzy+YwyTDx0WCHiZlZWQ6TDIWWicPEzKw8h0mGvMPEzCyTwySDJHJymJiZVeIwqUI+J99OxcysAodJFfI5uWViZlaBw6QKeTlMzMwqcZhUwS0TM7PKuj5MGh3PBBwmZmZZuj5MGh3PBCCfy/kb8GZmFXR9mDRDPufxTMzMKnGYVKHHLRMzs4ocJlXI5XzXYDOzShwmVejJ5XwB3sysgrrCRNJ0SflmF9OpfDsVM7PKqgoTSTlJl0j6lqR1wC+AtZIelPQxSce0tsz2csvEzKyyalsmPwCOAq4AFkbE4oiYD7wUuBP4qKQ3tajGtsvl5AvwZmYV9FS53vkRMVI6MyI2AjcAN0jqbWplHaQnJ1+ANzOroKqWyWRBUs86Byq3TMzMKssME0nT058zWl9OZ+rJyV9aNDOroJqWyWxJbwde0upiOlVeYnR8vN1lmJl1rGrC5Dzgd4Clkua3tpzOlM8JZ4mZWXnVhMndwFuAJyJiXYvr6Uj5nFsmZmaVZH6aKyIeSicfaHEtHSsZtrfdVZiZdS7fTqUKyXgmbpmYmZVTU5hI+pQktaqYTpWESburMDPrXLW2TLYBNxV9XPjlkn7c/LKapykjLcotEzOzSmoKk4j4c+Aa4HZJPwLeA7y/FYU1S1NGWsx72F4zs0qqvZ0KAJLOA94KbAcOAS6LiBWtKKyTJC0Th4mZWTm1dnN9APiLiDgHuAj4qqRzm15Vh+nJiTHfm8vMrKyaWiYRcW7R9M8kvYrkRo9nNbuwTpLLiTF/NtjMrKxqxzOZ9BNcEbGW5BvyZdfpBm6ZmJlVVvV4JpLeIenw4pmS+oAzJX0BuLTp1XWIXM7XTMzMKqm2m+uVJLdUuUbSkcAmYADIA7cCn4yI+1tTYvv1OEzMzCqqNkz+LiLeKelqYASYC+yMiE0tq6yD5OTxTMzMKqm2m+u89Od/R8RIRKydKkECHs/EzCxLtWHyXUl3AAslvUXSqZIGWllYJ8l7pEUzs4qq6uaKiPdKWgrcDhwJXAg8X9IwsDwifqt1JbZfT95hYmZWSdXfM4mIRyWdHxEPF+alQ/me2JLKOkhfPs/YeDA2HuRzXfsJaDOzutV6b66HS55vi4g7m1tS5+nrSd6m4VHf7NHMbDIez6QKDhMzs8rqChNJFzS7kE5WCJPdY2NtrsTMrDPV2zL5cFOr6HD9ebdMzMwqqTdMptRVaHdzmZlVVm+YTKnPyU6EicfuNTOblC/AV6HP3VxmZhU5TKrQ624uM7OK6g2TZ5paRYdzy8TMrLK6wiQiXtbsQjrZno8GO0zMzCbjbq4q9KdhMuKWiZnZpLo+TCRdIOnKzZs3170Nf5rLzKyyer8BP11SvtnFtEJE3BwRlw8NDdW9DV8zMTOrrKowkZSTdImkb0laB/wCWCvpQUkfk3RMa8tsL39p0cyssmpbJj8AjgKuABZGxOKImA+8FLgT+KikN7WoxrZzN5eZWWXVjmdyfkSMTDJ/W0TcANwgqbeJdXUUt0zMzCqrqmVSJkgA/rpo+vTGy+lMhWsmux0mZmaTqnqkxTJukXQ5sB04Hvhx4yV1Hl+ANzOrrO4wkfRvwGbghcCdEfFnTauqw+RyojcvXzMxMyuj7jCJiMskDQKnAC+S9LmI+P3mldZZ+vI5t0zMzMqoKUwkfQp4V0QEQETsJOna6srurWJ9PTl2j3qkRTOzydT6pcVtwE2SpgNIermkrg8SgMHePDuH3TIxM5tMTS2TiPhzSZcAt0vaTXLh/f0tqazDTOvvYefIaLvLMDPrSLV2c50HvJUkRA4BLouIFa0orNNM68uzY9jdXGZmk6m1m+sDwF9ExDnARcBXJZ3b9Ko60GCvw8TMrJxau7nOLZr+maRXATcAZzW7sE4zrS/Phm3D7S7DzKwjVXujR002PyLWAudVWqdbTOvrYcewr5mYmU2m2m6u2yS9Q9LhxTMl9QFnSvoCcGnTq+sgg315drqby8xsUtV2c/0SGAO+LukQYBMwAOSBW4FPRsT9rSmxM0zry7PdYWJmNqlqw+SsiLhc0u8BhwPzgJ0Rsal1pXWWaX09bpmYmZVRbTfXLZLuABYAbwYOBXa1rKoONK0vz/DYOKO+P5eZ2T6qaplExHskLQVuB44ELgSeL2kYWB4Rv9W6EjvDtL5klOIdI2PMytc12rGZWdeq+qPBEfGopPMj4uHCPEkzgBNbUlmHGUzDZOfwGLMGunYcMDOzutT6PZOHS55vIxm2t+tNtEx83cTMbB/ur6nStL4kd7fv9ndNzMxKOUyqNHMgCZNtDhMzs304TKpUuE6yZedImysxM+s8DpMqDQ2mYbLLLRMzs1IOkyq5ZWJmVp7DpEoz0msmmx0mZmb7cJhUKZ8TM/t72LLLYWJmVsphUoNZg71s2elrJmZmpRwmNZg54JaJmdlkHCY1SFomDhMzs1JdHyaSLpB05ebNmxve1tBgry/Am5lNouvDJCJujojLh4aGGt7W3Bl9bNi2uwlVmZl1l64Pk2aaO6OfjduHGRuPdpdiZtZRHCY1mDujn/GAjduH212KmVlHcZjUYO6MfgB3dZmZlXCY1GDujD7AYWJmVsphUoO5M90yMTObjMOkBhPdXFt9zcTMrJjDpAazBnroy+fcMjEzK+EwqYEk5s7oY73DxMxsLw6TGs2b2c/6rQ4TM7NiDpMaLZo9yJrndra7DDOzjuIwqdHi2dNY/dxOxv0teDOzCQ6TGi2eM43hsXGe2bqr3aWYmXUMh0mNFs+ZBsCqje7qMjMrcJjUaPHsQQBWbdzR5krMzDqHw6RGi2YPIsGTDhMzswkOkxr19+RZOGvAYWJmVsRhUodjFsxkxdNb212GmVnHcJjU4XkLZ7Jy/TZGx8bbXYqZWUdwmNThuAUzGR4d5/Fnt7e7FDOzjuAwqcNxC2cC8At3dZmZAQ6Tuhw9fwb5nHho7ZZ2l2Jm1hEcJnUY6M3zvIUzue/JTe0uxcysIzhM6nTaEbO5f9UmX4Q3M8NhUrdTjpjNjuExXzcxM8NhUrfTlswB4O7HNra5EjOz9nOY1GnRQYMcOXc6P/zl+naXYmbWdg6TBpxz3DzueORZdg6PtbsUM7O2cpg04NeOm8/u0XF+8siGdpdiZtZWDpMG/MrSOQwN9nLT/zzV7lLMzNrKYdKA/p48F5x0CLc8+DRbd420uxwzs7ZxmDTodScfxq6Rcb7zs6fbXYqZWds4TBp0yuEHcdS86XzxzseJiHaXY2bWFg6TBknirS9dyvI1W/jRSl+IN7OpyWHSBK87ZRHzZ/bzmdtWunViZlOSw6QJ+nvyvP3co7nrsY3c+vNn2l2Omdl+5zBpkktOP5xjF8zgQ9/6OTuGR9tdjpnZfuUwaZKefI4PvuZEVj+3kw/e/PN2l2Nmtl85TJrojKUH87azj+Lae1Zxs7/IaGZTiMOkyd79smM59YjZvPdr/8Oyx31HYTObGhwmTdabz/Gvbz6NRQcNctkXlnHfk8+1uyQzs5ZzmLTAnOl9fOEtpzM02MtvX3UXP1ixrt0lmZm1lMOkRRbPmcb1bzuTIw6ezluuvodP3LqCsXF/B8XMupPDpIXmzxrgxj84izecchifvm0lr/vnH7N8zeZ2l2Vm1nQOkxYb7Mvz8d88iU9ffDJPbdrFhZ/5Ee+/4QFWbdzR7tLMzJqmp90FTBUXnnQoZx87j09+72H+464nuf7e1bzhlMP432cewYmLhtpdnplZQzRV7iV12mmnxbJly9pdBgBrN+/ks7c/wnXLVrFrZJwXHDbEb556GK94/kLmzxpod3lmZhMk3RsRp2Wu5zBpn807R/j6T1dzzd2rWPHMViQ49fDZnH/CAl581FxOOHQW+ZzaXaaZTWEOkxKdGCYFEcEv123ju8uf5jvLn+ahtVsAmDXQwxlLD+aUI2bzgkVDPH/REEODvW2u1symEodJiU4Ok1Lrtuzijkef5Y5HnuWOR5/liWf3XKw/cu50jj9kJkfNm8HR82dw1LwZLJ03nWl9vvxlZs3nMClxIIVJqee2D/OzNZv52ZrNPLB6Ew8/s40nnt1O8ddWFs4aYNHsQRYdNLjPz/kz+xka7EVyl5mZ1abaMPGfsweA2dP7+NVj5/Grx86bmLd7dIwnnt3BI+u28cj6bTy2YQdrNu3g/lWb+M7ytYyM7f1HQm9eHDy9n7kz+5g7o5+5M/qZN7Ofg6f3cdC0PoYGe/d5DPTmHEBmVhWHyQGqvyfPsQtmcuyCmfssGxsP1m/dzZpNO1izaRfrt+5mw7bdbCj83DbMiqe3smHb7n1Cp1hfPseswR5mDfYya6CXGf09TOvLM73o5/S+Hqb355lW/DNdNtCbp78nx0BvnoHeHP09yfOcP1Rg1nUO2DCRtBT4ADAUERe1u55Oks+JhUMDLBwa4NQjyq8XEWzZOcrmnSMVH1t2jrBl1wg7hsfYsG03O4bH2L57lO3Do+waGa+5vr58jv40XJKQyZUETzLd15OjN588+vKip2i6N5+jd2K5JtbrzYu+dLqnMF2yXj4nenIiX/ToyeWKptP5koPPrEptCRNJ/w78BrAuIk4smv9K4B+APHBVRHy03DYi4lHgMknXt7rebiWJoWm9DE2r/xNiY+PBjuFRtu8eY/vwKDvSn9t3J0Gze3SMXSPj7BoZY/fovj93TzJ/665Rdo2MMTw2zuhYMDw2zsjYOCOj44ykz/cXiYlw6cnlyCkZCK0QOjmJnrwmeZ5LXqc9gZXLiZwgLyGl0+lrVDKdUyHMkvOUT9dXur1cuk6ueHriQTq/zHpF0/ni/eWS6UJtorAMYM96gqQuBEXzipez17p7lmti3b33oXS6sN1y+y1MU7Ldyfa71z5yVNxv6T5I17XqtatlcjXwGeCLhRmS8sA/AS8DVgP3SLqJJFg+UvL6t0SEb8XbAfI5MXOgl5kD++8jyxHB2HhMBMtI+tg7ePZMj44FI2PjE8/HxpPXjxb9HJ94Pl7yvHR5MDo+ztg4E+uOFT32fT7O6Pg4u0aDCBiPSB7jRdMB4+N7psfGgyjML14nPe6YbDqSaWu+JHAK00XByJ4FxfMmW5/SedoTbiraDxQvK2x77/X3qqton3vq2Hef7zj3GF578qLmvSmTaEuYRMQPJS0pmX06sDJtcSDpWuA1EfERklaMGZD8Z+nJi548DJJvdzkdI9JAGUsDqDh0xmNPCE9MlwkyKLwWgj3bKYRV8bYDiHR7henC8sJr915372Xje7128v2yz3bT+SU1FKaJ2Gu7E9Ol+52ob+95hf0VthUT72/Ra9LnkL5mYl4Uzd/7NXudp6JlxftL1ivaRsk+i+cV73Pv/e3ZR2HBQQ30PlSrk66ZLAJWFT1fDfxKuZUlHQx8GDhZ0hVp6JSuczlwOcDhhx/e3GrNOkzhL94cyl7ZrMk6KUwm+x9QtuEeEc8Cb6u0wYi4ErgSku+ZNFSdmZmV1Um3oF8NLC56fhjwVJtqMTOzGnRSmNwDHCPpSEl9wBuBm9pck5mZVaEtYSLpGuAO4DhJqyVdFhGjwNuBW4CHgOsi4sF21GdmZrVp16e5Li4z/9vAt/dzOWZm1qBO6uYyM7MDlMPEzMwa1vVhIukCSVdu3ry53aWYmXWtKTOeiaT1wBN1vnwusKGJ5RwIfMxTg495amjkmI+IiHlZK02ZMGmEpGXVDA7TTXzMU4OPeWrYH8fc9d1cZmbWeg4TMzNrmMOkOle2u4A28DFPDT7mqaHlx+xrJmZm1jC3TMzMrGEOEzMza5jDJIOkV0paIWmlpPe3u55mkbRY0g8kPSTpQUnvTOfPkfQ9Sb9Mf85O50vSp9P34QFJp7T3COojKS/pPknfTJ8fKemu9Hi/mt6xGkn96fOV6fIl7ay7XpIOknS9pF+k5/rMKXCO35X+m14u6RpJA914niX9u6R1kpYXzav53Eq6NF3/l5Iurbceh0kFRePSvwo4AbhY0gntrappRoH3RMTxwBnA/0mP7f3A9yPiGOD76XNI3oNj0sflwGf3f8lN8U6Su1IX/B3wyfR4nwMuS+dfBjwXEUcDn0zXOxD9A/DdiHgecBLJsXftOZa0CPgj4LSIOBHIkwxn0Y3n+WrglSXzajq3kuYAf0kyqu3pwF8WAqhmyZjFfkz2AM4Ebil6fgVwRbvratGx/ifwMmAFcEg67xBgRTr9OeDiovUn1jtQHiQDrn0fOBf4JsnonhuAntLzTTIUwpnpdE+6ntp9DDUe7yzgsdK6u/wcF4b/npOet28Cr+jW8wwsAZbXe26Bi4HPFc3fa71aHm6ZVDbZuPSL2lRLy6RN+5OBu4AFEbEWIP05P12tG96LTwHvA8bT5wcDmyIZSwf2PqaJ402Xb07XP5AsBdYDn0+79q6SNJ0uPscRsQb4OPAksJbkvN1Ld5/nYrWe26adc4dJZTWNS38gkjQDuAH444jYUmnVSeYdMO+FpN8A1kXEvcWzJ1k1qlh2oOgBTgE+GxEnA9vZ0+0xmQP+mNMumtcARwKHAtNJunhKddN5rka542za8TtMKuvqcekl9ZIEyVci4sZ09jOSDkmXHwKsS+cf6O/Fi4ELJT0OXEvS1fUp4CBJhUHiio9p4njT5UPAxv1ZcBOsBlZHxF3p8+tJwqVbzzHA+cBjEbE+IkaAG4Gz6O7zXKzWc9u0c+4wqaxrx6WXJODfgIci4hNFi24CCp/ouJTkWkph/pvTT4WcAWwuNKcPBBFxRUQcFhFLSM7jbRHx28APgIvS1UqPt/A+XJSuf0D9xRoRTwOrJB2XzjoP+Dldeo5TTwJnSJqW/hsvHHPXnucStZ7bW4CXS5qdtupens6rXbsvIHX6A3g18DDwCPCBdtfTxON6CUlz9gHg/vTxapL+4u8Dv0x/zknXF8kn2x4BfkbyaZm2H0edx34O8M10eilwN7AS+BrQn84fSJ+vTJcvbXfddR7rC4Fl6Xn+BjC7288x8NfAL4DlwJeA/m48z8A1JNeFRkhaGJfVc26Bt6THvxL43Xrr8e1UzMysYe7mMjOzhjlMzMysYQ4TMzNrmMPEzMwa5jAxM7OGOUzMzKxhDhMzM2uYw8Qsg6Rt6bggf9jk7e6zTUk/adK2z5f0pWZsy6waDhOz6hwE1Bwm6e0ryv0/22ebEXFWHbVN5iTgviZtyyyTw8SsOh8FjpJ0v6SPAUh6k6S703mfSwdTQ9KSdFTDfwZ+CiyW9A1J96YjAF5eYZvb0p/vTkcKXC7pjwtFFG37X9Nt3SppcJJ6TwLuS0cSvFrS36b3qjJrCd9OxSxD+gv+RJL7eZ2Yzjse+Hvg9RExkgbHnRHxxXR8mEeBsyLiznT9ORGxMf3Ffw9wNjCzeJtF+zqbZBS9M0juqXQX8KaIuC/d9kqSeyvhJyZWAAABXklEQVTdL+k64KaI+HJJzf8DXEJyP6arSpebNVtP9ipmNonzgFOBe9I/+AfZc7tvgCcKQZL6I0mvS6cXkwyf+nSZbb8E+HpEbAeQdCPwUvZ0Wz0WEfen0/eSjLY3IR1aYAnJjQB/PyLuqPXgzGrlMDGrj4AvRMQVZZZvn1hROodknI0zI2KHpNtJ7lZbaduV7C6aHiMJsmInkLR+5qTLzVrO10zMqrOVpFuq4PvARZLmQ9KNJemIMq8dAp5Lg+R5JN1Xk22z4IfAa9MxOaYDrwP+u4ZaTwJ+QjJuy+clLajhtWZ1cZiYVSEingV+nF4Q/1hE/Bz4c+BWSQ8A3wMOKfPy7wI96Xp/A9w52TaL9vVTkmsmd5NcL7kqImr5ZNZJwPKIeBj4U+C6tOvLrGV8Ad7MzBrmlomZmTXMYWJmZg1zmJiZWcMcJmZm1jCHiZmZNcxhYmZmDXOYmJlZw/4/xJeMAhGg+vAAAAAASUVORK5CYII=\n", 44 | "text/plain": [ 45 | "
" 46 | ] 47 | }, 48 | "metadata": {}, 49 | "output_type": "display_data" 50 | } 51 | ], 52 | "source": [ 53 | "def dummy_plot():\n", 54 | " plt.figure()\n", 55 | " plt.semilogy(y)\n", 56 | " plt.xlabel(\"Iteration $k$\")\n", 57 | " plt.ylabel('$f(x_k) - f(x^*)$')\n", 58 | " plt.title('A non converging algorithm')\n", 59 | " plt.show()\n", 60 | " \n", 61 | "dummy_plot()" 62 | ] 63 | }, 64 | { 65 | "cell_type": "markdown", 66 | "metadata": {}, 67 | "source": [ 68 | "You can update matplotlib's global parameter to have bigger (more readable) fonts by default, and also to use LaTeX for rendering of math equations:" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": 4, 74 | "metadata": {}, 75 | "outputs": [], 76 | "source": [ 77 | "# choose a large font size by default and use tex for math\n", 78 | "fontsize = 18\n", 79 | "params = {'axes.labelsize': fontsize + 2,\n", 80 | " 'font.size': fontsize + 2,\n", 81 | " 'legend.fontsize': fontsize + 2,\n", 82 | " 'xtick.labelsize': fontsize,\n", 83 | " 'ytick.labelsize': fontsize,\n", 84 | " 'text.usetex': True}\n", 85 | "plt.rcParams.update(params)" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": 5, 91 | "metadata": {}, 92 | "outputs": [ 93 | { 94 | "data": { 95 | "image/png": "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\n", 96 | "text/plain": [ 97 | "
" 98 | ] 99 | }, 100 | "metadata": {}, 101 | "output_type": "display_data" 102 | } 103 | ], 104 | "source": [ 105 | "dummy_plot()" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "metadata": {}, 111 | "source": [ 112 | "You can also change the font to match the font of your paper/report (provided the font is installed on your machine and matplotlib finds it, which can be tricky):" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": 6, 118 | "metadata": {}, 119 | "outputs": [], 120 | "source": [ 121 | "plt.rc('font', family='Times New Roman')" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": 7, 127 | "metadata": {}, 128 | "outputs": [ 129 | { 130 | "name": "stderr", 131 | "output_type": "stream", 132 | "text": [ 133 | "/home/mathurin/anaconda3/lib/python3.6/site-packages/matplotlib/font_manager.py:1331: UserWarning: findfont: Font family ['Times New Roman'] not found. Falling back to DejaVu Sans\n", 134 | " (prop.get_family(), self.defaultFamily[fontext]))\n" 135 | ] 136 | }, 137 | { 138 | "data": { 139 | "image/png": "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\n", 140 | "text/plain": [ 141 | "
" 142 | ] 143 | }, 144 | "metadata": {}, 145 | "output_type": "display_data" 146 | } 147 | ], 148 | "source": [ 149 | "dummy_plot() # title and xlabel font have changed" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": null, 155 | "metadata": {}, 156 | "outputs": [], 157 | "source": [] 158 | } 159 | ], 160 | "metadata": { 161 | "anaconda-cloud": {}, 162 | "kernelspec": { 163 | "display_name": "Python [conda env:anaconda3]", 164 | "language": "python", 165 | "name": "conda-env-anaconda3-py" 166 | }, 167 | "language_info": { 168 | "codemirror_mode": { 169 | "name": "ipython", 170 | "version": 3 171 | }, 172 | "file_extension": ".py", 173 | "mimetype": "text/x-python", 174 | "name": "python", 175 | "nbconvert_exporter": "python", 176 | "pygments_lexer": "ipython3", 177 | "version": "3.6.6" 178 | } 179 | }, 180 | "nbformat": 4, 181 | "nbformat_minor": 2 182 | } 183 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-sessions -------------------------------------------------------------------------------- /Reference_arrays_args.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Arrays and references\n", 8 | "\n", 9 | "Author: Pierre Ablin, Mathurin Massias\n", 10 | "\n", 11 | "Some caveats when using numpy arrays." 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import numpy as np" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 2, 26 | "metadata": {}, 27 | "outputs": [ 28 | { 29 | "name": "stdout", 30 | "output_type": "stream", 31 | "text": [ 32 | "[0. 0. 0. 0.]\n" 33 | ] 34 | } 35 | ], 36 | "source": [ 37 | "a = np.zeros(4)\n", 38 | "print(a)" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": 3, 44 | "metadata": {}, 45 | "outputs": [ 46 | { 47 | "name": "stdout", 48 | "output_type": "stream", 49 | "text": [ 50 | "[0. 0. 0. 0.]\n", 51 | "[12. 0. 0. 0.]\n" 52 | ] 53 | } 54 | ], 55 | "source": [ 56 | "# argument x is passed as reference (amongst others, this avoids a heavy memory copy if x is large):\n", 57 | "def f(x):\n", 58 | " x[0] = 12\n", 59 | " \n", 60 | "\n", 61 | "print(a)\n", 62 | "f(a)\n", 63 | "print(a)\n", 64 | "# modifying x inside the function also changes it outside" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 4, 70 | "metadata": {}, 71 | "outputs": [ 72 | { 73 | "name": "stdout", 74 | "output_type": "stream", 75 | "text": [ 76 | "[12. 0. 0. 0.]\n", 77 | "[12. 0. 0. 0.]\n" 78 | ] 79 | } 80 | ], 81 | "source": [ 82 | "# if we want to avoid side effects, we need to copy the argument when entering the function:\n", 83 | "def g(x):\n", 84 | " x = x.copy()\n", 85 | " x[0] = 24\n", 86 | "\n", 87 | "print(a)\n", 88 | "f(a)\n", 89 | "print(a)\n", 90 | "# a is no longer affected" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": 5, 96 | "metadata": {}, 97 | "outputs": [ 98 | { 99 | "name": "stdout", 100 | "output_type": "stream", 101 | "text": [ 102 | "[12. 0. 0. 0.]\n", 103 | "[12. 0. 0. 0.]\n" 104 | ] 105 | } 106 | ], 107 | "source": [ 108 | "# what happens if we do this?\n", 109 | "def h(x):\n", 110 | " x = x + 6\n", 111 | "\n", 112 | "print(a)\n", 113 | "h(a)\n", 114 | "print(a)\n", 115 | "# a is untouched: x = x + 6 means \"compute x + 6, and put this in a new variable called x\"" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": 6, 121 | "metadata": {}, 122 | "outputs": [ 123 | { 124 | "name": "stdout", 125 | "output_type": "stream", 126 | "text": [ 127 | "[12. 0. 0. 0.]\n", 128 | "[18. 6. 6. 6.]\n" 129 | ] 130 | } 131 | ], 132 | "source": [ 133 | "# but this will modify a:\n", 134 | "def h_view(x):\n", 135 | " x[:] = x + 6\n", 136 | "\n", 137 | "print(a)\n", 138 | "h_view(a)\n", 139 | "print(a)\n", 140 | "# x[:] = x + 6 means compute x + 6, and fill x with the result. \n", 141 | "# It is useful because we don't have to create a new array at each iteration, we just use the same." 142 | ] 143 | }, 144 | { 145 | "cell_type": "markdown", 146 | "metadata": {}, 147 | "source": [ 148 | "`x[:]` is what we call a _view_ on `x`: it is a different array (`a[:] is a` evaluates to False), but it points to the same data, with a potentially different way to move from one line/column to another. Views are useful because they allow to avoid a high memory usage. Let's see other examples of _views_:" 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": 7, 154 | "metadata": {}, 155 | "outputs": [ 156 | { 157 | "data": { 158 | "text/plain": [ 159 | "False" 160 | ] 161 | }, 162 | "execution_count": 7, 163 | "metadata": {}, 164 | "output_type": "execute_result" 165 | } 166 | ], 167 | "source": [ 168 | "a[:] is a" 169 | ] 170 | }, 171 | { 172 | "cell_type": "code", 173 | "execution_count": 8, 174 | "metadata": {}, 175 | "outputs": [ 176 | { 177 | "name": "stdout", 178 | "output_type": "stream", 179 | "text": [ 180 | "[[0. 1. 1.]\n", 181 | " [0. 1. 1.]\n", 182 | " [0. 1. 1.]\n", 183 | " [0. 1. 1.]\n", 184 | " [0. 1. 1.]]\n" 185 | ] 186 | } 187 | ], 188 | "source": [ 189 | "A = np.ones([5, 3])\n", 190 | "B = A.T # B is not A, but B shares A's data\n", 191 | "B[0, :] = 0 # change the first line of B\n", 192 | "print(A) # the first column of A has changed" 193 | ] 194 | }, 195 | { 196 | "cell_type": "markdown", 197 | "metadata": {}, 198 | "source": [ 199 | "This is useful for example when we compute the linear regression gradient `A.T.dot(A.dot(x) - b)`: numpy does not create a new array to store `A.T`. \n", 200 | "Other examples of views:" 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": 9, 206 | "metadata": {}, 207 | "outputs": [ 208 | { 209 | "name": "stdout", 210 | "output_type": "stream", 211 | "text": [ 212 | "[[1. 1. 1.]\n", 213 | " [1. 1. 1.]\n", 214 | " [0. 0. 0.]\n", 215 | " [1. 1. 1.]\n", 216 | " [1. 1. 1.]]\n" 217 | ] 218 | } 219 | ], 220 | "source": [ 221 | "A = np.ones([5, 3])\n", 222 | "B = A[::2, :] # B = even lines of A, with a view\n", 223 | "B[1, :] = 0\n", 224 | "print(A) # A has changed" 225 | ] 226 | }, 227 | { 228 | "cell_type": "code", 229 | "execution_count": 10, 230 | "metadata": {}, 231 | "outputs": [ 232 | { 233 | "name": "stdout", 234 | "output_type": "stream", 235 | "text": [ 236 | "[[1. 1. 1.]\n", 237 | " [1. 1. 1.]\n", 238 | " [1. 1. 1.]\n", 239 | " [1. 1. 1.]\n", 240 | " [1. 1. 1.]]\n" 241 | ] 242 | } 243 | ], 244 | "source": [ 245 | "A = np.ones([5, 3])\n", 246 | "B = A[[0, 2, 4], :] # B = even lines of A, without using a view\n", 247 | "B[1, :] = 2\n", 248 | "print(A) # A has not changed" 249 | ] 250 | }, 251 | { 252 | "cell_type": "code", 253 | "execution_count": 11, 254 | "metadata": {}, 255 | "outputs": [ 256 | { 257 | "name": "stdout", 258 | "output_type": "stream", 259 | "text": [ 260 | "139854550728496\n", 261 | "139854550665616\n", 262 | "False\n" 263 | ] 264 | } 265 | ], 266 | "source": [ 267 | "# A little more on the previous ideas, without functions:\n", 268 | "a = np.zeros(6)\n", 269 | "b = np.zeros(6)\n", 270 | "print(id(a)) # address in memory of a\n", 271 | "print(id(b)) # this address is different, a and b are not the same object\n", 272 | "print(a is b) # 'is' test if the two variables point to the same object\n", 273 | "# the addresses are different, it's not the same object" 274 | ] 275 | }, 276 | { 277 | "cell_type": "code", 278 | "execution_count": 12, 279 | "metadata": {}, 280 | "outputs": [ 281 | { 282 | "name": "stdout", 283 | "output_type": "stream", 284 | "text": [ 285 | "True\n", 286 | "[5. 0. 0. 0. 0. 0.]\n", 287 | "[5. 0. 0. 0. 0. 0.]\n" 288 | ] 289 | } 290 | ], 291 | "source": [ 292 | "b = a\n", 293 | "print(a is b) # True\n", 294 | "b[0] = 5\n", 295 | "print(b)\n", 296 | "print(a)\n", 297 | "# a and b point to the same object, so modifying b affects a:" 298 | ] 299 | }, 300 | { 301 | "cell_type": "code", 302 | "execution_count": 13, 303 | "metadata": {}, 304 | "outputs": [ 305 | { 306 | "name": "stdout", 307 | "output_type": "stream", 308 | "text": [ 309 | "[5. 0. 0. 0. 0. 0.]\n", 310 | "[10. 5. 5. 5. 5. 5.]\n" 311 | ] 312 | } 313 | ], 314 | "source": [ 315 | "b = b + 5 # b + 5 is computed and affected to a new variable called b (old b is deleted)\n", 316 | "print(a)\n", 317 | "print(b)\n", 318 | "# b is modified but a is not:" 319 | ] 320 | }, 321 | { 322 | "cell_type": "code", 323 | "execution_count": 14, 324 | "metadata": {}, 325 | "outputs": [ 326 | { 327 | "name": "stdout", 328 | "output_type": "stream", 329 | "text": [ 330 | "[10. 5. 5. 5. 5. 5.]\n", 331 | "[10. 5. 5. 5. 5. 5.]\n" 332 | ] 333 | } 334 | ], 335 | "source": [ 336 | "b = a\n", 337 | "b[:] = b + 5\n", 338 | "print(a)\n", 339 | "print(b)\n", 340 | "# b is modified and so is a:" 341 | ] 342 | }, 343 | { 344 | "cell_type": "code", 345 | "execution_count": 15, 346 | "metadata": {}, 347 | "outputs": [], 348 | "source": [ 349 | "# This can be tricky in SAG code. The following code does not do what you think it does:\n", 350 | "n, p = 10, 100\n", 351 | "def grad_i(i, x): \n", 352 | " return np.random.randn(p)\n", 353 | "\n", 354 | "memory_gradient = np.zeros([n, p])\n", 355 | "\n", 356 | "x = np.zeros(p)\n", 357 | "for idx in range(50):\n", 358 | " i = idx % n\n", 359 | " old_g_i = memory_gradient[i, :] # /!\\ old_g_i just points to memory_gradient[i, :]\n", 360 | " # so changing memory_gradient[i, :] will change old_g_i!\n", 361 | " new_g_i = grad_i(i, x)\n", 362 | " memory_gradient[i, :] = new_g_i # this also affect old_g_i as a side effect\n", 363 | " # old_g_i and new_g_i now have the same values!\n", 364 | " # so the mean of memory gradient would never change and the algorithm fails\n", 365 | " assert (new_g_i == old_g_i).all() # this is True, not what you expect.\n", 366 | " \n", 367 | " # other stuff of the algorithm\n", 368 | " # x -= step * ...\n", 369 | " " 370 | ] 371 | } 372 | ], 373 | "metadata": { 374 | "anaconda-cloud": {}, 375 | "kernelspec": { 376 | "display_name": "Python [conda env:anaconda3]", 377 | "language": "python", 378 | "name": "conda-env-anaconda3-py" 379 | }, 380 | "language_info": { 381 | "codemirror_mode": { 382 | "name": "ipython", 383 | "version": 3 384 | }, 385 | "file_extension": ".py", 386 | "mimetype": "text/x-python", 387 | "name": "python", 388 | "nbconvert_exporter": "python", 389 | "pygments_lexer": "ipython3", 390 | "version": "3.6.6" 391 | } 392 | }, 393 | "nbformat": 4, 394 | "nbformat_minor": 2 395 | } 396 | -------------------------------------------------------------------------------- /fast_python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Fast scientific code with Python" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "\n", 15 | "Authors: [Pierre Ablin](https://www.pierreablin.com), Antoine Tavant. \n", 16 | "\n", 17 | "\n", 18 | "Based on an idea by [Alexandre Gramfort](http://alexandre.gramfort.net/).\n", 19 | "\n" 20 | ] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "metadata": {}, 25 | "source": [ 26 | "\n", 27 | "\n", 28 | "**Dependencies:**\n", 29 | "\n", 30 | "For this notebook, we will need **numba, cython, numexpr, memory_profiler and line_profiler**. numba evolves quickly, so the most recent version is the best. To install all those:\n", 31 | "\n", 32 | "` $ pip install ...`\n", 33 | "\n", 34 | "\n", 35 | "\n", 36 | "To get the latest versions, running anaconda:\n", 37 | "\n", 38 | "` $ conda update ...`\n", 39 | "\n", 40 | "\n", 41 | "\n", 42 | "\n", 43 | "\n", 44 | "Python is the go-to langage when it comes to quickly translating an idea into code. It is very easy to write and read. The drawback is that most natural operations come with a lot of tests, conversion, type checking, etc... which renders scientific coding with the standard python library extremely slow. Luckily, there are a lot of tools and libraries to make Python code almost a fast as native fortran/C in most cases.\n", 45 | "\n", 46 | "### Is Python truly slow? \n", 47 | "Not if well optimized. Take the `scikit-learn` library, which contains many machine learning algorithms. It is written in Python, yet it contains the fastest implementation available of several massively used algorithms.\n", 48 | "\n", 49 | "As a side note: a great way to produce a scientific code in Python is first to write a working algorithm that might be slow. The important part is that it works. Then, profile it (i.e. time each of its component, or check the memory load if thats a problem too) to see which part can be improved and which part does not really have to be changed. It sounds obvious, but it is almost always useless to try to optimize a function that only makes for 5% of the total timing of the algorithm.\n", 50 | "\n", 51 | "\n", 52 | "With that in mind, parts of the algorithms that are not critical can be left in beautiful and clean python code while the rest has to be optimized.\n", 53 | "\n" 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "metadata": {}, 59 | "source": [ 60 | "### Test Case\n", 61 | "Let us try to solve a simple problem. Consider the Wallis product:\n", 62 | "$$P_n = 2 \\prod_{i=1}^{n}\\frac{4i^2}{4i^2 - 1}$$ which converges to $\\pi$. We are going to try to make its computation as fast as possible for large $n$.\n", 63 | "\n", 64 | "A very naive approach is the following:" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 1, 70 | "metadata": { 71 | "ExecuteTime": { 72 | "end_time": "2017-09-11T13:56:07.870147Z", 73 | "start_time": "2017-09-11T13:56:07.840070Z" 74 | }, 75 | "collapsed": false 76 | }, 77 | "outputs": [ 78 | { 79 | "data": { 80 | "text/plain": [ 81 | "3.1415141108281714" 82 | ] 83 | }, 84 | "execution_count": 1, 85 | "metadata": {}, 86 | "output_type": "execute_result" 87 | } 88 | ], 89 | "source": [ 90 | "def wallis1(n):\n", 91 | " out = 2.\n", 92 | " for i in range(1, n):\n", 93 | " out *= float((4 * i ** 2)) / float((4 * i ** 2 - 1))\n", 94 | " return out\n", 95 | "\n", 96 | "wallis1(10000)" 97 | ] 98 | }, 99 | { 100 | "cell_type": "markdown", 101 | "metadata": {}, 102 | "source": [ 103 | "## Vectorize\n", 104 | "The previous method is slow, because of the loop. The operations carried inside the loop are extremely slow and repeated many times.\n", 105 | "The most natural way to overcome this problem in python is to vectorize using numpy." 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": 2, 111 | "metadata": { 112 | "ExecuteTime": { 113 | "end_time": "2017-09-11T13:57:48.250758Z", 114 | "start_time": "2017-09-11T13:57:34.295403Z" 115 | }, 116 | "collapsed": false 117 | }, 118 | "outputs": [ 119 | { 120 | "name": "stdout", 121 | "output_type": "stream", 122 | "text": [ 123 | "7.27 ms ± 119 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n", 124 | "70.3 µs ± 2.11 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n" 125 | ] 126 | } 127 | ], 128 | "source": [ 129 | "import numpy as np\n", 130 | "\n", 131 | "def wallis2(n):\n", 132 | " int_list = np.arange(1, n)\n", 133 | " return 2 * np.prod( 4 * int_list ** 2 / (4 * int_list ** 2 - 1.))\n", 134 | "\n", 135 | "\n", 136 | "%timeit wallis1(10000) # Python loop\n", 137 | "%timeit wallis2(10000) # Vectorized with Numpy" 138 | ] 139 | }, 140 | { 141 | "cell_type": "markdown", 142 | "metadata": {}, 143 | "source": [ 144 | "We can see that this code is orders of magnitude faster than the previous one. Vectorizing is extremely important in scientific computing as vectors/matrices/tensors are by far the most common data structure encountered. Consequently, some very efficient libraries exist to work with vectors. This explains why numpy is at the core of scientific computing." 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "metadata": {}, 150 | "source": [ 151 | "Now, we can make this code faster by avoiding useless computations. Indeed, the product `4 * int_list ** 2` only has to be computed once." 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": 4, 157 | "metadata": { 158 | "ExecuteTime": { 159 | "end_time": "2017-09-11T13:58:34.070326Z", 160 | "start_time": "2017-09-11T13:58:21.358080Z" 161 | }, 162 | "collapsed": false 163 | }, 164 | "outputs": [ 165 | { 166 | "name": "stdout", 167 | "output_type": "stream", 168 | "text": [ 169 | "67.9 µs ± 409 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n", 170 | "58 µs ± 551 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n" 171 | ] 172 | } 173 | ], 174 | "source": [ 175 | "def wallis3(n):\n", 176 | " tmp = 4 * np.arange(1, n) ** 2\n", 177 | " return 2 * np.prod( tmp / (tmp - 1.))\n", 178 | "\n", 179 | "%timeit wallis2(10000) # Vectorized\n", 180 | "%timeit wallis3(10000) # Vectorized without unecessary computations" 181 | ] 182 | }, 183 | { 184 | "cell_type": "markdown", 185 | "metadata": {}, 186 | "source": [ 187 | "Another gain factor comes from the useless memory copies performed in the above call. Once `int_list` is computed, there is no need to keep it in memory. Further, when doing `tmp = 4 * int_list ** 2`, numpy takes int_list, creates an intermediate copy, then squares it. Then it takes that copy, and multiplies it by 4. These copies are unecessary. The way to efficiently manage memory and avoid unecessary copies is to use `+=`, `-=`, etc... You should also avoid as much as possible type conversion.\n", 188 | "\n", 189 | "There is another \"trick\" to speed up the computations here. In the previous algorithm, the operation `tmp / (tmp - 1.)` takes much longer than, for instance, dividing the vector `tmp` by a constant, because for each index of the array numpy has to grab an element from `tmp` and an element from `tmp - 1.`. Furthermore, there is no simple way to perform this operation without having to momentarily store a copy of `tmp - 1.`. Luckily, for this problem, we can rewrite $$\\frac{4 i^2}{4 i^2 - 1} = 1 + \\frac{1}{4 i^2 - 1}$$\n", 190 | "That kind of expression allows for a streamlined sequence of copyless operations, with only scalar - list operations and not list - list." 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": 5, 196 | "metadata": { 197 | "ExecuteTime": { 198 | "end_time": "2017-09-11T14:00:07.011040Z", 199 | "start_time": "2017-09-11T13:59:54.335413Z" 200 | }, 201 | "collapsed": false 202 | }, 203 | "outputs": [ 204 | { 205 | "name": "stdout", 206 | "output_type": "stream", 207 | "text": [ 208 | "62.1 µs ± 2.73 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n", 209 | "62.4 µs ± 464 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n" 210 | ] 211 | } 212 | ], 213 | "source": [ 214 | "def wallis4(n):\n", 215 | " tmp = np.arange(1, n, dtype=float) # Use float instead of integer to avoid type conversion\n", 216 | " tmp *= tmp # Legend has it that it is the fastest way to square\n", 217 | " tmp *= 4.\n", 218 | " tmp -= 1.\n", 219 | " np.reciprocal(tmp, out=tmp)\n", 220 | " tmp += 1.\n", 221 | " return 2. * np.prod(tmp)\n", 222 | "\n", 223 | "%timeit wallis3(10000) # Vectorized without unecessary computations\n", 224 | "%timeit wallis4(10000) # Vectorized with in place computations" 225 | ] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "metadata": {}, 230 | "source": [ 231 | "We have gained a little bit (a factor two compared to the naive numpy version). In order to precisely see where time is consumed, we can use a *line profiler*:" 232 | ] 233 | }, 234 | { 235 | "cell_type": "code", 236 | "execution_count": null, 237 | "metadata": { 238 | "collapsed": false, 239 | "scrolled": true 240 | }, 241 | "outputs": [], 242 | "source": [ 243 | "%load_ext line_profiler" 244 | ] 245 | }, 246 | { 247 | "cell_type": "markdown", 248 | "metadata": {}, 249 | "source": [ 250 | "The syntax to run it in ipython is: `%lprun -f function_name code_to_execute`, where `function_name` is the function that you want to profile during the execution of `code_to_execute`." 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": 6, 256 | "metadata": { 257 | "collapsed": false 258 | }, 259 | "outputs": [], 260 | "source": [ 261 | "%lprun -f wallis4 wallis4(1000000)" 262 | ] 263 | }, 264 | { 265 | "cell_type": "markdown", 266 | "metadata": {}, 267 | "source": [ 268 | "Observe that the most consuming task for a large $N$ is to simply compute the initial array..." 269 | ] 270 | }, 271 | { 272 | "cell_type": "markdown", 273 | "metadata": {}, 274 | "source": [ 275 | "## Cython\n", 276 | "\n", 277 | "Now, it is not always the case that operations can be vectorized as easily. Sometimes you just cannot avoid the loops. We can use Cython to write code that is going to be compiled in C.\n", 278 | "\n", 279 | "#### Installation :\n", 280 | "```\n", 281 | "pip install cython\n", 282 | "```\n", 283 | "\n", 284 | "Without conda, add :\n", 285 | "```bash\n", 286 | "sudo apt install python-dev python3-dev```" 287 | ] 288 | }, 289 | { 290 | "cell_type": "code", 291 | "execution_count": 10, 292 | "metadata": { 293 | "ExecuteTime": { 294 | "end_time": "2017-09-11T13:41:41.331312Z", 295 | "start_time": "2017-09-11T13:41:41.327463Z" 296 | }, 297 | "collapsed": false 298 | }, 299 | "outputs": [], 300 | "source": [ 301 | "%load_ext cython\n", 302 | "# Load Cython on jupyter to use it with %%magic" 303 | ] 304 | }, 305 | { 306 | "cell_type": "markdown", 307 | "metadata": {}, 308 | "source": [ 309 | "In cython, you have to type and define each variable that you are going to use beforehand, which is going to render our code a bit less readable. If cython does not understand an instruction, it will fall back to a python instruction, which might render everything incredibly slow. To debug, you can use the command --annotate which will hilight the lines which interact wit python (yellow inside of a loop means slow)." 310 | ] 311 | }, 312 | { 313 | "cell_type": "code", 314 | "execution_count": 11, 315 | "metadata": { 316 | "ExecuteTime": { 317 | "end_time": "2017-09-11T13:33:23.979726Z", 318 | "start_time": "2017-09-11T13:33:23.684983Z" 319 | }, 320 | "collapsed": true, 321 | "scrolled": true 322 | }, 323 | "outputs": [], 324 | "source": [ 325 | "%%cython\n", 326 | "import cython\n", 327 | "\n", 328 | "@cython.cdivision(True)\n", 329 | "def wallis5(int n):\n", 330 | " cdef double out = 2.\n", 331 | " cdef double tmp = 0.\n", 332 | " cdef int i\n", 333 | " for i in range(1, n):\n", 334 | " tmp = 4. * (i * i)\n", 335 | " out *= tmp / (tmp - 1.)\n", 336 | " return out" 337 | ] 338 | }, 339 | { 340 | "cell_type": "code", 341 | "execution_count": 9, 342 | "metadata": { 343 | "ExecuteTime": { 344 | "end_time": "2017-09-11T14:01:46.767011Z", 345 | "start_time": "2017-09-11T14:01:30.909439Z" 346 | }, 347 | "collapsed": false 348 | }, 349 | "outputs": [ 350 | { 351 | "name": "stdout", 352 | "output_type": "stream", 353 | "text": [ 354 | "The slowest run took 4.21 times longer than the fastest. This could mean that an intermediate result is being cached.\n", 355 | "10000 loops, best of 3: 44.7 µs per loop\n", 356 | "100000 loops, best of 3: 12.7 µs per loop\n" 357 | ] 358 | } 359 | ], 360 | "source": [ 361 | "%timeit wallis4(10000) # Best vectorized with Numpy\n", 362 | "%timeit wallis5(10000) # Raw loop in Cython" 363 | ] 364 | }, 365 | { 366 | "cell_type": "markdown", 367 | "metadata": {}, 368 | "source": [ 369 | "Cython is more efficient than numpy for this problem.\n", 370 | "\n", 371 | "An important advantage of the cython implementation is its memory load!\n", 372 | "To have a quick check at the memory load of a program, we can use `memory_profiler`." 373 | ] 374 | }, 375 | { 376 | "cell_type": "code", 377 | "execution_count": 10, 378 | "metadata": { 379 | "ExecuteTime": { 380 | "end_time": "2017-09-11T14:01:46.810760Z", 381 | "start_time": "2017-09-11T14:01:46.781166Z" 382 | }, 383 | "collapsed": true 384 | }, 385 | "outputs": [], 386 | "source": [ 387 | "%load_ext memory_profiler" 388 | ] 389 | }, 390 | { 391 | "cell_type": "code", 392 | "execution_count": 11, 393 | "metadata": { 394 | "ExecuteTime": { 395 | "end_time": "2017-09-11T14:04:59.557588Z", 396 | "start_time": "2017-09-11T14:04:59.048163Z" 397 | }, 398 | "collapsed": false, 399 | "scrolled": true 400 | }, 401 | "outputs": [ 402 | { 403 | "name": "stdout", 404 | "output_type": "stream", 405 | "text": [ 406 | "peak memory: 67.60 MiB, increment: 0.15 MiB\n", 407 | "peak memory: 67.61 MiB, increment: 0.01 MiB\n" 408 | ] 409 | } 410 | ], 411 | "source": [ 412 | "%memit wallis4(1000000) # Best vectorized with Numpy\n", 413 | "%memit wallis5(1000000) # Raw loop in Cython" 414 | ] 415 | }, 416 | { 417 | "cell_type": "markdown", 418 | "metadata": {}, 419 | "source": [ 420 | "Cython basically allows you to do most things you would want to do in C, and supports most functions of the standard library. \n", 421 | "\n", 422 | "## Numba\n", 423 | "\n", 424 | "Now, there is an incredible tool called Numba which is getting better and better, and aims at accelerating the code even more than Cython, without having these anoying variable declarations on top of the program. Using it is super easy: you take your naive approach, put @autojit on top and voilà." 425 | ] 426 | }, 427 | { 428 | "cell_type": "code", 429 | "execution_count": 6, 430 | "metadata": { 431 | "ExecuteTime": { 432 | "end_time": "2017-09-11T14:06:19.024592Z", 433 | "start_time": "2017-09-11T14:06:02.343640Z" 434 | }, 435 | "collapsed": false 436 | }, 437 | "outputs": [ 438 | { 439 | "name": "stdout", 440 | "output_type": "stream", 441 | "text": [ 442 | "62 µs ± 446 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n", 443 | "12.8 µs ± 121 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)\n" 444 | ] 445 | } 446 | ], 447 | "source": [ 448 | "from numba import autojit\n", 449 | "\n", 450 | "\n", 451 | "@autojit\n", 452 | "def wallis6(n):\n", 453 | " out = 2.\n", 454 | " for i in range(1, n):\n", 455 | " tmp = 4 * i**2\n", 456 | " out *= tmp / (tmp - 1.)\n", 457 | " return out\n", 458 | "\n", 459 | "%timeit wallis4(10000) # Best vectorized with Numpy\n", 460 | "%timeit wallis6(10000) # Raw Numba loop" 461 | ] 462 | }, 463 | { 464 | "cell_type": "markdown", 465 | "metadata": {}, 466 | "source": [ 467 | "Now let us compare all these methods:" 468 | ] 469 | }, 470 | { 471 | "cell_type": "code", 472 | "execution_count": 12, 473 | "metadata": { 474 | "ExecuteTime": { 475 | "end_time": "2017-09-11T14:11:24.179838Z", 476 | "start_time": "2017-09-11T14:06:26.911698Z" 477 | }, 478 | "collapsed": false, 479 | "scrolled": false 480 | }, 481 | "outputs": [ 482 | { 483 | "name": "stdout", 484 | "output_type": "stream", 485 | "text": [ 486 | "Timing for Numpy 1 ...\n", 487 | "Timing for Numpy 2 ...\n", 488 | "Timing for Numpy 3 ...\n", 489 | "Timing for Cython ...\n", 490 | "Timing for Numba ...\n" 491 | ] 492 | }, 493 | { 494 | "data": { 495 | "image/png": "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\n", 496 | "text/plain": [ 497 | "
" 498 | ] 499 | }, 500 | "metadata": {}, 501 | "output_type": "display_data" 502 | } 503 | ], 504 | "source": [ 505 | "%matplotlib inline\n", 506 | "import matplotlib.pyplot as plt\n", 507 | "import seaborn as sns; sns.set()\n", 508 | "sns.color_palette(\"colorblind\")\n", 509 | "# Use seaborn to have instantly beautiful plots !\n", 510 | "\n", 511 | "functions = [wallis2, wallis3, wallis4, wallis5, wallis6]\n", 512 | "names = ['Numpy 1', 'Numpy 2', 'Numpy 3', 'Cython', 'Numba']\n", 513 | "n_list = np.logspace(1, 7, num=7, dtype=int)\n", 514 | "\n", 515 | "\n", 516 | "plt.figure()\n", 517 | "for function, name in zip(functions, names):\n", 518 | " print('Timing for %s ...' % name)\n", 519 | " times = []\n", 520 | " for n in n_list:\n", 521 | " t = %timeit -oq function(n)\n", 522 | " times.append(t.best / float(n))\n", 523 | " plt.loglog(n_list, times, label=name)\n", 524 | "plt.xlabel('n')\n", 525 | "plt.ylabel('time / n (seconds)')\n", 526 | "plt.legend(loc='upper right')\n", 527 | "plt.show()" 528 | ] 529 | }, 530 | { 531 | "cell_type": "markdown", 532 | "metadata": {}, 533 | "source": [ 534 | "The results are pretty clear, on this problem, Cython and Numba are the winners. Numba is also much easier to use but lacks some features of Cython." 535 | ] 536 | }, 537 | { 538 | "cell_type": "markdown", 539 | "metadata": {}, 540 | "source": [ 541 | "# Array processing\n", 542 | "\n", 543 | "The previous example made Numba really shine. The reality is sometimes subtler.\n", 544 | "\n", 545 | "Let us consider the simple case of computing the mean of a function of a signal $x(t)$ of length $T$. That is, we want to compute $\\frac{1}{T}\\sum_{t=1}^T f(x(t))$. Let us take for $f$ a slightly complicated function: $$f(x) = \\cos(e^{- \\rvert x \\lvert })$$\n" 546 | ] 547 | }, 548 | { 549 | "cell_type": "markdown", 550 | "metadata": {}, 551 | "source": [ 552 | "### Numpy" 553 | ] 554 | }, 555 | { 556 | "cell_type": "code", 557 | "execution_count": 14, 558 | "metadata": { 559 | "ExecuteTime": { 560 | "end_time": "2017-09-11T14:11:24.202453Z", 561 | "start_time": "2017-09-11T14:11:24.194486Z" 562 | }, 563 | "collapsed": true 564 | }, 565 | "outputs": [], 566 | "source": [ 567 | "def function1(x):\n", 568 | " \"\"\"Standard array processing with numpy\"\"\"\n", 569 | " return np.mean(np.cos(np.exp(-np.abs(x))))\n", 570 | "\n", 571 | "def function2(x):\n", 572 | " \"\"\"Better array processing with numpy, limited number of copies\"\"\"\n", 573 | " tmp = np.abs(x)\n", 574 | " tmp *= -1.\n", 575 | " np.exp(tmp, out=tmp)\n", 576 | " np.cos(tmp, out=tmp)\n", 577 | " return np.mean(tmp)" 578 | ] 579 | }, 580 | { 581 | "cell_type": "markdown", 582 | "metadata": {}, 583 | "source": [ 584 | "### Cython" 585 | ] 586 | }, 587 | { 588 | "cell_type": "code", 589 | "execution_count": 15, 590 | "metadata": { 591 | "ExecuteTime": { 592 | "end_time": "2017-09-11T14:11:27.830919Z", 593 | "start_time": "2017-09-11T14:11:24.216111Z" 594 | }, 595 | "collapsed": true, 596 | "scrolled": true 597 | }, 598 | "outputs": [], 599 | "source": [ 600 | "%%cython\n", 601 | "from libc.math cimport exp, cos\n", 602 | "\n", 603 | "def function3(double[:] x):\n", 604 | " \"\"\"Array processing with Cython\"\"\"\n", 605 | " cdef double out = 0.\n", 606 | " cdef int T = x.shape[0]\n", 607 | " cdef int i\n", 608 | " for i in range(T):\n", 609 | " out += cos(exp(-abs(x[i])))\n", 610 | " return out / float(T)\n" 611 | ] 612 | }, 613 | { 614 | "cell_type": "markdown", 615 | "metadata": {}, 616 | "source": [ 617 | "### Numba" 618 | ] 619 | }, 620 | { 621 | "cell_type": "code", 622 | "execution_count": 16, 623 | "metadata": { 624 | "ExecuteTime": { 625 | "end_time": "2017-09-11T14:11:27.849202Z", 626 | "start_time": "2017-09-11T14:11:27.842804Z" 627 | }, 628 | "collapsed": true 629 | }, 630 | "outputs": [], 631 | "source": [ 632 | "@autojit(nopython=True)\n", 633 | "def function4(x):\n", 634 | " \"\"\"Array processing with numba\"\"\"\n", 635 | " out = 0.\n", 636 | " T = x.shape[0]\n", 637 | " for i in range(x.shape[0]):\n", 638 | " out += np.cos(np.exp(-np.abs(x[i])))\n", 639 | " return out / float(T)" 640 | ] 641 | }, 642 | { 643 | "cell_type": "markdown", 644 | "metadata": {}, 645 | "source": [ 646 | "### Numexpr\n", 647 | "\n", 648 | "Numexpr is a library that performs efficient expression evaluation. It is really great to compute complicated functions of large arrays." 649 | ] 650 | }, 651 | { 652 | "cell_type": "code", 653 | "execution_count": 17, 654 | "metadata": { 655 | "ExecuteTime": { 656 | "end_time": "2017-09-11T14:11:28.010848Z", 657 | "start_time": "2017-09-11T14:11:27.865252Z" 658 | }, 659 | "collapsed": true 660 | }, 661 | "outputs": [], 662 | "source": [ 663 | "import numexpr as ne\n", 664 | "\n", 665 | "def function5(x):\n", 666 | " \"\"\"Array processing using NumExpr\"\"\"\n", 667 | " tmp = ne.evaluate('cos(exp(-abs(x)))')\n", 668 | " return np.mean(tmp)\n", 669 | "\n", 670 | "def function6(x):\n", 671 | " return ne.evaluate('sum(cos(exp(-abs(x))))') / float(len(x))" 672 | ] 673 | }, 674 | { 675 | "cell_type": "markdown", 676 | "metadata": {}, 677 | "source": [ 678 | "### Comparison" 679 | ] 680 | }, 681 | { 682 | "cell_type": "code", 683 | "execution_count": 18, 684 | "metadata": { 685 | "ExecuteTime": { 686 | "end_time": "2017-09-11T14:18:15.933293Z", 687 | "start_time": "2017-09-11T14:11:28.032060Z" 688 | }, 689 | "collapsed": false 690 | }, 691 | "outputs": [ 692 | { 693 | "name": "stdout", 694 | "output_type": "stream", 695 | "text": [ 696 | "Timing for Numpy ...\n", 697 | "Timing for Numpy2 ...\n", 698 | "Timing for Cython ...\n", 699 | "Timing for Numba ...\n", 700 | "Timing for Numexpr ...\n", 701 | "Timing for Numexpr2 ...\n" 702 | ] 703 | }, 704 | { 705 | "data": { 706 | "image/png": "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\nmjZtStOmTRk1ahRr166lUyfLgjmLFi1i//79uLq60qhRI5o0aYJKpaJ58+akpqbeVLtUikL/sADy\njhs4k1fEnouX8XLU8HCQj83yXiEdLbsmXz5BQdZhNFofvIL+76baIIQQQtyp7KIn5ffff+eBBx7g\n008/LdX7cOzYMYxGIy1btrQea926NYcOHbJ+njJlCrGxsSxbtoxWrVqxc+dOAE6fPo2vr+9Nt81B\npWJIo2D8nRwB+PbcJf68lGOzrGVqcl8cnAMByEn7hfzMQzbLCiGEEOLa7CJJGTRoEC+//DJabekd\nINPT0/Hy8iq1qZOvry/FxcXWfRCuFhkZSVhYGAMGDOCVV15h9uzZldI+F42apxsH4+5g2bny88QL\nnMyx/XKsSu2If9hA1A7uAFw6u5GivKRKaYcQQojq1b59G+bMmVnm+JYtm+jfv1eV3LN//15s2bKp\nSq59q7GL4Z7yFBYW4ujoWOrYlc86nc5mnZdffvmG73coLZ66znXRaMrmbn4aLSPDQ1l6JJlio4mP\nT55n3N21CXYtu7qgRuNFYOPBpMavwmzSk3F6PSF3jcLB6eZ7dqqDWq0q9c87lcTBQuJQQmJhcafF\nYdu2rURF9aF163utx1QqBUVRqiwWarXK5u8ie1VVPwt2naRotdoyyciVz87OzpV+v9d/fo/BzXvT\nO7yzzfPe3q6Md3Lgvb0nKTaaWHM8lVcebIKvs6ONwg1xdhzKyQOrMRkLuXgqjqb3TUTjWPl7G1QV\nD4/Kj/GtSOJgIXEoIbGwuFPiEBISwrvvLuCrr76y9uy7umpRqRRrDCozFiqVgqurtkr2wrnV2HWS\nEhAQQHZ2NiaTCZXKkqVlZGTg5OSEh0f5GwDejE8O/Q+tyZm2wa1tng9Sq+nfIJB1J9PILtbzzp4T\njI+og4tGXbawpg6+dbpw6ewWigsyOL5/NUGNh6Ko7DrsqNUqPDycyckpxGi8czcPkzhYSBxKSCws\n7rQ4jB49jgUL3uCDD5YyfPgIAPLzizGZzOTkFJKbm8ljjz3Gl19uIjDQsl3KypXL+eOP/SxZsoJv\nvtnIN99s5L777ueTT2JxdHRkwoTJaLVOvPfeO+Tn59G79+NMmGDZKdlkMnPo0GGio1eTlJRIq1b3\nMG3aTAICLO87Hjz4J0uWvMfx48dQFIVWrVozY8bsSnkP80Zd+ZmobHb92zI8PByNRsOff/7JPffc\n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\nbACCgoJttu+XX7Yze/Z0+vcfRPfu9jNrqcKpktls5u233yY2Nha9Xs/WrVt59913cXZ25tVXX8XB\nwaEq2nlHahDiycQ+zVj82SEKj7fC+e7fwSmXjae/xVPrwQNB99qs92CAF5d1BnakZXEuv5h1p9J4\nslEQ6qt6X9QaF/zDBnHhxCpMxkIyznxBQKOncHS5fXbPFELYLyeNmpea17slhnuuiIhoTrduPVm0\naCGDBw+zHrfVs/3PtcTU6rK/bm1tVXKl3tKl0Tg5OZU65+Pja/0+IeEEarWa/fv30qlT11Llru4F\nMf79R+qVe9laLX7btq28/vps+vTpz8SJU8qcr0kV7kmJjY3lq6++Yvbs2daH7dChA9u2beODDz6o\n9Abe6SLCfBnZIxzF6EBhfGsUvWW9k0+OfcaRS8fKrdc51JfmPpZp4scu5/N10kXMV96I+puDky9+\nYQNAUWE26SxTk/W5VfcwQghxFSeNmtpuTtX2dTMJyhXjxk2iqKiQuLi11mMODg6YzWYKCkpmVqam\nptzQ9UNCQlGpVFy+nE1ISCghIaG4uLjy3ntvk5l5CbAMy+zd+xsLFiziu++2lJoebTabSUgo6Rk/\nduwofn7+eHh42Lzfvn2/8/rrs+nXbyCTJ0+9oTZXpQonKZ9++imzZs2ib9++1uyxW7duvP7662zc\nuLHSGyig7V2BDO7YGPROFB67B8XogMlsYuXhtSTlJNuso1IU+tUPIMzdktTsTc/hp/Nl11txcqt7\n1dTknL93TZapyUIIYYuHhydjx04iLa1koU0/Pz8CAgKIi4slNTWFzZs3snv3zmte559/NF7h4uJC\nz559eOutNzhwYD+JiaeZO3cWKSkpBAeHUFCQz7vvvsVTT43kvvva8vjjT7BgwRvo9SU9UosXL+TY\nsXj27v2N6Ojl9O3b3+a9jEYj8+bNoVWr1gwePJTMzEvWL3uZbl3hJOXcuXOEh4eXOd60aVPS09Mr\npVGirMdah9LroXqYC90pOtEKzCp0Rh1LD64mveCSzToalYohDYMIdLb0eG1LyWS/jcXeXH2a4xH4\nMAD6wvNcPC1Tk4UQAmwP5fToEUVERHOunFIUhRkzZhMff4ShQwewffuPDBs2soLXLfk8adIU2rS5\nn5kzX2bcuBE4OjqwcOF7KIrCihVLcHZ25oknngRgxIhnKC4uYvXq/1rrR0Z25KWXpjBnzkx69erD\n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\nasOGDTp9+rTdI9kiOztb+fn5ysvLU//+/bvtH7e7du1SUlKStmzZogkTJujjjz++oft3u5ISCoU0\nceLEZo02FAppyZIlGj58uMaMGaONGze2uN+QIUP0+uuvWzhpbLUlh8mTJ2vWrFmSIu04ISHB0plj\noS05TJo0SQsXLpQkFRcXd5lVlLa+NpYvX66XXnqpy5SUtuRw6NAhlZeX69lnn9WcOXN08uRJq8eO\nibZksXv3bvXt21dz587VokWL9NBDD1k9dodr62tDkvbt26fKysoucRHStuRwxx13qL6+XpJUU1Nz\nw783utXbPaFQSAsWLNCxY8eabV+5cqUOHTqk/Px8nTlzRjk5Obrlllv02GOPRfcZP368CgsLrR45\nJtqaQ3JysiTp8uXLysnJUU5OjuWzd6T2PB/i4uI0Z84cHTx4UB999JHVo3e4tmaxbds2DRgwQHff\nfbe6wnUh25qD1+vV7NmzNWnSJO3bt0+5ubnasmWLHd9Ch2lrFpcvX1ZpaanWrVungwcP6rXXXtOm\nTZvs+BY6RHt+TkjShx9+qJdfftnKkWOirTn06NFDR48e1eOPP66amhpt3rz5xh7Y7CaOHTtmZmdn\nm9nZ2eaAAQPMwsJC0zRNs7a21hw8eLBZVFQU3XfdunXmjBkzWnyNPXv2mIsXL7Zs5lhobw4nTpww\nJ06caBYUFFg6d0friOeDaZrmqVOnzPHjx1syc6y0J4tp06aZM2bMMKdPn27ec8895uzZsy2fv6O0\nJ4dgMGgGg8Ho7UcffdS6wWOgPVmsXr3azMvLi94eO3asdYN3sPb+nCgrKzOfeuopS2eOhfbksGLF\nCnPt2rXRrzN58uQbeuxu83ZPYWGhRo4cqU8//bTZX3yHDx9WOBzW0KFDo9uGDRumn3/+2Y4xY649\nOZw/f14vvPCCli9f3umXcNuTw7Zt26J/DXg8HsXHx1s3eAy0J4tNmzYpLy9P+fn56tmzZ6c+Pqc9\nOWzZskVr166N7n/TTTdZN3gMtCeL++67T99//70k6cSJE9GDiTuj9v7e2Lt3r0aNGmXZvLHSnhz8\nfr98Pp8kKT09XbW1tTf02N3m7Z6pU6ded3tJSYlSU1PlcjVFkZGRoWAwqLKyMqWlpVk1oiXak8P6\n9esVCAS0evVqmaapjIwMrVmzxqrRO1R7cpgwYYIWLVqkL7/8UqZp6s0337Rq7JjoqNfG7z8ctLNp\nTw5Tp07Vq6++qunTp8vlcumtt96yauyYaE8WjzzyiIqKijRlyhRJ0rJlyyyZORba+9o4efKkbrvt\nNktmjaX25DBz5kzl5ubqq6++UjgcvuHnQ7cpKX8kEAgoMTGx2bart0OhULPtI0aM0IgRIyybzUqt\nyaGz/zJujdbk4PP5tH79estns9qNvDYkaefOnZbMZbXW5OB2u/Xee+9ZPpvVWvuc6OzHq/2V1ubw\n/PPPWzqX1VqTQ48ePdr12ug2b/f8Ebfb3eIH7tXbSUlJdoxkC3KIIIcmZBFBDk3IIoIcIqzIoduX\nlMzMTJWXl6uxsTG6rbS0VB6PR36/38bJrEUOEeTQhCwiyKEJWUSQQ4QVOXT7kjJw4EC5XC7t378/\num3v3r0aNGiQjVNZjxwiyKEJWUSQQxOyiCCHCCty6PYlxePxKDs7W8uWLdOBAwe0Y8cObdy4UTNn\nzrR7NEuRQwQ5NCGLCHJoQhYR5BBhSQ5tPnG6E7v2PG/TNM1AIGAuXrzYvPfee80HH3yw2Tn+XRk5\nRJBDE7KIIIcmZBFBDhFW52CYZhe4TCQAAOhyuv3bPQAAwJkoKQAAwJEoKQAAwJEoKQAAwJEoKQAA\nwJEoKQAAwJEoKQAAwJEoKQAAwJEoKQAAwJEoKQAAwJFcdg8AANeTm5urzz77TIZh6Hqf3mEYhn75\n5RcbJgNgFT67B4AjVVdXKxgMRm+PHj1aS5cu1YQJE6LbMjIy7BgNgEVYSQHgSF6vV16vt8U2ignQ\nfXBMCgAAcCRKCgAAcCRKCgAAcCRKCgAAcCRKCgAAcCRKCgAAcCRKCgAAcCRKCoBOwTAMu0cAYDGu\nOAsAAByJlRQAAOBIlBQAAOBIlBQAAOBIlBQAAOBIlBQAAOBIlBQAAOBIlBQAAOBIlBQAAOBIlBQA\nAOBIlBQAAOBIlBQAAOBI/w9sgj0oVeaqiQAAAABJRU5ErkJggg==\n", 707 | "text/plain": [ 708 | "" 709 | ] 710 | }, 711 | "metadata": {}, 712 | "output_type": "display_data" 713 | } 714 | ], 715 | "source": [ 716 | "functions = [function1, function2, function3, function4, function5, function6]\n", 717 | "names = ['Numpy', 'Numpy2', 'Cython', 'Numba', 'Numexpr', 'Numexpr2']\n", 718 | "T_list = np.logspace(1, 8, num=8, dtype=int)\n", 719 | "x_list = [np.random.randn(T) for T in T_list]\n", 720 | "\n", 721 | "plt.figure()\n", 722 | "for function, name in zip(functions, names):\n", 723 | " print('Timing for %s ...' % name)\n", 724 | " times = []\n", 725 | " for x, T in zip(x_list, T_list):\n", 726 | " t = %timeit -oq function(x)\n", 727 | " times.append(t.best / float(T))\n", 728 | " plt.loglog(T_list, times, label=name)\n", 729 | "plt.xlabel('T')\n", 730 | "plt.ylabel('time per element (s)')\n", 731 | "plt.legend(loc='upper right')\n", 732 | "plt.show()" 733 | ] 734 | }, 735 | { 736 | "cell_type": "markdown", 737 | "metadata": {}, 738 | "source": [ 739 | "Numexpr is generally the go-to tool when it comes to large array processing. Unfortunately, the built in 'sum' function is not as fast as expected, it does not use parallelization, which explains why using `np.mean` is faster for large T." 740 | ] 741 | } 742 | ], 743 | "metadata": { 744 | "anaconda-cloud": {}, 745 | "kernelspec": { 746 | "display_name": "Python 3", 747 | "language": "python", 748 | "name": "python3" 749 | }, 750 | "language_info": { 751 | "codemirror_mode": { 752 | "name": "ipython", 753 | "version": 3 754 | }, 755 | "file_extension": ".py", 756 | "mimetype": "text/x-python", 757 | "name": "python", 758 | "nbconvert_exporter": "python", 759 | "pygments_lexer": "ipython3", 760 | "version": "3.6.3" 761 | }, 762 | "varInspector": { 763 | "cols": { 764 | "lenName": 16, 765 | "lenType": 16, 766 | "lenVar": 40 767 | }, 768 | "kernels_config": { 769 | "python": { 770 | "delete_cmd_postfix": "", 771 | "delete_cmd_prefix": "del ", 772 | "library": "var_list.py", 773 | "varRefreshCmd": "print(var_dic_list())" 774 | }, 775 | "r": { 776 | "delete_cmd_postfix": ") ", 777 | "delete_cmd_prefix": "rm(", 778 | "library": "var_list.r", 779 | "varRefreshCmd": "cat(var_dic_list()) " 780 | } 781 | }, 782 | "types_to_exclude": [ 783 | "module", 784 | "function", 785 | "builtin_function_or_method", 786 | "instance", 787 | "_Feature" 788 | ], 789 | "window_display": false 790 | } 791 | }, 792 | "nbformat": 4, 793 | "nbformat_minor": 2 794 | } 795 | --------------------------------------------------------------------------------