Module L10 · Partie G · Mathématiques de l’informatique
Algèbre linéaire numérique : le langage de l’IA et de la robotique.
Une image est une matrice. Un réseau de neurones est une suite de produits matriciels. La position d’un bras robot est une chaîne de matrices de rotation. Une régression est une projection orthogonale. Ce module apprend à calculer avec des vecteurs et des matrices — avec NumPy et en réimplémentant les algorithmes clés — pour que les modules d’IA et de robotique n’aient plus de mystère mathématique.
Durée : 3 séances · Prérequis : séances 14 et 19 (vecteurs, NumPy). Objectifs : produit matriciel et ses coûts, résolution de systèmes (Gauss, LU), moindres carrés, transformations géométriques 2D/3D, valeurs propres et SVD (intuition + usage), conditionnement et erreurs numériques.
Ce que vous saurez faire à la fin
- Écrire l’élimination de Gauss et expliquer pourquoi NumPy fait mieux.
- Résoudre un problème de moindres carrés (calibration de capteur, ajustement de droite/parabole).
- Composer rotations et translations en matrices homogènes pour un bras robot.
- Utiliser la SVD pour compresser une image et comprendre l’ACP.
Références : Linear Algebra and Its Applications (Strang) et ses cours MIT 18.06 en vidéo, 3Blue1Brown « Essence of linear algebra », programme de mathématiques MPSI/MP.
Fiche de cours · Définitions
Algèbre linéaire : définitions
Fiche de cours · Formules
Formules à connaître
Fiche de cours · Théorèmes et démonstrations
Démonstrations à savoir refaire (1/2)
Fiche de cours · Théorèmes et démonstrations
Démonstrations à savoir refaire (2/2)
Fiche de cours · Méthodes
Méthodes et pièges
inv(A) @ b : utiliser np.linalg.solve (LU avec pivot, plus stable, 3× moins cher). Système surdéterminé : np.linalg.lstsq (QR/SVD, plus stable que les équations normales dont le conditionnement est κ(A)²). Vérifier le résidu ‖Ax − b‖ et le conditionnement np.linalg.cond.Pièges : confondre A·B et B·A ; comparer des flottants avec == (utiliser allclose) ; inverser une matrice presque singulière (κ ≈ 10¹⁶ : résultat sans signification) ; oublier de centrer les données avant l’ACP ; degrés vs radians ; * (élément par élément) vs @ (produit matriciel) en NumPy.
Fiche de cours · Exercices corrigés
Exercices corrigés
01 / Vecteurs et matrices
Le produit matriciel : définition, coût, sens
Trois façons de lire A·x
1) Ligne par ligne : chaque composante du résultat est un produit scalaire (ligne de A) · x. 2) Colonne par colonne : A·x est une combinaison linéaire des colonnes de A, avec les coefficients x. 3) Application linéaire : A transforme l’espace (rotation, étirement, projection) et A·x est l’image de x. La deuxième lecture explique pourquoi « A·x = b a une solution ⇔ b est dans l’espace engendré par les colonnes ». NumPy appelle BLAS (code C/Fortran optimisé pour le cache et le SIMD) : 1000 fois plus rapide que la boucle Python, et c’est exactement ce que font les GPU en parallèle massif pour l’IA.
01 / Vecteurs et matrices
Transformations géométriques : rotations, homogènes, chaînes cinématiques
L’ordre compte : Rh(a) @ T(L, 0) tourne puis translate dans le repère tourné (c’est ce qu’un bras fait) ; T @ Rh ferait l’inverse. En 3D, on ajoute une dimension (matrices 4×4) et les rotations se paramètrent par angles d’Euler ou quaternions : c’est ce que ROS appelle un « transform » (tf2). La convention de Denavit-Hartenberg standardise ces chaînes pour les robots industriels.
02 / Systèmes linéaires
Élimination de Gauss : résoudre A·x = b
LU, et pourquoi on ne calcule jamais l’inverse
Gauss factorise A = L·U (triangulaire inférieure × supérieure) ; résoudre A·x = b devient deux substitutions en O(n²). Si on a plusieurs b (plusieurs mesures, un filtre de Kalman à chaque pas), on factorise une fois en O(n³) et on résout chaque fois en O(n²). L’inverse explicite coûte 3 fois plus, remplit de zéros les matrices creuses, et amplifie les erreurs. Règle : « inverser une matrice » se traduit toujours par solve. Pour des matrices symétriques définies positives (covariances, moindres carrés), Cholesky est 2 fois plus rapide que LU.
02 / Systèmes linéaires
Moindres carrés : ajuster un modèle à des mesures bruitées
La géométrie : projeter
Les colonnes de X engendrent un sous-espace ; d n’y est pas (bruit). La meilleure approximation X·c est la projection orthogonale de d sur ce sous-espace, donc le résidu d − X·c est orthogonal aux colonnes : Xᵀ(d − X·c) = 0, d’où les équations normales. C’est la régression linéaire, celle du module L13, avec une solution exacte en une ligne. Les équations normales élèvent le conditionnement au carré ; lstsq (via QR ou SVD) est numériquement plus sûr — préférez-le toujours.
03 / Valeurs propres et SVD
Valeurs propres : les directions que la matrice ne fait qu’étirer
Une matrice symétrique a des valeurs propres réelles et des vecteurs propres orthogonaux (théorème spectral) : c’est le cas des covariances, des laplaciens de graphes, des hessiennes — d’où son omniprésence en IA (ACP, optimisation) et en physique (modes de vibration d’un robot).
03 / Valeurs propres et SVD
SVD : compresser une image, comprendre l’ACP
Ce que dit la SVD
Toute matrice A (même rectangulaire) s’écrit U·Σ·Vᵀ avec U, V orthogonales et Σ diagonale positive : A envoie une base orthonormée (colonnes de V) sur une base orthonormée (colonnes de U) étirée par les valeurs singulières σ. Tronquer aux k premières donne la meilleure approximation de rang k (théorème d’Eckart-Young) : compression, débruitage, recommandation (Netflix), réduction de dimension avant apprentissage (ACP), pseudo-inverse pour les moindres carrés, et le « conditionnement » σ_max/σ_min qui mesure la sensibilité d’un système aux erreurs.
04 / Précision
Conditionnement et arithmétique flottante : quand le calcul ment
Sur un microcontrôleur sans unité flottante (Arduino Uno), chaque opération en float coûte des dizaines de cycles, et double n’existe pas (c’est un float 32 bits déguisé) : on travaille en virgule fixe (entiers avec facteur d’échelle). Module L18.
Cours
Cours 1 — Espaces vectoriels, bases, rang : le vocabulaire qui rend NumPy lisible
| Notion | Définition opérationnelle | En NumPy |
|---|---|---|
| Combinaison linéaire | Σ λ_i v_i | V @ lam (colonnes de V pondérées) |
| Espace engendré (span) | Ensemble des combinaisons linéaires | « b ∈ span(colonnes de A) » ⇔ Ax = b a une solution |
| Indépendance linéaire | Aucune combinaison non triviale ne donne 0 | np.linalg.matrix_rank(V) == V.shape[1] |
| Base, dimension | Famille libre et génératrice ; son cardinal | — |
| Rang | Dimension de l’espace des colonnes (= des lignes) | matrix_rank (via SVD : nombre de σ > ε) |
| Noyau | {x : Ax = 0} | Derniers vecteurs de Vᵀ dans la SVD (σ = 0) |
| Inversible | Carrée et rang plein ⇔ det ≠ 0 ⇔ noyau = {0} | Ne pas tester det != 0 en flottant : regarder le conditionnement |
| Orthogonalité | u·v = 0 ; base orthonormée : Qᵀ Q = I | np.linalg.qr |
Théorème du rang. Pour A de taille m×n : rang(A) + dim(noyau(A)) = n. Conséquence pour les systèmes : Ax = b a une solution unique si rang = n = m ; une infinité si rang < n (noyau non nul) ; aucune ou une infinité si m > n selon que b est dans l’espace des colonnes — d’où les moindres carrés quand il n’y en a pas.
Cours
Cours 2 — Exemple travaillé : ajuster un modèle de moteur par moindres carrés, avec incertitudes
Lecture. C’est l’identification de système (module L21 : le modèle du LQR vient de là). Le conditionnement dit si les colonnes sont « presque dépendantes » — si u et c étaient corrélés dans les mesures, k1 et k2 seraient mal séparés et leurs écarts-types énormes : il faut alors concevoir l’expérience (varier u et c indépendamment). La covariance (XᵀX)⁻¹ est aussi ce que manipule le filtre de Kalman (L19).
Cours
Cours 3 — Diagonalisation, puissances de matrices, systèmes dynamiques
Théorème. Si A (n×n) possède n vecteurs propres indépendants (colonnes de P) avec valeurs propres λ_i, alors A = P D P⁻¹ avec D = diag(λ_i), et Ak = P Dk P⁻¹. Donc l’évolution x_{k+1} = A x_k a pour solution x_k = Σ c_i λ_ik v_i : chaque mode croît ou décroît comme λ_ik.
- |λ_i| < 1 pour tout i ⇒ x_k → 0 : stable (L21 : pôles).
- Une valeur propre λ = 1 avec les autres < 1 ⇒ convergence vers le vecteur propre associé : état stationnaire (Markov, PageRank).
- |λ| > 1 ⇒ divergence ; λ complexes ⇒ oscillations (rotation dans le plan propre).
- Fibonacci : A = [[1,1],[1,0]], λ = φ et −1/φ ; F_n ≈ φⁿ/√5.
Toutes les matrices ne sont pas diagonalisables (bloc de Jordan [[1,1],[0,1]]), mais les symétriques le sont toujours, en base orthonormée (théorème spectral) — et la SVD diagonalise « au sens large » n’importe quelle matrice, même rectangulaire. En pratique : eigh pour les symétriques (rapide, stable), eig sinon, svd pour le rang et les moindres carrés.
TP guidé
TP — Identification d’un robot et compression d’images (sur PC, 2 h 30)
- Données. Enregistrez (ou simulez avec bruit si vous n’avez pas de robot) 200 lignes
pwm_gauche, pwm_droite, vitesse_lineaire, vitesse_angulaireà partir des encodeurs (séance 27). CSV → NumPy (np.loadtxt). - Identification. Modèle linéaire (v, ω) = M · (pwm_g, pwm_d) + biais. Estimez M et le biais par
lstsq; affichez les résidus (histogramme : gaussiens ? un biais systématique révèle une non-linéarité, par exemple la zone morte des moteurs). Ajoutez des termes quadratiques et comparez les résidus. Conditionnement ? - Inverser le modèle. Pour une consigne (v, ω) voulue, calculez les PWM par
solve(M est 2×2). Vérifiez sur le robot : erreur en boucle ouverte. Ce modèle inverse est votre « cinématique inverse » calibrée (L21). - Compression par SVD. Chargez une image en niveaux de gris (
PIL.Image.open(...).convert("L"),np.asarray). SVD ; reconstruction aux rangs 5, 20, 50, 100 ; erreur relative ‖A − A_k‖_F / ‖A‖_F et taux de compression ; figure 2×2. Trouvez le rang pour une erreur < 5 %. - ACP sur des données réelles.
from sklearn.datasets import load_digits(1797 chiffres 8×8, 64 dimensions). Centrer, SVD, projeter sur 2 composantes, nuage coloré par chiffre. Variance expliquée cumulée : combien de composantes pour 90 % ? Reconstruisez un chiffre avec 10 composantes. - Livrable. Notebook ou script + figures +
RESULTATS.md: matrice M avec incertitudes, choix de modèle justifié par les résidus, rang de compression, nombre de composantes ACP.
Exercices
Exercices auto-corrigés — matrices et transformations
Exercice 1 — Transformations homogènes 2D
Écrivez rotation(theta), translation(dx, dy), mise_a_echelle(sx, sy) (3×3 homogènes) et appliquer(M, points) (points : tableau n×2 → n×2). Puis rotation_autour(theta, cx, cy) : rotation autour d’un centre quelconque, par composition.
Correction
def rotation(t): c, s = np.cos(t), np.sin(t); return np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])
def translation(dx, dy): return np.array([[1, 0, dx], [0, 1, dy], [0, 0, 1.]])
def mise_a_echelle(sx, sy): return np.diag([sx, sy, 1.])
def appliquer(M, P): H = np.column_stack([P, np.ones(len(P))]); return (H @ M.T)[:, :2]
def rotation_autour(t, cx, cy): return translation(cx, cy) @ rotation(t) @ translation(-cx, -cy)Exercice 2 — Gauss-Jordan et inverse
Implémentez inverser(A) par Gauss-Jordan avec pivot partiel sur la matrice augmentée [A | I] (sans np.linalg), qui lève ValueError si A est singulière. Vérifiez A·A⁻¹ = I.
Correction
def inverser(A):
n = len(A); M = np.hstack([A.astype(float), np.eye(n)])
for k in range(n):
p = k + np.argmax(np.abs(M[k:, k]))
if abs(M[p, k]) < 1e-12: raise ValueError("matrice singulière")
M[[k, p]] = M[[p, k]]; M[k] /= M[k, k]
for i in range(n):
if i != k: M[i] -= M[i, k] * M[k]
return M[:, n:]Exercices
Exercices auto-corrigés — moindres carrés et valeurs propres
Exercice 3 — Ajuster un cercle par moindres carrés
Des points bruités sur un cercle (un obstacle rond vu par un lidar). L’équation x² + y² + a x + b y + c = 0 est linéaire en (a, b, c). Écrivez ajuster_cercle(points) → (cx, cy, r) avec lstsq.
Correction
def ajuster_cercle(P):
x, y = P[:, 0], P[:, 1]
A = np.column_stack([x, y, np.ones(len(P))]); b = -(x**2 + y**2)
a, bb, c = np.linalg.lstsq(A, b, rcond=None)[0]
cx, cy = -a / 2, -bb / 2; return cx, cy, np.sqrt(cx**2 + cy**2 - c)Exercice 4 — Puissance itérée et état stationnaire
a) puissance_iteree(A, iters) renvoie (λ_max, vecteur propre normalisé). b) stationnaire(P) renvoie la distribution stationnaire d’une matrice de transition (lignes sommant à 1) par puissance itérée sur Pᵀ. c) Vérifiez sur la chaîne météo du module.
Correction
def puissance_iteree(A, iters=500):
x = np.ones(len(A))
for _ in range(iters): x = A @ x; x /= np.linalg.norm(x)
return float(x @ A @ x), x
def stationnaire(P):
pi = np.ones(len(P)) / len(P)
for _ in range(1000): pi = pi @ P
return pi / pi.sum()05 / Défis
Défi ★ — Solveur et vérificateur
Consigne
1) Générez 200 systèmes aléatoires 5×5 et comparez votre gauss à np.linalg.solve (écart max). 2) Trouvez une matrice pour laquelle Gauss sans pivot échoue (division par zéro) alors que le système a une solution. 3) Ajustez une parabole y = a + bx + cx² par moindres carrés sur 30 points bruités et tracez.
Correction (extraits)
np.random.seed(0); ecart = 0
for _ in range(200):
A = np.random.rand(5, 5) + np.eye(5); b = np.random.rand(5)
ecart = max(ecart, np.abs(gauss(A, b) - np.linalg.solve(A, b)).max())
print(ecart)
A = np.array([[0., 1.], [1., 0.]]); b = np.array([1., 2.]) # pivot nul en (0,0) mais solution (2, 1)
x = np.linspace(-2, 2, 30); y = 1 + 0.5 * x - 0.8 * x**2 + np.random.normal(0, 0.2, 30)
X = np.column_stack([np.ones(30), x, x**2]); c = np.linalg.lstsq(X, y, rcond=None)[0]; print(c.round(2))05 / Défis
Défi ★★ — Cinématique inverse par jacobienne
Consigne
Pour le bras à 2 segments, la position de la main p(a1, a2) est non linéaire. Calculez la jacobienne J = ∂p/∂(a1, a2) (2×2, par différences finies ou à la main), puis itérez la méthode de Newton : Δa = J⁻¹ (cible − p). Atteignez 5 cibles aléatoires accessibles en moins de 20 itérations chacune ; tracez la trajectoire de la main. Que se passe-t-il quand le bras est tendu (a2 = 0) ? Regardez le déterminant de J.
Correction
def jacobienne(a, h=1e-6):
J = np.zeros((2, 2))
for k in range(2):
d = np.zeros(2); d[k] = h
J[:, k] = (p(a + d) - p(a - d)) / (2 * h)
return J
def newton(cible, a=np.array([0.5, 0.5]), tol=1e-6):
for it in range(50):
e = cible - p(a)
if np.linalg.norm(e) < tol: return a, it
a = a + np.linalg.solve(jacobienne(a), e) # pas de inv !
return a, 50
np.random.seed(3)
for _ in range(5):
r = np.random.uniform(30, 130); t = np.random.uniform(0, np.pi)
cible = r * np.array([np.cos(t), np.sin(t)])
a, it = newton(cible); print(cible.round(1), "→ angles", np.degrees(a).round(1), "en", it, "itérations")
print("det J bras tendu :", np.linalg.det(jacobienne(np.array([0.3, 0.0]))).round(6))Quand det J → 0 (bras tendu ou replié), la jacobienne est singulière : une singularité cinématique. Le bras ne peut pas bouger dans certaines directions et Newton diverge ; les contrôleurs industriels utilisent une pseudo-inverse amortie (Levenberg-Marquardt). Ce défi contient tout un chapitre de robotique (module L21).
05 / Défis
Défi ★★★ — Esprit prépa : itération QR et laplacien de graphe
Consigne
1) Implémentez l’algorithme QR pour les valeurs propres d’une matrice symétrique : A₀ = A ; Aₖ = Q·R ⇒ Aₖ₊₁ = R·Q (avec np.linalg.qr). Montrez que la diagonale converge vers les valeurs propres et comparez à eig. 2) Construisez le laplacien L = D − A d’un graphe à deux communautés faiblement reliées (module L05). Calculez le vecteur propre de la 2e plus petite valeur propre (vecteur de Fiedler) : son signe partitionne le graphe. Vérifiez. 3) Expliquez pourquoi le nombre de valeurs propres nulles de L est le nombre de composantes connexes.
Correction
def qr_valeurs_propres(A, iters=200):
A = A.copy()
for _ in range(iters):
Q, R = np.linalg.qr(A); A = R @ Q
return np.sort(np.diag(A))
S = np.random.rand(6, 6); S = S + S.T
print(qr_valeurs_propres(S).round(4)); print(np.sort(np.linalg.eigvalsh(S)).round(4))
# Deux communautés de 6 sommets, 2 arêtes entre elles
n = 12; A = np.zeros((n, n)); rng = np.random.default_rng(0)
for grp in (range(0, 6), range(6, 12)):
for i in grp:
for j in grp:
if i < j and rng.random() < 0.7: A[i, j] = A[j, i] = 1
A[2, 8] = A[8, 2] = 1; A[5, 6] = A[6, 5] = 1
L = np.diag(A.sum(1)) - A
val, vec = np.linalg.eigh(L)
print("valeurs propres :", val.round(3))
fiedler = vec[:, 1]
print("partition :", (fiedler > 0).astype(int))xᵀLx = Σ_{(i,j) arête} (x_i − x_j)² : L mesure la « rugosité » d’une fonction sur le graphe. Le vecteur constant donne 0 ; sur k composantes, k vecteurs indicateurs indépendants donnent 0, et réciproquement une valeur nulle impose x constant sur chaque composante. Le vecteur de Fiedler minimise la rugosité sous contrainte d’orthogonalité à la constante : il « coupe » le graphe là où il y a le moins d’arêtes. C’est le spectral clustering, utilisé en vision, en bio-informatique et en analyse de réseaux.
06 / Vérification
Pour résoudre A·x = b, on écrit :
Deux questions supplémentaires
1. Pourquoi une rotation a-t-elle R⁻¹ = Rᵀ ? Ses colonnes sont orthonormées : RᵀR = I.
2. Combien de nombres pour stocker une image 1000×1000 en rang 20 ? 20 × (1000 + 1000 + 1) ≈ 40 000 au lieu d’un million : 25 fois moins.
Référence
Les mots à retenir
| Mot | Définition |
|---|---|
| Produit matriciel | C[i,j] = Σ A[i,k]·B[k,j] ; O(n³) ; non commutatif. |
| Orthogonale | Matrice dont l’inverse est la transposée (rotations, réflexions). |
| Coordonnées homogènes | (x, y, 1) : translations et rotations en une seule matrice. |
| Gauss / LU | Élimination avec pivot ; factorisation pour résoudre vite plusieurs b. |
| Moindres carrés | Minimiser ‖Xc − d‖² ; projection orthogonale ; lstsq. |
| Valeur propre | λ tel que A·v = λ·v. |
| SVD | A = UΣVᵀ ; meilleure approximation de rang k. |
| ACP | SVD des données centrées : axes de variance maximale. |
| Conditionnement | σ_max/σ_min : amplification des erreurs. |
| Jacobienne | Matrice des dérivées partielles ; linéarisation locale. |
| Laplacien | D − A ; ses vecteurs propres révèlent la structure d’un graphe. |
Pour continuer
Vous calculez avec des matrices
Module suivant : probabilités et statistiques computationnelles — simuler, estimer, tester, inférer : les fondations de l’apprentissage automatique et des filtres de capteurs.
À faire chez soi
- Regarder les 15 vidéos de « Essence of linear algebra » (3Blue1Brown) et refaire chaque exemple en NumPy.
- Implémenter la factorisation QR par Gram-Schmidt et la comparer à
np.linalg.qrsur une matrice mal conditionnée. - Faire les exercices 1-10 du chapitre « moindres carrés » de Strang.