Annexe C — Algèbre linéaire

C.1 Vecteurs et matrices

Un vecteur de \mathbb{R}^n peut être vu comme un tableau de dimension n : \boldsymbol{a}=\begin{bmatrix}a_1\\a_2\\a_3\end{bmatrix} est un vecteur de dimension 3, contenant 3 composantes notées a_i.

La matrice \boldsymbol{A} = \begin{bmatrix}A_{11} & A_{12} \\ A_{21} & A_{22} \\ A_{31} & A_{32}\end{bmatrix} est de dimension 3\times 2 et contient des composantes A_{ij}, où i est l’indice de ligne et j l’indice de colonne.

Par défaut, tous les vecteurs peuvent être considérés comme des matrices colonnes (n lignes et une seule colonne).

En python, nous utilisons la bibliothèque numpy pour gérer les vecteurs et matrices.

import numpy as np 

a = np.array([ 1, 2, 3])
A = np.array([[ 1, 2, 3], [4, 5, 6]])

print("Vecteur a de dimension " + str(len(a)) + " :\n", a)
print("Matrice A de taille " + str(A.shape) + " :\n", A)

print("a_1 =", a[0])
print("A_23 =", A[1, 2])
Vecteur a de dimension 3 :
 [1 2 3]
Matrice A de taille (2, 3) :
 [[1 2 3]
 [4 5 6]]
a_1 = 1
A_23 = 6

Remarque : numpy affiche toujours les vecteurs en ligne, mais nous pouvons considérer qu’ils sont en colonne dans nos opérations (numpy se charge de le transposer dans le bon sens).

C.1.1 Transposée

La transposée \boldsymbol{A}^T = \begin{bmatrix}A_{11} & A_{21} & A_{31} \\ A_{12} & A_{22} & A_{32} \end{bmatrix} de la matrice \boldsymbol{A} s’obtient en inversant les lignes et les colonnes.

La transposée \boldsymbol{a}^T=\begin{bmatrix}a_1 & a_2 & a_3\end{bmatrix} du vecteur \boldsymbol{a} est donc une matrice ligne.

Si \boldsymbol{A}^T = \boldsymbol{A}, alors \boldsymbol{A} est dite symétrique.

En python/numpy, la transposée s’écrit « .T » :

print("Matrice A:\n", A)
print("Transposée de A :\n", A.T)
Matrice A:
 [[1 2 3]
 [4 5 6]]
Transposée de A :
 [[1 4]
 [2 5]
 [3 6]]

Remarque : A.T est une simple « vue » sur la matrice, ce qui signifie qu’elle ne nécessite pas de calculs pour l’obtenir, mais aussi que modifier A.T modifie aussi A.

C.1.2 Addition

Les additions de vecteurs ou de matrices de mêmes dimensions se font composante à composante (case par case).

En python/numpy, les opérateurs d’addition/soustraction sont étendus aux vecteurs et matrices :

A = np.array([[ 1, 2, 3], [4, 5, 6]])
B = np.array([[ 1, -2, -3], [4, 5, 6]])
print("A + B :\n", A+B)
A + B :
 [[ 2  0  0]
 [ 8 10 12]]

C.1.3 Produit scalaire

Le produit scalaire entre deux vecteurs \boldsymbol{a} et \boldsymbol{b} de même dimension n est donné par la somme des produits des composantes : \left\langle \boldsymbol{a}, \boldsymbol{b} \right\rangle = \sum_{i=1}^n a_i b_i En notation matricielle, le produit scalaire correspond à la multiplication matricielle de la transposée de \boldsymbol{a} avec \boldsymbol{b} : \left\langle \boldsymbol{a}, \boldsymbol{b} \right\rangle = \boldsymbol{a}^T \boldsymbol{b} A noter : le produit scalaire est symétrique, \boldsymbol{a}^T \boldsymbol{b} = \boldsymbol{b}^T \boldsymbol{a}.

C.1.4 Multiplication matricielle

Soit \boldsymbol{A} de dimension m\times n et \boldsymbol{B} de dimension n\times p. Le produit \boldsymbol{C} = \boldsymbol{A} \boldsymbol{B} a m lignes et p colonnes, et chaque composante de \boldsymbol{C} correspond au produit scalaire entre une ligne de la matrice \boldsymbol{A} et une colonne de la matrice \boldsymbol{B} : C_{ij} = \sum_{k=1}^n A_{ik} B_{kj} = \boldsymbol{A}_{i,:} \boldsymbol{B}_{:,j} \boldsymbol{A}_{i,:} est la ième ligne de \boldsymbol{A} et \boldsymbol{B}_{:,j} est la jème colonne de \boldsymbol{B}.

Par exemple, avec \boldsymbol{A} = \begin{bmatrix}A_{11} & A_{12} \\ A_{21} & A_{22} \\ A_{31} & A_{32}\end{bmatrix},\qquad \boldsymbol{B} = \begin{bmatrix}B_{11}& B_{12} \\ B_{21} & B_{22}\end{bmatrix} on obtient \boldsymbol{C} de dimension 3\times 2 avec \boldsymbol{C} = \boldsymbol{A} \boldsymbol{B} = \begin{bmatrix}A_{11} & A_{12} \\ A_{21} & A_{22} \\ A_{31} & A_{32}\end{bmatrix} \begin{bmatrix}B_{11}& B_{12} \\ B_{21} & B_{22}\end{bmatrix} = \begin{bmatrix}A_{11}B_{11} + A_{12}B_{21} & A_{11}B_{12} + A_{12}B_{22} \\ A_{21}B_{11} + A_{22}B_{21} & A_{21}B_{12} + A_{22}B_{22} \\ A_{31}B_{11} + A_{32}B_{21} & A_{31}B_{12} + A_{32}B_{22} \end{bmatrix} À noter : la multiplication matricielle n’est pas symétrique, en général \boldsymbol{A}\boldsymbol{B} \neq \boldsymbol{B} \boldsymbol{A}.

La transposée d’un produit matriciel inverse l’ordre du produit : (\boldsymbol{A} \boldsymbol{B})^T = \boldsymbol{B}^T \boldsymbol{A}^T

En python/numpy, la multiplication mattricielle s’écrit « @ » :

print("Produit scalaire a^T a :\n", a.T @ a)
print("Produit matriciel A^T A :\n", A.T @ A)
Produit scalaire a^T a :
 14
Produit matriciel A^T A :
 [[17 22 27]
 [22 29 36]
 [27 36 45]]

Remarque : on trouve la notation A.dot(B) ou np.dot(A, B) qui sont équivalents à A @ B.

C.1.5 Normes

La norme euclidienne d’un vecteur est \|\boldsymbol{a}\| = \sqrt{\sum_{i=1}^n a_i^2} = \sqrt{\boldsymbol{a}^T \boldsymbol{a}} La norme \ell_1 d’un vecteur est \|\boldsymbol{a}\|_1 = \sum_{i=1}^n |a_i| Il existe aussi beaucoup de normes matricielles différentes.

En python/numpy, les normes sont implémentée par np.linalg.norm :

print("Norme euclidienne de a =", np.linalg.norm(a), " ou", np.sqrt( a.T@a ) )
print("Norme l1 de a =", np.linalg.norm(a, ord=1) , " ou", np.sum( np.abs(a) ) )
Norme euclidienne de a = 3.7416573867739413  ou 3.7416573867739413
Norme l1 de a = 6.0  ou 6

C.1.6 Vecteurs et matrices particuliers

La notation \boldsymbol{0} est utilisée à la fois pour le vecteur et les matrices dont toutes les composantes sont nulles. De même, on note parfois \boldsymbol{1} le vecteur dont toutes les composantes valent 1.

La matrice identité est la matrice carrée notée \boldsymbol{I} neutre pour le produit matriciel : \boldsymbol{I} = \begin{bmatrix} 1 & & 0\\ & \ddots & \\ 0 & & 1\end{bmatrix} = diag(\boldsymbol{1}) et, pour toute matrice \boldsymbol{A} carrée et vecteur \boldsymbol{a}, \boldsymbol{A} \boldsymbol{I} = \boldsymbol{I} \boldsymbol{A} = \boldsymbol{A}\qquad et\qquad \boldsymbol{I} \boldsymbol{a} = \boldsymbol{a}

En python/numpy :

n=3
print("Vecteur nul : ", np.zeros(n) )
print("Vecteur de 1 : ", np.ones(n) )
print("Matrice de zéros :\n", np.zeros((n, 2*n)) )
print("Matrice identité :\n", np.eye(n) )
Vecteur nul :  [0. 0. 0.]
Vecteur de 1 :  [1. 1. 1.]
Matrice de zéros :
 [[0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0.]]
Matrice identité :
 [[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

C.2 Inégalité de Cauchy-Schwarz

L’inégalité de Cauchy-Schwarz permet de borner l’amplitude d’un produit scalaire en fonction des normes des vecteurs : pour tout vecteurs \boldsymbol{u} et \boldsymbol{v}, \left| \boldsymbol{u}^T \boldsymbol{v} \right| \leq \|\boldsymbol{u}\| \|\boldsymbol{v}\| et la borne est atteinte, |\boldsymbol{u}^T \boldsymbol{v} | = \|\boldsymbol{u}\| \|\boldsymbol{v}\|, si et seulement si les vecteurs sont colinéaires, c’est-à-dire si \boldsymbol{u} = \lambda \boldsymbol{v}.

C.3 Matrice inverse

Une matrice carrée \boldsymbol{A}\in\mathbb{R}^{n\times n} est dite inversible si sa matrice inverse \boldsymbol{A}^{-1} existe telle que \boldsymbol{A} \boldsymbol{A}^{-1} = \boldsymbol{A}^{-1}\boldsymbol{A} = \boldsymbol{I}

C.4 Rang d’une matrice

Le rang d’une matrice est le nombre de lignes ou colonnes linéairement indépendantes. De manière générale, pour une matrice à m lignes et n colonnes : \text{rang}(\boldsymbol{A}) \leq \min\{ m, n\} et \text{rang}(\boldsymbol{A}\boldsymbol{B}) \leq min\{ \text{rang}(\boldsymbol{A}), \text{rang}(\boldsymbol{B})\} (car la multiplication par \boldsymbol{B} revient à créer des combinaisons linéaires des colonnes de \boldsymbol{A}, et donc ne peut pas rendre indépendantes des colonnes qui ne l’étaient pas).

Le rang d’une matrice carrée correspond au nombre de valeurs propres non nulles. Pour une matrice rectangulaire, le rang correspond au nombre de valeurs singulières non nulles.

C.5 Décomposition en vecteurs et valeurs propres

Pour une matrice carrée \boldsymbol{A}\in\mathbb{R}^{n\times n} « diagonalisable » : \boldsymbol{A} = \boldsymbol{U} \boldsymbol{\Lambda}\boldsymbol{U}^{-1} avec \boldsymbol{\Lambda }=\begin{bmatrix} \lambda_1 & & 0\\ & \ddots & \\ 0 & & \lambda_n\end{bmatrix} \qquad \boldsymbol{U}= \begin{bmatrix} | & & | \\ \boldsymbol{u}_1 & \cdots & \boldsymbol{u}_n \\ | & & |\end{bmatrix}

  • \boldsymbol{\Lambda} est une matrice diagonale contenant les n valeurs propres \lambda_i,
  • \boldsymbol{U}\in\mathbb{R}^{n\times n} est une matrice carrée contenant les n vecteurs propres \boldsymbol{u}_i associés tels que \boldsymbol{A} \boldsymbol{u}_i = \lambda_i \boldsymbol{u}_i
    avec \boldsymbol{u}_i \neq \boldsymbol{0}.

Pour une matrice \boldsymbol{A}\in\mathbb{R}^{n\times n} symétrique (\boldsymbol{A}^T = \boldsymbol{A}) : \boldsymbol{U} est orthogonale (\boldsymbol{U}^{-1}=\boldsymbol{U}^T) et les \lambda_i sont réelles.


M = A.T @ A
print("Matrice M :\n", M)

# eigh calcule les vecteurs propres d'une matrice symétrique
Lambda, U = np.linalg.eigh(M)

print("U :\n", U, "\nValeurs propres (par ordre croissant) :\n", Lambda)

print("ULU^T :\n", U @ np.diag(Lambda) @ U.T )

print("M u = lambda * u ? Vérification pour u3 :\n")
print(M @ U[:,2])
print(Lambda[2] * U[:,2])
Matrice M :
 [[17 22 27]
 [22 29 36]
 [27 36 45]]
U :
 [[ 0.40824829 -0.80596391 -0.42866713]
 [-0.81649658 -0.11238241 -0.56630692]
 [ 0.40824829  0.58119908 -0.7039467 ]] 
Valeurs propres (par ordre croissant) :
 [2.91325866e-15 5.97327474e-01 9.04026725e+01]
ULU^T :
 [[17. 22. 27.]
 [22. 29. 36.]
 [27. 36. 45.]]
M u = lambda * u ? Vérification pour u3 :

[-38.7526545  -51.19565893 -63.63866337]
[-38.7526545  -51.19565893 -63.63866337]

C.6 Décomposition en valeurs singulières (SVD)

Toute matrice \boldsymbol{X}\in\mathbb{R}^{n\times d} possède une décomposition en valeurs singulières : \boldsymbol{X} = \boldsymbol{U}\boldsymbol{S} \boldsymbol{V}^T \qquad ou\ \underbrace{\begin{bmatrix} \text{---}\ \boldsymbol{x}_1^\top \text{---} \\\vdots\\ \text{---}\ \boldsymbol{x}_n^\top \text{---} \end{bmatrix}}_{n\times d} = \underbrace{\begin{bmatrix} | & & |\\ \boldsymbol{u}_1 & \cdots & \boldsymbol{u}_n \\ | & & |\end{bmatrix}}_{n\times n} \underbrace{\begin{bmatrix}\sigma_1& & 0 & 0 \\ & \ddots & & \vdots\\ 0 & & \sigma_n & 0\end{bmatrix}}_{n\times d} \underbrace{\begin{bmatrix} \text{---}\ \boldsymbol{v}_1^\top \text{---} \\\vdots\\ \text{---}\ \boldsymbol{v}_d^\top \text{---} \end{bmatrix}}_{d\times d} avec les matrices \boldsymbol{U}^T \boldsymbol{U} =\boldsymbol{U} \boldsymbol{U}^T= \boldsymbol{I},\quad et\quad \boldsymbol{V}^T \boldsymbol{V} =\boldsymbol{V} \boldsymbol{V}^T= \boldsymbol{I} qui contiennent les vecteurs singuliers à gauche et à droite et \boldsymbol{S} qui contient les valeurs singluières.

M = A.T @ A
print("Matrice M :\n", M)

# eigh calcule les vecteurs propres d'une matrice symétrique
Lambda, U = np.linalg.eigh(M)

print("U :\n", U, "\nValeurs propres (par ordre croissant) :\n", Lambda)

print("ULU^T :\n", U @ np.diag(Lambda) @ U.T )

print("M u = lambda * u ? Vérification pour u3 :\n")
print(M @ U[:,2])
print(Lambda[2] * U[:,2])
Matrice M :
 [[17 22 27]
 [22 29 36]
 [27 36 45]]
U :
 [[ 0.40824829 -0.80596391 -0.42866713]
 [-0.81649658 -0.11238241 -0.56630692]
 [ 0.40824829  0.58119908 -0.7039467 ]] 
Valeurs propres (par ordre croissant) :
 [2.91325866e-15 5.97327474e-01 9.04026725e+01]
ULU^T :
 [[17. 22. 27.]
 [22. 29. 36.]
 [27. 36. 45.]]
M u = lambda * u ? Vérification pour u3 :

[-38.7526545  -51.19565893 -63.63866337]
[-38.7526545  -51.19565893 -63.63866337]

C.6.1 Lien avec les vecteurs et valeurs propres

Dans la décomposition en valeurs singulières, les vecteurs \boldsymbol{v}_k et les valeurs singulières \sigma_k sont liés aux vecteurs/valeurs propres de \boldsymbol{X}^T \boldsymbol{X} : \boldsymbol{X}^T \boldsymbol{X} = \boldsymbol{V} \boldsymbol{S}^T \boldsymbol{U}^T \boldsymbol{U} \boldsymbol{S} \boldsymbol{V}^T = \boldsymbol{V} \boldsymbol{S}^T\boldsymbol{S} \boldsymbol{V}^T = \boldsymbol{V} \boldsymbol{S}^2 \boldsymbol{V}^T \boldsymbol{S}^2= diag(\sigma_1^2,\dots, \sigma_n^2,0,\dots). Donc \boldsymbol{X}^T \boldsymbol{X} \boldsymbol{v}_k = \sigma_k^2 \boldsymbol{v}_k, car, par exemple pour k=1 : \boldsymbol{X}^T \boldsymbol{X} \boldsymbol{v}_1 = \boldsymbol{V} \boldsymbol{S}^2 \boldsymbol{V}^T \boldsymbol{v}_1 = \boldsymbol{V} \boldsymbol{S}^2 \begin{bmatrix}1\\0\\ | \end{bmatrix} = \boldsymbol{V} \begin{bmatrix}\sigma_1^2\\0\\ | \end{bmatrix} = \sigma_1^2 \boldsymbol{v}_1

M = A.T @ A

# eigh calcule les vecteurs propres d'une matrice symétrique
Lambda, U = np.linalg.eigh(M)

print("U :\n", U[:,::-1], "\nValeurs propres (par ordre décroissant) :\n", Lambda[::-1] )

# svd
U, s, Vt = np.linalg.svd(A)
print("Vecteurs propres par SVD :\n", Vt.T, "\nValeurs propres par SVD (par ordre décroissant en ignorant les valeurs nulles) :\n", s**2)
U :
 [[-0.42866713 -0.80596391  0.40824829]
 [-0.56630692 -0.11238241 -0.81649658]
 [-0.7039467   0.58119908  0.40824829]] 
Valeurs propres (par ordre décroissant) :
 [9.04026725e+01 5.97327474e-01 2.91325866e-15]
Vecteurs propres par SVD :
 [[-0.42866713  0.80596391  0.40824829]
 [-0.56630692  0.11238241 -0.81649658]
 [-0.7039467  -0.58119908  0.40824829]] 
Valeurs propres par SVD (par ordre décroissant en ignorant les valeurs nulles) :
 [90.40267253  0.59732747]

A noter : les vecteurs propres ne sont retrouvés qu’au signe près, car si \boldsymbol{M}\boldsymbol{u} = \lambda\boldsymbol{u} alors \boldsymbol{M}(-\boldsymbol{u}) = \lambda (-\boldsymbol{u}) aussi.

C.6.2 SVD fine ou tronquée

Si d>n, alors la SVD fine construit une matrice \boldsymbol{S}_n ne contenant que les n premières colonnes de \boldsymbol{S} et une matrice \boldsymbol{V}_n^T ne contenant que les n premières lignes de \boldsymbol{V}^T, sans modifier le résultat : \boldsymbol{X} = \boldsymbol{U}\boldsymbol{S}_n \boldsymbol{V}_n^T

La SVD tronquée reprend ce principe, mais limite les calculs à p < n composantes. En revanche, dans ce cas, le résultat peut être modifié et \boldsymbol{X} \neq \hat{\boldsymbol{X}}= \boldsymbol{U} \boldsymbol{S}_p \boldsymbol{V}_p^T si p < rang(\boldsymbol{X}). Cependant, \hat{\boldsymbol{X}} constitue la meilleure approximation de rang p de \boldsymbol{X}.