Descendez le gradient
Au chapitre précédent, nous avons trouvé les minimums en résolvant à la main. C’est possible pour , mais pas pour la fonction d’erreur d’un réseau de neurones à dix millions de paramètres. Il faut une méthode qui s’approche du minimum pas à pas, sans jamais résoudre d’équation.
Revenez à notre randonneur dans le brouillard. Sa stratégie la plus naturelle : regarder dans quel sens le terrain descend le plus, faire quelques pas dans cette direction, et recommencer. C’est exactement la descente de gradient, la méthode d’optimisation la plus utilisée au monde. Dans ce chapitre, vous allez la programmer et la régler, en suivant les fonctions de notre TP.
Le principe : suivre la plus forte pente
Section intitulée « Le principe : suivre la plus forte pente »On part d’un point et l’on construit une suite de points par la règle
- est le point courant à l’itération ;
- est le gradient en ce point, et la direction de plus forte descente ;
- est le pas (step size), la longueur du déplacement. En apprentissage automatique, on l’appelle le taux d’apprentissage (learning rate).
Pourquoi est-ce que ça descend ? D’après Taylor à l’ordre 1, , qui est plus petit que dès que le gradient n’est pas nul et que est assez petit. Plus généralement, toute direction telle que est une direction de descente.
On s’arrête quand le gradient devient presque nul (on est près d’un point critique), ou après un nombre maximal d’itérations :
Dans notre TP, la tolérance valait et le nombre maximal d’itérations .
Tout le réglage tient dans le choix du pas. Trop petit, le randonneur avance à pas de fourmi. Trop grand, il enjambe la vallée et se retrouve sur le versant d’en face. Voyons cela sur des chiffres.
Le pas fixe
Section intitulée « Le pas fixe »La version la plus simple garde le même pas à chaque itération.
Un exemple en dimension 1
Section intitulée « Un exemple en dimension 1 »En TP, nous avons minimisé en partant de , dont le minimum exact est . Nous avons testé trois pas : 0,125, 0,1 et 0,01. Le pas 0,125 ne converge pas, le pas 0,01 converge plus lentement, et le pas 0,1 donne le meilleur résultat des trois : en sortie, et , pour un minimum exact .
Pourquoi un écart aussi net entre 0,1 et 0,125 ? Près du minimum, , donc une itération donne
L’erreur est multipliée à chaque pas par le facteur . Elle diminue si ce facteur est compris entre et , c’est-à-dire si . Ici , d’où la limite .
| Pas | Facteur | Comportement |
|---|---|---|
| 0,125 | l’erreur grandit en changeant de signe : pas de convergence | |
| 0,1 | oscillations de part et d’autre du minimum, qui s’amortissent | |
| 0,01 | approche monotone mais lente |
Si l’on rejoue le code du TP, on le voit très bien : avec 0,125, les itérées finissent par sauter indéfiniment entre environ 0,87 et 1,42 sans jamais s’approcher de 1,205 ; avec 0,01, elles ne sont encore qu’à 1,19 après les 20 itérations autorisées.
Un exemple en dimension 2
Section intitulée « Un exemple en dimension 2 »Passons à la quadratique du TP, , de minimum et de valeur . Son gradient vaut . Avec un pas fixe , nous avons obtenu :
| Départ | Point final | final |
|---|---|---|
La coordonnée est presque exacte, mais traîne. L’explication tient en une ligne. Une itération s’écrit, coordonnée par coordonnée :
Avec , l’erreur sur est multipliée par 0,6 à chaque pas, celle sur par 0,8 seulement. Depuis , après 20 itérations, : c’est exactement ce qu’affiche le TP. Les deux directions ont des courbures différentes (4 et 2, les valeurs propres de la Hessienne), et un pas unique ne peut pas convenir parfaitement aux deux.
Le pas optimal
Section intitulée « Le pas optimal »Plutôt que de fixer , on peut choisir à chaque itération le meilleur pas possible dans la direction de descente :
C’est une minimisation en une seule variable, qu’on appelle recherche linéaire (line search). Pour une fonction quadratique , elle se résout exactement. Notons . La fonction est un polynôme de degré 2 en , de dérivée . Elle s’annule pour
où est la matrice (définie positive) de la quadratique. C’est la formule que nous avons programmée en TP.
Ce n’est pas un hasard : au pas optimal, , ce qui s’écrit . Deux directions successives sont toujours orthogonales, et la trajectoire dessine un zigzag caractéristique. Le TP insistait d’ailleurs sur un détail : tracer les itérées dans un repère orthonormé, sans quoi ces angles droits ne se voient pas.
En TP, depuis , le pas optimal a atteint , où : bien plus près du minimum qu’avec le pas fixe (où valait encore 0,115). Sur , le résultat est encore plus frappant : le point final est exactement , avec . Pourquoi ? Sa matrice est , ses lignes de niveau sont des cercles centrés sur le minimum, et le gradient pointe droit vers le centre : un seul pas optimal suffit.
Le conditionnement, ennemi du gradient
Section intitulée « Le conditionnement, ennemi du gradient »Le zigzag est d’autant plus lent que les lignes de niveau sont allongées. On mesure cet allongement par le conditionnement de la Hessienne :
où et sont la plus grande et la plus petite valeur propre. Des cercles donnent ; des ellipses très aplaties, un énorme. Sur une quadratique, on montre que chaque itération à pas optimal multiplie l’erreur (mesurée dans la norme associée à ) par au plus
C’est aussi le meilleur facteur accessible avec un pas fixe, obtenu pour .
| Fonction | Facteur | Conséquence | |
|---|---|---|---|
| (cercles) | 1 | 0 | un pas suffit |
| 2 | 0,33 | convergence rapide | |
| ellipses aplaties | 100 | 0,98 | des centaines d’itérations |
Pour lutter contre un mauvais conditionnement, on peut changer d’échelle (préconditionnement), utiliser le gradient conjugué (qui résout une quadratique de dimension en au plus itérations) ou exploiter la courbure avec la méthode de Newton, au chapitre suivant.
Le pas adaptatif : la règle d’Armijo
Section intitulée « Le pas adaptatif : la règle d’Armijo »Sur une fonction non quadratique, le pas optimal n’a pas de formule. Le chercher exactement coûterait trop cher, et ce serait inutile : il suffit d’un pas raisonnable. La règle d’Armijo (backtracking line search) accepte un pas dès qu’il fait baisser suffisamment :
avec une petite constante (typiquement ). On part de et l’on divise le pas par 2 tant que la condition n’est pas remplie. On évite ainsi les pas trop grands sans connaître la constante .
Voici une descente de gradient propre, avec un critère d’arrêt sur la norme du gradient :
import numpy as np
def descente_gradient(grad, x0, pas, tol=1e-6, max_iter=10_000): """Descente de gradient à pas fixe ; s'arrête quand le gradient est presque nul.""" x = np.asarray(x0, dtype=float) for k in range(max_iter): g = grad(x) if np.linalg.norm(g) < tol: return x, k x = x - pas * g return x, max_iter
grad_f1 = lambda v: np.array([4 * v[0] + 3, 2 * v[1]])
for pas in [0.1, 1 / 3]: x, k = descente_gradient(grad_f1, [10, 10], pas) print(f"pas {pas:.3f} : {k} itérations, point final {x.round(4)}")# pas 0.100 : 76 itérations ; pas 0.333 : 17 itérations (les deux autour de (-0.75, 0))Le pas est le meilleur pas fixe pour : il divise le nombre d’itérations par plus de quatre.
En résumé
Section intitulée « En résumé »- La descente de gradient itère et s’arrête quand est petit.
- Avec un pas fixe, il faut , où majore la courbure ; au-delà, oscillations ou divergence, comme le pas 0,125 du TP.
- Le pas optimal minimise le long de la direction ; sur une quadratique, .
- Au pas optimal, deux directions successives sont orthogonales : la trajectoire zigzague.
- La vitesse dépend du conditionnement : facteur par itération. Des lignes de niveau circulaires () se résolvent en un pas.
- La règle d’Armijo choisit un pas qui fait suffisamment baisser , sans connaître .
Exercices
Section intitulée « Exercices »Exercice · Un pas à la main
facilePour , calculez une itération de gradient à pas fixe depuis avec , puis avec . Que remarquez-vous ?
Voir la solution
Le gradient vaut , soit en .
- Avec : .
- Avec : , le minimum exact.
Pour , la Hessienne vaut : chaque coordonnée de l’erreur est multipliée par à chaque pas. Avec , ce facteur est nul, et l’on arrive au minimum en une itération. C’est d’ailleurs exactement le pas optimal : quand .
En code
import numpy as np
grad_f2 = lambda v: np.array([2 * v[0], 2 * v[1] - 2])x0 = np.array([10.0, 10.0])for pas in [0.1, 0.5]: print(pas, x0 - pas * grad_f2(x0)) # 0.1 [8. 8.2] ; 0.5 [0. 1.] = le minimumgrad_f2 <- function(v) c(2 * v[1], 2 * v[2] - 2)x0 <- c(10, 10)for (pas in c(0.1, 0.5)) print(x0 - pas * grad_f2(x0))# 8 8.2 avec 0,1 ; 0 1 avec 0,5 : le minimum en une itérationExercice · Calculer un pas optimal
moyenSoit . Depuis , calculez le pas optimal, le nouveau point, et vérifiez l’orthogonalité des deux premières directions. Quel est le conditionnement, et quel facteur de réduction peut-on en attendre ?
Voir la solution
On a avec . En , le gradient vaut , donc . Alors et , d’où .
Nouveau point : . Le gradient y vaut et son produit scalaire avec est : les directions sont bien orthogonales.
Le conditionnement vaut , d’où un facteur par itération au pire : il faut environ 6 itérations pour diviser l’erreur par 10.
En code
import numpy as np
A = np.diag([2.0, 10.0]) # f = x² + 5y² = ½ x'Axx = np.array([5.0, 1.0])d0 = -A @ x # direction : -gradient = (-10, -10)alpha = d0 @ d0 / (d0 @ A @ d0)x1 = x + alpha * d0print(alpha, x1, (-A @ x1) @ d0) # 0.1667 [ 3.333 -0.667] 2e-14 : orthogonales
kappa = 10 / 2facteur = (kappa - 1) / (kappa + 1)print(kappa, facteur, np.log(0.1) / np.log(facteur)) # 5.0 0.667 5.68 : environ 6 itérations
# Vérification : on itère jusqu'à diviser l'erreur (norme A) par 10k, e0 = 0, np.sqrt(x @ A @ x)while np.sqrt(x @ A @ x) > e0 / 10: d = -A @ x x, k = x + (d @ d / (d @ A @ d)) * d, k + 1print(k) # 6A <- diag(c(2, 10)) # f = x² + 5y² = ½ x'Axx <- c(5, 1)d0 <- -A %*% x # direction : -gradient = (-10, -10)alpha <- sum(d0^2) / sum(d0 * (A %*% d0))x1 <- as.vector(x + alpha * d0)alpha; x1; sum(-A %*% x1 * d0) # 0.1667 ; 3.333 -0.667 ; 0 : orthogonales
kappa <- 10 / 2facteur <- (kappa - 1) / (kappa + 1)facteur; log(0.1) / log(facteur) # 0.667 ; 5.68 : environ 6 itérationsExercice · Le plus grand pas admissible
difficilePour et la descente à pas fixe :
- Pour quelles valeurs de la méthode converge-t-elle, quel que soit le point de départ ?
- Que se passe-t-il exactement pour ?
- Quel pas fixe minimise le plus lent des deux facteurs de réduction ?
Voir la solution
- Les erreurs sur et sont multipliées par et . Il faut et , soit (la première condition est la plus sévère). On retrouve avec .
- Pour , le facteur sur vaut : l’erreur sur change de signe à chaque pas sans diminuer. Depuis , alterne indéfiniment entre et (symétriques par rapport à ), tandis que converge (facteur 0). La méthode ne converge pas.
- On veut minimiser . Le maximum est le plus petit quand les deux facteurs sont opposés : , soit . Les deux facteurs valent alors en valeur absolue, ce qui est bien avec .
En code
import numpy as np
# Facteurs de réduction sur x et sur y : 1 - 4α et 1 - 2αpas = np.arange(10, 6000) / 10_000 # de 0,001 à 0,6pire = np.maximum(abs(1 - 4 * pas), abs(1 - 2 * pas))print(pas[pire < 1].max()) # 0.4999 : convergence si 0 < α < 0,5print(pas[pire.argmin()], pire.min()) # 0.3333 0.3334 : meilleur pas 1/3, facteur 1/3
# α = 0,5 depuis (10, 10) : x oscille sans convergergrad_f1 = lambda v: np.array([4 * v[0] + 3, 2 * v[1]])v = np.array([10.0, 10.0])for k in range(4): v = v - 0.5 * grad_f1(v) print(v) # [-11.5 0] [10 0] [-11.5 0] [10 0]# Facteurs de réduction sur x et sur y : 1 - 4α et 1 - 2αpas <- seq(0.001, 0.6, by = 1e-4)pire <- pmax(abs(1 - 4 * pas), abs(1 - 2 * pas))max(pas[pire < 1]) # 0.4999 : convergence si 0 < α < 0,5pas[which.min(pire)]; min(pire) # 0.3333 et 0.3334 : meilleur pas 1/3, facteur 1/3
# α = 0,5 depuis (10, 10) : x oscille sans convergergrad_f1 <- function(v) c(4 * v[1] + 3, 2 * v[2])v <- c(10, 10)for (k in 1:4) { v <- v - 0.5 * grad_f1(v); cat(v, "\n") }# -11.5 0 / 10 0 / -11.5 0 / 10 0