Aller au contenu

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 :

\[ \vec{v} \cdot \vec{w} = v_0 w_0 + v_1 w_1 + \cdots + v_{n-1} w_{n-1} = \sum_i v_i w_i \]

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 :

\[ \text{cos\_sim}(\vec{v}, \vec{w}) = \frac{\vec{v} \cdot \vec{w}}{\lVert v \rVert \times \lVert w \rVert} \]

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".

\[ \vec{e_1} = \begin{bmatrix} 1 \\ 0 \\ 0 \end{bmatrix}, \quad \vec{e_2} = \begin{bmatrix} 0 \\ 1 \\ 0 \end{bmatrix}, \quad \vec{e_3} = \begin{bmatrix} 0 \\ 0 \\ 1 \end{bmatrix} \]

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 = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

C = A + B    # [[6, 8], [10, 12]]
D = 3 * A    # [[3, 6], [9, 12]]
# 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\) :

\[ 2x + 3y = 7 \quad \Leftrightarrow \quad \begin{bmatrix} 2 & 3 \\ 1 & -4 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} 7 \\ 2 \end{bmatrix} \]

É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 :

\[ \det(A - \lambda I) = 0 \]

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 :

\[ \vec{r} = d \times M \times \vec{r} + \frac{1-d}{n} \times \vec{1} \]

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 :

\[ A = U \times \Sigma \times V^T \]
  • \(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