La descente de gradient#
Dans la leçon précédente, nous avons construit notre premier modèle capable d’apprendre ses poids à partir de données.
Nous avions alors une question importante :
Comment savoir dans quelle direction modifier un poids pour faire diminuer la perte ?
Pour y répondre, simplifions volontairement le problème. Commençons avec une seule donnée.
Un problème volontairement simple#
Notre modèle doit apprendre une relation très simple :
x → y
Utilisons quatre exemples :
Données :
x = 1 → y = 2
x = 2 → y = 4
x = 3 → y = 6
x = 4 → y = 8
À première vue, la relation est évidente :
y = 2 × x
Mais cette fois, nous ne donnerons pas directement la valeur 2 au modèle.
Commençons avec un poids w égal à 0.
Le modèle calcule donc sa prédiction avec :
prediction = w × xAu départ :
w = 0
Donc, pour x = 1 :
prediction = 0 × 1 = 0
La valeur prédite est donc loin de la valeur attendue y = 2.
Notre modèle a encore un peu de travail.
Mesurer l’erreur#
Nous avons besoin d’un moyen de mesurer à quel point la prédiction prediction s’éloigne de la valeur attendue y.
Utilisons une fonction de perte très simple :
Pour chaque donnée :
calculer prediction à partir de w et de x
calculer erreur = prediction - y
calculer perte = erreur²
Puis calculer la moyenne des valeurs de perte.
Pourquoi élever erreur au carré ?
Parce que la valeur obtenue est toujours positive et que les grandes erreurs contribuent davantage à la perte.
Prenons notre première situation, avec w = 0.
Pour x = 1 et y = 2 :
prediction = w × x
prediction = 0 × 1 = 0
erreur = prediction - y
erreur = 0 - 2 = -2
perte = erreur²
perte = (-2)² = 4
Faisons le même calcul avec les quatre données.
La perte moyenne obtenue est de 30.
La valeur actuelle de w nous donne donc un modèle encore très éloigné des valeurs attendues.
Il faut maintenant trouver comment modifier le poids w.
Trouver la direction#
Nous savons que la perte est trop élevée et que la valeur actuelle de w est 0.
Nous savons aussi, en regardant simplement les données, que la valeur finale de w devra être proche de 2.
Mais notre programme ne connaît pas cette valeur.
Il doit la trouver à partir des données.
Une première idée serait d’essayer différentes valeurs de w et de conserver celle qui donne la plus petite perte.
Cela fonctionnerait pour notre petit problème.
Mais dès que notre modèle possédera davantage de paramètres, cette méthode deviendra rapidement peu pratique.
Nous avons donc besoin d’une information plus utile :
Dans quelle direction faut-il déplacer le poids w pour faire diminuer la perte ?
Cette information est donnée par le gradient.
Le gradient#
Le gradient indique comment la valeur de la perte évolue lorsque nous modifions le poids w.
Dans notre modèle :
prediction = w × x
erreur = prediction - y
perte = erreur²
La dérivée de la fonction de perte par rapport au poids w nous donne alors :
gradient = 2 × x × erreurCalculons gradient pour chaque donnée, puis sa moyenne.
Nous obtenons ainsi une indication de la direction dans laquelle modifier w.
Modifier progressivement le poids w#
Une fois gradient calculé, nous pouvons modifier w :
w = w - taux_apprentissage × gradientLa variable taux_apprentissage contrôle la taille de la modification appliquée à w.
Dans notre expérience :
taux_apprentissage = 0.05
Le mécanisme devient alors :
predictioncalculer
erreurcalculer
pertecalculer
gradientmodifier
wrecommencer
Nous venons de construire une véritable boucle d’apprentissage.
Notre programme Python#
Voici maintenant le programme complet à exécuter.
donnees = [
(1, 2),
(2, 4),
(3, 6),
(4, 8),
]
w = 0.0
taux_apprentissage = 0.05
nombre_epochs = 20
for epoch in range(nombre_epochs):
gradient = 0.0
perte = 0.0
for x, y in donnees:
prediction = w * x
erreur = prediction - y
perte += erreur ** 2
gradient += 2 * x * erreur
gradient /= len(donnees)
perte /= len(donnees)
w -= taux_apprentissage * gradient
print(
"Epoch :", epoch + 1,
"| perte :", round(perte, 6),
"| gradient :", round(gradient, 6),
"| w :", round(w, 6)
)
print()
print("=== Modèle final ===")
print("Poids w :", w)
print()
print("=== Test ===")
for x, y in donnees:
prediction = w * x
print(
"x =", x,
"| attendu =", y,
"| prédit =", round(prediction, 4)
)
Regardons ce qui s’est réellement passé#
Le programme affiche l’évolution de perte, de gradient et de w à chaque epoch.
Voici le résultat obtenu :
Epoch : 1 | perte : 30.0 | gradient : -30.0 | w : 1.5
Epoch : 2 | perte : 1.875 | gradient : -7.5 | w : 1.875
Epoch : 3 | perte : 0.117188 | gradient : -1.875 | w : 1.96875
Epoch : 4 | perte : 0.007324 | gradient : -0.46875 | w : 1.992188
Epoch : 5 | perte : 0.000458 | gradient : -0.117188 | w : 1.998047
Epoch : 6 | perte : 2.9e-05 | gradient : -0.029297 | w : 1.999512
Epoch : 7 | perte : 2e-06 | gradient : -0.007324 | w : 1.999878
Epoch : 8 | perte : 0.0 | gradient : -0.001831 | w : 1.999969
Epoch : 9 | perte : 0.0 | gradient : -0.000458 | w : 1.999992
Epoch : 10 | perte : 0.0 | gradient : -0.000114 | w : 1.999998
Epoch : 11 | perte : 0.0 | gradient : -2.9e-05 | w : 2.0
Epoch : 12 | perte : 0.0 | gradient : -7e-06 | w : 2.0
Epoch : 13 | perte : 0.0 | gradient : -2e-06 | w : 2.0
Epoch : 14 | perte : 0.0 | gradient : -0.0 | w : 2.0
Epoch : 15 | perte : 0.0 | gradient : -0.0 | w : 2.0
Epoch : 16 | perte : 0.0 | gradient : -0.0 | w : 2.0
Epoch : 17 | perte : 0.0 | gradient : -0.0 | w : 2.0
Epoch : 18 | perte : 0.0 | gradient : -0.0 | w : 2.0
Epoch : 19 | perte : 0.0 | gradient : -0.0 | w : 2.0
Epoch : 20 | perte : 0.0 | gradient : -0.0 | w : 2.0
=== Modèle final ===
Poids w : 1.999999999998181
=== Test ===
x = 1 | attendu = 2 | prédit = 2.0
x = 2 | attendu = 4 | prédit = 4.0
x = 3 | attendu = 6 | prédit = 6.0
x = 4 | attendu = 8 | prédit = 8.0
Le résultat est assez parlant.
Dès la première epoch, le poids w passe de 0 à 1.5.
Puis w évolue progressivement :
w = 1.5w = 1.875w = 1.96875w = 1.992188w = 1.998047...w ≈ 2Pendant ce temps, la valeur de perte diminue elle aussi très rapidement.
Le programme ne connaît donc jamais directement la valeur finale de w. Il la découvre en utilisant gradient pour guider les corrections successives de w.
Et les prédictions ?#
Une fois l’apprentissage terminé, nous testons le modèle sur les mêmes données.
Le résultat est :
x = 1 | attendu = 2 | prédit = 2.0
x = 2 | attendu = 4 | prédit = 4.0
x = 3 | attendu = 6 | prédit = 6.0
x = 4 | attendu = 8 | prédit = 8.0
Le modèle a donc appris un poids w extrêmement proche de 2 :
w = 1.999999999998181
Avec cette valeur de w, les quatre valeurs de prediction correspondent aux valeurs attendues y.
Que vient-il réellement de se passer ?#
Nous n’avons jamais écrit dans le programme :
w = 2
Le programme a commencé avec :
w = 0
Puis il a utilisé les valeurs de x et de y présentes dans donnees pour corriger progressivement w.
À chaque epoch :
prediction.2. Il calcule
erreur.3. Il calcule
perte.4. Il calcule
gradient.5. Il modifie
w.6. Il recommence.
C’est cette répétition qui permet au modèle de progresser.
Nous sommes passés d’un modèle avec w = 0 à un modèle dont w est pratiquement égal à 2.
Pourquoi parle-t-on de « descente » ?#
Nous cherchons à réduire la valeur de perte.
On peut imaginer la perte comme une surface avec des hauteurs différentes.
Chaque valeur possible de w correspond alors à une position sur cette surface.
Le gradient gradient nous indique la pente à cet endroit.
La mise à jour :
w = w - taux_apprentissage × gradient
fait évoluer w dans la direction qui réduit perte.
Nous cherchons donc progressivement une zone où la valeur de perte devient minimale.
D’où le nom :
descente de gradient.
Dans notre expérience, cette descente est particulièrement simple : le modèle possède un seul poids w.
Une première vraie boucle d’apprentissage#
Avec notre premier modèle d’arrosage, nous avions déjà rencontré l’idée d’une correction :
erreur = label - resultat
Nous avions alors modifié les poids lorsque la prédiction était incorrecte.
Ici, nous allons plus loin.
Le modèle ne se contente plus de constater que la valeur de prediction est différente de la valeur attendue y.
Il mesure une perte perte et utilise le gradient gradient pour déterminer comment modifier progressivement le poids w.
Nous avons donc maintenant les éléments essentiels d’une boucle d’apprentissage :
donnees↓
Prédiction
prediction↓
Erreur
erreur↓
Perte
perte↓
Gradient
gradient↓
Mise à jour du poids
w↓
Nouvelle prédiction
prediction↓
…
Cette boucle constitue l’un des mécanismes fondamentaux du Machine Learning.
La suite#
Notre expérience était volontairement simple.
Un seul poids w, une seule relation entre x et y, et quatre données dans donnees suffisaient pour faire apparaître le mécanisme.
Mais notre modèle d’arrosage possédait déjà deux mesures :
- la température ;
- l’humidité.
Il avait donc deux poids à apprendre.
Et bientôt, nous voudrons aller beaucoup plus loin.
Que se passe-t-il lorsque notre modèle possède plusieurs paramètres et plusieurs couches de calcul ?
C’est à partir de là que nous allons commencer à voir apparaître les réseaux de neurones.