{"cells": [{"cell_type": "markdown", "metadata": {}, "source": ["# 2A.data - Calcul Matriciel, Optimisation\n", "\n", "[numpy arrays](https://docs.scipy.org/doc/numpy/reference/generated/numpy.array.html) sont la premi\u00e8re chose \u00e0 consid\u00e9rer pour acc\u00e9l\u00e9rer un algorithme. Les matrices sont pr\u00e9sentes dans la plupart des algorithmes et *numpy* optimise les op\u00e9rations qui s'y rapporte. Ce notebook est un parcours en diagonal."]}, {"cell_type": "code", "execution_count": 1, "metadata": {"collapsed": true}, "outputs": [], "source": ["%matplotlib inline"]}, {"cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [{"data": {"text/html": ["
run previous cell, wait for 2 seconds
\n", ""], "text/plain": [""]}, "execution_count": 3, "metadata": {}, "output_type": "execute_result"}], "source": ["from jyquickhelper import add_notebook_menu\n", "add_notebook_menu()"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Numpy arrays"]}, {"cell_type": "markdown", "metadata": {}, "source": ["La convention d'import classique de numpy est la suivante:"]}, {"cell_type": "code", "execution_count": 3, "metadata": {"collapsed": true}, "outputs": [], "source": ["import numpy as np"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### Creation d'un array: notion de datatype, et dimensions"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On part d'une liste python contenant des entiers. On peut cr\u00e9er un [array](http://docs.scipy.org/doc/numpy/reference/generated/numpy.array.html) numpy \u00e0 partir de cette liste. \n", "Cet array poss\u00e8de des attributs indiquant le data type, le nombre de dimensions de l'array, etc..."]}, {"cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[ 1 42 18]\n", "int32\n", "1\n", "(3,)\n", "3\n"]}, {"data": {"text/plain": ["array([ 1, 42, 18])"]}, "execution_count": 5, "metadata": {}, "output_type": "execute_result"}], "source": ["l = [1, 42, 18 ]\n", "a = np.array(l)\n", "print(a)\n", "print(a.dtype)\n", "print(a.ndim)\n", "print(a.shape)\n", "print(a.size)\n", "a\n"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On peut indiquer explicitement le [dtype](http://docs.scipy.org/doc/numpy/reference/generated/numpy.dtype.html) lors de la cr\u00e9ation de l'array. Sinon, Numpy s\u00e9lectionne automatiquement le [dtype](http://docs.scipy.org/doc/numpy/reference/generated/numpy.dtype.html).\n", "Numpy ajoute un grand nombre de [dtype](http://docs.scipy.org/doc/numpy/reference/generated/numpy.dtype.html) \u00e0 ceux de Python. Allez jeter un oeil \u00e0 la [liste](http://docs.scipy.org/doc/numpy/user/basics.types.html). "]}, {"cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[ 1. 42. 18.]\n", "float64\n"]}], "source": ["b = np.array(l, dtype=float)\n", "print(b)\n", "print(b.dtype)"]}, {"cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[ 1. 42. 18.]\n", "float64\n"]}], "source": ["l[0] = 1.0\n", "bb = np.array(l)\n", "print(bb)\n", "print(bb.dtype)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Assigner un float dans un array de type int va caster le float en int, et ne modifie pas le [dtype](http://docs.scipy.org/doc/numpy/reference/generated/numpy.dtype.html) de l'array."]}, {"cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([ 2, 42, 18])"]}, "execution_count": 8, "metadata": {}, "output_type": "execute_result"}], "source": ["a[0] = 2.5 \n", "a"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On peut forcer le casting dans un autre type avec [astype](http://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.astype.html) :"]}, {"cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([ 2.5, 42. , 18. ])"]}, "execution_count": 9, "metadata": {}, "output_type": "execute_result"}], "source": ["aa = a.astype(float)\n", "aa[0] = 2.5\n", "aa"]}, {"cell_type": "markdown", "metadata": {}, "source": ["A partir d'une liste de listes, on obtient un array bi-dimmensionnel.\n", "\n", "On peut le transposer ou encore l'aplatir en un array 1d"]}, {"cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[[0 1 2 3 4]\n", " [5 6 7 8 9]\n", " [0 1 2 3 4]]\n", "ndim:2\n", "shape:(3, 5)\n", "[[0 5 0]\n", " [1 6 1]\n", " [2 7 2]\n", " [3 8 3]\n", " [4 9 4]]\n", "shape transposed:(5, 3)\n", "[0 1 2 3 4 5 6 7 8 9 0 1 2 3 4]\n", "ndim flattened:1\n"]}], "source": ["c = np.array([range(5), range(5,10), range(5)])\n", "print(c)\n", "print(\"ndim:{}\".format(c.ndim))\n", "print(\"shape:{}\".format(c.shape))\n", "print(c.transpose()) #same as c.T\n", "print(\"shape transposed:{}\".format(c.T.shape))\n", "print(c.flatten())\n", "print(\"ndim flattened:{}\".format(c.flatten().ndim))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### Indexation, Slicing, Fancy indexing"]}, {"cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[[0 1 2 3 4]\n", " [5 6 7 8 9]\n", " [0 1 2 3 4]]\n"]}], "source": ["print(c)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["L'indexation des array multidimensionnels fonctionne avec des tuples.\n", "\n", "La syntaxe ``':'`` permet d'obtenir tous les \u00e9l\u00e9ments de la dimension."]}, {"cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["8\n", "[5 6 7]\n", "[4 9 4]\n"]}], "source": ["print(c[1,3])\n", "print(c[1,:3])\n", "print(c[:,4])"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Si on utilise pas un couple sur un array 2d on r\u00e9cup\u00e8re un array 1d"]}, {"cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[5 6 7 8 9] (5,)\n", "[5 6 7]\n"]}], "source": ["print(c[1], c[1].shape)\n", "print(c[1][:3])"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On peut aussi utiliser l'indexation par un array (ou une liste python) de bool\u00e9ens ou d'entiers (un mask). Cela s'appelle le fancy indexing. Un mask d'entiers permet de d\u00e9signer les \u00e9l\u00e9ments que l'on souhaite extraire via la liste de leurs indices, on peut aussi r\u00e9p\u00e9ter l'indice d'un \u00e9l\u00e9ment pour r\u00e9p\u00e9ter l'\u00e9lement dans l'array que l'on extrait."]}, {"cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["ar = [1 2 3 4 5 6 7 8 9]\n", "idx = [1 4 3 2 1 7 3]\n", "ar[idx] = [2 5 4 3 2 8 4]\n", "######\n", "idx_bool = [ True False False False False True True False True]\n", "ar[idx_bool] = [1 6 7 9]\n", "###### Que se passe-t-il dans chacun des cas suivants? ######\n", "Erreur boolean index did not match indexed array along dimension 0; dimension is 9 but corresponding boolean dimension is 4\n"]}], "source": ["ar = np.arange(1,10) #arange est l'equivalent de range mais retourne un numpy array\n", "print('ar = ',ar)\n", "idx = np.array([1, 4, 3, 2, 1, 7, 3])\n", "print('idx = ',idx)\n", "print(\"ar[idx] =\", ar[idx])\n", "print('######')\n", "idx_bool = np.ones(ar.shape, dtype=bool)\n", "idx_bool[idx] = False\n", "print('idx_bool = ', idx_bool)\n", "print('ar[idx_bool] = ', ar[idx_bool])\n", "print('######', 'Que se passe-t-il dans chacun des cas suivants?', '######' )\n", "try:\n", " print('ar[np.array([True, True, False, True])] = ', ar[np.array([True, True, False, True])])\n", "except Exception as e:\n", " # l'expression ar[[True, True, False, True]] d\u00e9clenche une erreur depuis numpy 1.13\n", " print(\"Erreur\", e)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Pourquoi parle-t-on de fancy indexing? Essayez d'indexer des listes python de la m\u00eame mani\u00e8re..."]}, {"cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [{"ename": "TypeError", "evalue": "range indices must be integers or slices, not list", "output_type": "error", "traceback": ["\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0mlist_python\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mrange\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m10\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mlist_python\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;32mTrue\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;32mFalse\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;32mTrue\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;31m# d\u00e9clenche une exception\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mTypeError\u001b[0m: range indices must be integers or slices, not list"]}], "source": ["list_python = range(10)\n", "list_python[[True, True, False, True]] # d\u00e9clenche une exception"]}, {"cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [{"ename": "TypeError", "evalue": "range indices must be integers or slices, not list", "output_type": "error", "traceback": ["\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mlist_python\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m3\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m7\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;31m# d\u00e9clenche une exception\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mTypeError\u001b[0m: range indices must be integers or slices, not list"]}], "source": ["list_python[[2, 3, 2, 7]] # d\u00e9clenche une exception"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### View contre Copy"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Cr\u00e9ons un array $d$. En plus de renvoyer directement un array, la fonction [arange](http://docs.scipy.org/doc/numpy/reference/generated/numpy.arange.html) permet aussi d'utiliser un step flottant. (Essayer avec le range de python pour voir)"]}, {"cell_type": "code", "execution_count": 16, "metadata": {"collapsed": true}, "outputs": [], "source": []}, {"cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([ 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. , 4.5, 5. , 5.5])"]}, "execution_count": 18, "metadata": {}, "output_type": "execute_result"}], "source": ["d = np.arange(1, 6, 0.5)\n", "d"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Un point important est que l'on ne recopie pas un array lorsqu'on effectue une assignation ou un slicing d'un array.\n", "On travaille dans ce cas avec une View sur l'array d'origine (shallow copy). Toute modification sur la View affecte l'array d'origine.\n", "\n", "Dans l'exemple qui suit, $e$ est une view sur $d$. Lorsqu'on modifie $e$, $d$ aussi est modifi\u00e9. (Remarquez au passage que numpy fournit quelques constantes bien pratiques....)"]}, {"cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([-3.14159265, 1.5 , -3.14159265, 2.5 , -3.14159265,\n", " 3.5 , 4. , 4.5 , 5. , 5.5 ])"]}, "execution_count": 19, "metadata": {}, "output_type": "execute_result"}], "source": ["e = d\n", "e[[0,2, 4]] = - np.pi\n", "e"]}, {"cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([-3.14159265, 1.5 , -3.14159265, 2.5 , -3.14159265,\n", " 3.5 , 4. , 4.5 , 5. , 5.5 ])"]}, "execution_count": 20, "metadata": {}, "output_type": "execute_result"}], "source": ["d"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Si on ne veut pas modifier $d$ indirectement, il faut travailler sur une copie de $d$ ([deep copy](https://docs.python.org/3.4/library/copy.html#copy.deepcopy))."]}, {"cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[-2.71828183 -2.71828183 -2.71828183 -2.71828183 3. 3.5 4.\n", " 4.5 5. 5.5 ]\n", "[ 1. 1.5 2. 2.5 3. 3.5 4. 4.5 5. 5.5]\n"]}], "source": ["d = np.linspace(1,5.5,10) #Question subsidiaire: en quoi est-ce diff\u00e9rent de np.arange avec un step float?\n", "f = d.copy()\n", "f[:4] = -np.e #il s'agit du nombre d'euler, pas de l'array e ;)\n", "print(f)\n", "print(d)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Ce point est important car source classique d'erreurs silencieuses: les erreurs les plus vicieuses car l'output sera faux mais python ne r\u00e2lera pas...\n", "\n", "Il faut un peu de temps pour s'habituer mais on finit par savoir de mani\u00e8re naturelle quand on travaille sur une view, quand on a besoin de faire une copie explicitement, etc... En tout cas, v\u00e9rifiez vos sorties, faites des tests de coh\u00e9rence, cela ne nuit jamais.\n", "\n", "Retenez par exemple que le [slicing](http://docs.scipy.org/doc/numpy/reference/arrays.indexing.html#basic-slicing-and-indexing) vous renvoie une view sur l'array, alors que le [fancy indexing](https://scipy-lectures.github.io/intro/numpy/array_object.html#fancy-indexing) effectue une copie.\n", "\n", "(Au passage, remarquez le [NaN](http://docs.scipy.org/doc/numpy/reference/generated/numpy.isnan.html) (=NotaNumber) d\u00e9j\u00e0 introduit lors de la s\u00e9ance 1 sur pandas qui est un module bas\u00e9 sur numpy)"]}, {"cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["d = [ 1. 1.5 2. 2.5 3. 3.5 4. 4.5 5. 5.5]\n", "\n", "slice_of_d = [ 2. 2.5 3. ]\n", "\n", "d = [ 1. 1.5 nan 2.5 3. 3.5 4. 4.5 5. 5.5]\n", "\n", "fancy_indexed_subarray = [ nan 2.5 3. ]\n", "\n", "d = [ 1. 1.5 nan 2.5 3. 3.5 4. 4.5 5. 5.5]\n"]}], "source": ["print('d = ',d)\n", "slice_of_d = d[2:5]\n", "print('\\nslice_of_d = ', slice_of_d)\n", "slice_of_d[0] = np.nan\n", "print('\\nd = ', d)\n", "mask = np.array([2, 3, 4])\n", "fancy_indexed_subarray = d[mask]\n", "print('\\nfancy_indexed_subarray = ', fancy_indexed_subarray)\n", "fancy_indexed_subarray[0] = -2\n", "print('\\nd = ', d)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### Manipulation de shape"]}, {"cell_type": "markdown", "metadata": {}, "source": ["La m\u00e9thode reshape permet de changer la forme de l'array. Il existe de nombreuses [manipulations possibles](http://docs.scipy.org/doc/numpy/reference/routines.array-manipulation.html)."]}, {"cell_type": "markdown", "metadata": {}, "source": ["On pr\u00e9cise \u00e0 [reshape](http://docs.scipy.org/doc/numpy/reference/generated/numpy.reshape.html) la forme souhait\u00e9e: par un entier si on veut un array 1d de cette longueur, ou un couple pour un array 2d de cette forme."]}, {"cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[ 0 1 2 3 4 5 6 7 8 9 10 11]\n"]}, {"data": {"text/plain": ["array([[ 0, 1, 2],\n", " [ 3, 4, 5],\n", " [ 6, 7, 8],\n", " [ 9, 10, 11]])"]}, "execution_count": 23, "metadata": {}, "output_type": "execute_result"}], "source": ["g = np.arange(12)\n", "print(g)\n", "g.reshape((4,3))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Par d\u00e9faut, [reshape](http://docs.scipy.org/doc/numpy/reference/generated/numpy.reshape.html) utilise l'\u00e9num\u00e9ration dans l'ordre du langage C (aussi appel\u00e9 \"row first\" ), on peut pr\u00e9ciser que l'on souhaite utiliser l'ordre de [Fortran](https://fr.wikipedia.org/wiki/Fortran) (\"column first\"). Ceux qui connaissent Matlab et R sont habitu\u00e9s \u00e0 l'ordre \"column-first\". [Voir l'article wikipedia](http://en.wikipedia.org/wiki/Row-major_order)"]}, {"cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([[ 0, 4, 8],\n", " [ 1, 5, 9],\n", " [ 2, 6, 10],\n", " [ 3, 7, 11]])"]}, "execution_count": 24, "metadata": {}, "output_type": "execute_result"}], "source": ["g.reshape((4,3), order='F')"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On peut utiliser -1 sur une dimension, cela sert de joker: numpy inf\u00e8re la dimension n\u00e9cessaire ! On peut cr\u00e9er directement des matrices de 0 et de 1 \u00e0 la dimension d'un autre array."]}, {"cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])"]}, "execution_count": 25, "metadata": {}, "output_type": "execute_result"}], "source": ["np.zeros_like(g)"]}, {"cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])"]}, "execution_count": 26, "metadata": {}, "output_type": "execute_result"}], "source": ["np.ones_like(g)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On peut aussi concatener ou stacker [horizontalement](http://docs.scipy.org/doc/numpy/reference/generated/numpy.hstack.html)/[verticalement](http://docs.scipy.org/doc/numpy/reference/generated/numpy.vstack.html) diff\u00e9rents arrays."]}, {"cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 0, 0, 0, 0,\n", " 0, 0, 0, 0, 0, 0, 0])"]}, "execution_count": 27, "metadata": {}, "output_type": "execute_result"}], "source": ["np.concatenate((g, np.zeros_like(g))) #Attention \u00e0 la syntaxe: le type d'entr\u00e9e est un tuple!"]}, {"cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],\n", " [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])"]}, "execution_count": 28, "metadata": {}, "output_type": "execute_result"}], "source": ["gmat = g.reshape((1, len(g)))\n", "np.concatenate((gmat, np.ones_like(gmat)), axis=0)"]}, {"cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 1, 1, 1, 1,\n", " 1, 1, 1, 1, 1, 1, 1]])"]}, "execution_count": 29, "metadata": {}, "output_type": "execute_result"}], "source": ["np.concatenate((gmat, np.ones_like(gmat)), axis=1)"]}, {"cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 1, 2, 3, 4,\n", " 5, 6, 7, 8, 9, 10, 11])"]}, "execution_count": 30, "metadata": {}, "output_type": "execute_result"}], "source": ["np.hstack((g, g))"]}, {"cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],\n", " [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]])"]}, "execution_count": 31, "metadata": {}, "output_type": "execute_result"}], "source": ["np.vstack((g,g))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Exercice 1: Echiquier et Crible d'Erathosth\u00e8ne"]}, {"cell_type": "markdown", "metadata": {}, "source": ["* Exercice 1-A Echiquier: Cr\u00e9er une matrice \u00e9chiquier (des 1 et des 0 altern\u00e9s) de taille 8x8, de deux fa\u00e7ons diff\u00e9rentes\n", " * en vous servant de slices \n", " * en vous servant de la fonction [tile](http://docs.scipy.org/doc/numpy/reference/generated/numpy.tile.html#numpy.tile)\n", "* Exercice 1-B Pi\u00e8ge lors d'une extraction 2d:\n", " * D\u00e9finir la matrice $M = \\left(\\begin{array}{ccccc} 1 & 5 & 9 & 13 & 17 \\\\ 2 & 6 & 10 & 14 & 18 \\\\ 3 & 7 & 11 & 15 & 19 \\\\ 4 & 8 & 12 & 16 & 20 \\\\ \\end{array}\\right)$\n", " * En **extraire** la matrice $\\left(\\begin{array}{ccc} 6 & 18 & 10 \\\\ 7 & 19 & 11 \\\\ 5 & 17 & 9 \\\\ \\end{array}\\right)$\n", "* Exercice 1-C Crible d'Erathosth\u00e8ne: On souhaite impl\u00e9menter un [crible d'Erathosth\u00e8ne](http://fr.wikipedia.org/wiki/Crible_d'%C3%89ratosth%C3%A8ne) pour trouver les nombres premiers inf\u00e9rieurs \u00e0 $N=1000$.\n", " * partir d'un array de bool\u00e9ens de taille N+1, tous \u00e9gaux \u00e0 True.\n", " * Mettre 0 et 1 \u00e0 False car ils ne sont pas premiers\n", " * pour chaque entier $k$ entre 2 et $\\sqrt{N}$: \n", " * si $k$ est premier: on passe ses multiples (entre $k^2$ et $N$) \u00e0 False\n", " * on print la liste des entiers premiers"]}, {"cell_type": "code", "execution_count": 31, "metadata": {"collapsed": true}, "outputs": [], "source": ["#Exo1a-1:\n", "\n", "#Exo1a-2:\n"]}, {"cell_type": "code", "execution_count": 32, "metadata": {"collapsed": true}, "outputs": [], "source": ["#Exo1B:\n"]}, {"cell_type": "code", "execution_count": 33, "metadata": {"collapsed": true}, "outputs": [], "source": ["#Exo1C:"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Manipulation et Op\u00e9rations sur les arrays"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Il existe un tr\u00e8s grand nombre de [routines pour manipuler les arrays numpy](http://docs.scipy.org/doc/numpy/reference/routines.html): \n", "Vous trouverez sans doute utiles les pages sp\u00e9cifiques aux routines de [stats](http://docs.scipy.org/doc/numpy/reference/routines.statistics.html) ou de [maths](http://docs.scipy.org/doc/numpy/reference/routines.math.html)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### Op\u00e9rations \u00e9l\u00e9ment par \u00e9l\u00e9ment"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On d\u00e9clare $a$ et $b$ sur lesquelles nous allons illustrer quelques op\u00e9rations"]}, {"cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[[ 1. 1.]\n", " [ 1. 1.]\n", " [ 1. 1.]]\n"]}, {"data": {"text/plain": ["array([[0, 1],\n", " [2, 3],\n", " [4, 5]])"]}, "execution_count": 35, "metadata": {}, "output_type": "execute_result"}], "source": ["a = np.ones((3,2))\n", "b = np.arange(6).reshape(a.shape)\n", "print(a)\n", "b"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Les op\u00e9rations arithm\u00e9tiques avec les scalaires, ou entre arrays s'effectuent \u00e9l\u00e9ment par \u00e9l\u00e9ment.\n", "Lorsque le dtype n'est pas le m\u00eame ($a$ contient des float, $b$ contient des int), numpy adopte le type le plus \"grand\" (au sens de l'inclusion).\n"]}, {"cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[[ 1. 4.]\n", " [ 9. 16.]\n", " [ 25. 36.]]\n", "[[ 3. 2.]\n", " [ 1. 0.]\n", " [ 1. 2.]]\n", "[[ 0.36787944 1. ]\n", " [ 2.71828183 7.3890561 ]\n", " [ 20.08553692 54.59815003]]\n"]}], "source": ["print( (a + b)**2 )\n", "print( np.abs( 3*a - b ) )\n", "f = lambda x: np.exp(x-1)\n", "print( f(b) )"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Remarquez que la division par z\u00e9ro ne provoque pas d'erreur mais introduit la valeur [inf](http://docs.scipy.org/doc/numpy/reference/generated/numpy.isinf.html) :"]}, {"cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([[0, 1],\n", " [2, 3],\n", " [4, 5]])"]}, "execution_count": 37, "metadata": {}, "output_type": "execute_result"}], "source": ["b"]}, {"cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [{"name": "stderr", "output_type": "stream", "text": ["c:\\Python36_x64\\lib\\site-packages\\ipykernel_launcher.py:1: RuntimeWarning: divide by zero encountered in true_divide\n", " \"\"\"Entry point for launching an IPython kernel.\n"]}, {"data": {"text/plain": ["array([[ inf, 1. ],\n", " [ 0.5 , 0.33333333],\n", " [ 0.25 , 0.2 ]])"]}, "execution_count": 38, "metadata": {}, "output_type": "execute_result"}], "source": ["1/b"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### Broadcasting"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Que se passe-t-il si les dimensions sont diff\u00e9rentes?"]}, {"cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([ 1., 1., 1., 1., 1., 1.])"]}, "execution_count": 39, "metadata": {}, "output_type": "execute_result"}], "source": ["c = np.ones(6)\n", "c"]}, {"cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [{"ename": "ValueError", "evalue": "operands could not be broadcast together with shapes (3,2) (6,) ", "output_type": "error", "traceback": ["\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mb\u001b[0m\u001b[1;33m+\u001b[0m\u001b[0mc\u001b[0m \u001b[1;31m# d\u00e9clenche une exception\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mValueError\u001b[0m: operands could not be broadcast together with shapes (3,2) (6,) "]}], "source": ["b+c # d\u00e9clenche une exception"]}, {"cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[[0 1]\n", " [2 3]\n", " [4 5]]\n", "[[0]\n", " [1]\n", " [2]]\n"]}, {"data": {"text/plain": ["array([[0, 1],\n", " [3, 4],\n", " [6, 7]])"]}, "execution_count": 41, "metadata": {}, "output_type": "execute_result"}], "source": ["c = np.arange(3).reshape((3,1))\n", "print(b,c, sep='\\n')\n", "b+c"]}, {"cell_type": "markdown", "metadata": {}, "source": ["L'op\u00e9ration pr\u00e9c\u00e9dente fonctionne car numpy effectue ce qu'on appelle un [broadcasting](http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html) de ``c`` : une dimension \u00e9tant commune, tout se passe comme si on dupliquait c sur la dimension non-partag\u00e9e avec b. Vous trouverez une explication visuelle simple [ici](http://www.tp.umu.se/~nylen/pylect/intro/numpy/numpy.html#broadcasting) :"]}, {"cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[[-1. 1. 2.]\n", " [-1. 1. 2.]\n", " [-1. 1. 2.]]\n"]}], "source": ["a = np.zeros((3,3))\n", "a[:,0] = -1\n", "b = np.array(range(3))\n", "print(a + b)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Par contre, il peut parfois \u00eatre utile de pr\u00e9ciser la dimension sur laquelle on souhaite broadcaster, on ajoute alors explicitement une dimension :"]}, {"cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["(3,)\n", "(3, 1)\n", "(1, 3)\n"]}], "source": ["print(b.shape)\n", "print(b[:,np.newaxis].shape) \n", "print(b[np.newaxis,:].shape)"]}, {"cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[[-1. 1. 2.]\n", " [-1. 1. 2.]\n", " [-1. 1. 2.]]\n", "[[-1. 0. 0.]\n", " [ 0. 1. 1.]\n", " [ 1. 2. 2.]]\n", "[[0 1 2]\n", " [1 2 3]\n", " [2 3 4]]\n", "[0 2 4]\n"]}], "source": ["print( a + b[np.newaxis,:] )\n", "print( a + b[:,np.newaxis] )\n", "print(b[:,np.newaxis]+b[np.newaxis,:])\n", "print(b + b)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### R\u00e9ductions"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On parle de r\u00e9ductions lorsque l'op\u00e9ration r\u00e9duit la dimension de l'array.\n", "Il en existe un grand nombre. Elles existent souvent sous forme de fonction de numpy ou de m\u00e9thodes d'un array numpy.\n", "On n'en pr\u00e9sente que quelques unes, mais le principe est le m\u00eame : par d\u00e9faut elles op\u00e8rent sur toutes les dimensions, mais on peut via l'argument *axis* pr\u00e9ciser la dimension selon laquelle on souhaite effectuer la r\u00e9duction."]}, {"cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[[0 1 2 3 4]\n", " [5 6 7 8 9]]\n", "45\n", "[ 5 7 9 11 13]\n", "[10 35]\n"]}], "source": ["c = np.arange(10).reshape((2,-1)) #Note: -1 is a joker! \n", "print(c)\n", "print(c.sum())\n", "print(c.sum(axis=0))\n", "print(np.sum(c, axis=1))"]}, {"cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["True\n", "0 9\n", "[0 5]\n"]}], "source": ["print(np.all(c[0] < c[1]))\n", "print(c.min(), c.max())\n", "print(c.min(axis=1))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Alg\u00e8bre lin\u00e9aire"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Vous avez un \u00e9ventail de fonctions pour faire de l'alg\u00e8bre lin\u00e9aire dans [numpy](http://docs.scipy.org/doc/numpy/reference/routines.linalg.html) ou dans [scipy](http://docs.scipy.org/doc/scipy/reference/linalg.html).\n", "Cela peut vous servir si vous cherchez \u00e0 faire une d\u00e9composition matricielle particuli\u00e8re ([LU](http://docs.scipy.org/doc/scipy-0.14.0/reference/generated/scipy.linalg.lu.html), [QR](http://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.qr.html), [SVD](http://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.svd.html),...), si vous vous int\u00e9ressez aux valeurs propres d'une matrice, etc..."]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### Exemples simples"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Commen\u00e7ons par construire deux arrays 2d correspondant \u00e0 une matrice triangulaire inf\u00e9rieure et une matrice diagonale :"]}, {"cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([[ 1., 0., 0.],\n", " [ 1., 1., 0.],\n", " [ 1., 1., 1.]])"]}, "execution_count": 47, "metadata": {}, "output_type": "execute_result"}], "source": ["A = np.tril(np.ones((3,3)))\n", "A"]}, {"cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([[1, 0, 0],\n", " [0, 2, 0],\n", " [0, 0, 3]])"]}, "execution_count": 48, "metadata": {}, "output_type": "execute_result"}], "source": ["b = np.diag([1,2, 3])\n", "b"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On a vu que les multiplications entre array s'effectuaient \u00e9l\u00e9ment par \u00e9lement.\n", "Si l'on souhaite faire des multiplications matricielles, il faut utiliser la fonction [dot](http://docs.scipy.org/doc/numpy/reference/generated/numpy.dot.html). La version 3.5 introduit un nouvel op\u00e9rateur [@](https://docs.python.org/3.6/whatsnew/3.5.html#pep-465-a-dedicated-infix-operator-for-matrix-multiplication) qui d\u00e9signe explicitement la multiplication matricielle."]}, {"cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[[ 1. 0. 0.]\n", " [ 1. 2. 0.]\n", " [ 1. 2. 3.]]\n", "[[ 1. 0. 0.]\n", " [ 0. 2. 0.]\n", " [ 0. 0. 3.]]\n", "[[ 1. 0. 0.]\n", " [ 2. 1. 0.]\n", " [ 3. 2. 1.]]\n"]}], "source": ["print(A.dot(b))\n", "print(A*b)\n", "print(A.dot(A))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On peut calculer l'inverse ou le d\u00e9terminant de $A$"]}, {"cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["1.0\n", "[[ 1. 0. 0.]\n", " [-1. 1. 0.]\n", " [ 0. -1. 1.]]\n", "[[ 1. 0. 0.]\n", " [ 0. 1. 0.]\n", " [ 0. 0. 1.]]\n"]}], "source": ["print(np.linalg.det(A))\n", "inv_A = np.linalg.inv(A)\n", "print(inv_A)\n", "print(inv_A.dot(A))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["... r\u00e9soudre des syst\u00e8mes d'equations lin\u00e9aires du type $Ax = b$..."]}, {"cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[1 2 3]\n", "[ 1. 1. 1.]\n", "[ 1. 2. 3.]\n"]}], "source": ["x = np.linalg.solve(A, np.diag(b))\n", "print(np.diag(b))\n", "print(x)\n", "print(A.dot(x))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["... ou encore obtenir les valeurs propres de $A$."]}, {"cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [{"data": {"text/plain": ["(array([ 1., 1., 1.]),\n", " array([[ 0.00000000e+00, 0.00000000e+00, 4.93038066e-32],\n", " [ 0.00000000e+00, 2.22044605e-16, -2.22044605e-16],\n", " [ 1.00000000e+00, -1.00000000e+00, 1.00000000e+00]]))"]}, "execution_count": 52, "metadata": {}, "output_type": "execute_result"}], "source": ["np.linalg.eig(A)"]}, {"cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([ 1., 1., 1.])"]}, "execution_count": 53, "metadata": {}, "output_type": "execute_result"}], "source": ["np.linalg.eigvals(A)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["#### Numpy Matrix"]}, {"cell_type": "markdown", "metadata": {}, "source": ["[Matrix](http://docs.scipy.org/doc/numpy/reference/generated/numpy.matrix.html) est une sous classe sp\u00e9cialis\u00e9e pour le calcul matriciel. Il s'agit d'un array numpy 2d qui conserve sa dimension 2d \u00e0 travers les op\u00e9rations. Pensez aux diff\u00e9rences que cela implique...\n", "On peut les construire classiquement depuis les array ou les objets pythons, ou via une string \u00e0 la Matlab ( o\u00f9 les points virgules indiquent les lignes)."]}, {"cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[[1 2 3]\n", " [4 5 6]\n", " [7 8 9]]\n", "[[1 2 3]\n", " [4 5 6]\n", " [7 8 9]]\n", "[[1 2 3]] [1 2 3]\n", "(1, 3) (3,)\n"]}], "source": ["m = np.matrix(' 1 2 3; 4 5 6; 7 8 9')\n", "a = np.arange(1,10).reshape((3,3))\n", "print(m)\n", "print(a)\n", "print(m[0], a[0])\n", "print(m[0].shape, a[0].shape)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Matrix surcharge par ailleurs les op\u00e9rateurs \\* et \\** pour remplacer les op\u00e9rations \u00e9l\u00e9ment par \u00e9l\u00e9ment par les op\u00e9rations matricielles.\n", "Enfin, une Matrix poss\u00e8de des attributs suppl\u00e9mentaires. Notamment, Matrix.I qui d\u00e9signe l'inverse, Matrix.A l'array de base. \n", "\n", "*Il est probable que cela \u00e9volue puisque Python 3.5 a introduit le symbol ``@`` pour la multiplication matricielle.*"]}, {"cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [{"data": {"text/plain": ["matrix([[ 30, 36, 42],\n", " [ 66, 81, 96],\n", " [102, 126, 150]])"]}, "execution_count": 55, "metadata": {}, "output_type": "execute_result"}], "source": ["m * m"]}, {"cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([[ 1, 4, 9],\n", " [16, 25, 36],\n", " [49, 64, 81]])"]}, "execution_count": 56, "metadata": {}, "output_type": "execute_result"}], "source": ["a * a"]}, {"cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [{"data": {"text/plain": ["matrix([[ 30, 36, 42],\n", " [ 66, 81, 96],\n", " [102, 126, 150]])"]}, "execution_count": 57, "metadata": {}, "output_type": "execute_result"}], "source": ["m * a # La priorit\u00e9 des matrix est plus importantes que celles des arrays"]}, {"cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["[[ 30 36 42]\n", " [ 66 81 96]\n", " [102 126 150]]\n", "[[ 1 4 9]\n", " [16 25 36]\n", " [49 64 81]]\n"]}], "source": ["print(m**2)\n", "print(a**2)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["La syntaxe est plus l\u00e9g\u00e8re pour effectuer du calcul matriciel"]}, {"cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["det 6.0 rank 3\n", "[[ 1.00000000e+00 3.99680289e-15 4.49640325e-15]\n", " [ 0.00000000e+00 1.00000000e+00 0.00000000e+00]\n", " [ -8.88178420e-16 0.00000000e+00 1.00000000e+00]]\n", "det 6.0 rank 3\n", "[[ 1.00000000e+00 8.88178420e-16 -1.77635684e-15]\n", " [ -6.66133815e-16 1.00000000e+00 0.00000000e+00]\n", " [ -3.33066907e-16 2.66453526e-15 1.00000000e+00]]\n"]}], "source": ["m[0,0]= -1\n", "print(\"det\", np.linalg.det(m), \"rank\",np.linalg.matrix_rank(m))\n", "print(m.I*m)\n", "a[0,0] = -1\n", "print(\"det\", np.linalg.det(a), \"rank\",np.linalg.matrix_rank(a))\n", "print(a.dot(np.linalg.inv(a)))"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### G\u00e9n\u00e9ration de nombres al\u00e9atoires et statistiques"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Le module [numpy.random](http://docs.scipy.org/doc/numpy/reference/routines.random.html) apporte \u00e0 python la possibilit\u00e9 de g\u00e9n\u00e9rer un \u00e9chantillon de taille $n$ directement, alors que le module natif de python ne produit des tirages que un par un. Le module [numpy.random](http://docs.scipy.org/doc/numpy/reference/routines.random.html) est donc bien plus efficace si on veut tirer des \u00e9chantillon cons\u00e9quents. Par ailleurs, [scipy.stats](http://docs.scipy.org/doc/scipy/reference/stats.html) fournit des m\u00e9thodes pour un tr\u00e8s grand nombre de distributions et quelques fonctions classiques de statistiques. "]}, {"cell_type": "markdown", "metadata": {}, "source": ["Par exemple, on peut obtenir un array 4x3 de tirages gaussiens standard (soit en utilisant [*randn*](http://docs.scipy.org/doc/numpy/reference/generated/numpy.random.randn.html#numpy.random.randn) ou [*normal*](http://docs.scipy.org/doc/numpy/reference/generated/numpy.random.normal.html#numpy.random.normal)):"]}, {"cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [{"data": {"text/plain": ["array([[-0.53862576, 0.7316812 , -0.43393759],\n", " [-0.39077735, -1.48022294, 0.61423791],\n", " [ 1.29123337, -2.92158205, -2.33375479],\n", " [-0.63012998, 0.37943656, 0.33758665]])"]}, "execution_count": 60, "metadata": {}, "output_type": "execute_result"}], "source": ["np.random.randn(4,3)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Pour se convaincre que [numpy.random](http://docs.scipy.org/doc/numpy/reference/routines.random.html) est plus efficace que le module *random* de base de python. On effectue un grand nombre de tirages gaussiens standard, en python pur et via numpy."]}, {"cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["9.04 s \u00b1 149 ms per loop (mean \u00b1 std. dev. of 7 runs, 1 loop each)\n"]}], "source": ["N = int(1e7)\n", "from random import normalvariate\n", "%timeit [normalvariate(0,1) for _ in range(N)]"]}, {"cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["301 ms \u00b1 7.46 ms per loop (mean \u00b1 std. dev. of 7 runs, 1 loop each)\n"]}], "source": ["%timeit np.random.randn(N)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Exercice 2 : marches al\u00e9atoires"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Simulez (**en une seule fois!**) 10000 marches al\u00e9atoires de taille 1000, partant de 0 et de pas +1 ou -1 \u00e9quiprobables \n", "\n", "* Faites un graphe repr\u00e9sentant la racine de la moyenne des carr\u00e9s des positions (=cumul des pas \u00e0 un instant donn\u00e9) en fonction du temps\n", "* Quels sont les amplitudes maximales et minimales atteintes parmi l'ensemble des marches al\u00e9atoires?\n", "* Combien de marches s'\u00e9loigne de plus de 50 de l'origine?\n", "* Parmi celles qui le font, quelle est la moyenne des temps de passage (i.e. le premier moment o\u00f9 ces marches d\u00e9passent +/-50)?\n", "\n", "Vous aurez peut-\u00eatre besoin des fonctions suivantes: [np.abs](http://docs.scipy.org/doc/numpy/reference/generated/numpy.absolute.html), [np.mean](http://docs.scipy.org/doc/numpy/reference/generated/numpy.mean.html), [np.max](http://docs.scipy.org/doc/numpy/reference/generated/numpy.maximum.html), [np.where](http://docs.scipy.org/doc/numpy/reference/generated/numpy.where.html), [np.argmax](http://docs.scipy.org/doc/numpy/reference/generated/numpy.argmax.html), [np.any](http://docs.scipy.org/doc/numpy/reference/generated/numpy.any.html), [np.cumsum](http://docs.scipy.org/doc/numpy/reference/generated/numpy.cumsum.html), [np.random.randint](http://docs.scipy.org/doc/numpy/reference/generated/numpy.random.randint.html)."]}, {"cell_type": "code", "execution_count": 62, "metadata": {"collapsed": true}, "outputs": [], "source": []}, {"cell_type": "markdown", "metadata": {}, "source": ["### Exercice 3 : retrouver la s\u00e9rie al\u00e9atoire \u00e0 partir des marches al\u00e9atoires\n", "\n", "L'exercice pr\u00e9c\u00e9dent montre comment g\u00e9n\u00e9rer une marche al\u00e9atoire \u00e0 partir d'une s\u00e9rie temporelle al\u00e9atoire. Comment retrouver la s\u00e9rie initiale \u00e0 partir de la marche al\u00e9atoire ?"]}, {"cell_type": "code", "execution_count": 63, "metadata": {"collapsed": true}, "outputs": [], "source": []}, {"cell_type": "markdown", "metadata": {}, "source": ["### Optimisation avec scipy"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Le module [scipy.optimize](http://docs.scipy.org/doc/scipy/reference/optimize.html) fournit un panel de m\u00e9thodes d'optimisation. En fonction du probl\u00e8me que vous souhaitez r\u00e9soudre, il vous faut choisir la m\u00e9thode ad\u00e9quate. Je vous conseille vivement la lecture de ce [tutoriel](http://scipy-lectures.github.io/advanced/mathematical_optimization/index.html) sur l'optimisation num\u00e9rique, \u00e9crit par Ga\u00ebl Varoquaux. \n"]}, {"cell_type": "markdown", "metadata": {}, "source": ["\n", "R\u00e9cemment, l'ensemble des solvers ont \u00e9t\u00e9 regroup\u00e9s sous deux interfaces, m\u00eame si on peut toujours faire appel \u00e0 chaque solver directement, ce qui n'est pas conseill\u00e9 car les entr\u00e9es sorties ne sont pas normalis\u00e9es (par contre vous devrez sans doute aller voir l'aide de chaque m\u00e9thode pour vous en servir): \n", "\n", "* Pour minimiser une fonction scalaire d'une ou plusieurs variables:[scipy.optimize.minimize](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html#scipy.optimize.minimize)\n", "* Pour minimiser une fonction scalaire d'une variable uniquement:[scipy.optimize.minimize_scalar](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize_scalar.html#scipy.optimize.minimize_scalar)\n", "\n", "Vous obtiendrez en sortie un objet de type [scipy.optimize.OptimizeResult](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.OptimizeResult.html#scipy.optimize.OptimizeResult)."]}, {"cell_type": "markdown", "metadata": {}, "source": ["Dans la suite, je d\u00e9veloppe un petit exemple inspir\u00e9 du [tutoriel](http://www.mathworks.fr/fr/help/optim/examples/tutorial-for-the-optimization-toolbox.html#zmw57dd0e494) de la toolbox d'optimisation de Matlab. Par ailleurs, la [documentation](http://www.mathworks.fr/fr/help/optim/ug/unconstrained-nonlinear-optimization-algorithms.html#brnoxxo) de cette toolbox est plut\u00f4t claire et peut toujours vous servir lorsque que vous avez besoin de vous rafraichir la m\u00e9moire sur l'optimisation num\u00e9rique."]}, {"cell_type": "markdown", "metadata": {}, "source": ["On commence par d\u00e9finir la fonction *bowl_peak*"]}, {"cell_type": "code", "execution_count": 64, "metadata": {"collapsed": true}, "outputs": [], "source": ["def bowl_peak(x,y): \n", " return x*np.exp(-x**2-y**2)+(x**2+y**2)/20"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On va ensuite chercher un exemple dans la gallerie matplotlib pour la repr\u00e9senter: [contour3d_demo3](http://matplotlib.org/examples/mplot3d/contour3d_demo3.html). On modifie l\u00e9g\u00e8rement le code pour l'utiliser avec *bowl_peak*"]}, {"cell_type": "code", "execution_count": 65, "metadata": {}, "outputs": [{"data": {"text/plain": ["(-0.5, 0.5)"]}, "execution_count": 66, "metadata": {}, "output_type": "execute_result"}, {"data": {"image/png": "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\n9jT6jecgl8ZQnWRCHcT8zRSau/A2t9VYas7OzhKLxRgZGbllwYIslzCmr6Nfewo5PwGaB/XyO4h3\njjA5PW3XvLc96KsYE9Y53jrk1QLp3bLOfD7PjRs38Hg8DA0NHZlTW3UYhmFTMY8dO0YqleJTn/oU\nL7zwApqmcfbsWT70oQ/x4Q9/+Mi3/TOOX9waMmx0czeyV4s2Mzs7S0dHB5cvX94VIA9DlbMeZrlc\nzlYmCSFYWFggEAgcjHFQKoDrcEvMUqlkl0CGhoZYzvlQhFmO3Op3W10+qM5MXXKIxViISDxHqH77\nXL3qzKirq4tLly7R3Vvi/k/f4P5PX+e3f7OHt7w+dCifia3hdDrRNI1YLEax7CGc7OCxn8aZXcjh\n1gRvuKRy6UyRxvpVFuZzm3aaTg9XxhWeGjVFLG84pXPPcYklQtQNWEnCasJUCnY3GKQj00wtmsNL\n6/xe5NosRmLVbKq1DZjNuqrxSVJKjNlR9OceRy5MgKKg9J1AOXERpe8EHoeT5o3vwnoAzs/Pk06n\ncbvdNDc322yEaprYYUM4Xagj51BHzmGsLVL+0T+iP/4wjvpWzn/gP6DV71xP3YkxsduQ160mS5qm\nsbi4yPLyMiMjI69aPTqVSnHjxg1aW1u5dOkSKysrfOpTn6K1tZVr165RX1/PzMyMzfD5RYjbNkMu\nlUoUi0WuXLmCqqo0NDTQ39+/L0AuLy9TLBbp6+vb9TVbG3a6rrO+vm573DocDjRNsz1ltzqJVYc+\ncQXhDaJ0Ht/3mKqtHqubKlNr5nils92mr+9+US6XmV3O8rdPN9Fbv8ZAcBync9M/WAjB7IZ50dbl\n9tJqgT/6k2lGJ7O0NLl479uauft8Pce6PQcGZykl4bU8jz8xw/XxLIthjYVl05j9+KCPd7ypibe8\nPoTXUzuOKJZI88q8xkS0lbLhoqs+xT1DWTpbfRslFEEiZ9aKSxXTxc4rI8zNTNLR0UFnZycisYpc\nnwXDMFkTzb01MwallBjTr6A/+T3k+jL461DP3ot66u4ab9/qsCh5AMPDwyiKYoN0Op0ml8vhcrlq\nQO+wIG15dSwuLHBcKeC58j2Evw7nr/0OwndrxjrVIB2LxUgmk7hcLlpaWuwmstfrPbL6rWEYTE1N\nkUwmOXHiBB6Ph2984xv86Z/+KQ8++CDvfve7b8da8YEO6LYF5EgkwujoKKlUinvuucdmNOwXa2tr\n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\npfimXyaZzbG4uEg6nd6xdLDfhabrum0E09fXV5WVlZDrcxgOF0rD4Z/SeQMS/AAAIABJREFULpeL\npqYmWwoeTxWYClcoax1obSGyuRWeeeZZvN7aJtFeGZkQEAqaWfG5/s2MplAyJzxnC4JoPM3CUhi/\nP0hTUxMup4JTBc1l1n99bjOz3R7Ktsy/VNZZjhZZyzvRy05kpYAorOJyFAgny0ykUgwNDW2eM72M\nsXAdcklEqAvR0rfr+ZeFHJUf/wOiZwj19e/Z8TX7eQhXiy2GHvlL1h5+lMQL18kth6k4HVRaGpC9\nbXguniZbX080Gt3xHFuGVqFQ6NDe0VLXqXzvr5Gr8zje+9EaY56a123UvRcWFhgeHravi2qF5PT0\n9Da2xGFBulKpMDExQaFQ4Ny5c7hcLh566CH+8i//ks9//vO86U1v+ldhUJTLZe677z4+/OEP88EP\nfnDb/3d2drKwsGD/27Lc/deM27apZ1lwTk5OEggEavxdd7LEtOhlmqbtSC+z7QLveReOy5tZT3Xp\nwOoyu1yuGpC25KnWDVItqa6+EaU0MOZfgWwc0TaI0niw8U1bw5KIr62tMTAwiNPXxNJG7dWhSOrc\nZRyVBPlMnFQqta+b3G5hCSIMw2B4ePjAXO+tIaVZjoikTd8JQ5qlkLagOcEjFosyNjaGpmk4HA6b\nE9sY8NEpk6hGGaVjGKW+dc/tVJ7/EfrjD+P8yO+jNNee2+phr4fxEN75eDYNgKwSgtXo9Pv9pNNp\nSqUSJ0+ePDSzQeYylL/7DeTcOOobfhnHxTfv+DpLPerz+RgcHNx3VVQN0jtR2ix2x9YHh9Uc7Onp\nob29ndnZWX7v936PU6dO8eCDD74qRkcHCSklv/mbv0ljYyNf+MIXdnzNd77zHb74xS/aTb1PfOIT\nXLly5dXapV9sloUFyHNzc6iqSldX146WmFbZ4P9v78zDoyrP/v85k8kkM9lIwhYSSCA7gRCSQHEp\nRa0L6ovlkmLVllqroq8selVsWlqqb1uE6mWxUkDEivZnBdRSqQVcEF7U1xC2gEASkkCE7PtkMsns\nz++PyTnOkG1CMiGG+VzXXGRmTs55zjC5z3Pu53t/b5PJRGJiYo+3Ndb/vImj9DT+9z2JamT3ZkCu\nRSF6vR6z2YxarcZkMhEUFERSUlKn2zcZ4bDjKC+A1sZeZ3ydftdFCSBLjOQ/IDkXW9sC+jbnf6RO\n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\nAElFTkSuQmCC\n", "text/plain": [""]}, "metadata": {}, "output_type": "display_data"}], "source": ["from mpl_toolkits.mplot3d import axes3d\n", "import matplotlib.pyplot as plt\n", "from matplotlib import cm #colormaps\n", "\n", "min_val = -2\n", "max_val = 2\n", "\n", "fig = plt.figure()\n", "ax = fig.gca(projection='3d')\n", "x_axis = np.linspace(min_val,max_val,100)\n", "y_axis = np.linspace(min_val,max_val,100)\n", "X, Y = np.meshgrid(x_axis, y_axis, copy=False, indexing='xy')\n", "Z = bowl_peak(X,Y)\n", "#X, Y, Z = axes3d.get_test_data(0.05)\n", "ax.plot_surface(X, Y, Z, rstride=5, cstride=5, alpha=0.2)\n", "cset = ax.contour(X, Y, Z, zdir='z', offset=-0.5, cmap=cm.coolwarm)\n", "cset = ax.contour(X, Y, Z, zdir='x', offset=min_val, cmap=cm.coolwarm)\n", "cset = ax.contour(X, Y, Z, zdir='y', offset=max_val, cmap=cm.coolwarm)\n", "\n", "ax.set_xlabel('X')\n", "ax.set_xlim(min_val, max_val)\n", "ax.set_ylabel('Y')\n", "ax.set_ylim(min_val, max_val)\n", "ax.set_zlabel('Z')\n", "ax.set_zlim(-0.5, 0.5)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On voit que le minimum se trouve pr\u00e8s de $[-\\frac{1}{2}, 0]$. On va utiliser ce point pour initialiser l'optimisation.\n", "On va tester diff\u00e9rentes m\u00e9thodes et comparer les sorties obtenues."]}, {"cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["---\n", "Method:Nelder-Mead\n", " final_simplex: (array([[ -6.69025421e-01, -1.44567490e-04],\n", " [ -6.69110179e-01, -1.81386054e-04],\n", " [ -6.68989849e-01, -2.01126337e-04]]), array([-0.40523686, -0.40523685, -0.40523684]))\n", " fun: -0.40523685823917283\n", " message: 'Optimization terminated successfully.'\n", " nfev: 38\n", " nit: 20\n", " status: 0\n", " success: True\n", " x: array([ -6.69025421e-01, -1.44567490e-04]) \n", "\n", "---\n", "Method:CG\n", " fun: -0.4052368583334503\n", " jac: array([ -2.12926418e-04, 3.72529030e-09])\n", " message: 'Desired error not necessarily achieved due to precision loss.'\n", " nfev: 24\n", " nit: 1\n", " njev: 3\n", " status: 2\n", " success: False\n", " x: array([ -6.69183901e-01, -3.71395638e-09]) \n", "\n", "---\n", "Method:BFGS\n", " fun: -0.40523687025688715\n", " hess_inv: array([[ 0.52865446, 0. ],\n", " [ 0. , 1. ]])\n", " jac: array([ -6.08339906e-06, 0.00000000e+00])\n", " message: 'Optimization terminated successfully.'\n", " nfev: 28\n", " nit: 6\n", " njev: 7\n", " status: 0\n", " success: True\n", " x: array([ -6.69075034e-01, -7.45058060e-09]) \n", "\n", "---\n", "Method:Powell\n", " direc: array([[ 0.00000000e+00, 1.00000000e+00],\n", " [ -6.85432298e-04, -4.67045589e-11]])\n", " fun: -0.40523687026669025\n", " message: 'Optimization terminated successfully.'\n", " nfev: 62\n", " nit: 2\n", " status: 0\n", " success: True\n", " x: array([ -6.69071822e-01, -1.15386055e-08]) \n", "\n", "---\n", "Method:COBYLA\n", " fun: -0.4052368678399868\n", " maxcv: 0.0\n", " message: 'Optimization terminated successfully.'\n", " nfev: 32\n", " status: 1\n", " success: True\n", " x: array([ -6.69108584e-01, -4.89154557e-05]) \n", "\n", "---\n", "Method:L-BFGS-B\n", " fun: -0.40523687026621352\n", " hess_inv: <2x2 LbfgsInvHessProduct with dtype=float64>\n", " jac: array([ 1.35447209e-06, 0.00000000e+00])\n", " message: b'CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL'\n", " nfev: 15\n", " nit: 3\n", " status: 0\n", " success: True\n", " x: array([ -6.69071114e-01, -8.35621530e-09]) \n", "\n"]}], "source": ["from scipy import optimize\n", "x0 = np.array([-0.5, 0])\n", "fun = lambda x: bowl_peak(x[0],x[1])\n", "methods = [ 'Nelder-Mead', 'CG', 'BFGS', 'Powell', 'COBYLA', 'L-BFGS-B' ]\n", "for m in methods:\n", " optim_res = optimize.minimize(fun, x0, method=m)\n", " print(\"---\\nMethod:{}\\n\".format(m),optim_res, \"\\n\")"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On trouve un minimum \u00e0 $-0.4052$ en $[-0.669, 0.000]$ pour toutes les m\u00e9thodes qui convergent. Notez le message de sortie de 'CG' qui signifie que le gradient ne varie plus assez. Personnellement, je ne trouve pas ce message de sortie tr\u00e8s clair. Le point trouv\u00e9 est bien l'optimum cherch\u00e9 pourtant. Notez aussi le nombre d'\u00e9valuations de la fonction (*nfev*) pour chaque m\u00e9thode, et le nombre d'\u00e9valuation de gradient (*njev*) pour les m\u00e9thodes qui reposent sur un calcul de gradient."]}, {"cell_type": "markdown", "metadata": {}, "source": ["Remarquez aussi que si on relance *Anneal* plusieurs fois, on n'est pas assur\u00e9 d'obtenir la m\u00eame solution, puisqu'il s'agit d'une m\u00e9taheuristique."]}, {"cell_type": "code", "execution_count": 67, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["---\n", "Method:L-BFGS-B - Test:0\n", " fun: -0.40523687025688715\n", " hess_inv: array([[ 0.52865446, 0. ],\n", " [ 0. , 1. ]])\n", " jac: array([ -6.08339906e-06, 0.00000000e+00])\n", " message: 'Optimization terminated successfully.'\n", " nfev: 28\n", " nit: 6\n", " njev: 7\n", " status: 0\n", " success: True\n", " x: array([ -6.69075034e-01, -7.45058060e-09]) \n", "\n", "---\n", "Method:L-BFGS-B - Test:1\n", " fun: -0.40523687025688715\n", " hess_inv: array([[ 0.52865446, 0. ],\n", " [ 0. , 1. ]])\n", " jac: array([ -6.08339906e-06, 0.00000000e+00])\n", " message: 'Optimization terminated successfully.'\n", " nfev: 28\n", " nit: 6\n", " njev: 7\n", " status: 0\n", " success: True\n", " x: array([ -6.69075034e-01, -7.45058060e-09]) \n", "\n", "---\n", "Method:L-BFGS-B - Test:2\n", " fun: -0.40523687025688715\n", " hess_inv: array([[ 0.52865446, 0. ],\n", " [ 0. , 1. ]])\n", " jac: array([ -6.08339906e-06, 0.00000000e+00])\n", " message: 'Optimization terminated successfully.'\n", " nfev: 28\n", " nit: 6\n", " njev: 7\n", " status: 0\n", " success: True\n", " x: array([ -6.69075034e-01, -7.45058060e-09]) \n", "\n", "---\n", "Method:L-BFGS-B - Test:3\n", " fun: -0.40523687025688715\n", " hess_inv: array([[ 0.52865446, 0. ],\n", " [ 0. , 1. ]])\n", " jac: array([ -6.08339906e-06, 0.00000000e+00])\n", " message: 'Optimization terminated successfully.'\n", " nfev: 28\n", " nit: 6\n", " njev: 7\n", " status: 0\n", " success: True\n", " x: array([ -6.69075034e-01, -7.45058060e-09]) \n", "\n"]}], "source": ["for i in range(4):\n", " optim_res = optimize.minimize(fun, x0, method='BFGS')\n", " print(\"---\\nMethod:{} - Test:{}\\n\".format(m,i),optim_res, \"\\n\")"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On va \u00e9valuer le temps de calcul n\u00e9cessaire \u00e0 chaque m\u00e9thode."]}, {"cell_type": "code", "execution_count": 68, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": ["Method:Nelder-Mead:\n", "894 \u00b5s \u00b1 40.2 \u00b5s per loop (mean \u00b1 std. dev. of 7 runs, 1000 loops each)\n", "############\n", "Method:CG:\n", "788 \u00b5s \u00b1 49 \u00b5s per loop (mean \u00b1 std. dev. of 7 runs, 1000 loops each)\n", "############\n", "Method:BFGS:\n", "592 \u00b5s \u00b1 4.57 \u00b5s per loop (mean \u00b1 std. dev. of 7 runs, 1000 loops each)\n", "############\n", "Method:Powell:\n", "1.01 ms \u00b1 26.4 \u00b5s per loop (mean \u00b1 std. dev. of 7 runs, 1000 loops each)\n", "############\n", "Method:COBYLA:\n", "193 \u00b5s \u00b1 12.3 \u00b5s per loop (mean \u00b1 std. dev. of 7 runs, 1000 loops each)\n", "############\n", "Method:L-BFGS-B:\n", "222 \u00b5s \u00b1 18.5 \u00b5s per loop (mean \u00b1 std. dev. of 7 runs, 1000 loops each)\n", "############\n"]}], "source": ["for m in methods:\n", " print(\"Method:{}:\".format(m))\n", " %timeit optim_res = optimize.minimize(fun, x0, method=m)\n", " print('############')"]}, {"cell_type": "markdown", "metadata": {}, "source": ["On peut aussi fournir des arguments suppl\u00e9mentaires \u00e0 la fonction qu'on optimise. Par exemple, les donn\u00e9es lorsque vous maximisez une log-vraissemblance. En voici un exemple: on consid\u00e8re une version rescaled de la fonction *bowl_peak*. Vous pourriez aussi utiliser une lambda fonction."]}, {"cell_type": "code", "execution_count": 69, "metadata": {}, "outputs": [{"name": "stdout", "output_type": "stream", "text": [" fun: 0.05000000675226609\n", " hess_inv: array([[ 1.40782352e+00, -1.59338758e+02],\n", " [ -1.59338758e+02, 7.19318682e+05]])\n", " jac: array([ -9.78726894e-06, 5.63450158e-08])\n", " message: 'Optimization terminated successfully.'\n", " nfev: 96\n", " nit: 23\n", " njev: 24\n", " status: 0\n", " success: True\n", " x: array([ 2.49997551, -1.22943768])\n", "#######\n", " fun: 0.05000000675226609\n", " hess_inv: array([[ 1.40782352e+00, -1.59338758e+02],\n", " [ -1.59338758e+02, 7.19318682e+05]])\n", " jac: array([ -9.78726894e-06, 5.63450158e-08])\n", " message: 'Optimization terminated successfully.'\n", " nfev: 96\n", " nit: 23\n", " njev: 24\n", " status: 0\n", " success: True\n", " x: array([ 2.49997551, -1.22943768])\n"]}], "source": ["def shifted_scaled_bowlpeak(x,a,b,c):\n", " return (x[0]-a)*np.exp(-((x[0]-a)**2+(x[1]-b)**2))+((x[0]-a)**2+(x[0]-b)**2)/c\n", "a = 2\n", "b = 3\n", "c = 10\n", "optim_res = optimize.minimize(shifted_scaled_bowlpeak, x0, args=(a,b,c), method='BFGS')\n", "print(optim_res)\n", "print('#######')\n", "optim_res = optimize.minimize(lambda x:shifted_scaled_bowlpeak(x,a,b,c), x0, method='BFGS')\n", "print(optim_res)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Vous pouvez continuer ce petit benchmark en ajoutant le gradient et la hessienne... les calculs seront plus pr\u00e9cis et plus rapides."]}, {"cell_type": "markdown", "metadata": {}, "source": ["### Exercice 4: simulation, r\u00e9gression, estimation par maximisation de la vraisemblance"]}, {"cell_type": "markdown", "metadata": {}, "source": ["* On commence par simuler la variable $Y = 3 X_1 -2 X_2 +2 + \\epsilon$ o\u00f9 $X_1,X_2,\\epsilon \\sim \\mathcal{N}(0,1)$ \n", "* On souhaite ensuite retrouver les coefficients dans la [r\u00e9gression lin\u00e9aire](http://fr.wikipedia.org/wiki/R%C3%A9gression_lin%C3%A9aire) de $Y$ sur $X_1$ et $X_2$ dans un mod\u00e8le avec constante, par la m\u00e9thode des Moindres Carr\u00e9s Ordinaires. On rappelle que la forme matricielle de l'estimateur des MCO est $\\hat{\\beta} = (X'X)^{-1}X'Y$\n", "* Enfin, $Y$ \u00e9tant normale, on souhaite estimer ses param\u00e8tres par maximisation de vraisemblance:\n", " * La densit\u00e9 s'\u00e9crit: $f(x, \\mu, \\sigma) = \\frac{1}{\\sigma \\sqrt{2\\pi} } e^{ -\\frac{(x-\\mu)^2}{2\\sigma^2} }$\n", " * La log-vraisemblance: $\\ln\\mathcal{L}(\\mu,\\sigma^2) = \\sum_{i=1}^n \\ln f(x_i;\\,\\mu,\\sigma^2) = -\\frac{n}{2}\\ln(2\\pi) - \\frac{n}{2}\\ln\\sigma^2 - \\frac{1}{2\\sigma^2}\\sum_{i=1}^n (x_i-\\mu)^2$.\n", " * L'\u00e9criture des conditions au premier ordre donne une formule ferm\u00e9e pour les estimateurs du maximum de vraisemblance: $\\hat{\\mu} = \\overline{x} \\equiv \\frac{1}{n}\\sum_{i=1}^n x_i$, $\\hat{\\sigma}^2 = \\frac{1}{n} \\sum_{i=1}^n (x_i - \\overline{x})^2$.\n", " * V\u00e9rifiez en les impl\u00e9mentant directement que vous trouvez bien la m\u00eame solution que le minimum obtenu en utilisant [scipy.optimize.minimize](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html) pour minimiser l'oppos\u00e9 de la log-vraissemblance."]}, {"cell_type": "code", "execution_count": 70, "metadata": {"collapsed": true}, "outputs": [], "source": []}, {"cell_type": "markdown", "metadata": {}, "source": ["### Exercice 5 : Optimisation quadratique (sous contraintes) avec cvxopt"]}, {"cell_type": "markdown", "metadata": {}, "source": ["Voir l'exercice 1 [ici](http://www.xavierdupre.fr/app/ensae_teaching_cs/helpsphinx/notebooks/td1a_cenonce_session9.html)"]}, {"cell_type": "markdown", "metadata": {}, "source": ["### R\u00e9f\u00e9rences"]}, {"cell_type": "markdown", "metadata": {}, "source": ["* [100 numpy exercises](http://www.loria.fr/~rougier/teaching/numpy.100/)\n", "* [Un tutoriel bien fait et tr\u00e8s complet sur numpy](http://www.tp.umu.se/~nylen/pylect/intro/numpy/numpy.html). L'un des auteurs n'est autre que Ga\u00ebl Varoquaux qui sera pr\u00e9sent pour la s\u00e9ance 3. Voir aussi l'[ensemble du tutoriel](http://www.tp.umu.se/~nylen/pylect/index.html) et notamment la [partie optimisation](http://www.tp.umu.se/~nylen/pylect/intro/scipy.html#optimization-and-fit-scipy-optimize) "]}, {"cell_type": "code", "execution_count": 71, "metadata": {"collapsed": true}, "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.6.1"}}, "nbformat": 4, "nbformat_minor": 2}