- Une matrice est une application linéaire. Le produit matriciel est la composition de deux applications, et multiplier une (m×k) par une (k×n) coûte m·k·n multiplications (p04-01, fiche à venir).
- Le gradient est la direction de plus forte pente et on descend en −η∇L (p03-02, pas 2). Cette chaîne-ci calcule le gradient ; p03-02 l'utilise. Ne pas confondre les deux moitiés d'une itération.
- La dérivée d'une fonction R → R est une pente locale : f(x + δ) ≈ f(x) + f′(x)δ. Tout le pas 2 consiste à lire ce « ≈ » en dimension quelconque.
- La compensation −1/p contre p(1−p). Déjà rencontrée comme « pourquoi la log-loss a un gradient sain » (p03-01, pas 6) ; ici elle devient un produit de jacobiennes.
La chaîne
Le décor : des millions de θ, une seule L
Un réseau est une composition, et la loss se pose au bout :
On veut ∂L/∂θ pour chaque θ. L'asymétrie du problème est tout entière dans le comptage : des millions d'entrées à dériver, une seule sortie à dériver. Le pas 5 montrera que c'est exactement cette asymétrie qui dicte le sens de parcours.
« Le réseau est une composition de fonctions, donc sa dérivée se calculera par la chain rule, donc il reste à choisir dans quel ordre enchaîner les facteurs, et ce choix se fera sur le comptage : des millions de paramètres contre une seule loss. »
La plus petite composition qui a toutes les pièces : x = 2, w = 0,5, b = 0, étiquette y = 1.
| étape | formule | valeur |
|---|---|---|
| pré-activation | wx + b = 0,5·2 + 0 | 1 |
| probabilité | σ(1) = 1/(1 + e−1) | 0,731 |
| loss | −log 0,731 | 0,313 |
Trois fonctions emboîtées, deux paramètres. À l'échelle d'un vrai réseau, seuls les nombres changent — pas le geste.
Une dérivée est une matrice tronc
Pour f : Rn → Rm, la jacobienne J ∈ Rm×n est l'application linéaire qui approxime f au point x :
Ligne i = le gradient de la sortie i. Cas m = 1 : J n'a qu'une ligne, c'est ∇⊤. Le gradient est donc un cas particulier de jacobienne, pas un objet à part.
« Localement une fonction est linéaire, donc sa dérivée est une matrice, donc composer des fonctions revient à multiplier leurs matrices locales. »
| bloc | dimensions | jacobienne au point courant |
|---|---|---|
| z = wx + b, vue en (w, b) | R2 → R | [ x 1 ] = [ 2 1 ] |
| p = σ(z) | R → R | [ p(1−p) ] = [ 0,197 ] |
| L = −log p | R → R | [ −1/p ] = [ −1,368 ] |
Chaque case est un nombre évalué en un point précis : 0,197 n'est la jacobienne de σ qu'en z = 1. Une couche dense h = Wa a pour jacobienne en a la matrice W elle-même — la dérivée d'une application linéaire est cette application.
Chain rule = produit de jacobiennes tronc
Composer deux applications linéaires, c'est multiplier leurs matrices. La même phrase, écrite localement, est la chain rule :
Deux choses à lire. L'ordre : celui de la composition, donc de droite à gauche. Le point d'évaluation : Jf se prend en g(x), pas en x — d'où H3, il faut avoir gardé g(x).
« La composée de deux applications linéaires est leur produit, donc la jacobienne d'une composée est le produit des jacobiennes, chacune prise au point courant, donc il faut avoir gardé les points de l'aller. »
Sur le neurone, en w :
Aucun facteur n'est élidé : trois blocs, trois facteurs, dans l'ordre inverse de l'aller. Le résultat est −0,538.
Contrôle indépendant. En différence finie, L(w + h) − L(w − h) sur 2h avec h = 10−6 donne −0,5378828 : le produit des trois jacobiennes tombe juste. C'est le test que micrograd fait passer à chaque nœud.
Et si l'on change le point : en z = 4, ∂p/∂z vaut 0,018 au lieu de 0,197. Le point d'évaluation n'est pas un détail, c'est ce qui rend le retour dépendant de l'aller.
Sur le neurone : chaque facteur a un sens
Trois facteurs, trois lectures. ∂L/∂p = −1/p : le log punit les petites probabilités, et d'autant plus fort qu'elles sont petites. ∂p/∂z = p(1−p) : la sigmoïde écrase aux extrêmes, au plus ¼ au centre. Leur produit vaut exactement p − y : les deux se compensent (p03-01, pas 6).
Reste ∂z/∂w = x : le gradient d'un poids est signal d'erreur × entrée.
« Le gradient d'un poids est le gradient de sa sortie fois son entrée, donc une entrée nulle n'apprend rien, donc un signal d'erreur nul n'apprend rien. »
| quantité | formule | valeur |
|---|---|---|
| ∂L/∂p | −1/p | −1,368 |
| ∂p/∂z | p(1−p) | 0,197 |
| ∂L/∂z | produit = p − y | −0,269 |
| ∂L/∂w | (p − y)·x | −0,538 |
| ∂L/∂b | (p − y)·1 | −0,269 |
Le produit −1,368 × 0,197 rend −0,2689 ; p − 1 = 0,7311 − 1 = −0,2689. Égalité exacte, pas numérique : (−1/p)·p(1−p) = −(1−p).
Les deux zéros. Si x = 0, ∂L/∂w = 0 quelle que soit l'erreur : ce poids n'apprend pas de cette observation. Si p = y, tous les poids en amont reçoivent 0. Un seul facteur nul suffit à éteindre la ligne entière.
Le neurone comme graphe. « Aller » remplit les valeurs en bleu, de gauche à droite. « Retour » remplit les grad en rouge, un nœud à la fois, en partant de L où grad = 1 ; les flèches rouges vont à contresens des grises. Chaque flèche porte sa dérivée locale : traverser une flèche, c'est multiplier par elle. Lis le rouge de droite à gauche : 1, puis ×(−1/p), puis ×p(1−p), puis ×x pour w et ×1 pour b.
L'ordre des produits décide du coût tronc
Le produit J3·J2·J1 est associatif : le résultat ne dépend pas du parenthésage, le coût si. Loss scalaire (H2) ⇒ J3 est une ligne.
Depuis la sortie, ligne × matrice = ligne : on reste une ligne à chaque étape. Depuis l'entrée, matrice × matrice = matrice pleine m×n, qu'on paye m·m·n.
C'est le VJP (vecteur-jacobienne) : on ne construit jamais de jacobienne intermédiaire, on transporte un vecteur.
« La loss est scalaire, donc la dernière jacobienne est une ligne, donc en partant d'elle chaque produit reste une ligne et coûte une matrice, donc partir de la sortie coûte m² là où partir de l'entrée coûte m²n. »
J3 ∈ R1×m, J2 ∈ Rm×m, J1 ∈ Rm×n. Une multiplication (a×b)(b×c) coûte abc.
| ordre | premier produit | puis | total |
|---|---|---|---|
| depuis la sortie | (J3J2) : 1·m·m = 106 | (·)J1 : 1·m·n = 106 | 2,0 · 106 |
| depuis l'entrée | (J2J1) : m·m·n = 109 | J3(·) : 1·m·n = 106 | 1,001 · 109 |
Rapport 500,5 — et il grandit avec la profondeur, chaque couche supplémentaire ajoutant un m² d'un côté contre un m²n de l'autre.
Mémoire aussi. L'ordre « depuis l'entrée » matérialise une matrice 1 000 × 1 000 ; l'ordre « depuis la sortie » ne stocke jamais qu'un vecteur de 1 000 nombres. Le facteur 500 sur le calcul se double d'un facteur 1 000 sur l'objet intermédiaire.
Nombre de multiplications des deux parenthésages, en log10, pour trois couches. Bouge m pour déplacer le curseur sur les deux courbes, bouge n pour redessiner les deux courbes : la ligne reste une ligne, la matrice explose. L'écart vertical entre les deux courbes est log10 du rapport ; à m = n = 1 000, il vaut 2,7 — soit le facteur 500,5 du tableau ci-dessus.
Backprop = VJP couche par couche
On note gk = ∂L/∂hk le gradient arrivant sur la sortie de la couche k. Deux lignes suffisent :
La première fait passer le gradient à la couche précédente, la seconde le dépose sur les paramètres. Chaque couche ne connaît que sa jacobienne locale : aucune n'a besoin de savoir ce qu'il y a autour. C'est exactement micrograd — un nœud reçoit grad, le multiplie par sa dérivée locale, le pousse à ses enfants.
« Chaque nœud reçoit le gradient de sa sortie, le multiplie par sa dérivée locale et l'accumule sur ses entrées, donc l'ordre est topologique inverse, donc on part de la loss. »
| nœud | g reçu | × dérivée locale | g poussé |
|---|---|---|---|
| L | 1 (racine) | — | 1 |
| p | 1 | × (−1/p) = −1,368 | −1,368 |
| z | −1,368 | × p(1−p) = 0,197 | −0,269 |
| w | −0,269 | × x = 2 | −0,538 |
| b | −0,269 | × 1 | −0,269 |
Chaque ligne est un VJP de taille 1. Sur une couche dense h = Wa, la même ligne s'écrit ga = ghW et ∂L/∂W = gh⊤a⊤ — toujours « signal × entrée », le pas 4 en dimension quelconque.
Ordre topologique inverse : un nœud ne peut pousser que lorsqu'il a reçu la totalité de ce qui lui revient. C'est la condition d'arrêt du tri, et c'est ce que le pas 7 rend nécessaire.
Accumulation : +=, jamais =
Un nœud utilisé deux fois reçoit deux contributions, une par chemin. Elles s'ajoutent :
Écrire grad = … au lieu de grad += … garde la dernière contribution arrivée et jette les autres : le résultat dépend alors de l'ordre de parcours. C'est le bug classique de micrograd, et il est silencieux — le code tourne, la loss descend de travers.
« Un nœud a autant de contributions que de chemins qui le traversent, donc la chain rule sur un graphe est une somme sur les chemins, donc le gradient s'accumule au lieu d'être écrasé. »
Avec les mêmes nombres que le fil rouge, a = 2 et b = 0,5, mais a branché deux fois :
| chemin | produit des dérivées locales | contribution |
|---|---|---|
| a → e → L | 1 × b | 0,5 |
| a → L | 1 | 1 |
| somme | — | 1,5 |
Contrôle : L(a) = ab + a = a(b + 1) = 1,5a, donc ∂L/∂a = 1,5. La différence finie confirme : 1,4999999998.
Avec = au lieu de +=, on lit 0,5 ou 1 selon l'ordre du parcours — jamais 1,5. Sur le fil rouge du pas 4, le bug est invisible : chaque nœud n'y a qu'un seul consommateur. Les poids partagés (convolutions, RNN dépliés, weight tying) sont précisément le cas où il mord.
L'autre sens existe : le forward mode
Rien n'interdit de multiplier depuis l'entrée. On transporte alors une colonne : J1v, puis J2(·), puis J3(·). C'est le JVP (jacobienne-vecteur), et il donne la dérivée directionnelle dans une direction v.
Son coût est proportionnel au nombre d'entrées à couvrir, celui du VJP au nombre de sorties. Un réseau a des millions d'entrées à dériver et une sortie : le mode inverse gagne. Un problème à peu d'entrées et beaucoup de sorties inverse la conclusion.
« Le mode direct transporte une colonne et coûte un passage par entrée, donc il faut autant de passages que de paramètres, donc sur un réseau à une seule loss c'est le mode inverse qui gagne. »
| mode | coût d'un passage | passages nécessaires | total |
|---|---|---|---|
| inverse (VJP) | m² + mn = 2,0 · 106 | 1 (la loss est scalaire) | 2,0 · 106 |
| direct (JVP) | mn + m² + m = 2,001 · 106 | n = 1 000 | 2,001 · 109 |
Rapport 1 000,5 : c'est le nombre d'entrées, exactement. Un passage direct coûte autant qu'un passage inverse — ce qui départage, c'est combien il en faut.
Où le direct gagne : une seule entrée à perturber et beaucoup de sorties à suivre (sensibilité d'une simulation à un paramètre, produits hessienne-vecteur en combinant les deux modes).
Où ça casse casse
Le retour est un produit de facteurs, et un produit long est un objet fragile : il tend vers 0 ou vers l'infini selon la taille typique de ses facteurs. Le reste de la casse est la facture de H1 et de H3.
« Le gradient d'une couche profonde est un produit de beaucoup de jacobiennes, donc sa taille est une puissance de la taille typique d'un facteur, donc elle s'écroule ou diverge, et tout le travail d'architecture consiste à garder ces facteurs près de 1. »
- Gradients qui s'évanouissent. p(1−p) ≤ ¼ : vingt sigmoïdes empilées donnent au mieux (¼)20 = 9,1 · 10−13, et bien moins dès qu'une couche sature. La couche 1 n'apprend plus. C'est le pas 3 lu sur vingt couches. Remèdes : ReLU (dérivée 1 dans la zone active), initialisation soignée, batchnorm, connexions résiduelles (qui ajoutent un 1 à chaque jacobienne locale, B2).
- Gradients qui explosent. Le même produit avec des facteurs > 1 : 1,520 = 3 325, et un pas de descente qui projette les poids à l'autre bout de l'espace. Remède nommé : clipping de la norme du gradient.
- Mémoire. H3 impose de garder toutes les activations de l'aller jusqu'au retour. C'est ce qui limite la taille de batch bien avant le nombre de paramètres, et ce que le gradient checkpointing échange contre du recalcul : on ne garde qu'une activation sur √k et on refait l'aller par morceaux.
- Non dérivable. max, argmax, arrondi, échantillonnage : pas de jacobienne, donc pas de chemin pour le gradient (H1 tombe). On remplace par une version lisse (softmax pour argmax, Gumbel-softmax pour un tirage catégoriel) ou on contourne par un estimateur qui ne dérive pas la loss (REINFORCE, straight-through).
Les trois facteurs du pas 4, en valeur absolue et en log10, en fonction de z (donc de p = σ(z)). En log, un produit est une somme : la courbe rouge |p − 1| est exactement la somme des deux autres, hauteur par hauteur. Bouge z : à droite la sigmoïde sature et p(1−p) plonge, mais 1/p remonte juste assez pour que le produit reste sain — c'est la compensation de p03-01. À gauche, les deux plongent ensemble : c'est là, et seulement là, que le facteur ¼ de la limite 1 fait son œuvre sur une couche cachée, où aucun log ne vient compenser.
Résumé
- Une dérivée est une matrice : la jacobienne J ∈ Rm×n est l'application linéaire locale ; le gradient en est le cas m = 1.
- Chain rule = produit des jacobiennes, chacune évaluée au point que l'aller a produit ⇒ garder les activations.
- Loss scalaire ⇒ la dernière jacobienne est une ligne ⇒ partir de la sortie garde une ligne : 2 · 106 au lieu de 1,001 · 109 sur trois couches de 1 000. C'est le VJP.
- Backprop : chaque nœud multiplie le gradient reçu par sa dérivée locale et l'accumule (
+=) sur ses entrées, en ordre topologique inverse. - Gradient d'un poids = signal d'erreur × entrée. Un facteur nul éteint toute la ligne.
- Le mode direct (JVP) coûte un passage par entrée ; il gagne quand les entrées sont rares et les sorties nombreuses — l'inverse d'un réseau.
- Casse : produits de facteurs < 1 (évanouissement), > 1 (explosion), mémoire des activations, opérations non dérivables.
Chaîne verbalisée — une prise, à voix haute
- Qu'est-ce qu'une jacobienne, en une phrase ?L'application linéaire qui approxime f localement : f(x + δ) ≈ f(x) + Jδ, de taille m×n, ligne i = gradient de la sortie i.
- Que dit la chain rule pour f∘g ?Jf(g(x))·Jg(x) : produit dans l'ordre de la composition, chaque facteur évalué au point courant — d'où la mémoire des activations.
- Pourquoi partir de la sortie ?Loss scalaire ⇒ dernière jacobienne = une ligne ⇒ ligne × matrice reste une ligne : m² + mn contre m²n + mn, facteur 500,5 à m = n = 1 000.
- Que fait un nœud en backprop ?grad_entrée += grad_sortie × dérivée locale, en ordre topologique inverse. Le
+=parce qu'un nœud utilisé deux fois reçoit deux chemins. - Sur le neurone logistique, que vaut ∂L/∂w et pourquoi ?(p − y)·x = −0,538 : −1/p et p(1 − p) se compensent en p − y, puis on multiplie par l'entrée.
- D'où vient l'évanouissement du gradient ?Le gradient d'une couche profonde est un produit de beaucoup de jacobiennes ; des facteurs < 1 (p(1−p) ≤ ¼) l'écrasent : (¼)20 = 9,1 · 10−13.
Ponts et cartes
analyse::gradient-jacobienne (J = application linéaire locale, m×n, ligne = gradient) · analyse::chain-rule (produit de jacobiennes, point d'évaluation, somme sur les chemins) · dl::backprop (gk−1 = gkJk, ordre topologique inverse, +=) · dl::vjp (ligne × matrice, m² + mn contre m²n, JVP à l'inverse).
Une carte qui résiste après cette chaîne est une carte à refondre, pas une section à relire.