.. _chap-tpIntroductionScikitLearn: ############################################### Travaux pratiques - Introduction à Scikit-learn ############################################### (correspond à 1 séance de TP) .. only:: html .. container:: notebook .. image:: cnam_theme/static/jupyter_logo.png :class: svg-inline `Cahier Jupyter `_ Références externes utiles : * `Documentation NumPy `_ * `Documentation SciPy `_ * `Documentation Matplotlib `_ * `Site scikit-learn `_ * `Site du langage python `_ ************************* Librairies indispensables ************************* Calculs avec NumPy et SciPy *************************** `NumPy `_ et `SciPy `_ sont des bibliothèques logicielles très utiles dans la résolution de problèmes de modélisation à partir de données. Cette séance de TP vise à vous donner les connaissances de base nécessaires mais est loin de couvrir toutes les fonctionnalités de ces deux librairies. Pour aller plus loin, vous êtes invités à regarder les documentations en ligne indiquées plus haut. NumPy ===== NumPy est une bibliothèque de calcul scientifique dédié à la manipulation de matrices et de tableaux en multiple dimensions. L'utilisation de fonctionnalités de NumPy commence par l'importation de cette librairie .. code-block:: python import numpy as np L'emploi de raccourcis (ici ``np`` plutôt que ``numpy``) permet de faciliter l'écriture des appels des fonctions de la librairie. Le type de base dans NumPy est le tableau unidimensionnel ou multidimensionnel composé d'éléments de **même** type. La classe correspondante est ``ndarray``, à ne pas confondre avec la classe Python ``array.array`` qui gère seulement des tableaux unidimensionnels (et présente des fonctionalités comparativement limitées). Principaux attributs de ``ndarray`` : .. only:: html +----------------+--------------------------------------------------------------+ | ndarray.ndim | dimension du tableau (nombre d'axes) | +----------------+--------------------------------------------------------------+ | ndarray.shape | tuple d'entiers indiquant la taille dans chaque dimension ; | | | ex : une matrice à *n* lignes et *m* colonnes : ``(n,m)`` | +----------------+--------------------------------------------------------------+ | ndarray.size | nombre total d'éléments du tableau | +----------------+--------------------------------------------------------------+ | ndarray.dtype | type de (tous) les éléments du tableau ; il est possible | | | d'utiliser les types prédéfinis comme ``numpy.int64`` ou | | | ``numpy.float64`` ou définir de nouveaux types | +----------------+--------------------------------------------------------------+ | ndarray.data | les données du tableau ; en général, pour accéder aux | | | données d'un tableau on passe plutôt par les indices | +----------------+--------------------------------------------------------------+ .. only:: jupyter * ndarray.ndim | dimension du tableau (nombre d'axes) * ndarray.shape | tuple d'entiers indiquant la taille dans chaque dimension (ex : une matrice à *n* lignes et *m* colonnes : ``(n,m)``) * ndarray.size | nombre total d'éléments du tableau * ndarray.dtype | type de (tous) les éléments du tableau ; il est possible d'utiliser les types prédéfinis comme ``numpy.int64`` ou ``numpy.float64`` ou définir de nouveaux types * ndarray.data | les données du tableau ; en général, pour accéder aux données d'un tableau on passe plutôt par les indices. Création de tableaux -------------------- De nombreuses méthodes de création de tableaux sont disponibles. D'abord, un tableau peut être créé à partir d'une liste (ou d'un tuple) Python, à condition que tous les éléments soient de même type (le type des éléments du tableau est déduit du type des éléments de la liste ou tuple). NumPy infère automatiquement le type des éléments de la matrice à partir du types des objets Python. .. code-block:: python ti = np.array([1, 2, 3, 4]) print(ti) # array([1, 2, 3, 4]) print(ti.dtype) # dtype('int64') tf = np.array([1.5, 2.5, 3.5, 4.5]) print(tf.dtype) # dtype('float64') .. admonition:: Note ``int64`` et ``float64`` signifient que ces nombres sont encodés sur 64 bits (en double précision). Les listes sont transformées en simples tableaux unidimensionnels, les listes de listes (de même taille) en tableaux bidimensionnels, et ainsi de suite. Par exemple : .. code-block:: python tf2d = np.array([[1.5, 2, 3], [4, 5, 6]]) print(tf2d) # array([[ 1.5, 2. , 3. ], # [ 4. , 5. , 6. ]]) Le type du tableau peut être indiqué explicitement à la création, des conversions sont effectuées pour les valeurs fournies : .. code-block:: python tff = np.array([[1, 2, 3], [4, 5, 6]], dtype=float) print(tff) # array([[ 1., 2., 3.], # [ 4., 5., 6.]]) tfi = np.array([[1.5, 2, 3], [4, 5, 6]], dtype=np.int32) print(tfi) # array([[1, 2, 3], # [4, 5, 6]]) print(tfi.dtype) # dtype('int32') On accède aux éléments du tableau NumPy avec la même syntaxe que pour accéder aux éléments d'une liste Python : .. code-block:: python t = np.array([1, 2, 3, 4, 5, 6]) print(t[2]) # 3ème élément du tableau .. admonition:: Note L'indexation des éléments commence à 0. Pour un tableau multidimensionnel (par exemple, une matrice), il suffit d'indiquer la liste des indices de l'élément recherché : .. code-block:: python t = np.array([[1,2,3], [4,5,6]]) print(t[0]) # 1ère ligne print(t[0, 1]) # élément en 1ère ligne, 2ème colonne Les tableaux NumPy possèdent plusieurs attributs utiles, comme ``shape`` pour obtenir leur taille ou ``ndim`` pour déterminer leur nombre de dimensions. .. code-block:: python print(tfi.shape) # (2, 3) print(tfi.ndim) # 2 print(tfi.size) # 6 Il est souvent nécessaire de créer des tableaux remplis de 0, de 1, ou dont le contenu n'est pas initialisé. Par défaut, le type des tableaux ainsi créés est ``float64``. .. admonition:: Question Créez un tableau de booléens (type ``bool``) à deux lignes et deux colonnes de sorte à ce que la première ligne contiennent uniquement des valeurs ``True`` et la seconde uniquement des valeurs ``False``. Vérifiez ses dimensions et son type avec les attributs présentés ci-dessus. .. ifconfig:: tpml in ('private') .. only:: jupyter .. code-block:: python .. ifconfig:: tpml in ('public') .. admonition:: Correction .. code-block:: python # par exemple t = np.array([[True, True], [False, False]]) # ou en exploitant la conversion 0 => False, 1 => True t = np.array([[1, 1], [0, 0]], dtype=bool) print(t.shape) print(t.dtype) Plutôt que de spécifier manuellement toutes les valeurs du tableau, NumPy dispose de fonctions préintégrées pour générer des tableaux et des matrices courantes. .. code-block:: python # Créer un tableau de 3 lignes et 4 colonnes rempli de zéros tz2d = np.zeros((3,4)) print(tz2d) # array([[ 0., 0., 0., 0.], # [ 0., 0., 0., 0.], # [ 0., 0., 0., 0.]]) # Créer un tableau de 3 lignes et 4 colonnes rempli de uns tu2d = np.ones((3,4)) print(tu2d) # array([[ 1., 1., 1., 1.], # [ 1., 1., 1., 1.], # [ 1., 1., 1., 1.]]) id2d = np.eye(5) print(id2d) # ... tni2d = np.empty((3,4)) print(tni2d) # ... .. admonition:: Question : Expliquez les deux derniers résultats obtenus. .. ifconfig:: tpml in ('public') .. admonition:: Correction : Le premier tableau ``id2d`` représente une matrice identité d'ordre 5, le second tableau ``tni2d`` représente une matrice non initialisée à 3 lignes et 4 colonnes. .. code-block:: python print(id2d) # array([[ 1., 0., 0., 0., 0.], # [ 0., 1., 0., 0., 0.], # [ 0., 0., 1., 0., 0.], # [ 0., 0., 0., 1., 0.], # [ 0., 0., 0., 0., 1.]]) Des tableaux peuvent être initialisés aussi par des séquences générées, par exemple : .. code-block:: python ts1d = np.arange(0, 40, 5) print(ts1d) # array([ 0, 5, 10, 15, 20, 25, 30, 35]) ts1d2 = np.linspace(0, 35, 8) print(ts1d2) # array([ 0., 5., 10., 15., 20., 25., 30., 35.]) ta2d = np.random.rand(3,5) print(ta2d) # array([[ 0.80125647, 0.6638199 , 0.77308665, 0.97916913, 0.34123528], # [ 0.50651726, 0.75819493, 0.20378306, 0.31850636, 0.57625289], # [ 0.9262173 , 0.80369567, 0.10563466, 0.17166009, 0.87835762]]) .. admonition:: Question : Quel est le résultat de l'appel ``np.random.rand(3,5)`` ? Regardez la `documentation NumPy `_. .. ifconfig:: tpml in ('public') .. admonition:: Correction : Construction d'un tableau à trois lignes et 5 colonnes, dont les éléments sont initialisés par tirage aléatoire suivant une loi normale uniforme avec des valeurs dans l'intervalle :math:`[0, 1]`. .. admonition:: Question : Générez le contenu d'un tableau de même taille (3, 5) à partir plutôt d'une loi normale (de moyenne 0 et variance 1). .. ifconfig:: tpml in ('public') .. admonition:: Correction : C'est la fonction ``.randn()`` qui doit être employée à la place. Les tableaux peuvent être redimensionnés en utilisant ``reshape`` : .. code-block:: python tr = np.arange(20) print(tr) # array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, # 17, 18, 19]) # Réordonne le tableau en une matrice à 4 lignes et 5 colonnes tr = tr.reshape(4,5) print(tr) # array([[ 0, 1, 2, 3, 4], # [ 5, 6, 7, 8, 9], # [10, 11, 12, 13, 14], # [15, 16, 17, 18, 19]]) # Réordonne le tableau en une matrice à 2 lignes et 10 colonnes print(tr.reshape(2,10)) # array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9], # [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]]) print(tr.reshape(20)) # array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, # 17, 18, 19]) .. admonition:: Question Est-il possible de redimensionner le tableau ``t`` suivant en une matrice à 4 lignes et 3 colonnes ? Si non, combien faudrait-il de lignes ? .. ifconfig:: tpml in ('private') .. code-block:: python t = np.array([0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2]) # complétez... .. ifconfig:: tpml in ('public') .. admonition:: Correction ``t.shape`` nous indique que le tableau contient 15 éléments, ce qui ne rentre pas dans un tableau de dimensions 4 par 3. Il faudrait par exemple 5 lignes : .. code-block:: python t = np.array([0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2]) # t.reshape(4,3) # renvoie ValueError: cannot reshape array of size 15 into shape (4,3) t.reshape(5,3) Affichage des tableaux ---------------------- Les tableaux unidimensionnels sont affichés comme des listes, les tableaux bidimensionnels comme des matrices et les tableaux tridimensionnels comme des listes de matrices : .. code-block:: python ta1d = np.random.rand(5) print(ta1d) # array([ 0.68431206, 0.22183178, 0.50668827, 0.40924377, 0.35185467]) ta3d = np.random.rand(2,3,5) print(ta3d) Si un tableau est considéré trop grand pour être affiché en entier, NumPy affiche le début et la fin, avec des ``...`` au milieu : .. code-block:: python ta2d = np.random.rand(30,50) print(ta2d) Accès aux composantes d'un tableau, extraction de parties d'un tableau ---------------------------------------------------------------------- Nous avons déjà lors du TP précédent vu l'utilisation de ``:`` pour définir des intervalles d'indices dans l'accès à des listes. L'utilisation pour les tableaux unidimensionnels est similaire : .. code-block:: python print(tr) #array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, # 17, 18, 19]) # Accès aux 6 premiers éléments print(tr[:6]) # array([0, 1, 2, 3, 4, 5]) # Parcours des 6 premiers éléments en avançant de 2 par 2 print(tr[:6:2]) # la dernière valeur indique le pas de l'échantillonnage # array([0, 2, 4]) Les données ne sont pas copiées de ``tr`` vers un nouveau tableau ``a``, la modification d'un élément de ``a`` avec ``a[0] = 3`` change aussi le contenu de ``tr[0]``. .. code-block:: python a = tr[:2] print(a) # array([0, 1]) a[0] = 3 print(tr) # array([ 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, # 17, 18, 19]) Pour obtenir une copie il faut utiliser ``copy()`` : .. code-block:: python a = tr[:2].copy() print(a) # array([3, 1]) a[0] = 0 print(a) # array([0, 1]) print(tr) # array([ 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, # 17, 18, 19]) On peut procéder de la même façon pour les tableaux multidimensionnels : .. code-block:: python ta2d = np.random.rand(3,5) print(ta2d) # array([[ 0.27681055, 0.98760001, 0.44322499, 0.65422127, 0.21553808], # [ 0.0691005 , 0.09843615, 0.62915029, 0.58226529, 0.28827975], # [ 0.44979767, 0.30371289, 0.77210828, 0.30087438, 0.95948081]]) print(ta2d[0,0]) # 0.27681054817098449 # Extraction de la première ligne print(ta2d[0,:]) # array([ 0.27681055, 0.98760001, 0.44322499, 0.65422127, 0.21553808]) print(ta2d[0]) # array([ 0.27681055, 0.98760001, 0.44322499, 0.65422127, 0.21553808]) # Extraction de la première colonne print(ta2d[:,0]) # array([ 0.27681055, 0.0691005 , 0.44979767]) # Extraction d'une sous-matrice print(ta2d[:2,:2]) # array([[ 0.27681055, 0.98760001], # [ 0.0691005 , 0.09843615]]) .. admonition:: Question : Faites l'extraction des colonnes d'indice impaire de ``ta2d``. .. ifconfig:: tpml in ('public') .. admonition:: Correction : Toutes les lignes (donc ``:,``), la première colonne est 1 et on va jusqu'au bout avec un pas de 2 (donc ``1::2``) : .. code-block:: python print(ta2d[:,1::2]) Pour les tableaux multidimensionnels, lors des itérations c'est le dernier indice qui change le plus vite, ensuite l'avant-dernier, et ainsi de suite. Par exemple, pour les tableaux bidimensionels c'est l'indice de colonne qui change d'abord et ensuite celui de ligne (le tableau est lu ligne après ligne) : .. code-block:: python for x in ta2d: print(x) # [ 0.27681055 0.98760001 0.44322499 0.65422127 0.21553808] # [ 0.0691005 0.09843615 0.62915029 0.58226529 0.28827975] # [ 0.44979767 0.30371289 0.77210828 0.30087438 0.95948081] Lecture et écriture des données d'un tableau -------------------------------------------- Les fonctions de lecture / d'écriture de tableaux depuis /dans des fichiers sont variées, nous regarderons rapidement deux des plus simples et plus rapides car les fichiers de données ont en général des formats assez simples. La fonction ``numpy.loadtxt(fname, dtype=, comments='#', delimiter=None, converters=None, skiprows=0, usecols=None, unpack=False, ndmin=0)``, qui retourne un ``ndarray``, réalise une lecture à partir d'un fichier texte et est bien adaptée aux tableaux bidimensionnels ; chaque ligne de texte doit contenir un même nombre de valeurs. Les principaux paramètres sont : * ``fname`` : fichier ou chaîne de caractères ; si le fichier a une extension ``.gz`` ou ``.bz2``, il est d'abord décompressé. * ``dtype`` : type, optionnel, ``float`` par défaut. * ``comments`` : chaîne de caractères, optionnel, indique une liste de caractères employée dans le fichier pour précéder des commentaires (à ignorer lors de la lecture). * ``delimiter`` : chaîne de caractères, optionnel, indique la chaîne de caractères employée pour séparer des valeurs, par défaut ' ' (l'espace). * ``converters`` : dictionnaire, optionnel, pour permettre des conversions. * ``skiprows`` : entier, optionnel, pour le nombre de lignes à sauter en début de fichier (par défaut 0). * ``usecols`` : séquence, optionnel, indique les colonnes à lire ; par ex. ``usecols = [1,4,5]`` extrait la 2ème, 5ème et 6ème colonne ; par défaut toutes les colonnes sont extraites. * ``unpack`` : booléen, optionnel, ``false`` par défaut ; si ``true``, le tableau est transposé. * ``ndmin`` : entier, optionnel, le tableau a au moins ``ndmin`` dimensions ; par défaut ``0``. (voir `http://docs.scipy.org/doc/numpy/reference/generated/numpy.loadtxt.html `_) D'abord, téléchargez le fichier ``geyser.txt`` en cliquant sur `ce lien `__ ou en exécutant la commande ``wget http://cedric.cnam.fr/~crucianm/src/geyser.txt`` dans un terminal. Vérifiez que le fichier a bien été enregistré dans le répertoire où l'interpréteur Python s'exécute (ou dans celui du *notebook* Jupyter). .. only:: jupyter .. code-block:: python !wget http://cedric.cnam.fr/~crucianm/src/geyser.txt .. code-block:: python geyser = np.loadtxt('geyser.txt', delimiter=' ') print(geyser.shape) # (272, 2) print(geyser[:10,:]) # array([[ 3.6 , 79. ], # [ 1.8 , 54. ], # [ 3.333, 74. ], # [ 2.283, 62. ], # [ 4.533, 85. ], # [ 2.883, 55. ], # [ 4.7 , 88. ], # [ 3.6 , 85. ], # [ 1.95 , 51. ], # [ 4.35 , 85. ]]) Une fonction plus flexible est ``genfromtxt``, vous pouvez regarder son utilisation dans la `documentation `_. La fonction ``numpy.savetxt(fname, X, fmt='%.18e', delimiter=' ', newline='\n', header='', footer='', comments='# ')`` permet d'écrire un tableau dans un fichier texte. Les paramètres sont : * ``fname`` : fichier ; si le fichier a une extension ``.gz`` ou ``.bz2``, il est compressé. * ``X`` : le tableau à écrire dans le fichier texte. * ``fmt`` : chaîne de caractères, optionnel ; indique le formatage du texte écrit. * ``delimiter`` : chaîne de caractères, optionnel, indique la chaîne de caractères employée pour séparer des valeurs, par défaut `` `` (l'espace). * ``newline`` : chaîne de caractères, optionnel, indique le caractère à employer pour séparer des lignes. * ``header`` : chaîne de caractères, optionnel, indique le commentaire à ajouter au début du fichier. * ``footer`` : chaîne de caractères, optionnel, indique le commentaire à ajouter à la fin du fichier. * ``comments`` : caractère à ajouter avant *header* et *footer* pour en faire des commentaires ; par défaut ``#``. (voir `http://docs.scipy.org/doc/numpy/reference/generated/numpy.savetxt.html `_) Utilisation : .. code-block:: python np.savetxt('geyserv_nouveau.txt', geyser, delimiter=', ') Dans un terminal, affichez les 10 premières lignes du fichier enregistré avec ``head geyserv_nouveau.txt``. .. only:: jupyter .. code-block:: bash !head geyserv_nouveau.txt Autres opérations d'entrée et sortie : * ``fromfile(file[, dtype, count, sep])`` : Construction d'un tableau à partir d'un fichier texte ou binaire. * ``fromregex(file, regexp, dtype)`` : Construction d'un tableau à partir d'un fichier texte, avec un parseur d'expressions régulières. * ``genfromtxt()`` : Fonction plus flexible pour la construction d'un tableau à partir d'un fichier texte, avec une gestion des valeurs manquantes. * ``load(file[, mmap_mode, allow_pickle, ...])`` : Lecture de tableaux (ou autres objets) à partir de fichiers ``.npy``, ``.npz`` ou autres fichiers de données sérialisées. * ``loadtxt(fname[, dtype, comments, delimiter, ...])`` : Lecture de données à partir d'un fichier texte. * ``ndarray.tofile(fid[, sep, format])`` : Ecriture d'un tableau dans un fichier texte ou binaire (par défaut). * ``save(file, arr[, allow_pickle, fix_imports])`` : Ecriture d'un tableau dans un fichier binaire de type ``.npy``. * ``savetxt(fname, X[, fmt, delimiter, newline, ...])`` : Ecriture d'un tableau dans un fichier texte. * ``savez(file, *args, **kwds)`` : Ecriture de plusieurs tableaux dans un fichier de type ``.npz`` sans compression. * ``savez_compressed(file, *args, **kwds)`` : Ecriture de plusieurs tableaux dans un fichier de type ``.npz`` avec compression. Voir `http://docs.scipy.org/doc/numpy/neps/npy-format.html `_ pour le format des fichiers de type ``.npy`` et ``.npz``. Opérations simples sur les tableaux ----------------------------------- Concaténation de tableaux bidimensionnels : .. code-block:: python tu2d = np.ones((2,2)) tb2d = np.ones((2,2)) * 2 # Concatène les deux matrices selon leur première dimension (lignes) tcl = np.concatenate((tu2d,tb2d)) print(tcl) # array([[ 1., 1.], # [ 1., 1.], # [ 2., 2.], # [ 2., 2.]]) # C'est équivalent à écrire : tcl = np.concatenate((tu2d,tb2d), axis=0) # ou encore tcl = np.vstack((tu2d, tb2d)) .. admonition:: Question En utilisant la fonction ``.hstack()``, concaténez les tableaux ``tu2d`` et ``tb2d`` selon les colonnes. .. ifconfig:: tpml in ('private') .. only:: jupyter .. code-block:: python .. ifconfig:: tpml in ('public') .. admonition:: Correction .. code-block:: python # On peut utiliser concatenate selon l'axe 1 (les colonnes) tcl = np.concatenate((tu2d,tb2d), axis=1) # ou encore hstack tcl = np.hstack((tu2d, tb2d)) print(tcl) # array([[ 1., 1., 2., 2.], # [ 1., 1., 2., 2.]]) Ajouter un tableau unidimensionnel comme colonne à un tableau bidimensionnel. ``newaxis`` permet de créer une nouvelle dimension dans un tableau existant : .. code-block:: python from numpy import newaxis tu1d = np.ones(2) print(tu1d) # array([ 1., 1.]) print(tu1d[:,newaxis]) # array([[ 1.], # [ 1.]]) print(np.column_stack((tu1d[:,newaxis],tb2d))) # array([[ 1., 2., 2.], # [ 1., 2., 2.]]) print(np.hstack((tu1d[:,newaxis],tb2d))) # array([[ 1., 2., 2.], # [ 1., 2., 2.]]) Opérations arithmétiques élément par élément : .. code-block:: python tsomme = tb2d - tu1d print(tsomme) # array([[ 1., 1.], # [ 1., 1.]]) print(tb2d*5) # array([[ 10., 10.], # [ 10., 10.]]) print(tb2d**2) # array([[ 4., 4.], # [ 4., 4.]]) tc = np.hstack((tb2d,tu1d[:,newaxis])) print(tc) # array([[ 2., 2., 1.], # [ 2., 2., 1.]]) print(tc > 1) # array([[ True, True, False], # [ True, True, False]], dtype=bool) print(tb2d * tb2d) # array([[ 4., 4.], # [ 4., 4.]]) tb2d *= 3 print(tb2d) # array([[ 6., 6.], # [ 6., 6.]]) tb2d += tu2d print(tb2d) # array([[ 7., 7.], # [ 7., 7.]]) .. admonition:: Question Créez un tableau à 10 lignes et 10 colonnes dont tous les éléments valent 0,5. .. ifconfig:: tpml in ('private') .. only:: jupyter .. code-block:: python .. ifconfig:: tpml in ('public') .. admonition:: Correction .. code-block:: python # Une possibilité t = np.ones((10, 10)) t /= 2 # équivalent à t = t/2 NumPy expose également des méthodes pratiques pour extraire des statistiques d'un tableau : .. code-block:: python print(tb2d.sum()) # 28.0 geyser = np.loadtxt('geyser.txt', delimiter=' ') print(geyser.min()) # 1.6000000000000001 print(geyser.max()) # 96.0 print(geyser.sum()) # 20232.676999999996 print(geyser.sum(axis=0)) # array([ 948.677, 19284. ]) print(geyser.sum(axis=1)) ... Algèbre linéaire ---------------- .. code-block:: python g0 = geyser[:2,:] print(g0) # array([[ 3.6, 79. ], # [ 1.8, 54. ]]) Transposition d'une matrice : .. code-block:: python g0.transpose() Inversion : .. code-block:: python np.linalg.inv(g0) Produit matriciel : .. code-block:: python g0.dot(tu2d) # ou np.dot(g0,tu2d) .. code-block:: python g0.dot(np.eye(2)) Trace : .. code-block:: python g0.trace() Résolution de systèmes linéaires : .. code-block:: python y = np.array([[5.], [7.]]) np.linalg.solve(g0, y) Valeurs et vecteurs propres : .. code-block:: python vpvp = np.linalg.eig(g0) print(vpvp) Cela permet d'obtenir les valeurs propres (chacune répétée suivant sa multiplicité) et les vecteurs propres tels que la colonne ``vpvp[1][:,i]`` est le vecteur propre unitaire (de norme égale à 1) correspondant à la valeur propre ``vpvp[0][i]``. .. admonition:: Question : Comment vérifier facilement ce calcul ? .. ifconfig:: tpml in ('public') .. admonition:: Correction : Simplement en vérifiant la relation de diagonalisation de la matrice symétrique initiale : .. code-block:: python md = np.diag(vpvp[0]) vpvp[1].dot(md.dot(vpvp[1].transpose())) Vectorisation de fonctions -------------------------- Des fonctions Python qui travaillent sur des scalaires peuvent être vectorisées, c'est à dire travailler sur des tableaux, élément par élément. Par exemple : .. code-block:: python def addsubtract(a,b): if a > b: return a - b else: return a + b print(addsubtract(2,3)) # 5 vec_addsubtract = np.vectorize(addsubtract) print(tu2d) # array([[ 1., 1.], # [ 1., 1.]]) print(g0) # array([[ 3.6, 79. ], # [ 1.8, 54. ]]) print(vec_addsubtract(g0,tu2d)) # array([[ 2.6, 78. ], # [ 0.8, 53. ]]) SciPy ===== SciPy ajoute de très nombreuses fonctions permettant de faire des calculs scientifiques. Il est possible d'importer SciPy en entier avec ``import scipy`` mais il est préférable de se limiter aux fonctionnalités utilisées, par exemple ``from scipy import linalg``. ``scipy.linalg`` contient toutes les fonctions de ``numpy.linalg`` plus quelques autres. Par ailleurs, ``scipy.linalg`` est systématiquement compilée avec un support BLAS/LAPACK, alors que pour ``numpy.linalg`` cela n'est pas obligatoire. Les calculs peuvent ainsi être plus rapide si c'est ``scipy.linalg`` qui est utilisée. Exemples : .. code-block:: python from scipy import linalg print(linalg.det(g0)) # 52.19999999999998 print(linalg.norm(g0)) # par défaut, norme de Frobenius # 95.776823918941901 print(linalg.norm(g0,'fro')) # 95.776823918941901 print(linalg.norm(g0,1)) # norme L1 # 133.0 print(linalg.eig(g0)) # (array([ 0.92097563+0.j, 56.67902437+0.j]), array([[-0.99942549, -0.83004523], # [ 0.03389222, -0.55769609]])) Statistiques ------------ De nombreuses fonctionnalités statistiques sont disponibles dans le module ``scipy.stats``, comme la génération de valeurs aléatoires suivant différentes distributions, des statistiques descriptives et des tests statistiques. Regardez les fonctionnalités de ``scipy.stats`` sur `http://docs.scipy.org/doc/scipy/reference/tutorial/stats.html `_. .. admonition:: Question : Générez en utilisant ``scipy.stats`` une matrice 6 x 6 dont les valeurs sont issues d'une loi normale de moyenne 1 et écart-type 3. Calculez les statistiques descriptives par ligne et par colonne. Que constatez-vous ? .. ifconfig:: tpml in ('private') .. only:: jupyter .. code-block:: python .. ifconfig:: tpml in ('public') .. admonition:: Correction : Il faut utiliser ``norm.rvs()`` avec les valeurs appropriées pour la localisation et l'échelle : .. code-block:: python from scipy import stats from scipy.stats import norm eln = norm.rvs(loc=1, scale=3, size=36) meln = eln.reshape(6,6) for i in np.arange(6): stats.describe(meln[i,:]) # pour la ligne i stats.describe(meln[:,i]) # pour la colonne i Évidemment, les statistiques descriptives diffèrent de façon significative entre lignes et entre colonnes. .. admonition:: Question : Calculez les statistiques descriptives par ligne et par colonne pour le tableau ``geyser``. .. ifconfig:: tpml in ('private') .. only:: jupyter .. code-block:: python .. ifconfig:: tpml in ('public') .. admonition:: Correction : .. code-block:: python from scipy import stats stats.describe(geyser[:,0]) # première colonne stats.describe(geyser[:,1]) # seconde colonne # etc. Visualisation avec Matplotlib ***************************** * `Documentation Matplotlib `_ Matplotlib permet de construire de très nombreux types de graphiques directement à partir de l'interpréteur Python et nous sera très utile pour visualiser des données, des fonctions de décision, etc. Nous nous servirons principalement de ``matplotlib.pyplot``, à importer en début de session : .. code-block:: python import matplotlib.pyplot as plt .. only:: jupyter Dans les *notebooks* Jupyter, il faut utiliser la commande magique suivante pour activer la visualisation interactive : .. code-block:: python %matplotlib widget Construisons d'abord un graphique très simple : .. code-block:: python plt.plot(np.random.rand(100)) # données générées aléatoirement plt.show() # affichage du graphique L'appel à ``np.random.rand(100)`` a généré un tableau unidimensionnel de 100 valeurs aléatoires suivant une distribution uniforme, chaque valeur a été affichée (l'abscisse est l'indice de la valeur dans le tableau, l'ordonnée est la valeur même) et les valeurs d'indices succesifs ont été reliées entre elles. Cette représentation n'a de sens que si un lien existe entre indices successifs ; par exemple, les données correspondent à une série chronologique et les indices correspondent aux mesures successives. Utiliser ``plt.show(block=False)`` à la place de ``plt.show()`` permet de continuer à travailler en conservant le graphique. Néanmoins, les appels suivants à ``plt.plot()`` modifieront le même graphique (tant que sa fenêtre graphique n'est pas fermée). Une alternative est d'utiliser le mode interactif avec ``plt.ion()`` (pour y entrer) et ``plt.ioff()`` pour en sortir. En mode interactif ``plt.show()`` n'est plus nécessaire, le graphique est affiché et modifié directement. Les données ``geyser`` représentent deux telles séries chronologiques : les durées des éruptions du geyser Old Faithful et les durées des pauses entre éruptions successives. Pour mieux comprendre ce qui est affiché il est nécessaire d'indiquer des étiquettes pour les axes, d'ajouter une légende et éventuellement de donner un titre au graphique. C'est ce que font les instructions suivantes : .. code-block:: python plt.plot(geyser) plt.title('Old Faithful Geyser Data') # intitulé du graphique plt.xlabel('numéro éruption') # signification axe des x plt.ylabel('durée éruption/pause (min)') # signification axe des y plt.legend(('Durée éruption', 'Durée pause'), loc='best') plt.show() On observe que chaque colonne du tableau ``geyser`` a été représentée comme une série à part. .. admonition:: Question : Les deux séries ont des moyennes et des écart-types très différents. Transformez le tableau ``geyser`` pour obtenir une moyenne de 0 et un écart-type de 1 pour chacune de ces séries et affichez-les de nouveau. .. ifconfig:: tpml in ('private') .. only:: jupyter .. code-block:: python .. ifconfig:: tpml in ('public') .. admonition:: Correction : .. code-block:: python geyser = (geyser - geyser.mean(axis=0)) / geyser.std(axis=0) Lorsque les observations sont issues de tirages indépendants, il est d'usage de représenter le vecteur correspondant à chaque observation comme un point, éventuellement identifié par son indice. Il est possible de transmettre à la fonction ``plot`` deux tableaux unidimensionnels (de même taille) représentant les coordonnées :math:`x` et respectivement :math:`y` des données : .. code-block:: python plt.plot(geyser[:,0], geyser[:,1]) plt.show() Le résultat est illisible car par défaut les points d'indices successifs sont reliés entre eux. Il faut modifier l'affichage, en précisant par ex. que l'on souhaite afficher chaque point comme un ``+`` de couleur rouge : .. code-block:: python plt.plot(geyser[:,0], geyser[:,1],'r+') plt.show() Regardez la description complète de la commande ``plot`` sur `http://matplotlib.org/api/pyplot_summary.html `_. .. admonition:: Question : Multipliez une des colonnes par 2.5 et affichez le résultat. .. ifconfig:: tpml in ('private') .. only:: jupyter .. code-block:: python .. ifconfig:: tpml in ('public') .. admonition:: Correction .. code-block:: python plt.plot(geyser[:,0], 2.5 * geyser[:,1], 'r+') plt.show() Il est également possible d'afficher des nuages de points en 3D en utilisant la commande ``scatter`` : .. code-block:: python from mpl_toolkits.mplot3d import Axes3D fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax.scatter(np.random.randn(100), np.random.randn(100), np.random.randn(100)) plt.show() Le graphique peut être tourné en 3D à l'aide de la souris (bouton gauche maintenu appuyé). Il est posible d'avoir plusieurs figures ouvertes en même temps si chacune a son propre identifiant (``fig1``, ``fig2``, ...) et si les appels à la fonction ``show()`` ne sont pas bloquants (utilisation de ``plt.show(block=False)``). Regardez les commandes utilisables pour obtenir des graphiques en 3D sur `http://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html `_. .. admonition:: Question : En vous servant de la documentation, modifiez la couleur des points et ajoutez des étiquettes sur les axes. .. ifconfig:: tpml in ('public') .. admonition:: Correction : L'argument ``c=`` permet de modifier la couleur des points, par exemple ``c='r'`` affichera des points en rouge (``r`` pour *red*). ``plt.xlabel('abscisses')`` et ``plt.ylabel('ordonnées')`` permettent de spécifier les étiquettes des axes. .. admonition:: Question : Complétez le tableau ``geyser`` d'une troisième colonne issue de tirages aléatoires et affichez le résultat sous forme graphique. .. ifconfig:: tpml in ('public') .. admonition:: Correction : .. code-block:: python random_column = np.random.rand(len(geyser), 1) geyser_random = np.concatenate((geyser, random_column), axis=1) print(geyser_random.shape) fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax.scatter(geyser_random[:,0], geyser_random[:,1], geyser_random[:,2]) plt.show() Il est possible d'afficher les étiquettes textuelles associées aux observations. Pour cela, il faut d'abord chercher des données adaptées en entrant dans une fenêtre terminal (ou en les téléchargeant depuis `ce lien `__): .. only:: jupyter .. code-block:: bash !wget http://cedric.cnam.fr/~crucianm/src/mammals.csv .. only:: html .. code-block:: bash wget http://cedric.cnam.fr/~crucianm/src/mammals.csv Vous pouvez regarder le contenu de ce fichier. Nous sélectionnons pour une visualisation les colonnes suivantes : * durée sommeil sans rêve (h/jour) * durée totale sommeil (h/jour) * index d'exposition durant le sommeil (1-5) La lecture des données sera faite dans deux tableaux différents, un pour les trois variables numériques et l'autre pour les noms des espèces : .. code-block:: python import numpy as np # si pas encore fait import matplotlib.pyplot as plt # si pas encore fait mammals248 = np.loadtxt('mammals.csv', delimiter=';', usecols=[2,4,8], skiprows=1) print(mammals248[:5,:]) noms = np.genfromtxt('mammals.csv', dtype='str', delimiter=';', usecols=[0], skip_header=1) print(noms) Pour l'affichage : .. code-block:: python from mpl_toolkits.mplot3d import Axes3D # si pas encore fait fig = plt.figure() ax = fig.add_subplot(111, projection='3d') for i in range(len(noms)): x,y,z = mammals248[i,0],mammals248[i,1],mammals248[i,2] ax.scatter(x,y,z) ax.text(x,y,z,noms[i]) plt.show() Il est possible d'utiliser le clic droit de la souris pour faire un zoom. .. admonition:: Question : Pour améliorer la lisibilité, affichez seulement une observation sur deux. .. ifconfig:: tpml in ('public') .. admonition:: Correction : Dans la boucle ``for`` ci dessus, remplacez ``range(len(noms))`` par ``np.arange(0, len(noms), 2)``. *************************** Introduction à Scikit-learn *************************** * `Site scikit-learn `_ Charger et sauvegarder des données ********************************** À partir de fichiers qui présentent des formats simples, les données peuvent être lues en utilisant la fonction ``loadtxt`` de NumPy. La fonction ``genfromtxt`` de NumPy donne une flexibilité supplémentaire mais peut ralentir la lecture pour des fichiers de taille plus importante. Le package ``sklearn.dataset`` inclut des fonctions supplémentaires de lecture, de génération de données, ainsi que plusieurs ensemble de données qu'il n'est plus nécessaire de télécharger. Vous pouvez examiner les fonctions proposées sur `http://scikit-learn.org/stable/datasets/ `_. Les données sont représentées en général par un tuple ``(X, y)``, où ``X`` est un tableau bidimensionnel NumPy de ``n_samples x n_features`` contenant les valeurs des variables d'entrée et ``y`` un tableau unidimensionnel NumPy de longueur ``n_samples`` contenant les résultats désirés. Il est ainsi possible de lire directement des données en format SVMlight / LibSVM, dans lequel chaque observation correspond à une ligne de fichier sous la forme suivante : ``