
import numpy as np
algèbre linéaire¶
Un des premières utilisations de la librarie numpy
sera faite dans le cadre des cours de mathématiques et de la manipulation de matrices.
Aussi allons-nous voir dans ce notebook les quelques fonctions d’algèbre linéaire qui vont vous être utiles, naturellement il vous faudra vous référer au site de Python-scientifique pour chercher des fonctions plus avancées.
Il faut savoir que les fonctions d’algèbre linéaire des libraries np.linalg
sont tout particulièrement efficaces et ce pour plusieurs raisons:
déjà ces fontions s’appuient sur les algorithmes efficaces
ensuite ces fonctions sont codées dans des langages de bas niveau très proches de la mémoire donc rapides
enfin les implémentations tirent parti du multithreading (où un programme est découpé en sous-programmes s’exécutant en même temps) qui permet de grandement améliorer les temps de calculs.
Il existe des différences entre les fonctions de numpy
et de scipy
mais nous n’en dirons pas plus référez vous aux explications des concepteurs de ces librairies comme là https://docs.scipy.org/doc/numpy/reference/routines.linalg.html
import numpy as np
La première chose à savoir c’est que naturellement les fonctions numpy
vont s’appliquer sur des tableaux de dimensions supérieures à 2 mais, nous n’allons voir ici, que les vecteurs et matrices donc rester en dimension < à 3.
Nous n’avons vu pour l’instant que des opérations élément par élément. Comme par exemple le np.mult
ou encore *
, qui est le produit matriciel de Hadamard. Voyons maintenant les fonctions dédiées à l’algèbre linéaire.
application linéaire¶
On va se mettre d’accord sur matrice, vecteur et produit en numpy
.
Attention, comme les matrices et les vecteurs seront des np.ndarray
, il faudra naturellement faire attention au type des éléments, lors d’éventuelles modifications. Si on tente de modifier un élément d’une matrice de type entier avec un nombre flottant, celui-ci sera tronqué et transformé en entier: vous perdrez de l’information.
la matrice¶
Une matrice $A$ de dimension $m$ lignes et $n$ colonnes, qui représente une application linéaire $f$ d’un espace de dimension $n$ vers un espace de dimension $m$, est un np.ndarray
de forme $(m, n)$.
Voila un exemple d’une matrice $A$ de dimension $(2 \times 3)$
A = np.array([[2, 3, -7], [6, -4, 5]])
A
array([[ 2, 3, -7],
[ 6, -4, 5]])
le vecteur¶
Un vecteur $V$ de taille $n$, qui représente un vecteur d’un espace vectoriel de dimension $n$, est un np.ndarray
de forme $(n,)$.
Voila un exemple de vecteur $V$ de dimension $(3,)$
V = np.array([1, -3, 8])
V
array([ 1, -3, 8])
Attention les numpy.ndarray
suivant ne sont pas des vecteurs mais des matrices. Il y a bien deux dimensions.
np.array([[100, 200, 300]]).shape
(1, 3)
np.array([[100], [200], [300]]).shape
(3, 1)
Celui là est un vecteur au sens de l’UE11.
np.array([100, 200, 300]).shape
(3,)
le produit d’une matrice et d’un vecteur¶
Le produit $A \cdot V$ représente $f(V)$.
Quel est en numpy
le produit qu’on appelle ici $\times$ ? C’est la fonction np.dot
(ou encore la méthode np.ndarray.dot
).
Calculons $A \cdot V$
# la fonction
np.dot(A, V)
array([-63, 58])
# la méthode
A.dot(V)
array([-63, 58])
le produit de deux matrices¶
Maintenant prenons $A$ et $B$, deux matrices représentant respectivement les applications linéaires $f$ et $g$, le produit de matrices $A \cdot B$ est la composition des applications $ƒ ○ g$.
$A$ on l’a déjà, prenons $B$
B = np.array([[-5, 4], [3, 9], [-4, 2]])
B
array([[-5, 4],
[ 3, 9],
[-4, 2]])
np.dot(A, B)
array([[ 27, 21],
[-62, -2]])
A.dot(B)
array([[ 27, 21],
[-62, -2]])
raccourci @ pour le produit matriciel¶
Il s’avère que numpy
possède un fonction np.matmul
(qui en dimension 2 est à peu près équivalente à np.dot
) et qui s’écrit aussi sous la forme @
np.matmul(A, B)
array([[ 27, 21],
[-62, -2]])
A @ B
array([[ 27, 21],
[-62, -2]])
Il existe des différences entre np.matmul
et np.dot
tout d’abord , la multiplication par un scalaire n’est possible qu’avec np.dot
mais surtout en dimensions supérieure à 2 leur comportement diffère complètement.
Préférer utiliser np.dot
(ou demandez à votre professeur de math).
le produit scalaire¶
Si on applique le produit np.dot
à deux vecteurs on en obtient le produit scalaire.
V1 = np.array([1, -3, 8])
V1
array([ 1, -3, 8])
V2 = np.array([-6, 4, -7])
V2
array([-6, 4, -7])
np.dot(V1, V2)
-74
la norme de vecteur¶
Pour un vecteur $V1 =[v_1, …, v_n]$, , la norme np.linalg.norm
sera la racine carré du produit scalaire par lui-même $\displaystyle \left|{ {V}}\right|{2}={\sqrt {v{1}^{2}+\cdots +v_{n}^{2}}}$
# la fonction idoine
np.linalg.norm(V1)
8.602325267042627
# qu'on peut naturellement paraphraser en
np.sqrt(V1.dot(V1))
8.602325267042627
Pour les autres normes de matrices et vecteurs regardez là https://np.org/doc/stable/reference/generated/np.linalg.norm.html#np.linalg.norm
la transposée d’une matrice¶
C’est la fonction np.transpose
. Elle a une version courte .T
pour écrire des codes plus lisibles.
A = np.arange(1, 13).reshape(4, 3)
A
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12]])
np.transpose(A)
array([[ 1, 4, 7, 10],
[ 2, 5, 8, 11],
[ 3, 6, 9, 12]])
A.T
array([[ 1, 4, 7, 10],
[ 2, 5, 8, 11],
[ 3, 6, 9, 12]])
les matrices identité¶
En numpy
la fonction porte le très joli nom de eye
(parce que eye et I se prononcent pareil), elle renvoie des matrices de type flottant.
A = np.eye(5)
A
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.]])
le déterminant d’une matrice¶
Il est donné par la fonction np.linalg.det
A = 2*np.eye(3)
A
array([[2., 0., 0.],
[0., 2., 0.],
[0., 0., 2.]])
np.linalg.det(A)
7.999999999999998
Essayez de l’appliquer à une matrice qui n’est pas carrée ? Vous allez recevoir une np.linalg.LinAlgError
!
B = np.random.random(size=(3, 4))*10
try:
np.linalg.det(B)
except np.linalg.LinAlgError as e:
print(f'Oups pas bon ! ou en mieux dit "{e}"')
Oups pas bon ! ou en mieux dit "Last 2 dimensions of the array must be square"
A vous de jouer. Faites une isométrie (une transformation qui conserve les longueurs) et calculez son déterminant.
# votre code ici (une solution dessous)
M = np.array([[0, -1], [1, 0]])
print(M)
np.linalg.det(M)
[[ 0 -1]
[ 1 0]]
1.0
# une isométrie
M = np.array([[0, -1], [1, 0]])
print(M)
np.linalg.det(M)
[[ 0 -1]
[ 1 0]]
1.0
les matrices diagonales¶
On peut créer une matrice diagonale à partir de la liste des éléments de sa diagonale
np.diag([1., 2, 3])
array([[1., 0., 0.],
[0., 2., 0.],
[0., 0., 3.]])
On peut aussi extraire la matrice diagonale d’une matrice.
A = np.random.randint(-100, +100, size=(3, 3))
A
array([[-82, 59, 97],
[ 55, -95, 56],
[ 13, -39, 57]])
# returns the diagonal of b
np.diag(A)
array([-82, -95, 57])
# creates a diagonal matrix
np.diag([1, 2, 3])
array([[1, 0, 0],
[0, 2, 0],
[0, 0, 3]])
la trace d’une matrice¶
C’est la somme des éléments de sa diagonale.
A = np.arange(16).reshape(4, 4)
A
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
np.trace(A)
30
np.sum(np.diag(A))
30
l’inversion d’une matrice¶
c’est le calcul de $A^{-1}$
# prenons une rotation
R = np.array([0, 1, 0, 0, 0, 1, 1, 0, 0]).reshape(3, 3)
R
array([[0, 1, 0],
[0, 0, 1],
[1, 0, 0]])
# pour calculer son inverse
np.linalg.inv(R)
array([[0., 0., 1.],
[1., 0., 0.],
[0., 1., 0.]])
pour nous convaincre que ça fonctionne comme attendu :
lorsqu’en maths on écrit $A^{-1}A = I$, en informatique c’est $A^{-1}A \approx I$ (presque égal à cause des approximations)
c’est l’intérêt de np.close
que de comparer si deux tableaux sont presque identiques
# on prend maintenant une matrice quelconque
n = 3
A = np.random.random(size=(n, n))
A
array([[0.74888908, 0.21829506, 0.78494135],
[0.37038305, 0.16662601, 0.90668149],
[0.21865492, 0.12902651, 0.37001961]])
# son inverse
Ainv = np.linalg.inv(A)
Vérifions que leur produit fait bien l’identité
np.dot(Ainv, A) == np.eye(n)
array([[False, False, False],
[False, False, False],
[False, False, False]])
Et bien oui on a des valeurs approchées.
np.isclose( Ainv @ A, np.eye(n))
array([[ True, True, True],
[ True, True, True],
[ True, True, True]])
les valeurs propres d’une matrice (eigen values)¶
On va calculer les $v$ tels que:
$f(v) = \lambda v$
$M \cdot v = \lambda v$
M = np.random.random(size=(3, 3))
M
array([[0.2850117 , 0.34332719, 0.9855683 ],
[0.89049106, 0.10968501, 0.16301911],
[0.51723741, 0.49578672, 0.0873873 ]])
ls, vs = np.linalg.eig(M) # eigen_values, eigen_vectors
ls, vs
(array([ 1.31920128+0.j , -0.41855864+0.31394671j,
-0.41855864-0.31394671j]),
array([[ 0.66280856+0.j , 0.43285248-0.3321126j ,
0.43285248+0.3321126j ],
[ 0.55563722+0.j , -0.74731234+0.j ,
-0.74731234-0.j ],
[ 0.5019483 +0.j , 0.05712049+0.37496862j,
0.05712049-0.37496862j]]))
Vérifions qu’on a bien $M \cdot v = \lambda v$
Attention les vecteurs propres sont dans une matrice (3, 3) et chacun d’eux est une des 3 colonnes de la matrice.
# le premier vecteur propre
v0 = vs[:, 0] # toutes les lignes et la colonne 0
# la première valeur propre
l0 = ls[0]
np.all(np.isclose(np.dot(M, v0), l0*v0))
True
À vous de jouer, à l’aide d’une boucle for-Python, parcourez les valeurs et les vecteurs propres de $M$ et vérifiez $M \cdot v = \lambda v$.
# votre code ici
Prenons la symétrie x↔︎y et calculons ses valeurs et vecteurs propres.
S = np.array([[0, 1], [1, 0]])
values, vectors = np.linalg.eig(S)
print(values)
# les vecteurs sont normés, naturellement
print(vectors)
[ 1. -1.]
[[ 0.70710678 -0.70710678]
[ 0.70710678 0.70710678]]
résolution d’un système linéaire¶
On va trouver les racines du système linéaire $A \cdot x = b$
Prenons une matrice $A$.
A = np.random.random(size=(3, 3))
A
array([[0.83955708, 0.05954924, 0.12420057],
[0.44674119, 0.96525272, 0.83231312],
[0.2034883 , 0.85556744, 0.73852452]])
Prenons un vecteur $b$.
b = np.array([1, 2, 3])
Calculons la racine de l’équation $A \cdot x = b$
np.linalg.solve?
x = np.linalg.solve(A, b)
x
array([ -6.04959087, -63.64422492, 79.45971427])
np.all( np.isclose( np.dot(A, x), b) )
True
Si la matrice n’est pas inversible ou si elle n’est pas carrée, une erreur np.linalg.LinAlgError
sera levée
les fonctions dont nous avons parlé (rapidement)¶
fonctions |
comportement |
---|---|
|
produit de matrice |
|
normes de matrice |
|
transposition de matrice |
|
déterminant |
|
inversion |
|
valeurs propres |
|
résolution de système linéaire |
|
matrice identité |
|
matrice diagonale |
Les exercices intéressants seront faits dans l’UE11.
exercices¶
construire une matrice diagonale à-la-main (peu d’intérêt)¶
On vous demande d’écrire une fonction matdiag
qui
accepte un paramètre qui est une liste de flottants [$x_1$, $x_2$, …, $x_n$]
retourne une matrice carrée diagonale dont les éléments valent
$$ m_{ii} = x_i $$ $$ m_{ij} = 0 \ pour\ i ≠ j $$
Retournez toujours un tableau de type
float64
Programmez une approche naïve et une approche à base de slicing.
https://nbhosting.inria.fr/auditor/notebook/python-mooc:exos/w7/w7-s05-x5-matdiag
remplir une matrice : m(i, j) = xi * xj¶
On vous demande d’écrire une fonction qui
accepte un nombre quelconque de paramètres, $x_1$, $x_2$, …, $x_n$, tous des flottants
retourne une matrice carrée symétrique $M$ dont les termes valent
$$ m_{ij} = x_i . x_j $$
Indices. Vous pouvez utiliser l’opérateur @
, la méthode array.dot()
, le broadcasting, la transposée d’une matrice .T
.
https://nbhosting.inria.fr/auditor/notebook/python-mooc:exos/w7/w7-s05-x6-xixj