LangueFR
Français
English
Retour au sommaire du cours

Mathématiques du ML : optimisation, algèbre linéaire, SVD et PCA

Presque tout l'apprentissage automatique se ramène à deux objets : un problème d'optimisation que l'on résout par descente de gradient, et des décompositions matricielles qui révèlent la structure des données. Ce module traite les deux depuis le problème qu'ils posent, avec une insistance particulière sur la SVD et la PCA — les deux outils qui reviennent partout, de la compression à la détection d'anomalies.

1. Apprendre, c'est minimiser un risque

On cherche une fonction \(f_\theta\), paramétrée par \(\theta\), qui prédit \(y\) à partir de \(x\). L'idéal serait de minimiser le risque, c'est-à-dire l'erreur moyenne sur la distribution réelle des données :

\[ R(\theta) = \mathbb{E}_{(x,y)\sim \mathcal{D}}\big[\ell(f_\theta(x), y)\big] \]

Mais cette distribution \(\mathcal{D}\) est inconnue : on n'observe qu'un échantillon. On minimise donc le risque empirique, la moyenne de l'erreur sur les données dont on dispose, complété d'un terme de régularisation qui pénalise la complexité :

\[ \hat{R}(\theta) = \frac{1}{n}\sum_{i=1}^{n} \ell\big(f_\theta(x_i), y_i\big) \;+\; \lambda\,\Omega(\theta) \]

Tout le vocabulaire du ML se lit dans cette formule, et il vaut la peine de s'y arrêter. La fonction de perte \(\ell\) encode ce que l'on considère comme une erreur — c'est un choix, pas une donnée. Le surapprentissage est exactement l'écart entre \(\hat R\), que l'on minimise, et \(R\), qui nous intéresse : un modèle peut atteindre un risque empirique nul en mémorisant les données sans rien avoir appris. La régularisation \(\Omega\) — typiquement \(\|\theta\|_2^2\) ou \(\|\theta\|_1\) — restreint l'espace des fonctions accessibles pour réduire cet écart ; le module 10 montre qu'elle équivaut à une croyance a priori sur l'ordre de grandeur des paramètres.

les pertes usuelles et ce qu'elles supposent
Quadratique   (ŷ - y)²            régression. Pénalise les grandes erreurs au CARRÉ :
                                  un point aberrant tire tout le modèle vers lui.
Absolue (MAE) |ŷ - y|             robuste aux aberrants, non dérivable en 0.
Huber         quadratique près    le compromis : quadratique au centre, linéaire
              de 0, linéaire loin  au-delà d'un seuil δ.
Entropie      -Σ y_k log(ŷ_k)     classification. Pénalise la CONFIANCE dans une
croisée                           erreur, pas seulement l'erreur.
Hinge         max(0, 1 - y·ŷ)     marge (SVM, module 14).
Pinball       asymétrique         régression sur un quantile (module 17).

Pourquoi ne pas optimiser directement le taux d'erreur ?

En classification, ce qui nous intéresse est souvent le taux de bonnes réponses. On ne peut pourtant pas l'optimiser directement : c'est une fonction en escalier, de gradient nul presque partout et indéfini aux sauts — la descente de gradient n'a rien à suivre. L'entropie croisée est une approximation continue et dérivable de cet objectif : c'est ce qu'on appelle une perte de substitution. Il faut garder cette distinction en tête, car elle explique qu'un modèle puisse améliorer sa perte sans améliorer sa métrique métier.

2. Descente de gradient : le mécanisme

Le gradient \(\nabla \hat R(\theta)\) est le vecteur des dérivées partielles : il pointe dans la direction de plus forte croissance de la fonction. Pour minimiser, on avance donc à l'opposé, d'un pas \(\eta\) appelé taux d'apprentissage :

\[ \theta_{t+1} = \theta_t - \eta\, \nabla \hat{R}(\theta_t) \]

L'image usuelle est celle d'un randonneur dans le brouillard : il ne voit pas la vallée, mais il sent la pente sous ses pieds et descend dans la direction la plus raide. Deux questions se posent immédiatement, et ce sont les seules qui comptent : quelle taille de pas ? et quand s'arrêter ?

Python — la descente de gradient, écrite à la main
import numpy as np

def descente(X, y, eta=0.01, n_iter=1000):
    """Régression linéaire par descente de gradient, sans bibliothèque."""
    n, p = X.shape
    theta = np.zeros(p)
    historique = []

    for t in range(n_iter):
        residus = X @ theta - y                 # erreur de prédiction
        perte = (residus ** 2).mean()           # risque empirique (MSE)
        gradient = 2 / n * X.T @ residus        # dérivée analytique du MSE
        theta -= eta * gradient                 # LE pas de descente
        historique.append(perte)

        if t > 0 and abs(historique[-2] - perte) < 1e-10:   # critère d'arrêt
            break
    return theta, historique
l'effet du taux d'apprentissage
η trop petit (1e-5)    la perte descend, mais il faudrait 100 000 itérations.
                       Symptôme : courbe presque plate, qui descend encore à la fin.

η bien réglé (1e-2)    décroissance rapide puis plateau. C'est ce qu'on veut.

η trop grand (1.0)     la perte OSCILLE, puis explose vers l'infini ou NaN.
                       Symptôme : perte qui remonte, ou nan au bout de 3 itérations.

  perte │╲                      perte │╲    ╱╲                perte │╲
        │ ╲___                        │ ╲__╱  ╲                     │ ╲
        │     ╲____                   │        ╲_                   │  ╲___________
        └──────────── trop petit      └──────────── trop grand      └──────── correct

Deux garde-fous théoriques valent d'être connus. Si \(\hat R\) est convexe — une seule vallée, pas de minimum local — et si son gradient est \(L\)-lipschitzien, la méthode converge vers le minimum global pour tout \(0 \lt \eta \lt 2/L\), où \(L\) est la plus grande courbure de la fonction. En pratique on ne connaît pas \(L\), d'où la recherche empirique du taux (module 16). Et en deep learning, la fonction n'est pas convexe : on ne cherche plus le minimum global, on se contente d'un bon minimum local — ce qui, empiriquement, suffit largement.

3. Pourquoi ça converge lentement

Pour une fonction quadratique de matrice hessienne \(H\) (la matrice des dérivées secondes, qui décrit la courbure), la vitesse de convergence dépend du conditionnement \(\kappa = \lambda_{\max}/\lambda_{\min}\), rapport de la plus grande à la plus petite valeur propre. L'erreur décroît comme :

\[ \left(\frac{\kappa - 1}{\kappa + 1}\right)^t \]

Pour \(\kappa = 10\), le facteur vaut 0,82 par itération : la convergence est rapide. Pour \(\kappa = 10^4\), il vaut 0,9998 : il faut des dizaines de milliers d'itérations pour progresser.

la géométrie du problème
  κ ≈ 1 : lignes de niveau CIRCULAIRES     κ ≫ 1 : vallée ALLONGÉE
  le gradient pointe vers le minimum        le gradient pointe vers les PAROIS

        ╭───────╮                          ╭─────────────────────────╮
      ╭─┤   ·───┼─► minimum              ╭─┤ ·╲                      │
      │ ╰───────╯                        │ │   ╲╱╲╱╲╱╲──► minimum    │
      ╰─────────╯                        │ ╰──────────────────────── ╯
                                         ╰───────────────────────────╯
      trajet direct                       zigzag : on rebondit d'une paroi à l'autre
                                          et on avance très peu dans le bon sens
La conséquence pratique la plus importante de tout le module. Des variables d'échelles très différentes — une surface en m² (valeurs autour de 70), un nombre de pièces (autour de 3), un prix en euros (autour de 250 000) — produisent un conditionnement catastrophique : la fonction de coût est une vallée extrêmement allongée, et la descente de gradient y rampe. Standardiser les variables (soustraire la moyenne, diviser par l'écart type) rend les lignes de niveau presque circulaires et peut accélérer la convergence d'un facteur mille. Ce n'est pas une coquetterie statistique : c'est ce qui rend l'optimisation possible. C'est aussi pourquoi la batch normalization aide tant en deep learning (module 16) — elle re-conditionne le problème à chaque couche.

4. SGD, momentum, Adam

Le problème du coût

Calculer \(\nabla \hat R\) demande de parcourir les \(n\) exemples. Avec un million d'images, un seul pas devient hors de prix — et il en faut des milliers. La descente stochastique (SGD) estime le gradient sur un petit lot tiré au hasard :

\[ \nabla \hat{R}(\theta) \;\approx\; \frac{1}{|B|}\sum_{i \in B} \nabla \ell\big(f_\theta(x_i), y_i\big) \]

L'estimation est bruitée mais non biaisée — elle vise juste en moyenne — et surtout mille fois moins coûteuse. On fait donc mille pas approximatifs au lieu d'un pas exact, un échange presque toujours gagnant. Le bruit a même une vertu : il aide à s'échapper des points selles, ces zones plates qui ne sont ni des minima ni des maxima et qui sont omniprésentes en grande dimension — bien plus que les minima locaux, contrairement à une idée reçue.

Momentum : lisser la trajectoire

Pour traverser la vallée allongée de la section précédente sans zigzaguer, on accumule une vitesse, comme une bille lourde qui ne change pas de direction à chaque bosse :

\[ v_{t+1} = \beta v_t + \nabla \hat R(\theta_t), \qquad \theta_{t+1} = \theta_t - \eta\, v_{t+1} \]

Avec \(\beta = 0{,}9\), le pas effectif est amplifié d'un facteur \(1/(1-\beta) = 10\) dans les directions où le gradient reste constant, tandis que les composantes qui oscillent s'annulent d'un pas sur l'autre. La variante de Nesterov évalue le gradient au point extrapolé plutôt qu'au point courant, ce qui la rend un peu plus stable.

Adam : un taux par paramètre

L'idée des méthodes adaptatives : les paramètres n'ont pas tous la même échelle de gradient, donc pas la même taille de pas idéale. Adam combine le momentum (moment d'ordre 1) et une normalisation par l'amplitude typique du gradient (moment d'ordre 2) :

\[ m_t = \beta_1 m_{t-1} + (1-\beta_1)g_t, \qquad v_t = \beta_2 v_{t-1} + (1-\beta_2)g_t^2 \]
\[ \hat m_t = \frac{m_t}{1-\beta_1^t}, \quad \hat v_t = \frac{v_t}{1-\beta_2^t}, \qquad \theta_{t+1} = \theta_t - \eta\,\frac{\hat m_t}{\sqrt{\hat v_t} + \varepsilon} \]

Le rapport \(\hat m_t / \sqrt{\hat v_t}\) est une sorte de « rapport signal sur bruit » du gradient : un paramètre dont le gradient est constamment dans le même sens avance vite, un paramètre dont le gradient oscille avance lentement. Les corrections de biais \(\hat m, \hat v\) compensent l'initialisation à zéro des moments, qui sous-estimerait fortement les premiers pas. Valeurs usuelles : \(\beta_1 = 0{,}9\), \(\beta_2 = 0{,}999\), \(\varepsilon = 10^{-8}\).

Python — Adam en douze lignes, pour voir ce qu'il fait
def adam(gradient_fn, theta, eta=1e-3, b1=0.9, b2=0.999, eps=1e-8, n_iter=1000):
    m = np.zeros_like(theta)          # moyenne mobile du gradient
    v = np.zeros_like(theta)          # moyenne mobile du gradient AU CARRÉ

    for t in range(1, n_iter + 1):
        g = gradient_fn(theta)
        m = b1 * m + (1 - b1) * g
        v = b2 * v + (1 - b2) * g ** 2
        m_corr = m / (1 - b1 ** t)    # correction de biais : sans elle, les premiers
        v_corr = v / (1 - b2 ** t)    # pas seraient beaucoup trop petits
        theta -= eta * m_corr / (np.sqrt(v_corr) + eps)
    return theta

AdamW corrige un défaut subtil mais réel : dans Adam, la pénalité \(L^2\) ajoutée à la perte est elle aussi divisée par \(\sqrt{\hat v_t}\), si bien que la régularisation n'est plus uniforme entre paramètres. AdamW découple la décroissance des poids du gradient adaptatif ; c'est aujourd'hui le choix par défaut pour les transformers.

quel optimiseur choisir
SGD + momentum   converge souvent vers de MEILLEURS minima (meilleure généralisation),
                 mais demande un réglage soigneux du taux et d'un planificateur.
                 Référence en vision, pour des CNN entraînés de zéro.
Adam / AdamW     robuste, marche presque tout de suite. Défaut en NLP, transformers,
                 et sur tout problème aux gradients hétérogènes ou creux.
L-BFGS           second ordre approché : excellent sur les petits problèmes lisses et
                 déterministes (peu de paramètres, pas de mini-lots). Inutilisable en
                 deep learning : mémoire et incompatibilité avec le bruit stochastique.

Planification et écrêtage

Un taux constant finit par empêcher la convergence fine : on tourne autour du minimum sans jamais s'en approcher. On le fait donc décroître. Le schéma cosine annealing avec warmup est devenu standard : quelques centaines de pas de montée linéaire — le temps que les statistiques des moments adaptatifs se stabilisent —, puis une décroissance en cosinus jusqu'à presque zéro.

S'y ajoute le gradient clipping, qui borne la norme du gradient : \(g \leftarrow \min(1, c/\|g\|)\,g\). Un seul lot atypique peut produire un gradient énorme qui projette les paramètres dans une zone absurde et détruit des heures d'entraînement ; l'écrêtage garde la direction mais limite la longueur du pas. Indispensable sur les RNN et les transformers.

Python — en pratique, avec PyTorch
opt = torch.optim.AdamW(modele.parameters(), lr=3e-4, weight_decay=0.01)
sched = torch.optim.lr_scheduler.OneCycleLR(opt, max_lr=3e-4,
                                            total_steps=n_epoques * len(chargeur))
for xb, yb in chargeur:
    opt.zero_grad(set_to_none=True)
    perte_fn(modele(xb), yb).backward()
    torch.nn.utils.clip_grad_norm_(modele.parameters(), max_norm=1.0)   # écrêtage
    opt.step()
    sched.step()                                    # le taux évolue à CHAQUE pas

5. Algèbre linéaire : résoudre sans inverser

La règle la plus utile de tout ce module : on n'inverse jamais une matrice. Pour résoudre \(Ax = b\), calculer \(A^{-1}\) puis multiplier coûte environ deux fois plus cher et est numériquement bien moins stable que de résoudre directement le système. L'inverse est un objet mathématique commode, pas un objet numérique.

Python
import numpy as np
from scipy.linalg import cho_factor, cho_solve

# À NE PAS FAIRE
x = np.linalg.inv(A) @ b

# À FAIRE : factorisation LU avec pivot
x = np.linalg.solve(A, b)

# Si A est symétrique définie positive (matrice de covariance, noyau de GP) :
# Cholesky, deux fois plus rapide encore
c, low = cho_factor(A)
x = cho_solve((c, low), b)

# Si le système est surdéterminé (moindres carrés) : QR, sans jamais former AᵀA
beta, residus, rang, valeurs_sing = np.linalg.lstsq(A, b, rcond=None)
les décompositions à connaître
LU        A = PLU     systèmes carrés généraux               O(n³/3)
Cholesky  A = LLᵀ     A symétrique définie positive          O(n³/6) — la plus rapide
QR        A = QR      moindres carrés, orthogonalisation     O(2mn²)
Spectrale A = QΛQᵀ    A symétrique : axes et valeurs propres réels
SVD       A = UΣVᵀ    TOUTE matrice, même rectangulaire      la plus informative

Le cas des moindres carrés

La solution analytique \(\hat\beta = (X^\top X)^{-1} X^\top y\) est correcte en mathématiques et dangereuse en calcul : former \(X^\top X\) élève le conditionnement au carré, puisque \(\kappa(X^\top X) = \kappa(X)^2\). Avec des variables corrélées — donc un \(\kappa(X)\) déjà élevé —, la précision s'effondre, et l'on peut obtenir des coefficients aberrants sur des données parfaitement propres. C'est pourquoi lstsq passe par une QR ou une SVD, et pourquoi sklearn.linear_model.LinearRegression appelle scipy.linalg.lstsq plutôt que la formule fermée.

6. La SVD, en détail

La décomposition en valeurs singulières s'applique à n'importe quelle matrice réelle \(A\) de taille \(m \times n\) — carrée ou non, inversible ou non :

\[ A = U \Sigma V^\top, \qquad U \in \mathbb{R}^{m\times m},\; \Sigma \in \mathbb{R}^{m\times n},\; V \in \mathbb{R}^{n\times n} \]

avec \(U\) et \(V\) orthogonales (\(U^\top U = I\) : leurs colonnes forment une base orthonormée) et \(\Sigma\) diagonale à coefficients positifs décroissants \(\sigma_1 \geq \sigma_2 \geq \dots \geq 0\), les valeurs singulières.

Intuition géométrique

Toute application linéaire, aussi compliquée soit-elle, se décompose en trois opérations élémentaires : une rotation (\(V^\top\)), une dilatation selon les axes (\(\Sigma\)), une seconde rotation (\(U\)). Autrement dit, l'image de la sphère unité par \(A\) est toujours un ellipsoïde : les \(\sigma_i\) sont les longueurs de ses demi-axes, et les colonnes de \(U\) leurs directions. Une valeur singulière quasi nulle signifie que \(A\) écrase complètement une direction de l'espace — c'est la signature d'une redondance dans les données.

Liens utiles

  • \(A^\top A = V \Sigma^\top\Sigma V^\top\) : les \(\sigma_i^2\) sont les valeurs propres de \(A^\top A\), et \(V\) en donne les vecteurs propres. Calculer la SVD directement reste plus stable que diagonaliser \(A^\top A\), pour la raison de conditionnement vue plus haut.
  • Le rang de \(A\) est le nombre de \(\sigma_i\) non nuls ; numériquement, le nombre de \(\sigma_i\) au-dessus d'un seuil.
  • Le conditionnement vaut \(\kappa = \sigma_1/\sigma_r\) : la SVD le donne donc directement.
  • La pseudo-inverse \(A^{+} = V\Sigma^{+}U^\top\) (où \(\Sigma^+\) inverse les \(\sigma_i\) non nuls et laisse les autres à zéro) fournit la solution de norme minimale des moindres carrés, y compris pour un système sous-déterminé.

Approximation de rang faible : Eckart–Young

En ne gardant que les \(k\) premiers termes de la somme :

\[ A_k = \sum_{i=1}^{k}\sigma_i u_i v_i^\top \]

on obtient la meilleure approximation de rang \(k\) qui existe, au sens des normes de Frobenius et spectrale. Aucune autre matrice de rang \(k\), obtenue par quelque méthode que ce soit, ne fait mieux — et l'erreur commise est connue exactement :

\[ \|A - A_k\|_F^2 = \sum_{i \gt k} \sigma_i^2 \]

Ce théorème fonde la compression par SVD, la PCA, le débruitage et les systèmes de recommandation par factorisation matricielle (module 23).

Python — compression d'image, pour voir le théorème à l'œuvre
import numpy as np, matplotlib.pyplot as plt

img = plt.imread("photo.png").mean(axis=2)          # niveaux de gris, (m, n)
U, s, Vt = np.linalg.svd(img, full_matrices=False)

# combien de composantes pour conserver 95 % de "l'énergie" ?
energie = np.cumsum(s ** 2) / np.sum(s ** 2)
k = int(np.searchsorted(energie, 0.95) + 1)

approx = (U[:, :k] * s[:k]) @ Vt[:k]                # reconstruction de rang k
taux = k * (img.shape[0] + img.shape[1] + 1) / img.size
print(f"k={k} : {taux:.1%} des coefficients d'origine, 95 % de l'énergie conservée")
# une photo 1024×1024 se reconstruit souvent avec k ≈ 60, soit 12 % des données

Coût et passage à l'échelle

La SVD complète coûte \(O(\min(mn^2, m^2n))\) — impraticable sur une grande matrice. Deux issues : la SVD tronquée par méthode de Lanczos (scipy.sparse.linalg.svds), efficace sur les matrices creuses, et surtout la SVD randomisée (la fonction randomized_svd de sklearn.utils.extmath), qui projette d'abord les données sur un sous-espace aléatoire de dimension \(k+p\) et coûte \(O(mnk)\). Sur une matrice 100 000 × 10 000 dont on veut 50 composantes, c'est la différence entre quelques secondes et plusieurs heures. L'idée sous-jacente est remarquable : une projection aléatoire préserve approximativement les distances (lemme de Johnson-Lindenstrauss), donc capture l'essentiel du sous-espace dominant.

Où la SVD apparaît

  • PCA — section suivante : c'est littéralement une SVD des données centrées ;
  • Ridge — la régularisation s'y lit comme un filtrage spectral (voir plus bas) ;
  • LSA — réduction d'une matrice terme-document, ancêtre des embeddings (module 22) ;
  • Recommandation — factorisation d'une matrice utilisateurs × items très creuse (module 23) ;
  • Débruitage et détection d'anomalies — le signal se concentre dans les grandes valeurs singulières, le bruit se répartit sur les petites ;
  • Réduction de modèle — la POD des simulations physiques est une SVD de champs (module 21) ;
  • LoRA — l'adaptation de LLM par matrices de rang faible repose sur la même intuition (module 22).

Ridge vu par la SVD

En écrivant \(X = U\Sigma V^\top\), la prédiction de la régression ridge devient :

\[ \hat{y}_{\text{ridge}} = \sum_{i=1}^{p} u_i \frac{\sigma_i^2}{\sigma_i^2 + \lambda}\, u_i^\top y \]

La lecture est limpide : ridge projette \(y\) sur les directions principales et rétrécit chacune d'un facteur \(\sigma_i^2/(\sigma_i^2+\lambda)\), compris entre 0 et 1. Les directions de forte variance (\(\sigma_i^2 \gg \lambda\)) sont conservées presque intactes ; celles de faible variance — précisément celles où l'estimation est instable — sont écrasées. La régularisation n'est donc pas un bricolage numérique : c'est un filtrage spectral qui atténue ce qui est mal déterminé.

7. La PCA, en détail

L'analyse en composantes principales cherche un système d'axes orthogonaux, ordonnés, sur lesquels projeter les données en perdant le moins d'information possible.

Deux formulations équivalentes

Variance maximale. On cherche la direction unitaire \(w\) qui maximise la variance des projections. Avec \(X\) centré et \(C = \frac{1}{n-1}X^\top X\) la matrice de covariance :

\[ \max_{\|w\|=1}\; \operatorname{Var}(Xw) = \max_{\|w\|=1}\; w^\top C\, w \]

Le lagrangien \(w^\top C w - \lambda(w^\top w - 1)\), dérivé et annulé, donne \(Cw = \lambda w\) : les directions principales sont les vecteurs propres de la covariance, et la variance capturée par chacune est sa valeur propre. On itère en imposant l'orthogonalité aux directions déjà retenues.

Erreur de reconstruction minimale. On cherche le sous-espace de dimension \(k\) minimisant \(\sum_i \|x_i - \Pi_k x_i\|^2\), où \(\Pi_k\) est la projection sur ce sous-espace. C'est exactement le problème d'Eckart–Young : la solution est donnée par les \(k\) premiers vecteurs singuliers. Les deux formulations coïncident — maximiser la variance conservée, c'est minimiser ce qu'on jette.

Le lien avec la SVD

Si \(X\) (centré) se décompose en \(U\Sigma V^\top\), alors \(C = \frac{1}{n-1}V\Sigma^2V^\top\). Donc :

  • les axes principaux sont les colonnes de \(V\) (les loadings) ;
  • les composantes principales (coordonnées projetées, ou scores) valent \(XV = U\Sigma\) ;
  • la variance expliquée par l'axe \(i\) vaut \(\sigma_i^2/(n-1)\).

C'est pourquoi toutes les implémentations sérieuses passent par la SVD de \(X\) plutôt que par la diagonalisation de \(X^\top X\) : même résultat, bien meilleure stabilité numérique.

Python — PCA à la main, puis avec scikit-learn
import numpy as np
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

X = np.random.randn(500, 20) @ np.random.randn(20, 20)   # données corrélées

# ── version manuelle ──
Xs = StandardScaler().fit_transform(X)          # centrer ET réduire (voir encadré)
U, s, Vt = np.linalg.svd(Xs, full_matrices=False)

scores = U * s                                   # = Xs @ Vt.T : les composantes
var_expliquee = s ** 2 / np.sum(s ** 2)
print(np.cumsum(var_expliquee)[:5])              # part de variance cumulée

# reconstruction avec k composantes, et erreur associée
k = 5
X_reconstruit = scores[:, :k] @ Vt[:k]
erreur = np.linalg.norm(Xs - X_reconstruit) ** 2 / np.linalg.norm(Xs) ** 2
print(f"erreur relative avec {k} composantes : {erreur:.1%}")

# ── version scikit-learn (identique, au signe des axes près) ──
pca = PCA(n_components=0.95)                     # garder 95 % de la variance
Z = pca.fit_transform(Xs)
print(Z.shape, pca.explained_variance_ratio_.sum())
print(pca.components_[0])                        # le premier axe, en fonction des variables
Centrer et réduire, ou rien ne va. La PCA maximise une variance : une variable exprimée en mètres et une autre en millimètres n'ont pas le même poids, et l'axe principal suivra simplement l'unité la plus grande. Sur des variables d'unités hétérogènes, la standardisation est obligatoire — cela revient à faire la PCA sur la matrice de corrélation plutôt que de covariance. Sur des variables déjà homogènes — des pixels, les longueurs d'onde d'un spectre —, centrer seulement peut être préférable, car réduire amplifierait le bruit des canaux peu variables.

Combien de composantes garder ?

  • Variance cumulée — le plus courant : garder 90 ou 95 % de la variance ;
  • Coude du scree plot — la rupture de pente du spectre des \(\sigma_i^2\), là où les composantes suivantes n'apportent presque plus rien ;
  • Critère de Kaiser — sur données standardisées, garder les valeurs propres supérieures à 1 : une composante doit expliquer plus qu'une variable d'origine ; grossier mais commode ;
  • Validation croisée — si la PCA sert d'étape de prétraitement, le nombre de composantes est un hyperparamètre comme un autre, à optimiser sur la performance finale.

Interpréter, et ne pas sur-interpréter

Les loadings (colonnes de \(V\)) disent comment chaque variable d'origine contribue à chaque axe : ils permettent parfois de nommer un axe — « taille globale », « contraste chaud/froid ». Mais une composante principale reste une combinaison linéaire de toutes les variables : elle n'a aucune raison d'avoir un sens physique, et le signe des axes est arbitraire (rien ne distingue \(w\) de \(-w\)). Pour une décomposition interprétable à coefficients positifs, la NMF est souvent plus parlante ; pour séparer des sources indépendantes, c'est l'ICA.

Usages, limites et variantes

PCA : à quoi ça sert vraiment
Décorréler des variables     les composantes sont orthogonales par construction
Compresser / débruiter       le bruit se disperse dans les petites valeurs propres
Visualiser en 2D             projection sur les 2 axes de plus forte variance
Accélérer un modèle          moins de dimensions = moins de calcul
Détecter des anomalies       erreur de reconstruction élevée = point atypique
Traiter la colinéarité       évite l'explosion de variance des coefficients

Limites. La PCA est linéaire : elle ne capture pas une variété courbe (un rouleau enroulé sur lui-même). Elle est non supervisée : rien ne garantit que les axes de forte variance soient ceux qui prédisent \(y\) — on peut parfaitement jeter la direction la plus informative, ce que corrigent la PLS ou une LDA en classification. Elle est sensible aux valeurs aberrantes, puisqu'une variance se calcule au carré. Et elle détruit l'interprétabilité des variables d'origine.

Variantes utiles : IncrementalPCA (par lots, pour des données hors mémoire), la version randomisée (par défaut dans scikit-learn dès que la matrice est grande), la kernel PCA (version non linéaire par astuce du noyau, module 14), la sparse PCA (axes à peu de variables non nulles, donc lisibles), et pour la visualisation pure, UMAP ou t-SNE — non linéaires, excellents visuellement, mais non paramétriques, non reproductibles d'une exécution à l'autre, et dont les distances entre groupes ne veulent rien dire.

PCA et fuite de données. La PCA doit être ajustée sur le pli d'entraînement seulement. L'ajuster sur l'ensemble des données avant la validation croisée fait entrer l'information du jeu de test dans la définition des axes : le score obtenu est optimiste, et l'écart n'apparaît qu'en production. La seule protection fiable est de la placer dans un Pipeline (module 3).

8. Métriques d'évaluation

Une métrique traduit un objectif métier en un nombre à optimiser. Se tromper de métrique, c'est optimiser efficacement la mauvaise chose — et c'est irrattrapable par la suite.

Régression

métriques de régression
MSE    moyenne des carrés d'erreur    pénalise fortement les grandes erreurs
RMSE   racine du MSE                  même unité que la cible : interprétable
MAE    moyenne des écarts absolus     robuste aux aberrants ; erreur "typique"
MAPE   erreur relative en %           parlante, mais explose si y proche de 0
                                      et pénalise plus les surestimations
R²     1 - SSE/SST                    part de variance expliquée ; peut être NÉGATIF
                                      si le modèle fait pire que la moyenne

RMSE et MAE ne répondent pas à la même question, et ce n'est pas un détail : minimiser le RMSE conduit à prédire la moyenne conditionnelle, minimiser le MAE la médiane conditionnelle. Sur des données à queue lourde — des prix, des durées — les deux modèles optimaux diffèrent réellement, et le choix doit venir de l'usage.

Classification

Tout part de la matrice de confusion, qui croise vérité et prédiction :

matrice de confusion
                    prédit positif      prédit négatif
  réel positif      VP (vrai positif)   FN (faux négatif) ← on l'a raté
  réel négatif      FP (faux positif)   VN (vrai négatif)
                    ↑ fausse alerte
\[ \text{Précision} = \frac{VP}{VP+FP}, \qquad \text{Rappel} = \frac{VP}{VP+FN}, \qquad F_1 = 2\,\frac{P \cdot R}{P + R} \]

La précision répond à « parmi mes alertes, combien sont justes ? », le rappel à « parmi les cas réels, combien ai-je attrapés ? ». Les deux s'opposent par le seuil de décision : le baisser augmente le rappel et dégrade la précision. Le \(F_1\) est leur moyenne harmonique, qui pénalise le déséquilibre entre les deux.

Python — les métriques en pratique
from sklearn.metrics import (classification_report, confusion_matrix,
                             roc_auc_score, average_precision_score,
                             log_loss, brier_score_loss)

p = modele.predict_proba(X_test)[:, 1]
y_pred = (p >= 0.5).astype(int)

print(confusion_matrix(y_test, y_pred))
print(classification_report(y_test, y_pred, digits=3))   # précision, rappel, F1 par classe
print("ROC-AUC :", roc_auc_score(y_test, p))
print("PR-AUC  :", average_precision_score(y_test, p))   # à préférer en déséquilibre
print("log-loss:", log_loss(y_test, p))                  # évalue les PROBABILITÉS
print("Brier   :", brier_score_loss(y_test, p))          # calibration

ROC-AUC ou PR-AUC ?

L'AUC-ROC mesure la probabilité qu'un positif tiré au hasard soit mieux classé qu'un négatif. Son défaut : le taux de faux positifs \(FP/(FP+VN)\) a un dénominateur énorme quand les négatifs dominent, si bien qu'une AUC de 0,95 peut masquer une précision de 3 % sur un problème à 0,1 % de positifs. En classe très déséquilibrée — fraude, défaut, maladie rare — la courbe précision-rappel est la bonne lecture, car ses deux axes ne dépendent que des positifs et des alertes.

Deux métriques souvent oubliées et pourtant essentielles : la log-loss \(-\frac{1}{n}\sum_i [y_i\log p_i + (1-y_i)\log(1-p_i)]\), qui évalue les probabilités et non les décisions, et le score de Brier (erreur quadratique sur les probabilités), qui mesure la calibration. Un modèle peut parfaitement ordonner les cas — bonne AUC — tout en annonçant des probabilités fausses, ce qui rend inexploitable toute décision fondée sur un coût attendu (module 24). On corrige par calibration isotonique ou de Platt (module 14).

Enfin, l'accuracy est une métrique piégeuse : sur un problème à 99 % de négatifs, prédire toujours « négatif » donne 99 % d'accuracy et un modèle sans aucune valeur. La bonne démarche part du coût métier : si un faux négatif coûte cent fois un faux positif, ce rapport doit apparaître dans le choix du seuil — c'est l'objet du module 24.

Ce qu'il faut retenir du module

  • Apprendre = minimiser un risque empirique régularisé ; la perte encode ce qu'on appelle une erreur, et c'est un choix.
  • Le taux d'apprentissage est l'hyperparamètre critique : trop grand, ça diverge ; trop petit, ça rampe.
  • Le conditionnement gouverne la vitesse de convergence — d'où l'obligation de standardiser les variables.
  • SGD échange l'exactitude du gradient contre le nombre de pas ; momentum lisse, Adam adapte le pas par paramètre.
  • AdamW par défaut ; SGD + momentum quand on peut se permettre de régler finement.
  • Ne jamais inverser une matrice : solve, Cholesky, QR, SVD.
  • SVD = rotation · dilatation · rotation, et meilleure approximation de rang \(k\) (Eckart–Young) ; elle éclaire aussi ridge comme un filtrage spectral.
  • PCA = SVD des données centrées-réduites ; à ajuster dans un pipeline, linéaire, non supervisée, sensible aux aberrants.
  • Choisir la métrique à partir du coût métier ; PR-AUC en classes déséquilibrées ; surveiller la calibration.

OptimisationAdamAlgèbre linéaireSVDPCAMétriques