Algebre lineaire pour l'ingénieur¶
Les matrices ne sont pas qu'un sujet academique — elles font tourner la recommandation, la recherche et la compression dans presque tous les systèmes modernes.
Vecteurs et espaces vectoriels¶
Un vecteur est une liste ordonnée de nombres réels. Dans le contexte de l'ingénierie, un vecteur peut représenter :
- un document (vecteur de fréquences de mots)
- un utilisateur (vecteur de préférences)
- une image (vecteur de pixels)
- un point dans un espace géométrique
import numpy as np
# Vecteur en dimension 3
v = np.array([2.0, -1.0, 3.0])
w = np.array([1.0, 4.0, -2.0])
# Operations de base
somme = v + w # [3, 3, 1]
scalaire = 2.5 * v # [5, -2.5, 7.5]
Produit scalaire¶
Le produit scalaire de deux vecteurs \(\vec{v}\) et \(\vec{w}\) est :
Propriété géométrique fondamentale : \(\vec{v} \cdot \vec{w} = \lVert v \rVert \times \lVert w \rVert \times \cos(\theta)\) ou \(\theta\) est l'angle entre les vecteurs.
- \(\vec{v} \cdot \vec{w} = 0\) : vecteurs orthogonaux (perpendiculaires)
- \(\vec{v} \cdot \vec{w} > 0\) : angle aigu (vecteurs "dans la même direction")
- \(\vec{v} \cdot \vec{w} < 0\) : angle obtus (vecteurs "en directions opposees")
produit_scalaire = np.dot(v, w) # 2*1 + (-1)*4 + 3*(-2) = -8
norme_v = np.linalg.norm(v) # sqrt(4 + 1 + 9) = sqrt(14)
Norme et distance¶
La norme euclidienne (longueur) d'un vecteur : \(\lVert v \rVert = \sqrt{\vec{v} \cdot \vec{v}}\)
La distance entre deux vecteurs : \(d(\vec{v}, \vec{w}) = \lVert \vec{v} - \vec{w} \rVert\)
Similarite cosinus¶
Plutôt que la distance, la similarite cosinus mesure l'angle entre vecteurs, independamment de leur norme :
Valeur dans [-1, 1]. Utile pour les embeddings et la recherche semantique : deux textes qui parlent du même sujet auront des vecteurs proches en direction, même si l'un est plus long que l'autre.
def similarite_cosinus(v, w):
return np.dot(v, w) / (np.linalg.norm(v) * np.linalg.norm(w))
# Deux phrases similaires -> valeur proche de 1
# Deux phrases opposees -> valeur proche de -1
# Phrases sans rapport -> valeur proche de 0
Base et dimension¶
Une base d'un espace vectoriel est un ensemble de vecteurs lineairement indépendants qui "engendrent" tout l'espace. La dimension est le nombre de vecteurs dans cette base.
Un ensemble de vecteurs est lineairement indépendant si aucun d'eux ne peut s'écrire comme combinaison lineaire des autres — aucun est "redondant".
Tout vecteur \(\vec{v} = [a, b, c] = a \vec{e_1} + b \vec{e_2} + c \vec{e_3}\).
Matrices : opérations fondamentales¶
Une matrice est un tableau rectangulaire de nombres. Une matrice \(A\) de dimensions \(m \times n\) a \(m\) lignes et \(n\) colonnes.
Opérations¶
# A de dimension (m x k), B de dimension (k x n)
# Produit C = A @ B de dimension (m x n)
# C[i][j] = somme sur k de A[i][k] * B[k][j]
A = np.array([[1, 2], [3, 4]]) # 2x2
B = np.array([[5, 6], [7, 8]]) # 2x2
C = A @ B
# C[0][0] = 1*5 + 2*7 = 19
# C[0][1] = 1*6 + 2*8 = 22
# C[1][0] = 3*5 + 4*7 = 43
# C[1][1] = 3*6 + 4*8 = 50
# ATTENTION : A @ B ≠ B @ A en general
Transposee, inverse, determinant¶
| Opération | Notation | Définition | Code numpy |
|---|---|---|---|
| Transposee | A^T | Lignes et colonnes echangees | A.T |
| Inverse | A^-1 | A @ A^-1 = I (matrice identité) | np.linalg.inv(A) |
| Determinant | det(A) | Scalaire mesurant le "volume" de A | np.linalg.det(A) |
| Rang | rank(A) | Dimension de l'image de A | np.linalg.matrix_rank(A) |
Le determinant est zero si et seulement si la matrice est singuliere (non inversible). Cela signifie que les lignes (ou colonnes) sont lineairement dependantes — la matrice "ecrase" l'espace dans au moins une dimension.
Systèmes lineaires¶
Un système de \(m\) equations a \(n\) inconnues s'écrit matriciellement \(Ax = b\) :
Élimination de Gauss¶
L'algorithme de Gauss transforme la matrice augmentee \([A|b]\) en forme triangulaire supérieure par des opérations elementaires sur les lignes, puis remonte par substitution.
def gauss(A, b):
n = len(b)
M = np.hstack([A.astype(float), b.reshape(-1, 1)])
for col in range(n):
# Pivot : echanger avec la ligne ayant le plus grand element
pivot = np.argmax(np.abs(M[col:, col])) + col
M[[col, pivot]] = M[[pivot, col]]
# Elimination
for row in range(col + 1, n):
if M[col, col] != 0:
facteur = M[row, col] / M[col, col]
M[row] -= facteur * M[col]
# Remontee
x = np.zeros(n)
for i in range(n - 1, -1, -1):
x[i] = (M[i, -1] - np.dot(M[i, i+1:n], x[i+1:])) / M[i, i]
return x
# En pratique : utiliser directement
x = np.linalg.solve(A, b)
Interpretation géométrique¶
-
Système 2x2 : chaque equation est une droite dans le plan. La solution est l'intersection.
-
Système 3x3 : chaque equation est un plan dans \(\mathbb{R}^3\). La solution est l'intersection des trois plans.
-
Pas de solution : droites parallèles (equations incompatibles)
-
Infiniment de solutions : droites confondues (equations redondantes, rang deficient)
-
Cas 1 : \(\det(A) \neq 0\) -- solution unique
-
Cas 2 : \(\det(A) = 0\) et \(b\) compatible -- infiniment de solutions
-
Cas 3 : \(\det(A) = 0\) et \(b\) incompatible -- pas de solution
Moindres carres¶
Quand le système est sur-contraint (\(m > n\), plus d'equations que d'inconnues), il n'existe généralement pas de solution exacte. La solution aux moindres carres minimise \(\lVert Ax - b \rVert^2\) :
# Ajustement d'une droite y = ax + b sur des donnees
x_data = np.array([1, 2, 3, 4, 5])
y_data = np.array([2.1, 3.9, 6.2, 7.8, 10.1])
# Construire la matrice de Vandermonde
A = np.column_stack([x_data, np.ones(len(x_data))])
# Solution aux moindres carres
coeffs, _, _, _ = np.linalg.lstsq(A, y_data, rcond=None)
# coeffs = [a, b] -> droite de regression
Valeurs propres et vecteurs propres¶
Un vecteur propre de la matrice \(A\) est un vecteur non nul \(\vec{v}\) tel que \(A\vec{v} = \lambda \vec{v}\) pour un scalaire \(\lambda\) (la valeur propre associee).
Intuition géométrique : \(\vec{v}\) est un vecteur que \(A\) ne fait que "etirer" (ou comprimer) --- il ne change pas de direction.
Calcul¶
Les valeurs propres sont les racines du polynome caractéristique :
Pour chaque valeur propre \(\lambda\), le vecteur propre \(\vec{v}\) est dans le noyau de \((A - \lambda I)\).
A = np.array([[4, 1],
[2, 3]])
valeurs, vecteurs = np.linalg.eig(A)
# valeurs : [5., 2.]
# vecteurs : colonnes = vecteurs propres associes
# Verification : A @ v = λ * v
v0 = vecteurs[:, 0] # premier vecteur propre
print(A @ v0) # [0.70710..., 0.70710...]
print(valeurs[0] * v0) # [0.70710..., 0.70710...] ✓
Diagonalisation¶
Si \(A\) a \(n\) vecteurs propres lineairement indépendants, on peut écrire \(A = P D P^{-1}\) ou \(D\) est diagonale (valeurs propres) et \(P\) contient les vecteurs propres en colonnes.
L'intérêt : calculer \(A^k = P D^k P^{-1}\), et \(D^k\) est trivial (on élevé chaque valeur propre à la puissance \(k\)). Cela rend certains calculs iteratifs extremement efficaces.
Note
Toutes les matrices ne sont pas diagonalisables. Les matrices symetriques réelles (\(A = A^T\)) le sont toujours et ont des valeurs propres réelles — une propriété essentielle pour les covariances et les matrices de similarite.
Applications¶
PageRank : le vecteur propre qui classe le web¶
PageRank (Brin & Page, 1998) modélisé un "navigateur aléatoire" qui suit les liens avec probabilite \(d\) et teleporte aléatoirement avec probabilite \(1-d\). Le score d'une page est proportionnel à la somme des scores des pages qui la pointent, ponderee par leur nombre de liens sortants.
Formulation matricielle : si \(M\) est la matrice de transition (colonne \(j\) = distribution de probabilite depuis la page \(j\)), le vecteur PageRank \(\vec{r}\) satisfait :
C'est un problème de vecteur propre : \(\vec{r}\) est le vecteur propre dominant de la matrice modifiée, associe à la valeur propre 1.
def pagerank(matrice_liens, d=0.85, iterations=100):
n = matrice_liens.shape[0]
# Normaliser les colonnes pour avoir une matrice stochastique
sommes_colonnes = matrice_liens.sum(axis=0)
sommes_colonnes[sommes_colonnes == 0] = 1 # eviter division par zero
M = matrice_liens / sommes_colonnes
# Partir d'une distribution uniforme
r = np.ones(n) / n
for _ in range(iterations):
r = d * M @ r + (1 - d) / n * np.ones(n)
return r # scores PageRank normalises
# Exemple : 4 pages avec des liens entre elles
liens = np.array([
[0, 1, 1, 0], # page 0 est liee depuis pages 1 et 2
[1, 0, 0, 1],
[0, 1, 0, 1],
[1, 0, 1, 0],
])
scores = pagerank(liens)
La convergence est garantie par le théorème de Perron-Frobenius : une matrice stochastique positive a une valeur propre dominante unique egale a 1.
SVD et compression d'images¶
La decomposition en valeurs singulieres (SVD) factorise toute matrice \(A\) (\(m \times n\)) en :
- \(U\) (\(m \times m\)) : vecteurs singuliers gauches (colonnes orthonormees)
- \(\Sigma\) (\(m \times n\)) : valeurs singulieres sur la diagonale, decroissantes
- \(V^T\) (\(n \times n\)) : vecteurs singuliers droits
L'approximation de rang \(k\) (garder seulement les \(k\) plus grandes valeurs singulieres) est la meilleure approximation de rang \(k\) au sens de la norme de Frobenius --- théorème d'Eckart-Young.
from PIL import Image
# Charger une image en niveaux de gris
img = np.array(Image.open("photo.png").convert("L"), dtype=float)
# SVD
U, S, Vt = np.linalg.svd(img, full_matrices=False)
# Reconstruction avec k valeurs singulieres
def reconstruire(U, S, Vt, k):
return U[:, :k] @ np.diag(S[:k]) @ Vt[:k, :]
# Compression a differents rangs
for k in [5, 20, 50, 100]:
img_compresse = reconstruire(U, S, Vt, k)
taux = k * (U.shape[0] + 1 + Vt.shape[1]) / (img.shape[0] * img.shape[1])
print(f"k={k:3d} : {taux:.1%} des donnees originales")
# k=5 : 0.4% — image tres floue, structures globales visibles
# k=20 : 1.7% — qualite acceptable
# k=50 : 4.2% — difficile a distinguer de l'original
# k=100: 8.5% — presque identique
Chaque valeur singuliere représenté "l'importance" d'une composante. Les premières capturent les grandes structures (fond, contours principaux) ; les dernières, les détails fins.
Note
Vous n'avez pas besoin de coder SVD — vous devez comprendre ce qu'elle fait. NumPy, scikit-learn, PyTorch l'implementent avec des algorithmes numeriquement stables (Golub-Reinsch). Ce qui compte : savoir que SVD decompose une transformation en trois étapes (rotation, etirement, rotation), et que tronquer a rang \(k\) donne la meilleure approximation compacte possible.
Systèmes de recommandation : factorisation matricielle¶
Une plateforme de streaming a une matrice \(R\) (utilisateurs \(\times\) films) contenant les notes. La plupart des cases sont vides (un utilisateur n'a vu qu'une fraction des films). L'objectif : prédire les notes manquantes.
La factorisation matricielle decomposes \(R \approx U \times V^T\) ou \(U\) (utilisateurs \(\times k\)) et \(V\) (films \(\times k\)) sont des matrices de facteurs latents de faible rang \(k\).
from sklearn.decomposition import NMF # Non-negative Matrix Factorization
# Matrice de notes (0 = non vu)
R = np.array([
[5, 3, 0, 1], # utilisateur 0
[4, 0, 4, 1], # utilisateur 1
[1, 1, 0, 5], # utilisateur 2
[0, 0, 5, 4], # utilisateur 3
])
# Factorisation en k=2 facteurs latents
model = NMF(n_components=2, random_state=42)
U = model.fit_transform(R) # (4 x 2) : profils utilisateurs
V = model.components_.T # (4 x 2) : profils films
# Predire toutes les notes
R_predit = U @ V.T
print(R_predit.round(1))
# Les notes manquantes sont maintenant estimees
# La note predite pour utilisateur 3, film 0 :
print(f"Note predite : {R_predit[3, 0]:.1f}")
Les facteurs latents (colonnes de \(U\) et \(V\)) représentent des dimensions semantiques implicites : genres, niveaux de complexité, styles --- jamais nommes explicitement mais appris depuis les données.
Embeddings et recherche semantique¶
Les grands modèles de langue (LLM) représentent les mots, phrases ou documents comme des vecteurs denses dans un espace de haute dimension (768, 1536, 3072 dimensions selon le modèle). Ces vecteurs sont les embeddings.
La similarite semantique se mesure par la similarite cosinus :
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("all-MiniLM-L6-v2")
phrases = [
"Le chien court dans le parc",
"Un chien qui court en plein air",
"Le chat dort sur le canape",
"Python est un langage de programmation",
]
embeddings = model.encode(phrases) # shape: (4, 384)
# Matrice de similarite
def matrice_similarite(E):
# Normaliser
norms = np.linalg.norm(E, axis=1, keepdims=True)
E_norm = E / norms
return E_norm @ E_norm.T
sim = matrice_similarite(embeddings)
# sim[0, 1] ≈ 0.87 -> tres similaires (meme idee)
# sim[0, 2] ≈ 0.31 -> peu similaires (animaux differents, actions differentes)
# sim[0, 3] ≈ 0.05 -> non lies
La recherche semantique ("trouve les documents les plus proches de cette requête") se ramene a trouver les vecteurs les plus similaires — un problème de plus proche voisin en haute dimension, résolu par des index approximatifs (FAISS, Annoy, HNSW).
Recapitulatif¶
| Concept | Formule clé | Application principale |
|---|---|---|
| Produit scalaire | \(\vec{v} \cdot \vec{w} = \sum_i v_i w_i\) | Similarite, projection |
| Similarite cosinus | \((\vec{v} \cdot \vec{w}) / (\lVert v \rVert \times \lVert w \rVert)\) | Recherche semantique, embeddings |
| Système \(Ax = b\) | Gauss, moindres carres | Régression, ajustement de modèles |
| Valeurs propres | \(A\vec{v} = \lambda \vec{v}\) | PageRank, PCA, stabilité |
| Diagonalisation | \(A = P D P^{-1}\) | Calcul de puissances, dynamiques |
| SVD | \(A = U \Sigma V^T\) | Compression, LSA, recommandation |
| Factorisation rang k | \(R \approx U V^T\) | Systèmes de recommandation |
| Embeddings | Vecteurs denses dans \(\mathbb{R}^n\) | Recherche semantique, LLM |