← Retour au portfolio RSS

· 13 min de lecture

M1 · Vecteurs, produit scalaire et normes

La géométrie derrière la similarité, les distances et la régularisation : le vecteur comme objet central du ML.

Mastering AI · Required Maths · Semaine 1 (algèbre linéaire) · Partie 1

Objectif de l’étape

En ML, tout est vecteur : un exemple, une ligne de features, les poids d’un modèle, un embedding, un gradient. Trois opérations reviennent sans cesse : additionner, mesurer une longueur (la norme) et comparer deux directions (le produit scalaire).

À la fin de cet article, tu sauras :

  • lire un vecteur à la fois comme un point et comme une flèche ;
  • interpréter un produit scalaire comme une projection et une mesure d’alignement ;
  • choisir entre les normes L 1 L_1 , L 2 L_2 et L ∞ L_\infty , et entre distance euclidienne et similarité cosinus ;
  • reconnaître ces objets dans la régression linéaire, le kNN, la régularisation et l’attention.

C’est un rappel : on va vite sur les définitions et on insiste sur l’intuition géométrique, qui sert ensuite partout.


Notions clés

1. Un vecteur : un point et une flèche

Intuition. Une liste de n n nombres, vue de deux façons : un point dans un espace à n n dimensions, ou une flèche depuis l’origine (une direction et une longueur).

x = ( x 1 , x 2 , … , x n ) ∈ ℝ n x = (x_1, x_2, \dots, x_n) \in \mathbb{R}^n

  • Une ligne de ton dataset est un point de ℝ n \mathbb{R}^n , avec n n le nombre de features. Les 9 features de la partie 14 font de chaque (région, instant) un point de ℝ 9 \mathbb{R}^9 .
  • Les poids w w d’un modèle linéaire sont une flèche dans ce même espace.

Deux opérations de base, qui se font composante par composante :

  • addition : u + v = ( u 1 + v 1 , … , u n + v n ) u + v = (u_1 + v_1, \dots, u_n + v_n) , on met les flèches bout à bout ;
  • multiplication par un scalaire : λ u \lambda u , on étire ou on retourne la flèche.

Ces deux opérations sont exactement ce que fait une mise à jour de descente de gradient, w ← w − η ∇ L w \leftarrow w - \eta \nabla L (M10).

2. Le produit scalaire : mesurer l’alignement

Intuition. Le produit scalaire répond à la question « à quel point u u va-t-il dans la même direction que v v ? ». Grand et positif si elles sont alignées, nul si elles sont perpendiculaires, négatif si elles s’opposent.

Définition algébrique, celle qu’on calcule :

⟨ u , v ⟩ = u ⊤ v = ∑ i = 1 n u i v i \langle u, v \rangle = u^\top v = \sum_{i=1}^{n} u_i v_i

Définition géométrique, celle qu’on comprend :

⟨ u , v ⟩ = ‖ u ‖ 2 ‖ v ‖ 2 cos θ \langle u, v \rangle = \|u\|_2 \, \|v\|_2 \cos\theta

où θ \theta est l’angle entre les deux flèches. Les deux définitions sont égales, et c’est ce qui rend le produit scalaire si utile : un calcul bête (multiplier et sommer) donne une information géométrique (un angle).

cos θ \cos\theta Signe de ⟨ u , v ⟩ \langle u, v\rangle Interprétation
1 1 maximal même direction
0 0 nul orthogonaux : aucune information commune
− 1 -1 minimal directions opposées

Projection. La part de u u qui va dans la direction de v v :

p r o j v ( u ) = ⟨ u , v ⟩ ⟨ v , v ⟩ v \mathrm{proj}_v(u) = \frac{\langle u, v\rangle}{\langle v, v\rangle}\, v

Projection de u = (3, 4) sur la direction de v = (4, 0) : la projection vaut (3, 0) et le résidu (0, 4) est orthogonal à v. θ est l’angle entre u et v.

Ce qui reste, u − p r o j v ( u ) u - \mathrm{proj}_v(u) , est orthogonal à v v . Sur la figure, avec u = ( 3 , 4 ) u = (3, 4) et v = ( 4 , 0 ) v = (4, 0) , la projection vaut ( 3 , 0 ) (3, 0) et le résidu ( 0 , 4 ) (0, 4) est bien vertical. On reverra cette décomposition « partie expliquée + résidu orthogonal » dans les moindres carrés (M2) et dans la PCA (M3).

Où on le retrouve en ML :

  • Modèle linéaire : y ^ = w ⊤ x + b \hat y = w^\top x + b est un produit scalaire. Le modèle mesure à quel point l’exemple x x est aligné avec la direction w w (partie 16).
  • Neurone : chaque neurone calcule w ⊤ x + b w^\top x + b avant son activation (partie 21).
  • Attention : le score entre une requête q q et une clé k k est q ⊤ k q^\top k (partie 23).

3. Les normes : mesurer une longueur

Intuition. Une norme mesure la « taille » d’un vecteur. Il existe plusieurs façons raisonnables de le faire, selon ce qu’on veut pénaliser.

‖ x ‖ 1 = ∑ i | x i | ‖ x ‖ 2 = ∑ i x i 2 = ⟨ x , x ⟩ ‖ x ‖ ∞ = max i | x i | \begin{aligned} \|x\|_1 &= \sum_i |x_i| \\ \|x\|_2 &= \sqrt{\sum_i x_i^2} = \sqrt{\langle x, x \rangle} \\ \|x\|_\infty &= \max_i |x_i| \end{aligned}
  • L 2 L_2 (euclidienne) : la longueur « à vol d’oiseau ». Elle pénalise fortement les grandes composantes, à cause du carré.
  • L 1 L_1 (Manhattan) : la distance en ne se déplaçant que le long des axes, comme dans un quadrillage de rues. Toutes les composantes comptent proportionnellement.
  • L ∞ L_\infty : seule la plus grande composante compte.

Exemple : pour u = ( 3 , 4 ) u = (3, 4) , on a ‖ u ‖ 2 = 5 \|u\|_2 = 5 , ‖ u ‖ 1 = 7 \|u\|_1 = 7 et ‖ u ‖ ∞ = 4 \|u\|_\infty = 4 .

Les boules unité. L’ensemble des points de norme 1 a une forme différente selon la norme : un losange pour L 1 L_1 , un cercle pour L 2 L_2 , un carré pour L ∞ L_\infty .

Boules unité des normes L1 (losange), L2 (cercle) et L∞ (carré). Les pointes du losange L1 sont sur les axes, là où une coordonnée vaut exactement zéro.

Ce n’est pas un détail esthétique : les pointes du losange L 1 L_1 sont sur les axes, c’est-à-dire là où certaines coordonnées valent exactement zéro. C’est pour ça que la régularisation L 1 L_1 (Lasso) met des poids à zéro et sélectionne des features, alors que L 2 L_2 (Ridge) les réduit sans les annuler. La démonstration complète arrive en M11, et on l’utilisera en partie 16.

4. Distance euclidienne ou similarité cosinus ?

Intuition. Deux questions différentes : « ces deux points sont-ils proches ? » et « ces deux flèches pointent-elles dans la même direction ? ».

d ( u , v ) = ‖ u − v ‖ 2 cos ( u , v ) = ⟨ u , v ⟩ ‖ u ‖ 2 ‖ v ‖ 2 \begin{aligned} d(u, v) &= \|u - v\|_2 \\ \cos(u, v) &= \frac{\langle u, v\rangle}{\|u\|_2\,\|v\|_2} \end{aligned}
  • La distance est sensible à l’échelle : un vecteur et son double sont loin l’un de l’autre.
  • Le cosinus ignore l’échelle : un vecteur et son double ont un cosinus de 1.

Pour des profils d’accès mémoire, la différence est fondamentale : deux régions qui ont le même rythme mais pas la même intensité sont très différentes au sens euclidien, et identiques au sens du cosinus. Le code ci-dessous le montre en chiffres.

Attention à l’échelle des features. Avec la distance euclidienne, une feature en millisecondes (valeurs autour de 500) écrase une feature entre 0 et 1. C’est pour ça qu’on standardise les features avant un kNN, une régression régularisée ou un réseau de neurones (partie 14).


Exemple de code

Quatre blocs indépendants : les opérations de base, la comparaison euclidienne/cosinus sur des profils d’accès, les boules unité, et un kNN écrit à la main.

Opérations de base

import numpy as np

u = np.array([3.0, 4.0])
v = np.array([4.0, 0.0])

print("u + v      =", u + v)
print("2u         =", 2 * u)
print("<u, v>     =", u @ v)                          # produit scalaire
print("||u||_2    =", np.linalg.norm(u))              # 5.0
print("||u||_1    =", np.linalg.norm(u, ord=1))       # 7.0
print("||u||_inf  =", np.linalg.norm(u, ord=np.inf))  # 4.0

cos_theta = (u @ v) / (np.linalg.norm(u) * np.linalg.norm(v))
print("angle(u,v) =", np.degrees(np.arccos(cos_theta)).round(2), "degrés")

# projection de u sur la direction de v
proj = (u @ v) / (v @ v) * v
print("proj_v(u)  =", proj, "| résidu :", u - proj, "| <résidu, v> =", (u - proj) @ v)

L’angle vaut 53,13°, la projection de u u sur v v vaut ( 3 , 0 ) (3, 0) , et le résidu ( 0 , 4 ) (0, 4) est bien orthogonal à v v : leur produit scalaire est nul.

Euclidienne contre cosinus, sur des profils d’accès

On décrit chaque région par ses accès sur les 20 dernières agrégations : un vecteur de ℝ 20 \mathbb{R}^{20} .

import numpy as np

t = np.arange(20)
motif = ((t % 5) < 2).astype(float)          # actif 2 agrégations sur 5

profils = {
    "A : périodique, faible":  2 * motif,
    "B : périodique, intense": 8 * motif,             # même rythme, 4x plus d'accès
    "C : chaude, régulière":   np.full(20, 3.0),
    "D : périodique, décalée": 2 * np.roll(motif, 2), # même intensité que A, hors phase
}
noms = list(profils)
X = np.array([profils[n] for n in noms])


def dist_euclid(a, b):
    return np.linalg.norm(a - b)


def sim_cosinus(a, b):
    return (a @ b) / (np.linalg.norm(a) * np.linalg.norm(b))


print("Distance euclidienne / similarité cosinus avec A :")
for n, x in zip(noms[1:], X[1:]):
    print(f"  {n:<26} euclid = {dist_euclid(X[0], x):6.2f} | cosinus = {sim_cosinus(X[0], x):.3f}")
Comparé à A Distance euclidienne Cosinus
B : même rythme, 4× plus intense 16,97 (la plus loin) 1,000 (identique)
C : chaude, régulière 10,77 0,632
D : même rythme, décalé de 2 pas 8,00 (la plus proche) 0,000 (orthogonal)

Deux constats importants :

  • selon la distance euclidienne, la région décalée D est plus proche de A que la région B, qui a pourtant exactement le même rythme. L’intensité domine tout ;
  • le cosinus reconnaît que A et B ont le même motif, mais il trouve D orthogonale à A : les deux régions ne sont jamais actives aux mêmes instants. Aucune de ces deux mesures ne capture le fait que D a le même rythme que A, à un décalage près.

C’est exactement pour ça qu’en partie 14 on ne compare pas les profils bruts : on construit des features invariantes au décalage, comme periode_estimee et phase. L’outil qui mesure la ressemblance d’un signal avec lui-même décalé s’appelle l’autocorrélation (M7).

Les boules unité

Ce script reproduit la figure de la section 3.

import numpy as np
import matplotlib.pyplot as plt

theta = np.linspace(0, 2 * np.pi, 400)
directions = np.stack([np.cos(theta), np.sin(theta)], axis=1)

fig, ax = plt.subplots(figsize=(5, 5))
for p, nom in [(1, "$L_1$"), (2, "$L_2$"), (np.inf, "$L_\\infty$")]:
    normes = np.linalg.norm(directions, ord=p, axis=1)
    pts = directions / normes[:, None]        # on ramène chaque direction à norme 1
    ax.plot(pts[:, 0], pts[:, 1], label=nom)
ax.set_aspect("equal")
ax.axhline(0, c="gray", lw=0.5)
ax.axvline(0, c="gray", lw=0.5)
ax.legend()
ax.set_title("Boules unité : $\\{x : \\|x\\|_p = 1\\}$")
plt.show()

L’astuce : on prend des directions dans toutes les orientations, puis on divise chacune par sa norme p p . On obtient le losange, le cercle et le carré.

k plus proches voisins, à la main

Le kNN est l’algorithme le plus directement géométrique : pour classer un point, on regarde la classe majoritaire de ses k k voisins les plus proches. Tout dépend donc de la distance choisie.

On classe des profils périodiques (classe 1) contre des profils aléatoires (classe 0), avec des intensités très variées, de 0,5 à 10.

import numpy as np

rng = np.random.default_rng(0)


def knn_predire(X_train, y_train, x, k=5, distance="l2"):
    if distance == "l2":
        d = np.linalg.norm(X_train - x, axis=1)
    elif distance == "l1":
        d = np.abs(X_train - x).sum(axis=1)
    elif distance == "cosinus":
        d = 1 - (X_train @ x) / (np.linalg.norm(X_train, axis=1) * np.linalg.norm(x) + 1e-12)
    else:
        raise ValueError(distance)
    voisins = np.argsort(d)[:k]
    return np.bincount(y_train[voisins]).argmax()


def generer(n, rng):
    """Profils périodiques (1) ou aléatoires (0), d'intensités variées."""
    Xs, ys = [], []
    for _ in range(n):
        intensite = rng.uniform(0.5, 10)
        if rng.random() < 0.5:
            periode = rng.integers(3, 7)
            x = ((np.arange(20) + rng.integers(0, periode)) % periode == 0) * intensite
            ys.append(1)
        else:
            x = (rng.random(20) < 0.25) * intensite
            ys.append(0)
        Xs.append(x + rng.normal(0, 0.1, 20))
    return np.array(Xs), np.array(ys)


X_tr, y_tr = generer(400, rng)
X_te, y_te = generer(200, rng)
for dist in ("l2", "l1", "cosinus"):
    preds = np.array([knn_predire(X_tr, y_tr, x, k=5, distance=dist) for x in X_te])
    print(f"kNN distance {dist:<8} accuracy = {np.mean(preds == y_te):.3f}")
Distance Accuracy
L 2 L_2 0,745
L 1 L_1 0,745
cosinus 0,860

Le cosinus gagne nettement, parce que la classe dépend de la forme du profil et pas de son intensité. Avec L 1 L_1 ou L 2 L_2 , un profil périodique intense ressemble davantage à un profil aléatoire intense qu’à un profil périodique faible. Le bon choix de distance est un choix de modélisation : il encode ce qu’on considère comme « semblable ».


Exercice pratique

  1. À la main. Pour u = ( 1 , 2 , 2 ) u = (1, 2, 2) et v = ( 2 , 0 , − 1 ) v = (2, 0, -1) , calcule ‖ u ‖ 1 \|u\|_1 , ‖ u ‖ 2 \|u\|_2 , ‖ u ‖ ∞ \|u\|_\infty , ⟨ u , v ⟩ \langle u, v\rangle et l’angle entre u u et v v . Vérifie avec NumPy.
  2. Inégalité de Cauchy-Schwarz. Tire 10 000 paires de vecteurs aléatoires de ℝ 10 \mathbb{R}^{10} et vérifie numériquement que | ⟨ u , v ⟩ | ≤ ‖ u ‖ 2 ‖ v ‖ 2 |\langle u, v\rangle| \le \|u\|_2\|v\|_2 . À partir de la définition géométrique, explique pourquoi c’est toujours vrai et quand il y a égalité.
  3. Normaliser pour réconcilier. Dans le kNN, divise chaque vecteur par sa norme L 2 L_2 (pour X_tr et X_te) avant d’utiliser la distance L 2 L_2 . Que devient l’accuracy ? Montre que pour des vecteurs de norme 1, ‖ u − v ‖ 2 2 = 2 − 2 cos ( u , v ) \|u - v\|_2^2 = 2 - 2\cos(u, v) .
  4. Invariance au décalage. Écris une fonction qui calcule le cosinus maximal entre a a et toutes les versions décalées de b b (np.roll(b, s) pour s = 0 , … , 19 s = 0, \dots, 19 ). Vérifie qu’elle reconnaît enfin A et D comme semblables. Puis intègre-la au kNN : l’accuracy s’améliore-t-elle ?
  5. Fil rouge. Dans les traces de la partie 14, quelle feature serait dominée par les autres en distance euclidienne si on ne standardisait pas ? Regarde les ordres de grandeur de depuis_dernier (jusqu’à 500) et de frac_active_20 (entre 0 et 1).

Indice pour la question 3 : développe ‖ u − v ‖ 2 = ⟨ u − v , u − v ⟩ \|u - v\|^2 = \langle u - v, u - v\rangle par bilinéarité du produit scalaire.


Ressources pour aller plus loin

  • 3Blue1Brown, Essence of Linear Algebra, épisodes 1, 2 et 9 (Dot products and duality) : l’intuition visuelle, en vidéo.
  • Deisenroth, Faisal, Ong, Mathematics for Machine Learning, chapitre 3 (géométrie analytique), gratuit en ligne.
  • Gilbert Strang, Introduction to Linear Algebra, chapitre 1.
  • Rappel sur le blog : NumPy : penser en tableaux, pour la vectorisation des calculs.

Ce que tu dois savoir faire avant de passer à la suite

  • Calculer à la main normes L 1 L_1 , L 2 L_2 , L ∞ L_\infty et produit scalaire
  • Interpréter un produit scalaire comme une mesure d’alignement et une projection
  • Dessiner de mémoire les boules unité L 1 L_1 , L 2 L_2 , L ∞ L_\infty , et dire où sont les pointes du losange
  • Choisir entre distance euclidienne et cosinus selon que l’intensité compte ou non
  • Expliquer pourquoi on standardise les features avant un algorithme basé sur des distances
  • Reconnaître un produit scalaire dans y ^ = w ⊤ x + b \hat y = w^\top x + b

Partie suivante : M2 · Les matrices comme transformations. Une matrice applique une transformation à des vecteurs, et une couche de réseau de neurones n’est rien d’autre que ça.